1;(function ($, window, StateManager, undefined) { 2 'use strict'; 3 4 var $body = $('body'); 5 6 /** 7 * Plugin for handling the filter functionality and 8 * all other actions for changing the product listing. 9 * It handles the current set of category parameters and applies 10 * them to the current top location url when something was 11 * changed by the user over the filter form, action forms or 12 * the action links. 13 * 14 * ** Filter Form ** 15 * The filter form exists of different filter components, 16 * the filter submit button and the labels for active filters. 17 * Each component is rendered in a single panel and has its own functionality. 18 * All single components are handled by the "filterComponent" plugin. 19 * The plugin for the components fires correct change events for each type 20 * of component, so the "listingActions" plugin can listen on the changes 21 * of the user. A filter form has to be a normal form with the selector, 22 * which is set in the plugin options, so the form can be found by the plugin. 23 * The actual submitting of the form will always be prevented to build the complex 24 * category parameters out of the serialized form data. 25 * 26 * Example: 27 * <form id="filter" method="get" data-filter-form="true"> 28 * 29 * 30 * ** Action Forms ** 31 * You can apply different category parameters over additional action forms. 32 * In most cases these forms are auto submitting forms using the "autoSubmit" plugin, 33 * which change just one parameter via a combo- or checkbox. So with these 34 * action forms you have the possibility to apply all kind of category parameters 35 * like sorting, layout type, number of products per page etc. 36 * 37 * Example: 38 * <form method="get" data-action-form="true"> 39 * <select name="{$shortParameters.sSort}" data-auto-submit="true"> 40 * {...} 41 * </select> 42 * </form> 43 * 44 * 45 * ** Action Links ** 46 * You can also apply different category parameter via direct links. 47 * Just use the corresponding get parameters in the href attribute of the link. 48 * The new parameter will be added to the existing category parameters. 49 * If the parameter already exists the value will be updated with the new one. 50 * 51 * Example: 52 * <a href="?p=1&l=list" data-action-link="true">list view</a> 53 * 54 */ 55 $.plugin('swListingActions', { 56 57 defaults: { 58 59 /** 60 * The selector for the filter panel form. 61 */ 62 filterFormSelector: '*[data-filter-form="true"]', 63 64 /** 65 * The selector for the single filter components. 66 */ 67 filterComponentSelector: '*[data-filter-type]', 68 69 /** 70 * The selector for the button which shows and hides the filter panel. 71 */ 72 filterTriggerSelector: '*[data-filter-trigger="true"]', 73 74 /** 75 * The selector for the icon inside the filter trigger button. 76 */ 77 filterTriggerIconSelector: '.action--collapse-icon', 78 79 /** 80 * The selector for the filter panel element. 81 */ 82 filterContainerSelector: '.action--filter-options', 83 84 /** 85 * The selector for the inner filter container which used to for the loading indicator 86 * if the off canvas menu is active 87 */ 88 filterInnerContainerSelector: '.filter--container', 89 90 /** 91 * The selector for additional listing action forms. 92 */ 93 actionFormSelector: '*[data-action-form="true"]', 94 95 /** 96 * The selector for additional listing action links. 97 */ 98 actionLinkSelector: '*[data-action-link="true"]', 99 100 /** 101 * The selector for the container where the active filters are shown. 102 */ 103 activeFilterContSelector: '.filter--active-container', 104 105 /** 106 * The selector for the button which applies the filter changes. 107 */ 108 applyFilterBtnSelector: '.filter--btn-apply', 109 110 /** 111 * The css class for active filter labels. 112 */ 113 activeFilterCls: 'filter--active', 114 115 /** 116 * The close icon element which is used for the active filter labels. 117 */ 118 activeFilterIconCls: 'filter--active-icon', 119 120 /** 121 * The css class for the filter panel when it is completely collapsed. 122 */ 123 collapsedCls: 'is--collapsed', 124 125 /** 126 * The css class for the filter container when it shows only the preview of the active filters. 127 */ 128 hasActiveFilterCls: 'is--active-filter', 129 130 /** 131 * The css class for active states. 132 */ 133 activeCls: 'is--active', 134 135 /** 136 * The css class for disabled states. 137 */ 138 disabledCls: 'is--disabled', 139 140 /** 141 * Selector for the element that contains the found product count. 142 */ 143 filterCountSelector: '.filter--count', 144 145 /** 146 * Class that will be added to the apply filter button 147 * when loading the results. 148 */ 149 loadingClass: 'is--loading', 150 151 /** 152 * The characters used as a prefix to identify property field names. 153 * The properties will be merged in one GET parameter. 154 * For example properties with field names beginning with __f__"ID" 155 * will be merged to &f=ID1|ID2|ID3|ID4 etc. 156 * 157 */ 158 propertyPrefixChar: '__', 159 160 /** 161 * The buffer time in ms to wait between each action before firing the ajax call. 162 */ 163 bufferTime: 850, 164 165 /** 166 * The time in ms for animations. 167 */ 168 animationSpeed: 400, 169 170 /** Css class which will be added when the user uses instant filter results */ 171 instantFilterActiveCls: 'is--instant-filter-active', 172 173 /** 174 * class to select the listing div 175 */ 176 listingSelector: '.listing--container > .listing', 177 178 /** 179 * class to select the pagination bars 180 */
181 paginationSelector: '.listing--paging.panel--paging', 182 183 /** 184 * data attribute which indicates whether infinite scrolling is used or not 185 */ 186 infiniteScrollingAttribute: 'data-infinite-scrolling', 187 188 /** 189 * selector for the page size select box 190 */ 191 paginationBarPerPageSelector: '.per-page--field.action--field', 192 193 /** 194 * selector for the hidden input field of the filter form which stores the current page 195 */ 196 pageInputSelector: 'input[name=p]', 197 198 /** 199 * selector for the hidden input field of the filter form which stores the current sorting 200 */ 201 sortInputSelector: 'input[name=o]', 202 203 /** 204 * selector for the hidden input field of the filter form which stores the current amount of products per page 205 */ 206 perPageInputSelector: 'input[name=n]', 207 208 /** 209 * selector for the sorting select box 210 */ 211 sortActionFormSelector: '.action--sort', 212 213 /** 214 * selector for the products per page select box 215 */ 216 perPageActionFormSelector: '.action--per-page', 217 218 /** 219 * selector for the wrapper of the whole listing 220 */ 221 listingWrapperSelector: '.listing--wrapper', 222 223 /** 224 * The selector for the element which get the loading indicator after customer activates a filter 225 */ 226 loadingIndSelector: '.listing--wrapper', 227 228 /** 229 * The selector for "no filter result found" container 230 */ 231 noResultContainerSelector: '.listing-no-filter-result .alert', 232 233 /** 234 * Class for loading indicator, added and removed on the configurable `listingSelector` element 235 */ 236 isLoadingCls: 'is--loading', 237 238 /** 239 * Configuration for the loading indicator 240 */ 241 loadingIndConfig: { 242 theme: 'light', 243 animationSpeed: 100, 244 closeOnClick: false 245 }, 246 247 /** 248 * selector for the filter close button, which is only visible in off canvas 249 */ 250 filterCloseBtnSelector: '.filter--close-btn', 251 252 /** 253 * icon for the filter close button 254 */ 255 closeFilterOffCanvasBtnIcon: '<i class="icon--arrow-right"></i>', 256 257 /** 258 * selector for the search page headline 259 */ 260 searchHeadlineProductCountSelector: '.search--headline .headline--product-count', 261 262 /** 263 * selector for the filter facet container 264 */ 265 filterFacetContainerSelector: '.filter--facet-container', 266 267 /** 268 * selector for the filter action button bottom 269 */ 270 filterActionButtonBottomSelector: '.filter--actions.filter--actions-bottom', 271 272 /** 273 * selector for the parent of the loading indicator in if the filters in sidebar mode 274 */ 275 sidebarLoadingIndicatorParentSelector: '.content-main--inner', 276 277 /** 278 * selector for the jquery.add-article plugin to enable support for the off canvas cart 279 */ 280 addArticleSelector: '*[data-add-article="true"]', 281 282 /** 283 * Threshold for the scroll position when the user switches pages (in both modes e.g. infinite scrolling & page change) 284 */ 285 listingScrollThreshold: -10 286 }, 287 288 /** 289 * Initializes the plugin. 290 */ 291 init: function () { 292 var me = this, 293 filterCount; 294 295 me.applyDataAttributes(); 296 297 $('.sidebar-filter--loader').appendTo('.sidebar-filter--content'); 298 me.$filterForm = $(me.opts.filterFormSelector); 299 me.$filterComponents = me.$filterForm.find(me.opts.filterComponentSelector); 300 me.$filterTrigger = me.$el.find(me.opts.filterTriggerSelector); 301 me.$filterTriggerIcon = me.$filterTrigger.find(me.opts.filterTriggerIconSelector); 302 me.$filterCont = $(me.opts.filterContainerSelector); 303 me.$actionForms = $(me.opts.actionFormSelector); 304 me.$actionLinks = $(me.opts.actionLinkSelector); 305 me.$activeFilterCont = me.$filterForm.find(me.opts.activeFilterContSelector); 306 me.$applyFilterBtn = me.$filterForm.find(me.opts.applyFilterBtnSelector); 307 me.$listing = $(me.opts.listingSelector); 308 me.$pageInput = $(me.$filterForm.find(me.opts.pageInputSelector)); 309 me.$sortInput = $(me.$filterForm.find(me.opts.sortInputSelector)); 310 me.$perPageInput = $(me.$filterForm.find(me.opts.perPageInputSelector)); 311 me.$listingWrapper = me.$el.parent(me.opts.listingWrapperSelector); 312 me.$closeFilterOffCanvasBtn = $(me.opts.filterCloseBtnSelector); 313 me.$filterFacetContainer = me.$filterForm.find(me.opts.filterFacetContainerSelector); 314 me.$filterActionButtonBottom = me.$filterForm.find(me.opts.filterActionButtonBottomSelector); 315 me.$sidebarModeLoadionIndicator = $(me.opts.sidebarLoadingIndicatorParentSelector); 316 me.$noFilterResultContainer = $(me.opts.noResultContainerSelector); 317 318 me.searchHeadlineProductCount = $(me.opts.searchHeadlineProductCountSelector); 319 me.listingUrl = me.$filterForm.attr('data-listing-url'); 320 me.loadFacets = me.$filterForm.attr('data-load-facets') === 'true'; 321 me.showInstantFilterResult = me.$filterForm.attr('data-instant-filter-result') === 'true'; 322 me.isInfiniteScrolling = me.$listing.attr(me.opts.infiniteScrollingAttribute); 323 me.isFilterpanelInSidebar = me.$filterForm.attr('data-is-in-sidebar') === 'true'; 324 325 me.controllerURL = window.location.href.split('?')[0]; 326 me.resetLabel = me.$activeFilterCont.attr('data-reset-label'); 327 me.propertyFieldNames = []; 328 me.activeFilterElements = {}; 329 me.categoryParams = {};
330 me.urlParams = ''; 331 me.bufferTimeout = 0; 332 me.closeFilterOffCanvasBtnText = me.$closeFilterOffCanvasBtn.html(); 333 me.closeFilterOffCanvasBtnTextWithProducts = me.$closeFilterOffCanvasBtn.attr('data-show-products-text'); 334 335 me.getPropertyFieldNames(); 336 me.setCategoryParamsFromTopLocation(); 337 me.createActiveFiltersFromCategoryParams(); 338 me.createUrlParams(); 339 340 filterCount = Object.keys(me.activeFilterElements).length; 341 342 me.updateFilterTriggerButton(filterCount > 1 ? filterCount - 1 : filterCount); 343 me.initStateHandling(); 344 me.registerEvents(); 345 346 me.$loadingIndicatorElement = $(me.opts.loadingIndSelector); 347 me.$offCanvasLoadingIndicator = $(me.opts.filterInnerContainerSelector); 348 349 $.subscribe('action/fetchListing', $.proxy(me.onSendListingRequest, me)); 350 351 me.disableActiveFilterContainer(true); 352 353 var isFiltered = me.$filterForm.attr('data-is-filtered'); 354 if (isFiltered > 0 && me.loadFacets) { 355 me.getFilterResult(me.urlParams, true, false); 356 } 357 }, 358 359 /** 360 * Initializes the state manager for specific device options. 361 */ 362 initStateHandling: function () { 363 var me = this, 364 enterFn = $.proxy(me.onEnterMobile, me), 365 exitFn = $.proxy(me.onExitMobile, me); 366 367 StateManager.registerListener([ 368 { 369 state: 'xs', 370 enter: enterFn, 371 exit: exitFn 372 }, 373 { 374 state: 's', 375 enter: enterFn, 376 exit: exitFn 377 } 378 ]); 379 380 $.publish('plugin/swListingActions/onInitStateHandling', [me]); 381 }, 382 383 /** 384 * Called when entering the xs or s viewport. 385 * Removes/Clears style attributes that were set in higher viewports. 386 */ 387 onEnterMobile: function () { 388 var me = this, 389 opts = me.opts; 390 391 me.$filterForm.removeAttr('style'); 392 me.$filterFacetContainer.removeAttr('style'); 393 me.$filterActionButtonBottom.removeAttr('style'); 394 395 me.disableActiveFilterContainer(false); 396 397 me.$filterCont.removeClass(opts.collapsedCls); 398 399 me.$filterTrigger.removeClass(opts.activeCls); 400 401 $.publish('plugin/swListingActions/onEnterMobile', [me]); 402 }, 403 404 /** 405 * @param {boolean} disabled 406 */ 407 disableActiveFilterContainer: function (disabled) { 408 if (this.showInstantFilterResult || this.isFilterpanelInSidebar) { 409 return; 410 } 411 412 if (disabled) { 413 this.$activeFilterCont.addClass(this.opts.disabledCls); 414 } else if (this.$activeFilterCont.hasClass(this.opts.disabledCls)) { 415 this.$activeFilterCont.removeClass(this.opts.disabledCls); 416 } 417 }, 418 419 /** 420 * Called when exiting the xs or s viewport. 421 * Add the disabled class to the active filter container 422 * when it has active filter elements. 423 */ 424 onExitMobile: function () { 425 if (StateManager.isCurrentState(['xs', 's'])) { 426 return; 427 } 428 429 if (Object.keys(this.activeFilterElements).length && !this.isFilterpanelInSidebar) { 430 this.disableActiveFilterContainer(true); 431 } 432 433 $.publish('plugin/swListingActions/onExitMobile', [this]); 434 }, 435 436 /** 437 * Registers all necessary events. 438 */ 439 registerEvents: function () { 440 this._on(this.$filterForm, 'submit', $.proxy(this.onFilterSubmit, this)); 441 this._on(this.$actionForms, 'submit', $.proxy(this.onActionSubmit, this)); 442 this._on(this.$actionLinks, 'click', $.proxy(this.onActionLink, this)); 443 this._on(this.$filterComponents, 'onChange', $.proxy(this.onComponentChange, this)); 444 this._on(this.$filterTrigger, 'click', $.proxy(this.onFilterTriggerClick, this)); 445 446 this._on($body, 'click', $.proxy(this.onBodyClick, this)); 447 448 this.$activeFilterCont.on(this.getEventName('click'), '.' + this.opts.activeFilterCls, $.proxy(this.onActiveFilterClick, this)); 449 this.$listingWrapper.on(this.getEventName('submit'), this.opts.actionFormSelector, $.proxy(this.onActionSubmit, this)); 450 this.$listingWrapper.on(this.getEventName('click'), this.opts.actionLinkSelector, $.proxy(this.onActionLink, this)); 451 452 $.publish('plugin/swListingActions/onRegisterEvents', [this]); 453 }, 454 455 /** 456 * Called by event listener on submitting the filter form. 457 * Gets the serialized form data and applies it to the category params. 458 * 459 * @param {Event} event 460 */ 461 onFilterSubmit: function (event) { 462 event.preventDefault(); 463 464 var formData = this.$filterForm.serializeArray(), 465 categoryParams = this.setCategoryParamsFromData(formData, false); 466 467 this.applyCategoryParams(categoryParams); 468 469 $.publish('plugin/swListingActions/onFilterSubmit', [this, event]); 470 }, 471 472 /** 473 * Called by event listener on submitting an action form. 474 * Gets the serialized form data and applies it to the category params. 475 *
476 * Depending on which action is submitted the hidden input fields 477 * of the form filter are set to the new value if instantFilterResult is active. 478 * 479 * @param {Event} event 480 */ 481 onActionSubmit: function (event) { 482 event.preventDefault(); 483 484 var $form = $(event.currentTarget), 485 formData = $form.serializeArray(), 486 categoryParams = this.setCategoryParamsFromData(formData, true); 487 488 if (this.showInstantFilterResult) { 489 // first array element is always page number 490 this.setPageInput(this.getFormValue(formData, 'p')); 491 492 // second array element is always whether sorting or products per pages 493 if (this.isSortAction($form)) { 494 this.setSortInput(this.getFormValue(formData, 'o')); 495 } else if (this.isPerPageAction($form)) { 496 this.setPerPageInput(this.getFormValue(formData, 'n')); 497 } 498 } 499 this.applyCategoryParams(categoryParams); 500 501 $.publish('plugin/swListingActions/onActionSubmit', [this, event]); 502 }, 503 504 /** 505 * @param {Object} data 506 * @param {string} key 507 * @returns {string} 508 */ 509 getFormValue: function (data, key) { 510 var value = ''; 511 $.each(data, function (index, item) { 512 if (item.name === key) { 513 value = item.value; 514 } 515 }); 516 return value; 517 }, 518 519 /** 520 * @param {Object} $form 521 * @return {boolean} 522 */ 523 isSortAction: function ($form) { 524 return $form.is(this.opts.sortActionFormSelector); 525 }, 526 527 /** 528 * 529 * @param {Object} $form 530 * @return {boolean} 531 */ 532 isPerPageAction: function ($form) { 533 return $form.is(this.opts.perPageActionFormSelector); 534 }, 535 536 /** 537 * Helper method to set the hidden input field for the current page of the filter form 538 * 539 * @param {int} value 540 */ 541 setPageInput: function (value) { 542 this.$pageInput.val(value); 543 }, 544 545 /** 546 * Helper method to set the hidden input field for the current sorting of the filter form 547 * 548 * @param {int} value 549 */ 550 setSortInput: function (value) { 551 this.$sortInput.val(value); 552 }, 553 554 /** 555 * Helper method to set the hidden input field for products per page of the filter form 556 * 557 * @param {int} value 558 */ 559 setPerPageInput: function (value) { 560 this.$perPageInput.val(value); 561 }, 562 563 /** 564 * Called by event listener on clicking on an action link. 565 * Reads the parameter in the href attribute and adds it to the 566 * category params. 567 * 568 * @param {Event} event 569 */ 570 onActionLink: function (event) { 571 event.preventDefault(); 572 573 var me = this, 574 $link = $(event.currentTarget), 575 linkParams = $link.attr('href').split('?')[1], 576 linkParamsArray = linkParams.split('&'), 577 paramValue; 578 579 if (me.showInstantFilterResult) { 580 // Update page number in web form 581 $.each(linkParamsArray, function(index, param) { 582 paramValue = param.split('='); 583 584 if (paramValue[0] === 'p') { 585 me.setPageInput(paramValue[1]); 586 } 587 }); 588 } 589 590 this.applyCategoryParams( 591 this.setCategoryParamsFromUrlParams(linkParams) 592 ); 593 594 $.publish('plugin/swListingActions/onActionLink', [this, event]); 595 }, 596 597 /** 598 * Called by event listener on clicking the filter trigger button. 599 * Opens and closes the filter form panel. 600 * 601 * @param {Event} event 602 */ 603 onFilterTriggerClick: function (event) { 604 event.preventDefault(); 605 606 if (StateManager.isCurrentState(['xs', 's'])) { 607 return; 608 } 609 610 if (this.$filterCont.hasClass(this.opts.collapsedCls)) { 611 this.closeFilterPanel(); 612 } else { 613 this.openFilterPanel(); 614 } 615 616 $.publish('plugin/swListingActions/onFilterTriggerClick', [this, event]); 617 }, 618 619 /** 620 * Closes all filter panels if the user clicks anywhere else. 621 * 622 * @param {Event} event 623 */ 624 onBodyClick: function (event) { 625 var $target = $(event.target); 626 627 if (!$target.is(this.opts.filterComponentSelector + ', ' + this.opts.filterComponentSelector + ' *')) { 628 $.each(this.$filterComponents, function (index, item) { 629 $(item).data('plugin_swFilterComponent').close(); 630 }); 631 } 632 633 $.publish('plugin/swListingActions/onBodyClick', [this, event]); 634 }, 635 636 /** 637 * Called by event listener on the change event of the 638 * single filter components. Applies the changes of the 639 * component values to the category params. 640 * 641 * @param {Event} event 642 */ 643 onComponentChange: function (event) {
644 var urlParams, 645 formData, 646 categoryParams; 647 648 if (this.showInstantFilterResult) { 649 this.setPageInput(1); 650 } 651 652 formData = this.$filterForm.serializeArray(); 653 categoryParams = this.setCategoryParamsFromData(formData); 654 655 urlParams = this.createUrlParams(categoryParams); 656 657 this.createActiveFiltersFromCategoryParams(categoryParams); 658 659 this.enableButtonLoading(); 660 this.buffer($.proxy(this.getFilterResult, this, urlParams, this.loadFacets, this.showInstantFilterResult), this.opts.bufferTime); 661 662 $.publish('plugin/swListingActions/onComponentChange', [this, event]); 663 }, 664 665 /** 666 * Called by event listener on clicking an active filter label. 667 * It removes the clicked filter param form the set of active filters 668 * and updates the specific component. 669 * 670 * @param {Event} event 671 */ 672 onActiveFilterClick: function (event) { 673 var me = this, 674 $activeFilter = $(event.currentTarget), 675 param = $activeFilter.attr('data-filter-param'), 676 isMobile = StateManager.isCurrentState(['xs', 's']); 677 678 if (param === 'reset') { 679 // Reset all facets 680 $.each(me.activeFilterElements, function (key) { 681 me.removeActiveFilter(key); 682 me.resetFilterProperty(key); 683 }); 684 685 // Reset all options inside the facets 686 $.each(me.$filterComponents, function (i, component) { 687 var $component = $(component), 688 componentPlugin = $component.data('plugin_swFilterComponent'); 689 690 $.each(componentPlugin.$inputs, function(i, item) { 691 componentPlugin.disable($(item), false); 692 componentPlugin.disableComponent(false); 693 }); 694 695 $component 696 .removeClass(me.opts.disabledCls) 697 .find('.' + me.opts.disabledCls) 698 .removeClass(me.opts.disabledCls); 699 }); 700 701 if (!isMobile && !me.$filterCont.hasClass(me.opts.collapsedCls)) { 702 me.applyCategoryParams(); 703 } 704 } else if (!me.$activeFilterCont.hasClass(me.opts.disabledCls)) { 705 me.removeActiveFilter(param); 706 me.resetFilterProperty(param); 707 } 708 709 $.publish('plugin/swListingActions/onActiveFilterClick', [me, event]); 710 }, 711 712 /** 713 * @returns {Array} 714 */ 715 getPropertyFieldNames: function () { 716 var me = this; 717 718 $.each(me.$filterComponents, function (index, item) { 719 var $comp = $(item), 720 types = ['value-list', 'value-list-single', 'value-tree', 'media', 'value-tree-single', 'date'], 721 type = $comp.attr('data-filter-type'), 722 fieldName = $comp.attr('data-field-name'); 723 724 if (types.indexOf(type) >= 0 && me.propertyFieldNames.indexOf(fieldName) === -1) { 725 me.propertyFieldNames.push(fieldName); 726 } 727 }); 728 729 $.publish('plugin/swListingActions/onGetPropertyFieldNames', [me, me.propertyFieldNames]); 730 731 return me.propertyFieldNames; 732 }, 733 734 /** 735 * Converts given form data to the category parameter object. 736 * You can choose to either extend or override the existing object. 737 * 738 * @param {Object} formData 739 * @param {boolean} extend 740 * @returns {Object} 741 */ 742 setCategoryParamsFromData: function (formData, extend) { 743 var tempParams = {}; 744 745 $.each(formData, function (index, item) { 746 if (item['value']) { 747 tempParams[item['name']] = item['value']; 748 } 749 }); 750 751 if (extend) { 752 return $.extend(this.categoryParams, tempParams); 753 } 754 755 this.categoryParams = tempParams; 756 757 $.publish('plugin/swListingActions/onSetCategoryParamsFromData', [this, tempParams]); 758 759 return tempParams; 760 }, 761 762 /** 763 * Converts top location parameters to the category parameter object. 764 * 765 * @returns {Object} 766 */ 767 setCategoryParamsFromTopLocation: function () {
768 var urlParams = window.location.search.substr(1), 769 categoryParams = this.setCategoryParamsFromUrlParams(urlParams); 770 771 $.publish('plugin/swListingActions/onSetCategoryParamsFromData', [this, categoryParams]); 772 773 return categoryParams; 774 }, 775 776 /** 777 * Converts url parameters to the category parameter object. 778 * 779 * @param urlParamString 780 * @returns {Object} 781 */ 782 setCategoryParamsFromUrlParams: function (urlParamString) { 783 var me = this, 784 categoryParams, 785 params; 786 787 if (urlParamString.length <= 0) { 788 categoryParams = {}; 789 790 $.publish('plugin/swListingActions/onSetCategoryParamsFromUrlParams', [me, categoryParams]); 791 792 return categoryParams; 793 } 794 795 categoryParams = me.categoryParams; 796 params = urlParamString.split('&'); 797 798 $.each(params, function (index, item) { 799 var param = item.split('='); 800 801 param = $.map(param, function (val) { 802 val = val.replace(/\+/g, '%20'); 803 return decodeURIComponent(val); 804 }); 805 806 if (param[1] === 'reset') { 807 delete categoryParams[param[0]]; 808 } else if (me.propertyFieldNames.indexOf(param[0]) !== -1) { 809 var properties = param[1].split('|'); 810 811 $.each(properties, function (index, property) { 812 categoryParams[me.opts.propertyPrefixChar + param[0] + me.opts.propertyPrefixChar + property] = property; 813 }); 814 } else { 815 categoryParams[param[0]] = param[1]; 816 } 817 }); 818 819 $.publish('plugin/swListingActions/onSetCategoryParamsFromUrlParams', [me, categoryParams]); 820 821 return categoryParams; 822 }, 823 824 /** 825 * Converts the category parameter object to url parameters 826 * and applies the url parameters to the current top location. 827 * 828 * @param {Object} categoryParams 829 */ 830 applyCategoryParams: function (categoryParams) { 831 var params = categoryParams || this.categoryParams, 832 urlParams = this.createUrlParams(params); 833 834 this.applyUrlParams(urlParams); 835 836 $.publish('plugin/swListingActions/onApplyCategoryParams', [this, categoryParams]); 837 }, 838 839 /** 840 * Converts the category parameter object to url parameters. 841 * 842 * @param {Object} categoryParams 843 * @returns {string} 844 */ 845 createUrlParams: function (categoryParams) { 846 var catParams = categoryParams || this.categoryParams, 847 params = this.cleanParams(catParams), 848 filterList = []; 849 850 $.each(params, function (key, value) { 851 filterList.push(encodeURIComponent(key) + '=' + encodeURIComponent(value)); 852 }); 853 854 this.urlParams = '?' + filterList.join('&'); 855 856 $.publish('plugin/swListingActions/onCreateUrlParams', [this, this.urlParams]); 857 858 return this.urlParams; 859 }, 860 861 /** 862 * @param {Object} params 863 * @returns {Object} 864 */ 865 cleanParams: function (params) { 866 var me = this, 867 propertyParams = {}; 868 869 $.each(params, function (key, value) { 870 if (key.substr(0, 2) === me.opts.propertyPrefixChar) { 871 var propertyKey = key.split(me.opts.propertyPrefixChar)[1]; 872 873 if (propertyParams[propertyKey] !== undefined) { 874 propertyParams[propertyKey] += '|' + value; 875 } else { 876 propertyParams[propertyKey] = value; 877 } 878 } else { 879 propertyParams[key] = value; 880 } 881 }); 882 883 return propertyParams; 884 }, 885 886 /** 887 * Applies given url params to the top location. 888 * 889 * @param {string}
889 urlParams 890 */ 891 applyUrlParams: function (urlParams) { 892 var params = urlParams || this.urlParams, 893 formData, 894 categoryParams, 895 paramsForFilterResult; 896 897 if (this.showInstantFilterResult) { 898 formData = this.$filterForm.serializeArray(); 899 900 categoryParams = this.setCategoryParamsFromData(formData); 901 902 paramsForFilterResult = this.createUrlParams(categoryParams); 903 904 this.enableButtonLoading(); 905 this.buffer($.proxy(this.getFilterResult, this, paramsForFilterResult, false, this.showInstantFilterResult), this.opts.bufferTime); 906 } else { 907 window.location.href = this.getListingUrl(params, false); 908 } 909 910 $.publish('plugin/swListingActions/onApplyUrlParams', [this, urlParams]); 911 }, 912 913 /** 914 * Returns the full url path to the listing 915 * including all current url params. 916 * 917 * @param {string} urlParams 918 * @param {boolean} encode 919 * @returns {string} 920 */ 921 getListingUrl: function (urlParams, encode) { 922 var params = urlParams || this.urlParams, 923 url; 924 925 if (encode) { 926 url = encodeURI(this.controllerURL + params); 927 } else { 928 url = this.controllerURL + params; 929 } 930 931 $.publish('plugin/swListingActions/onGetListingUrl', [this, url, urlParams, encode]); 932 933 return url; 934 }, 935 936 /** 937 * Buffers a function by the given buffer time. 938 * 939 * @param {function} func 940 * @param {int} bufferTime 941 */ 942 buffer: function (func, bufferTime) { 943 if (this.bufferTimeout) { 944 clearTimeout(this.bufferTimeout); 945 } 946 947 this.bufferTimeout = setTimeout(func, bufferTime); 948 949 $.publish('plugin/swListingActions/onBuffer', [this, this.bufferTimeout, func, bufferTime]); 950 }, 951 952 /** 953 * Resets the current buffer timeout. 954 */ 955 resetBuffer: function () { 956 this.bufferTimeout = 0; 957 958 $.publish('plugin/swListingActions/onResetBuffer', [this, this.bufferTimeout]); 959 }, 960 961 /** 962 * Event listener which allows to send listing ajax request to load facets, total count and/or listings 963 * 964 * @param {object} event 965 * @param {object} params 966 * @param {boolean} loadFacets 967 * @param {boolean} loadProducts 968 * @param {function} callback 969 */ 970 onSendListingRequest: function (event, params, loadFacets, loadProducts, callback) { 971 var formData = this.$filterForm.serializeArray(); 972 973 $.each(formData, function (index, item) { 974 if (!params.hasOwnProperty(item.name)) { 975 if (!item.value || (typeof item.value === 'string' && item.value.length <= 0)) { 976 return; 977 } 978 979 params[item.name] = item.value; 980 } 981 }); 982 983 this.sendListingRequest(params, loadFacets, loadProducts, callback, true); 984 }, 985 986 /** 987 * @param {string|object} params 988 * @param {boolean} loadFacets 989 * @param {boolean} loadProducts 990 * @param {function} callback 991 * @param {boolean} appendDefaults 992 */ 993 sendListingRequest: function (params, loadFacets, loadProducts, callback, appendDefaults) { 994 if (typeof params === 'object') { 995 params = '?' + $.param(params); 996 } 997 998 this.resetBuffer(); 999 1000 $.ajax({ 1001 type: 'get', 1002 url: this.buildListingUrl(params, loadFacets, loadProducts), 1003 success: $.proxy(callback, this) 1004 }); 1005 $.publish('plugin/swListingActions/onGetFilterResult', [this, params]); 1006 }, 1007 1008 /** 1009 * Gets the counted result of found products 1010 * with the current applied category parameters. 1011 * Updates the filter submit button on success. 1012 * 1013 * @param {string}
1013 urlParams 1014 * @param {boolean} loadFacets 1015 * @param {boolean} loadProducts 1016 */ 1017 getFilterResult: function (urlParams, loadFacets, loadProducts) { 1018 var me = this, 1019 params = urlParams || me.urlParams, 1020 loadingIndicator = me.$loadingIndicatorElement; 1021 1022 if (me.$filterCont.is('.off-canvas.is--open')) { 1023 loadingIndicator = me.$offCanvasLoadingIndicator; 1024 } else if (me.isFilterpanelInSidebar) { 1025 loadingIndicator = me.$sidebarModeLoadionIndicator; 1026 } 1027 1028 me.resetBuffer(); 1029 me.enableLoading(loadingIndicator, loadProducts, function () { 1030 // send ajax request to load products and facets 1031 me.sendListingRequest(params, loadFacets, loadProducts, function (response) { 1032 me.disableLoading(loadingIndicator, loadProducts, response, function () { 1033 me.updateListing(response); 1034 // publish finish event to update filter panels 1035 $.publish('plugin/swListingActions/onGetFilterResultFinished', [me, response, params]); 1036 }); 1037 }); 1038 }); 1039 }, 1040 1041 /** 1042 * Enables the loading animation in the listing 1043 * 1044 * @param {Object} loadingIndicator 1045 * @param {boolean} loadProducts 1046 * @param {function} callback 1047 */ 1048 enableLoading: function (loadingIndicator, loadProducts, callback) { 1049 callback = $.isFunction(callback) ? callback : $.noop; 1050 1051 if (loadProducts) { 1052 this.$listing.addClass(this.opts.isLoadingCls); 1053 1054 loadingIndicator.setLoading( 1055 true, 1056 this.opts.loadingIndConfig 1057 ).then( 1058 $.proxy(callback, this) 1059 ); 1060 } else { 1061 this.enableButtonLoading(); 1062 callback.call(this); 1063 } 1064 }, 1065 1066 /** 1067 * Enables the button reload animation 1068 */ 1069 enableButtonLoading: function () { 1070 if (!this.showInstantFilterResult) { 1071 this.$applyFilterBtn.addClass(this.opts.loadingClass); 1072 } 1073 }, 1074 1075 /** 1076 * Disables the loading animation for the listing 1077 * 1078 * @param {Object} loadingIndicator 1079 * @param {boolean} loadProducts 1080 * @param {Object} response 1081 * @param {function} callback 1082 */ 1083 disableLoading: function (loadingIndicator, loadProducts, response, callback) { 1084 callback = $.isFunction(callback) ? callback : $.noop; 1085 1086 if (loadProducts) { 1087 // disable loading indicator 1088 loadingIndicator.setLoading(false).then( 1089 $.proxy(callback, this) 1090 ); 1091 } else { 1092 this.$applyFilterBtn.removeClass(this.opts.loadingClass); 1093 this.updateFilterButton(response.totalCount); 1094 callback.call(this); 1095 } 1096 }, 1097 1098 /** 1099 * Builds the URL by taking the basic path and adding parameters to it. 1100 * 1101 * @param {string} formParams 1102 * @param {boolean} loadProducts 1103 * @param {boolean} loadFacets 1104 * @returns {string} 1105 */ 1106 buildListingUrl: function (formParams, loadFacets, loadProducts) { 1107 var url = this.listingUrl + formParams; 1108 1109 if (loadProducts) { 1110 url += '&loadProducts=1'; 1111 } 1112 if (loadFacets) { 1113 url += '&loadFacets=1'; 1114 } 1115 1116 return url; 1117 }, 1118 1119 /** 1120 * Updates the listing with new products 1121 * 1122 * @param {Object} response 1123 */ 1124 updateListing: function (response) { 1125 var html, 1126 pages; 1127 1128 if (!response.hasOwnProperty('listing')) { 1129 this.$listing.removeClass(this.opts.isLoadingCls); 1130 return; 1131 } 1132 1133 this.updateFilterCloseButton(response.totalCount); 1134 this.updateSearchHeadline(response.totalCount); 1135 this.updateNoResultContainer(response.totalCount); 1136 1137 html = response.listing.trim(); 1138 1139 this.$listing.html(html); 1140 this.$listing.removeClass(this.opts.isLoadingCls); 1141 1142 window.history.pushState('data', '', window.location.href.split('?')[0] + this.urlParam
1142s); 1143 1144 $.publish('plugin/swListingActions/updateListing', [this, html]); 1145 1146 StateManager.updatePlugin(this.opts.addArticleSelector, 'swAddArticle'); 1147 1148 if (this.isInfiniteScrolling) { 1149 pages = Math.ceil(response.totalCount / this.$perPageInput.val()); 1150 1151 // update infinite scrolling plugin and data attributes for infinite scrolling 1152 this.$listing.attr('data-pages', pages); 1153 this.$listing.data('plugin_swInfiniteScrolling').destroy(); 1154 StateManager.addPlugin(this.opts.listingSelector, 'swInfiniteScrolling'); 1155 $.publish('plugin/swListingActions/updateInfiniteScrolling', [this, html, pages]); 1156 } else { 1157 this.updatePagination(response); 1158 this.scrollToTopPagination(); 1159 } 1160 }, 1161 1162 /** 1163 * Scrolls to the top paging bar 1164 */ 1165 scrollToTopPagination: function () { 1166 var $htmlBodyCt = $('html, body'), 1167 listingScrollThreshold = this.opts.listingScrollThreshold, 1168 listingActionPos = this.$el.offset().top + listingScrollThreshold, 1169 scrollTop = $htmlBodyCt.scrollTop(); 1170 1171 // Browser compatibility 1172 if (scrollTop === 0) { 1173 scrollTop = $('body').scrollTop(); 1174 } 1175 1176 if (scrollTop > listingActionPos) { 1177 $htmlBodyCt.animate({ 1178 scrollTop: listingActionPos 1179 }, this.opts.animationSpeed); 1180 } 1181 }, 1182 1183 /** 1184 * Updates the off canvas filter close button with the amount of products 1185 * 1186 * @param {int} totalCount 1187 */ 1188 updateFilterCloseButton: function (totalCount) { 1189 var filterCount = Object.keys(this.activeFilterElements).length; 1190 1191 if (filterCount > 0) { 1192 this.$closeFilterOffCanvasBtn.html(this.closeFilterOffCanvasBtnTextWithProducts.replace('%s', totalCount) + this.opts.closeFilterOffCanvasBtnIcon); 1193 1194 $.publish('plugin/swListingActions/updateFilterCloseBtnWithProductsCount', [this, totalCount]); 1195 } else { 1196 this.$closeFilterOffCanvasBtn.html(this.closeFilterOffCanvasBtnText); 1197 1198 $.publish('plugin/swListingActions/updateFilterCloseBtnDefault', [this]); 1199 } 1200 1201 this.updateFilterTriggerButton(filterCount > 1 ? filterCount - 1 : filterCount); 1202 }, 1203 1204 /** 1205 * Updates the headline of the search page with the new total count 1206 * 1207 * @param {int} totalCount 1208 */ 1209 updateSearchHeadline: function (totalCount) { 1210 if (this.searchHeadlineProductCount.length > 0) { 1211 this.searchHeadlineProductCount.html(totalCount); 1212 } 1213 }, 1214 1215 /** 1216 * @param {int} totalCount 1217 */ 1218 updateNoResultContainer: function (totalCount) { 1219 if (totalCount > 0) { 1220 if (!this.$noFilterResultContainer.hasClass('is--hidden')) { 1221 this.$noFilterResultContainer.addClass('is--hidden'); 1222 } 1223 return; 1224 } 1225 if (this.$noFilterResultContainer.hasClass('is--hidden')) { 1226 this.$noFilterResultContainer.removeClass('is--hidden'); 1227 } 1228 }, 1229 1230 /** 1231 * Updates the html for the listing pagination in case infinite scrolling is disabled 1232 * 1233 * @param {Object} response 1234 */ 1235 updatePagination: function (response) { 1236 var html = response.pagination.trim(); 1237 1238 $(this.opts.paginationSelector).replaceWith(html); 1239 StateManager.updatePlugin(this.opts.paginationBarPerPageSelector, 'swAutoSubmit'); 1240 1241 $.publish('plugin/swListingActions/updatePagination', [this, html]); 1242 }, 1243 1244 /** 1245 * Updates the layout of the filter submit button 1246 * with the new count of found products. 1247 * 1248 * @param {int} count 1249 */ 1250 updateFilterButton: function (count) { 1251 this.$applyFilterBtn.find(this.opts.filterCountSelector).html(count); 1252 1253 if (count <= 0) { 1254 this.$applyFilterBtn.attr('disabled', 'disabled'); 1255 } else { 1256 this.$applyFilterBtn.removeAttr('disabled'); 1257 } 1258 1259 $.publish('plugin/swListingActions/onUpdateFilterButton', [this, c
1259ount]); 1260 }, 1261 1262 /** 1263 * Updates the layout of the filter trigger button 1264 * on mobile viewports with the current count of active filters. 1265 * 1266 * @param activeFilterCount 1267 */ 1268 updateFilterTriggerButton: function (activeFilterCount) { 1269 this.$filterTriggerIcon.html(activeFilterCount || ''); 1270 1271 $.publish('plugin/swListingActions/onUpdateFilterTriggerButton', [this, activeFilterCount]); 1272 }, 1273 1274 /** 1275 * Creates the labels for active filters from the category params. 1276 * 1277 * @param categoryParams 1278 */ 1279 createActiveFiltersFromCategoryParams: function (categoryParams) { 1280 var me = this, 1281 count = 0, 1282 params = categoryParams || this.categoryParams; 1283 1284 $.each(this.activeFilterElements, function (key) { 1285 if (params[key] === undefined || params[key] === 0) { 1286 me.removeActiveFilter(key); 1287 } 1288 }); 1289 1290 $.each(params, function (key, value) { 1291 me.createActiveFilter(key, value); 1292 }); 1293 1294 $.each(this.activeFilterElements, function () { 1295 count++; 1296 }); 1297 1298 if (count > 1) { 1299 this.createActiveFilterElement('reset', this.resetLabel); 1300 } 1301 1302 this.$filterCont.toggleClass(this.opts.hasActiveFilterCls, (count > 0)); 1303 if (this.showInstantFilterResult && count > 0) { 1304 this.$filterCont.addClass(this.opts.instantFilterActiveCls); 1305 } 1306 1307 if (!this.opts.isFilterpanelInSidebar) { 1308 this.$activeFilterCont.toggleClass( 1309 this.opts.collapsedCls, 1310 this.$filterCont.hasClass(this.opts.collapsedCls) 1311 ); 1312 } 1313 1314 $.publish('plugin/swListingActions/onCreateActiveFiltersFromCategoryParams', [this, categoryParams]); 1315 }, 1316 1317 /** 1318 * Creates an active filter label for the given parameter. 1319 * If the label for the given parameter already 1320 * exists it will be updated. 1321 * 1322 * @param {string} param 1323 * @param {string} value 1324 */ 1325 createActiveFilter: function (param, value) { 1326 var label = this.createActiveFilterLabel(param, value); 1327 1328 if (label !== undefined && label.length) { 1329 if (this.activeFilterElements[param] !== undefined) { 1330 this.updateActiveFilterElement(param, label); 1331 } else { 1332 this.createActiveFilterElement(param, label); 1333 } 1334 } 1335 1336 $.publish('plugin/swListingActions/onCreateActiveFilter', [this, param, value]); 1337 }, 1338 1339 /** 1340 * Creates the DOM element for an active filter label. 1341 * 1342 * @param {string} param 1343 * @param {string} label 1344 */ 1345 createActiveFilterElement: function (param, label) { 1346 this.activeFilterElements[param] = $('<span>', { 1347 'class': this.opts.activeFilterCls, 1348 'html': this.getLabelIcon() + label, 1349 'data-filter-param': param 1350 }).appendTo(this.$activeFilterCont); 1351 1352 $.publish('plugin/swListingActions/onCreateActiveFilterElement', [this, param, label]); 1353 }, 1354 1355 /** 1356 * Updates the layout of an existing filter label element. 1357 * 1358 * @param param 1359 * @param {string} label 1360 */ 1361 updateActiveFilterElement: function (param, label) { 1362 this.activeFilterElements[param].html(this.getLabelIcon() + label); 1363 1364 $.publish('plugin/swListingActions/onUpdateActiveFilterElement', [this, param, label]); 1365 }, 1366 1367 /** 1368 * Removes an active filter label from the set and from the DOM. 1369 * 1370 * @param param 1371 */ 1372 removeActiveFilter: function (param) { 1373 this.activeFilterElements[param].remove(); 1374 1375 delete this.activeFilterElements[param]; 1376 1377 $.publish('plugin/swListingActions/onRemoveActiveFilter', [this, param]); 1378 }, 1379 1380 /** 1381 * Resets a filter parameter and updates 1382 * the component based on the component type. 1383 * 1384 * @param {string} param 1385 */ 1386 resetFilterProperty: function (param) { 1387 var $input, 1388 rangeSlider; 1389 1390 if (param === 'rating') { 1391 $input = this.$filterForm.find('.filter--rating .
1391is--active input[name="rating"]'); 1392 $input.removeAttr('checked').trigger('change'); 1393 } else { 1394 $input = this.$filterForm.find('[name="' + this.escapeDoubleQuotes(param) + '"]'); 1395 if ($input.is('[data-range-input]')) { 1396 rangeSlider = $input.parents('[data-range-slider="true"]').data('plugin_swRangeSlider'); 1397 rangeSlider.reset($input.attr('data-range-input')); 1398 } else if ($input.is('[data-datepicker="true"]') || $input.is('[data-date-range-input]')) { 1399 $input.trigger('clear'); 1400 } else { 1401 $input.removeAttr('checked').trigger('change'); 1402 } 1403 } 1404 1405 $.publish('plugin/swListingActions/onResetFilterProperty', [this, param]); 1406 }, 1407 1408 /** 1409 * Creates the correct label content for an active 1410 * filter label based on the component type. 1411 * 1412 * @param {string} param 1413 * @param {string} value 1414 * @returns {string} 1415 */ 1416 createActiveFilterLabel: function (param, value) { 1417 var $label, 1418 labelText = '', 1419 valueString = value + ''; 1420 1421 if (param === 'rating' && value > 0) { 1422 labelText = this.createStarLabel(value); 1423 } else { 1424 $label = this.$filterForm.find('label[for="' + this.escapeDoubleQuotes(param) + '"]'); 1425 1426 if ($label.is('[data-range-label]')) { 1427 labelText = $label.prev('span').html() + $label.html(); 1428 } else if ($label.is('[data-date-range-label]')) { 1429 labelText = $label.html() + ' ' + $label.next('[data-date-range-input]').attr('data-display-value'); 1430 } else if ($label.find('img').length) { 1431 labelText = $label.find('img').attr('alt'); 1432 } else if ($label.closest(this.opts.filterComponentSelector).is('[data-filter-type="radio"]')) { 1433 var activeRadioId = $label.closest(this.opts.filterComponentSelector).find('input:checked').attr('id'); 1434 labelText = this.$filterForm.find('label[for="' + this.escapeDoubleQuotes(activeRadioId) + '"]').html(); 1435 } else if (value > 0 || valueString.length > 0) { 1436 labelText = $label.html(); 1437 } 1438 } 1439 1440 $.publish('plugin/swListingActions/onCreateActiveFilterLabel', [this, labelText, param, value]); 1441 1442 return labelText; 1443 }, 1444 1445 /** 1446 * Only escapes a " if it's not already escaped 1447 * 1448 * @param {string} str 1449 * @returns string 1450 */ 1451 escapeDoubleQuotes: function (str) { 1452 return str.replace(/\\([\s\S])|(")/g, '\\$1$2'); 1453 }, 1454 1455 /** 1456 * Creates the label content for the special rating component. 1457 * 1458 * @param {int} stars 1459 * @returns {string} 1460 */ 1461 createStarLabel: function (stars) { 1462 var label = '', 1463 i = 0; 1464 1465 for (i; i < 5; i++) { 1466 if (i < stars) { 1467 label += '<i class="icon--star"></i>'; 1468 } else { 1469 label += '<i class="icon--star-empty"></i>'; 1470 } 1471 } 1472 1473 $.publish('plugin/swListingActions/onCreateStarLabel', [this, label, stars]); 1474 1475 return label; 1476 }, 1477 1478 /** 1479 * Returns the html string of the delete icon 1480 * for an active filter label. 1481 * 1482 * @returns {string} 1483 */ 1484 getLabelIcon: function () { 1485 var icon = '<span class="' + this.opts.activeFilterIconCls + '"></span>'; 1486 1487 $.publish('plugin/swListingActions/onCreateStarLabel', [this, icon]); 1488 1489 return icon; 1490 }, 1491 1492 /** 1493 * Opens the filter form panel based on the current state. 1494 */ 1495 openFilterPanel: function () { 1496 if (!this.$filterCont.hasClass(this.opts.hasActiveFilterCls)) { 1497 this.$activeFilterCont.slideDown(this.opts.animationSpeed); 1498 } 1499 1500 this.$filterFacetContainer.slideDown(this.opts.animationSpeed); 1501 this.$filterActionButtonBottom.slideDown(this.opts.animationSpeed); 1502 1503 this.disableActiveFilterContainer(false); 1504 this.$filterCont.addClass(this.opts.collapsedCls); 1505 this.$filterTrigger.addClass(this.opts.activeCls); 1506 1507 $.publish('plugin/swListingActions/onOpenFilterPanel', [this]); 1508 }, 1509 1510 /** 1511 * Closes the filter form panel based on the current state. 1512 */ 1513 closeFilterPanel: function () { 1514 if (!this.$filterCont.hasClass(this.opts.hasActiveFilterCls)) { 1515 this.$activeFilterCont.slideUp(this.opts.animationSpeed); 1516 } 1517 1518 this.$filterFacetContainer.slideUp(this.opts.animationSpeed); 1519 this.$filterActionButtonBottom.slideUp(this.opts.animationSpeed); 1520 1521 this.disableActiveFilterContainer(true); 1522 this.$filterCont.removeClass(this.opts.collapsedCls); 1523 this.$filterTrigger.removeClass(this.opts.activeCls); 1524 1525 $.publish('plugin/swListingActions/onCloseFilterPanel', [this]); 1526 }, 1527 1528 /** 1529 * Destroys the plugin. 1530 */ 1531 destroy: function () { 1532 this.$el.off(this.getEventName('click'), '.' + this.opts.activeFilterCls); 1533 this.$listingWrapper.off(this.getEventName('submit'), this.opts.actionFormSelector); 1534 this.$listingWrapper.off(this.getEventName('click'), this.opts.actionLinkSelector); 1535 1536 this._destroy(); 1537 } 1538 }); 1539})(jQuery, window, StateManager, undefined);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.