PageSourceSearch

https://www.connectingup.org/sites/all/modules/contrib/ajax_facets/misc/ajax_facets.js?tm9dxp

js connectingup.org collected 2026-10-02 05:09:03 UTC 32,539 bytes, 829 lines download raw bytes

1(function ($) {
2
3  /**
4   * Class containing functionality for Facet API.
5   */
6  Drupal.ajax_facets = {};
7  // Store current query value.
8  Drupal.ajax_facets.queryState = null;
9  // State of each facet.
10  Drupal.ajax_facets.facetQueryState = null;
11  // Determine if it is first page load. Will be reset in Drupal.ajax_facets.initHistoryState.
12  Drupal.ajax_facets.firstLoad = true;
13  // Settings of each ajax facet.
14  Drupal.ajax_facets.facetsSettings = {};
15  // Buttons for ajax facets.
16  Drupal.ajax_facets.ajax_facets_buttons = false;
17  // Force update of the results
18  Drupal.ajax_facets.force_update_results = false;
19  // Tooltip timeout handler.
20  Drupal.ajax_facets.tooltipTimeout;
21  // Name of the facet which was clicked.
22  Drupal.ajax_facets.current_facet_name = undefined;
23
24  // You can use it for freeze facet form elements while ajax is processing.
25  Drupal.ajax_facets.beforeAjaxCallbacks = {};
26
27  // You can use it to make actions after the ajax has updated the content.
28  Drupal.ajax_facets.afterContentUpdateCallbacks = {};
29
30  Drupal.ajax_facets.beforeAjax = function () {
31    $.each(Drupal.ajax_facets.beforeAjaxCallbacks, function () {
32      if ($.isFunction(this)) {
33        this();
34      }
35    });
36  };
37
38  Drupal.ajax_facets.afterContentUpdate = function (ajax, response) {
39    Drupal.ajax_facets.force_update_results = false;
40    $.each(Drupal.ajax_facets.afterContentUpdateCallbacks, function () {
41      if ($.isFunction(this)) {
42        this(ajax, response);
43      }
44    });
45  };
46
47  Drupal.behaviors.ajax_facets = {
48    attach: function (context, settings) {
49      if (!Drupal.ajax_facets.queryState) {
50        // Add property for views and set data about facet values.
51        Drupal.ajax_facets.queryState = {
52          'views': new Array(),
53          'f': settings.facetapi.defaultQuery ? settings.facetapi.defaultQuery : []
54        };
55
56        // We will send original search path to server to get back proper reset links.
57        if (settings.facetapi.searchPath) {
58          Drupal.ajax_facets.queryState['searchPath'] = settings.facetapi.searchPath;
59        }
60        if (settings.facetapi.index_id) {
61          Drupal.ajax_facets.queryState['index_id'] = settings.facetapi.index_id;
62        }
63        // Check view name and display name for all views.
64        if (settings.facetapi.views) {
65          $.each(settings.facetapi.views, function(key, view) {
66            Drupal.ajax_facets.queryState['views'][key] = {};
67            // Get view dom id.
68            var viewDomId = Drupal.ajax_facets.getViewDomId(settings, key);
69            if (viewDomId) {
70              // Take settings from view if it works by AJAX.
71              if (Drupal.views && Drupal.views.instances['views_dom_id:' + viewDomId]) {
72                $.extend(Drupal.ajax_facets.queryState['views'][key], Drupal.views.instances['views_dom_id:' + viewDomId].settings);
73              }
74              // If this view doesn't use ajax.
75              else {
76                Drupal.ajax_facets.queryState['views'][key] = {
77                  'view_dom_id': viewDomId,
78                  'view_name': view.view_name,
79                  'view_display_id': view.view_display_id
80                };
81                // Respect view arguments.
82                var nameDisplay = view.view_name + ':' + view.view_display_id;
83                if (settings.facetapi.view_args[nameDisplay]) {
84                  Drupal.ajax_facets.queryState['views'][key]['view_args'] = settings.facetapi.view_args[nameDisplay];
85                }
86                // Respect view path.
87                if (settings.facetapi.view_path[nameDisplay]) {
88                  Drupal.ajax_facets.queryState['views'][key]['view_path'] = settings.facetapi.view_path[nameDisplay];
89                }
90              }
91            }
92
93            // Respect arguments in exposed form.
94            if (view.view_name && view.view_display_id) {
95              var nameDisplay = view.view_name + ':' + view.view_display_id;
96              if (settings.facetapi.exposed_input[nameDisplay]) {
97                $.extend(Drupal.ajax_facets.queryState, settings.facetapi.exposed_input[nameDisplay]);
98              }
99            }
100          });
101        }
102
103        if (settings.facetapi.facet_field) {
104          Drupal.ajax_facets.queryState['facet_field'] = settings.facetapi.facet_field;
105        }
106      }
107
108      // Iterates over facet settings, applies functionality like the "Show more"
109      // links for block realm facets.
110      // @todo We need some sort of JS API so we don't have to make decisions
111      // based on the realm.
112      if (settings.facetapi) {
113        for (var index in settings.facetapi.facets) {
114          Drupal.ajax_facets.bindResetLink(settings.facetapi.facets[index].id, index, settings);
115
116          // Checkboxes.
117          if (settings.facetapi.facets[index].widget == 'facetapi_ajax_checkboxes') {
118            $('#' + settings.facetapi.facets[index].id + '-wrapper input.facet-multiselect-checkbox:not(.processed)').change(
119              [settings.facetapi.facets[index]],
120              Drupal.ajax_facets.processCheckboxes
121            ).addClass('processed');
122          }
123
124          // Selectboxes.
125          if (settings.facetapi.facets[index].widget == 'facetapi_ajax_select') {
126            $('#' + settings.facetapi.facets[index].id + '-wrapper select:not(.processed)').each(function () {
127              $(this).change(
128                [settings.facetapi.facets[index]],
129                Drupal.ajax_facets.processSelectbox
130              ).addClass('processed');
131            });
132          }
133
134          // Links.
135          if (settings.facetapi.facets[index].widget == 'facetapi_ajax_links') {
136            $('#' + settings.facetapi.facets[index].id + '-wrapper a:not(.processed)').each(function () {
137              $(this).click(
138                [settings.facetapi.facets[index]],
139                Drupal.ajax_facets.processLink
140              ).addClass('processed');
141            });
142          }
143
144          // Ranges.
145          if (settings.facetapi.facets[index].widget == 'facetapi_ajax_ranges') {
146            $('#' + settings.facetapi.facets[index].id + '-wrapper div.slider-wrapper:not(.processed)').each(function () {
147              var $sliderWrapper = $(this);
148              // Fix JS exception when doing mixed type search - Added by James 2021-10-18
149              try{
150                $sliderWrapper.slider({
151                  range: true,
152                  min: parseFloat($sliderWrapper.data('min')),
153                  max: parseFloat($sliderWrapper.data('max')),
154                  values: [ parseFloat($sliderWrapper.data('min-val')), parseFloat($sliderWrapper.data('max-val')) ],
155                  stop: function( event, ui ) {
156                    Drupal.ajax_facets.processSlider($sliderWrapper, ui.values[0], ui.values[1]);
157                  }
158                }).addClass('processed');
159              }
160              catch{
161                $sliderWrapper.addClass('processed');
162              }
163
164              // Bind input fields.
165              var $sliderWrapperParent = $sliderWrapper.parent();
166              $sliderWrapperParent.find("input[type='text']").each(function() {
167                $(this).change(function() {
168                  var min = parseFloat($sliderWrapperParent.find('.ajax-facets-slider-amount-min').val());
169                  var max = parseFloat($sliderWrapperParent.find('.ajax-facets-slider-amount-max').val());
170                  // If values are numeric and min less than max.
171                  if (isFinite(min) && isFinite(max) && min < max) {
172                    $sliderWrapper.slider('values', 0, min);
173                    $sliderWrapper.slider('values', 1, max);
174                    Drupal.ajax_facets.processSlider($sliderWrapper, min, max);
175                  }
176                });
177              });
178
179            });
180          }
181
182          if (null != settings.facetapi.facets[index].limit) {
183            // Applies soft limit to the list.
184            if (Drupal.facetapi) {
185              Drupal.facetapi.applyLimit(settings.facetapi.facets[index]);
186            }
187          }
188
189          // Save settings for each facet by name.
190          Drupal.ajax_facets.facetsSettings[settings.facetapi.facets[index].facetName] = settings.facetapi.facets[index];
191        }
192      }
193
194      // Hide blocks with ajax-facets-empty-behavior.
195      $('.ajax-facets-empty-behavior').parents('.block--ajax_facets').hide();
196
197      // Add facets tooltip.
198      $('body').once(function () {
199        $(this).append('<div id="ajax-facets-tooltip"><span></span></div>');
200      });
201    }
202  };
203
204  Drupal.behaviors.ajax_facets_block = {
205    attach: function (context, settings) {
206      // Reset all link.
207      $('.ajax-facets-reset-all-link:not(.processed)').bind('click', function (e) {
208        e.preventDefault();
209
210        // Clear all facets from queryState facets array.
211        Drupal.ajax_facets.queryState.f = [];
212
213        // Send ajax query with first found facet.
214        Drupal.ajax_facets.force_update_results = true;
215        Drupal.ajax_facets.sendAjaxQuery({
216          pushStateNeeded: true,
217          searchResultsNeeded: true
218        });
219      }).addClass('processed');
220
221      var $submitLink = $('.ajax-facets-submit-all-link:not(.processed)');
222      if ($submitLink.length) {
223        // Submit link found.
224        Drupal.ajax_facets.ajax_facets_buttons = true;
225        // Submit all link.
226        $submitLink.bind('click', function (e) {
227          e.preventDefault();
228          // Send ajax query with first found facet.
229          Drupal.ajax_facets.force_update_results = true;
230          Drupal.ajax_facets.sendAjaxQuery({
231            pushStateNeeded: true,
232            searchResultsNeeded: true
233          });
234        }).addClass('processed');
235      }
236    }
237  };
238
239
240  Drupal.ajax_facets.bindResetLink = function (facetWrapperId, index, settings) {
241    var facet_values = Drupal.ajax_facets.getFacetValues();
242    var $facetWrapper = $('#' + facetWrapperId + '-wrapper');
243    if (facet_values[settings.facetapi.facets[index]['facetName']]) {
244      $facetWrapper.find('a.reset-link').show();
245    } else {
246      $facetWrapper.find('a.reset-link').hide();
247    }
248
249    $facetWrapper.find('a.reset-link:not(".processed")').addClass('processed').click(function (event) {
250      var $facet = $(this).parent().find('[data-facet-name]').first();
251      var facetName = $facet.data('facet-name');
252      Drupal.ajax_facets.excludeCurrentFacet(facetName);
253      Drupal.ajax_facets.sendAjaxQuery({
254        pushStateNeeded: true,
255        searchResultsNeeded: true
256      });
257      event.preventDefault();
258    });
259  };
260
261  /**
262   * Callback for onClick event for widget selectbox.
263   */
264  Drupal.ajax_facets.processSelectbox = function (event) {
265    var $this = $(this),
266      facetName = $this.data('facet-name');
267    // Init history.
268    Drupal.ajax_facets.initHistoryState($this);
269    // If facets are already defined in queryState.
270    if (Drupal.ajax_facets.queryState['f']) {
271      // Exclude all values for this facet from query.
272      Drupal.ajax_facets.excludeCurrentFacet(facetName);
273
274      /* Default value. */
275      if ($this.find(":selected").val() == '_none') {
276        delete Drupal.ajax_facets.queryState['f'][Drupal.ajax_facets.queryState['f'].length];
277      } else {
278        Drupal.ajax_facets.queryState['f'][Drupal.ajax_facets.queryState['f'].length] = facetName + ':' + $this.find(":selected").val();
279      }
280    }
281
282    Drupal.ajax_facets.sendAjaxQuery({
283      pushStateNeeded: !Drupal.ajax_facets.ajax_facets_buttons,
284      searchResultsNeeded: !Drupal.ajax_facets.ajax_facets_buttons
285    }, $this);
286  };
287
288  /**
289   * Callback for onClick event for widget checkboxes.
290   */
291  Drupal.ajax_facets.processCheckboxes = function (event) {
292    var $this = $(this),
293      facetName = $this.data('facet-name'),
294      name_value = facetName + ':' + $this.attr('data-facet-value'),// $.data can round decimal values like 4.0 to 4, avoid it.
295      rawFacetName = $this.data('raw-facet-name');
296    // Init history.
297    Drupal.ajax_facets.initHistoryState($this);
298    // If facets are already defined in queryState.
299    if (Drupal.ajax_facets.queryState['f']) {
300      var queryNew = [];
301      if ($this.is(':checked')) {
302        var addCurrentParam = true;
303        for (var index in Drupal.ajax_facets.queryState['f']) {
304          if (Drupal.ajax_facets.queryState['f'].hasOwnProperty(index)) {
305            // If we already have this value in queryState.
306            if (Drupal.ajax_facets.queryState['f'][index] == name_value) {
307              addCurrentParam = false;
308            }
309          }
310        }
311        // Add new value if need.
312        if (addCurrentParam) {
313          // Exclude all other values of this facet from query.
314          if (Drupal.ajax_facets.facetsSettings[rawFacetName].limit_active_items) {
315            Drupal.ajax_facets.excludeCurrentFacet(facetName);
316          }
317          Drupal.ajax_facets.queryState['f'][Drupal.ajax_facets.queryState['f'].length] = name_value;
318        }
319      }
320      // If we unset filter, remove it from query.
321      else {
322        for (var index in Drupal.ajax_facets.queryState['f']) {
323          if (Drupal.ajax_facets.queryState['f'].hasOwnProperty(index)) {
324            if (Drupal.ajax_facets.queryState['f'][index] != name_value) {
325              queryNew[queryNew.length] = Drupal.ajax_facets.queryState['f'][index];
326            }
327          }
328        }
329        Drupal.ajax_facets.queryState['f'] = queryNew;
330      }
331    }
332
333    // Fix to remove duplicate facet params - Added by James 2021-10-18
334    Drupal.ajax_facets.queryState['f'] = [...new Set(Drupal.ajax_facets.queryState['f'])].sort();
335
336    Drupal.ajax_facets.sendAjaxQuery({
337      pushStateNeeded: !Drupal.ajax_facets.ajax_facets_buttons,
338      searchResultsNeeded: !Drupal.ajax_facets.ajax_facets_buttons
339    }, $this);
340  };
341
342  /**
343   * Callback for onClick event for widget links.
344   */
345  Drupal.ajax_facets.processLink = function (event) {
346    var $this = $(this),
347      facetName = $this.data('facet-name'),
348      name_value = facetName + ':' + $this.attr('data-facet-value'),// $.data can round decimal values like 4.0 to 4, avoid it.
349      rawFacetName = $this.data('raw-facet-name');
350    // Init history.
351    Drupal.ajax_facets.initHistoryState($this);
352    // If facets are already defined in queryState.
353    if (Drupal.ajax_facets.queryState['f']) {
354      var queryNew = [];
355      /* Handle value - deactivate. */
356      if ($this.hasClass('facetapi-active')) {
357        for (var index in Drupal.ajax_facets.queryState['f']) {
358          if (Drupal.ajax_facets.queryState['f'].hasOwnProperty(index)) {
359            if (Drupal.ajax_facets.queryState['f'][index] !== name_value) {
360              queryNew[queryNew.length] = Drupal.ajax_facets.queryState['f'][index];
361            }
362          }
363        }
364        Drupal.ajax_facets.queryState['f'] = queryNew;
365      }
366      /* Handle value - activate. */
367      else if ($this.hasClass('facetapi-inactive')) {
368        var addCurrentParam = true;
369        for (var index in Drupal.ajax_facets.queryState['f']) {
370          if (Drupal.ajax_facets.queryState['f'].hasOwnProperty(index)) {
371            if (Drupal.ajax_facets.queryState['f'][index] === name_value) {
372              addCurrentParam = false;
373            }
374          }
375        }
376        if (addCurrentParam) {
377          // Exclude all other values of this facet from query.
378          if (Drupal.ajax_facets.facetsSettings[rawFacetName].limit_active_items) {
379            Drupal.ajax_facets.excludeCurrentFacet(facetName);
380          }
381          Drupal.ajax_facets.queryState['f'][Drupal.ajax_facets.queryState['f'].length] = name_value;
382        }
383      }
384    }
385
386    Drupal.ajax_facets.sendAjaxQuery({
387      pushStateNeeded: !Drupal.ajax_facets.ajax_facets_buttons,
388      searchResultsNeeded: !Drupal.ajax_facets.ajax_facets_buttons
389    }, $this);
390    event.preventDefault();
391  };
392
393  /**
394   * Callback for slide event for widget ranges.
395   */
396  Drupal.ajax_facets.processSlider = function($sliderWrapper, min, max) {
397    var $this = $(this),
398      facetName = $sliderWrapper.data('facet-name');
399    // Init history.
400    Drupal.ajax_facets.initHistoryState($sliderWrapper);
401    // If facets are already defined in queryState.
402    if (Drupal.ajax_facets.queryState['f']) {
403      // Exclude all values for this facet from query.
404      Drupal.ajax_facets.excludeCurrentFacet(facetName);
405      Drupal.ajax_facets.queryState['f'][Drupal.ajax_facets.queryState['f'].length] = facetName + ':[' + min + ' TO ' + max + ']';
406    }
407
408    Drupal.ajax_facets.sendAjaxQuery({
409      pushStateNeeded: !Drupal.ajax_facets.ajax_facets_buttons,
410      searchResultsNeeded: !Drupal.ajax_facets.ajax_facets_buttons
411    }, $this);
412  };
413
414  /**
415   * Exclude all values for this facet from query.
416   */
417  Drupal.ajax_facets.excludeCurrentFacet = function (facetName) {
418    facetName = facetName + ':';
419    var queryNew = [];
420    for (var index in Drupal.ajax_facets.queryState['f']) {
421      if (Drupal.ajax_facets.queryState['f'].hasOwnProperty(index)) {
422        if (Drupal.ajax_facets.queryState['f'][index].substring(0, facetName.length) != facetName) {
423          queryNew[queryNew.length] = Drupal.ajax_facets.queryState['f'][index];
424        }
425      }
426    }
427    Drupal.ajax_facets.queryState['f'] = queryNew;
428  };
429
430  /**
431   * Send ajax.
432   */
433  Drupal.ajax_facets.sendAjaxQuery = function (options, $facet) {
434    Drupal.ajax_facets.beforeAjax();
435    var data = Drupal.ajax_facets.queryState;
436    // Render the exposed filter data to send along with the aja
436x request
437    $.each(Drupal.ajax_facets.queryState.views, function (key, view) {
438      var exposedFormId = '#views-exposed-form-' + view.view_name + '-' + view.view_display_id;
439      exposedFormId = exposedFormId.replace(/\_/g, '-');
440      $.each($(exposedFormId).serializeArray(), function (index, value) {
441        data[value.name] = value.value;
442      });
443    });
444
445    // Set or reset current facet.
446    Drupal.ajax_facets.current_facet_name = $facet ? $facet.data('raw-facet-name') : undefined;
447
448    // Do not auto update facets and counts if auto update is turned off in block settings.
449    if (!Drupal.settings.ajax_facets.auto_update_facets && !options.searchResultsNeeded && !options.pushStateNeeded) {
450      return;
451    }
452
453    // Notify Drupal do we need search results or not.
454    data.searchResultsNeeded = options.searchResultsNeeded;
455
456    var settings = {
457      url: encodeURI(Drupal.settings.basePath + Drupal.settings.pathPrefix + 'ajax/ajax_facets/refresh'),
458      submit: {'ajax_facets': data}
459    };
460    var ajax = new Drupal.ajax(false, false, settings);
461    ajax.success = function (response, status) {
462      // Push new state only on successful ajax response.
463      if (options.pushStateNeeded) {
464        Drupal.ajax_facets.writeState(false);
465      }
466      // Pass back to original method.
467      Drupal.ajax.prototype.success.call(this, response, status);
468    };
469    ajax.eventResponse(ajax, {});
470  };
471
472  Drupal.ajax_facets.getFacetValues = function () {
473    var f = Drupal.ajax_facets.queryState.f;
474    var facets_values = {};
475    var symbol = ':';
476    jQuery.each(f, function (k, v) {
477      var parts = v.split(symbol);
478      var value = parts[parts.length - 1];
479      var appendix = symbol + value;
480      var key = decodeURIComponent(v.substr(0, v.length - appendix.length));
481      facets_values[key] = value;
482    });
483    return facets_values;
484  };
485
486  /* Get view dom id for both modes of views - ajax/not ajax. */
487  Drupal.ajax_facets.getViewDomId = function(settings, key) {
488    // We use facetapi_view_display_id to find data in settings settings.facetapi.view_dom_id,
489    // because it can has there values like view_dom_id + numeric suffix
490    // if the same views display used several times on the page.
491    var nameDisplay = settings.facetapi.views[key].view_name + ':' + settings.facetapi.views[key].facetapi_view_display_id;
492    if (settings.facetapi.view_dom_id[nameDisplay]) {
493      var view = $('.view-dom-id-' + settings.facetapi.view_dom_id[nameDisplay]);
494      if (view.length > 0) {
495        return settings.facetapi.view_dom_id[nameDisplay]
496      }
497    }
498    return false;
499  };
500
501  /**
502   * Returns query string with selected facets.
503   */
504  Drupal.ajax_facets.getFacetsQueryUrl = function (baseUrl) {
505    var query = {
506      'f': []
507    };
508
509    // Clone variable.
510    $.extend(true, query.f, Drupal.ajax_facets.queryState.f);
511
512    // Facetapi module has a bug when facet name encodes twice.
513    // For example to get this facet work 'category:name:pineapple' it should be 'category%253Aname%3Apineapple'.
514    // It means that first ':' was encoded twice. Why we don't patch facetapi? Because a lot of sites have already
515    // used facetapi module and they links (with wrong encoded facets names) has been indexed by search engines
516    // like Bing or Google. So we just bring this behaviour to ajax_facets module.
517    // Encode each facet filter name (it have already encoded once in FacetapiAjaxWidgetCheckboxes::buildListItems()).
518    for (var filter in query.f) {
519      query.f[filter] = encodeURI(query.f[filter]);
520    }
521
522    if (Drupal.ajax_facets.queryState.query) {
523      query.query = Drupal.ajax_facets.queryState.query;
524    }
525
526    if (Drupal.ajax_facets.queryState.order) {
527      query.order = Drupal.ajax_facets.queryState.order;
528      query.sort = Drupal.ajax_facets.queryState.sort;
529    }
530
531    if (Drupal.ajax_facets.queryState.pages) {
532      query.pages = Drupal.ajax_facets.queryState.pages;
533    }
534
535    // Respect existing query parameters in url.
536    // Merge respected parameters with facet parameters recursively without duplicates.
537    $.extend(true, query, Drupal.ajax_facets.simplifyObject(Drupal.ajax_facets.getAdditionalQueryParameters()));
538
539    // Add query string to base url.
540    if (!$.isEmptyObject(query)) {
541      // Clear empty items.
542      for (var item in query) {
543        if (typeof query[item] == 'undefined' || query[item].length == 0) {
544          delete query[item];
545        }
546      }
547      var params = $.param(query);
548      // If we have params.
549      if (params.length) {
550        // This change allows selected facets with ampersands in their data to be correctly decoded - Modifed by James 2021-10-18
551        // Original only breaks when directly going to an URL in the browser e.g. /search?f[]=search_api_aggregation_2:Accounting,%20Analysis%20&%20Reporting
552        // It treats the '&' as part of the query string 'Accounting, Analysis & Reporting'
553        params = params.replace('f%5B%5D', 'f[]'); // Manually decode facet keys so we can just deal with facet values
554        let newParams = [];
555        let searchParams = new URLSearchParams(params); // Parse query -- https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams
556        for(const [key, value] of searchParams){
557          let decodedValue = decodeURIComponent(decodeURIComponent(value)); // Double decode to make every reserved character equal (some will be double encoded, some will be single encoded)
558          newParams.push(key + '=' + encodeURIComponent(decodedValue)); // Encode component once
559        }
560        // Re-create query string
561        if(newParams){
562          newParams = newParams.join('&');
563        }
564        // Update params if different
565        if(params != newParams){
566          params = newParams;
567        }
568        baseUrl += '?' + params; // No longer need to decode here as reserved characters are now properly encoded once
569      }
570    }
571
572    return baseUrl;
573  };
574
575  /**
576   * Returns additional (not facet) query parameters from current url.
577   */
578  Drupal.ajax_facets.getAdditionalQueryParameters = function () {
579    var result = {};
580
581    // If we have GET params.
582    if (window.location.href.indexOf('?') !== -1) {
583      var respectedParameters = window.location.href.split('?')[1].split('&');
584
585      // Get all query parameters in array.
586      for (var i = 0; i < respectedParameters.length; i++) {
587        var pair = respectedParameters[i].split('=');
588
589        // Remove brackets from multiple parameter.
590        pair[0] = pair[0].replace(/\[\d*\]/, '');
591
592        // Remove encoded brackets from multiple parameter. (Added by James 2021-10-18)
593        // This fixes duplicated facets that are present in initial URL when facets are changed after load by AJAX
594        pair[0] = pair[0].replace('f%5B%5D', 'f');
595
596        // We interested only in additional parameters but not in facet parameters.
597        if (pair[0] !== 'f') {
598          if (!result[pair[0]]) {
599            result[pair[0]] = [];
600            result[pair[0]].push(pair[1]);
601          } else {
602            result[pair[0]].push(pair[1]);
603          }
604        }
605      }
606    }
607    return result;
608  };
609
610  /**
611   * Returns simplified object.
612   *
613   * If object has array property with only one element then that array will be turn into simple value instead of array.
614   */
615  Drupal.ajax_facets.simplifyObject = function (obj) {
616    for (var name in obj) {
617      if (obj[name].length === 1) {
618        obj[name] = obj[name][0];
619      }
620    }
621
622    return obj;
623  };
624
625  /**
626   * Initialize the history state. We only want to do this on the initial page
627   * load but we can't call it until after a facet has been clicked because we
628   * need to communicate which one is being "deactivated" for our ajax success
629   * handler.
630   */
631  Drupal.ajax_facets.initHistoryState = function ($facet) {
632    // Set the initial state only initial page load.
633    if (Drupal.ajax_facets.firstLoad) {
634      Drupal.ajax_facets.firstLoad = false;
635      Drupal.ajax_facets.writeState(true);
636    }
637  };
638
639  /**
640   * Writes new state to browser history, either as a push or replace call.
641   *
642   * History.js library fires "statechange" event even on API push/replace calls.
643   * So before pushing new state to history we should unbind from this event and after bind again.
644   *
645   * @param {boolean} replace Set to true to replace state, rather than push it.
646   */
647  Drupal.ajax_facets.writeState = function (replace) {
648    var stateUrl = null,
649      state = {
650        facets: Drupal.ajax_facets.queryState['f']
651      },
652      title = document.title,
653      method = 'replaceState';
654
655    if (replace !== true) {
656      stateUrl = Drupal.ajax_facets.getFacetsQueryUrl(Drupal.settings.facetapi.searchUrl);
657      method = 'pushState';
658    }
659
660    // Try to use HTML5 history object.
661    if (history[method]) {
662      history[method](state, title, stateUrl);
663    }
664    else {
665      // If history.js available - use it.
666      if (Drupal.settings.facetapi.isHistoryJsExists) {
667        var $window = $(window);
668
669        $window.unbind('statechange', Drupal.ajax_facets.reactOnStateChange);
670        History[method](state, title, stateUrl);
671        $window.bind('statechange', Drupal.ajax_facets.reactOnStateChange);
672      }
673    }
674  };
675
676  /**
677   * Callback for back/forward browser buttons.
678   */
679  Drupal.ajax_facets.reactOnStateChange = function () {
680    var state = null,
681      facets = [];
682
683    // Try to use HTML5 history object.
684    if (history.pushState) {
685      if (state = history.state) {
686        facets = state.facets;
687      }
688    }
689    else {
690      // If history.js available - use it.
691      if (Drupal.settings.facetapi.isHistoryJsExists) {
692        if (state = History.getState()) {
693          facets = state.data.facets;
694        }
695      }
696    }
697
698    // Do something only if paths are match and facets is defined.
699    if (window.location.pathname === Drupal.settings.facetapi.searchUrl && facets) {
700      Drupal.ajax_facets.queryState['f'] = facets;
701      Drupal.ajax_facets.force_update_results = true;
702      Drupal.ajax_facets.sendAjaxQuery({
703        pushStateNeeded: false,
704        searchResultsNeeded: true
705      });
706    }
707  };
708
709  // If user opened new page and then clicked browser's back button then would not be fired "statechange" event.
710  // So we need to bind on 'statechange' event and react only once.
711  // All farther work does Drupal.ajax_facets.pushState() function.
712  // Try to use HTML5 history object.
713  if (history) {
714    window.onpopstate = function () {
715      Drupal.ajax_facets.reactOnStateChange();
716    };
717  } else {
718    // If history.js Adapter available - use it to bind "statechange" event.
719    if (History.Adapter && Drupal.ajax_facets.firstLoad) {
720      History.Adapter.bind(window, 'statechange', Drupal.ajax_facets.reactOnStateChange);
721    }
722  }
723
724  /* Show tooltip if facet results are not updated by ajax (in settings). */
725  Drupal.ajax_facets.showTooltip = function ($, response) {
726    // Reset the timeout handler to avoid troubles when user is clicking on items very fast.
727    window.clearTimeout(Drupal.ajax_facets.tooltipTimeout);
728
729    // If we have chosen facet.
730    if (Drupal.ajax_facets.current_facet_name) {
731      var pos = $('[data-raw-facet-name=' + Drupal.ajax_facets.current_facet_name + ']').offset(),
732        $tooltip = $('#ajax-facets-tooltip');
733      $tooltip.css('top', pos.top - 15);
734      $tooltip.css('left', pos.left - $tooltip.width() - 40);
735      $tooltip.show();
736      $tooltip.find('span').html(Drupal.t('Found:') + ' ' + response.total_results);
737
738      Drupal.ajax_facets.tooltipTimeout = setTimeout(function () {
739        $tooltip.hide(250);
740      }, 3000);
741    }
742  };
743
744  if (Drupal.ajax) {
745    // Command for process search results and facets by ajax.
746    Drupal.ajax.prototype.commands.ajax_facets_update_content = function(ajax, response) {
747      if (response.data.activeItems) {
748        Drupal.ajax_facets.facetQueryState = response.data.activeItems;
749      }
750      // After Ajax success we should update reset, apply link to handle proper redirects.
751      if (response.data.resetUrls && Drupal.settings.facetapi.facets) {
752        for (var index in Drupal.settings.facetapi.facets) {
753          if (response.data.resetUrls[Drupal.settings.facetapi.facets[index].facetName]) {
754            // Update path from response.
755            Drupal.settings.facetapi.facets[index].resetPath = response.data.resetUrls[Drupal.settings.facetapi.facets[index].facetName];
756          }
757        }
758      }
759
760      // Update content.
761      if (response.data.newContent) {
762        for (var id in response.data.newContent) {
763          var $blockToReplace = $('#' + id + '-wrapper');
764          if ($blockToReplace.size()) {
765            $blockToReplace.replaceWith(response.data.newContent[id]);
766          }
767          var $block = $('#' + id + '-wrapper').parents('div.block--ajax_facets:not
767(:visible)');
768          if ($block.size()) {
769            $block.show();
770          }
771        }
772      }
773
774      /* Update results if we have they and ajax facets buttons are not enabled or force mode is enabled. */
775      var show_tip = false;
776      var buttons_exist = Drupal.ajax_facets.ajax_facets_buttons,
777        force_update_results = Drupal.ajax_facets.force_update_results;
778      if (!buttons_exist || force_update_results) {
779        $.each(response.data.views, function (key, view) {
780          $('.view-dom-id-' + view.view_dom_id).replaceWith(view.content);
781        });
782
783        // Update the exposed form separately if needed.
784        $.each(response.data.views, function(key, view) {
785          if (view.exposed_form) {
786            var viewId = view.view_name + '-' + view.view_display_id;
787            $('#views-exposed-form-' + viewId.replace(/_/g, '-')).replaceWith(view.exposed_form);
788          }
789        });
790      }
791      else {
792        show_tip = true;
793      }
794
795      // As some blocks could be empty in results of filtering - hide them.
796      if (response.data.hideBlocks) {
797        for (var id in response.data.hideBlocks) {
798          if (response.data.hideBlocks.hasOwnProperty(id)) {
799            // Search for drupal blocks (id="block-facetapi-xcogxrrjodjldoogufmypf7sc4rpofuk").
800            var $block = $('#' + response.data.hideBlocks[id]);
801            if ($block.size()) {
802              $block.hide();
803            }
804            // Search for panel panes (class="block-facetapi-xcogxrrjodjldoogufmypf7sc4rpofuk").
805            else {
806              var $pane = $('.' + response.data.hideBlocks[id]);
807              if ($pane.size()) {
808                $pane.hide();
809              }
810            }
811          }
812        }
813      }
814
815      if (response.data.settings.views) {
816        Drupal.settings.views = response.data.settings.views;
817      }
818
819      // We add this here so we can run custom code after content updates
820      Drupal.ajax_facets.afterContentUpdate(ajax, response);
821
822      Drupal.attachBehaviors();
823      // Show the tooltip if need.
824      if (show_tip) {
825        Drupal.ajax_facets.showTooltip($, response.data);
826      }
827    };
828  }
829})(jQuery);

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.