PageSourceSearch

https://www.pace.edu/modules/contrib/views_ajax_history/js/views_ajax_history.js?tlultg

js pace.edu collected 2026-09-24 08:28:43 UTC 13,556 bytes, 406 lines download raw bytes

1/**
2 * @file
3 * Contains views_ajax_history.js.
4 */
5
6(function ($, Drupal, drupalSettings) {
7
8  // Need to keep this to check if there are extra parameters in the original URL.
9  var original = {
10    path: window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port : '') + window.location.pathname,
11    // @TODO integrate #1359798 without breaking history.js
12    query: window.location.search || ''
13  };
14
15 window.onpageshow = function (event) {
16   if (event.persisted) {
17     window.location.reload()
18   }
19 };
20
21  Drupal.ViewsAjaxHistory = Drupal.ViewsAjaxHistory || {};
22
23  /**
24   * Keep the original beforeSubmit method to use it later.
25   */
26  Drupal.ViewsAjaxHistory.beforeSubmit = Drupal.Ajax.prototype.beforeSubmit;
27
28  /**
29   * Keep the original beforeSerialize method to use it later.
30   */
31  Drupal.ViewsAjaxHistory.beforeSerialize = Drupal.Ajax.prototype.beforeSerialize;
32
33  /**
34   * Keep the original beforeSend method to use it later.
35   */
36  Drupal.ViewsAjaxHistory.beforeSend = Drupal.Ajax.prototype.beforeSend;
37
38  Drupal.behaviors.viewsAjaxHistory = {
39    attach: function (context, drupalSettings) {
40      // Init the current page too, because the first loaded pager element do
41      // not have loadable history and will not work the back button.
42      if (once('views-ajax-history-first-page-load', 'body').length) {
43        drupalSettings.viewsAjaxHistory.onloadPageItem = drupalSettings.viewsAjaxHistory.renderPageItem;
44      }
45    }
46  };
47
48  /**
49   * Modification of Drupal.Views.parseQueryString() to allow extracting multivalues fields.
50   *
51   * @param query
52   *   String, either a full url or just the query string.
53   */
54  var parseQueryString = function (query) {
55    var args = {};
56    var pos = query.indexOf('?');
57    if (pos != -1) {
58      query = query.substring(pos + 1);
59    }
60    var pairs = query.split('&');
61    var pair, key, value;
62    for (var i in pairs) {
63      if (typeof(pairs[i]) == 'string') {
64        pair = pairs[i].split('=');
65        // Ignore the 'q' path argument, if present.
66        if (pair[0] != 'q' && pair[1]) {
67          key = decodeURIComponent(pair[0].replace(/\+/g, ' '));
68          value = decodeURIComponent(pair[1].replace(/\+/g, ' '));
69          // Field name ends with [], it's multivalues.
70          if (/\[\]$/.test(key)) {
71            if (!(key in args)) {
72              args[key] = [value];
73            }
74            // Don't duplicate values.
75            else if (!$.inArray(value, args[key]) !== -1) {
76              args[key].push(value);
77            }
78          }
79          else {
80            args[key] = value;
81          }
82        }
83      }
84    }
85    return args;
86  };
87
88  /**
89   * Strip views values and duplicates from URL.
90   *
91   * @param url
92   *   String with the full URL to clean up.
93   * @param viewArgs
94   *   Object containing field values from views.
95   *
96   * @return url
97   *   String URL with views values and reduced duplicates.
98   */
99  var cleanURL = function (url, viewArgs) {
100    var args = ('reset' in viewArgs) ? {} : parseQueryString(url);
101    var query = [];
102
103    // With clean urls off we need to add the 'q' parameter.
104    if (/\?/.test(drupalSettings.views.ajax_path)) {
105      query.push('q=' + Drupal.Views.getPath(url));
106    }
107
108    $.each(args, function (name, value) {
109      // Use values from viewArgs if they exists.
110      if (name in viewArgs) {
111        value = viewArgs[name];
112      }
113      if (Array.isArray(value)) {
114        $.merge(query, $.map($.unique(value), function (sub) {
115          return name + '=' + encodeURIComponent(sub);
116        }));
117      }
118      else {
119        query.push(name + '=' + encodeURIComponent(value));
120      }
121    });
122
123    url = window.location.href.split('?');
124    return url[0] + (query.length ? '?' + query.join('&') : '');
125  };
126
127  /**
128   * Remove the functions from the state. They can't been pushed into the history.
129   *
130   * @param state
131   *  Object containing the state to be cleaned.
132   *
133   * @return state
134   *  Object that has been cleaned up.
135   */
136  var cleanStateForHistory = function (state) {
137    var stateWithNoFunctions = {};
138    for (var key in state) {
139      if (typeof state[key] !== "function") {
140        stateWithNoFunctions[key] = state[key];
141      }
142    }
143    return stateWithNoFunctions;
144  };
145
146  /**
147   * Parse a URL query string.
148   *
149   * @param queryString
150   *   String containing the query to parse.
151   */
152  var parseQuery = function (queryString) {
153    var query = {};
154    $.map(queryString.split('&'), function (val) {
155      var s = val.split('=');
156      query[s[0]] = s[1];
157    });
158    return query;
159  };
160
161  /**
162   * Unbind 'popstate' when adding a new state to avoid an infinite loop.
163   *
164   * We only use the 'popstate' event to trigger refresh on back of forward click.
165   *
166   * @param options
167   *   Object containing the values from views' AJAX call.
168   * @param url
169   *   String with the current URL to be cleaned up.
170   */
171  var addState = function (options, url) {
172    // The data in the history state must be serializable.
173    var historyOptions = $.extend({}, options);
174
175    // Store the actual view's dom id.
176    drupalSettings.viewsAjaxHistory.lastViewDomID = options.data.view_dom_id;
177    $(window).unbind('popstate', loadView);
178    history.pushState(cleanStateForHistory(historyOptions), document.title, cleanURL(url, options.data));
179    $(window).bind('popstate', loadView);
180  };
181
182  /**
183   * Make an AJAX request to update the view when navigating back and forth.
184   */
185  var loadView = function () {
186    var options;
187
188    // This should be the first loaded page, so init the options object.
189    if (history.state === null) {
190      var viewsAjaxSettingsKey = 'views_dom_id:' + drupalSettings.viewsAjaxHistory.lastViewDomID;
191      if (drupalSettings.views.ajaxViews.hasOwnProperty(viewsAjaxSettingsKey)) {
192        var viewsAjaxSettings = drupalSettings.views.ajaxViews[viewsAjaxSettingsKey];
193        var initialExposedInput = drupalSettings.viewsAjaxHistory.initialExposedInput[viewsAjaxSettingsKey];
194        $.extend(viewsAjaxSettings,initialExposedInput);
195        viewsAjaxSettings.page = drupalSettings.viewsAjaxHistory.onloadPageItem;
196        options = {
197          data: viewsAjaxSettings,
198          url: drupalSettings.views.ajax_path
199        };
200      }
201    }
202    else {
203      options = history.state;
204    }
205
206    // Drupal's AJAX options.
207    var settings = $.extend({
208      submit: options.data,
209      setClick: true,
210      event: 'click',
211      selector: '.view-dom-id-' + options.data.view_dom_id,
212      progress: { type: 'throbber' },
213      httpMethod: 'GET',
214    }, options);
215
216    var viewsAjaxSubmit = Drupal.ajax(settings);
217    // Trigger ajax call.
218    viewsAjaxSubmit.execute();
219  };
220
221  /**
222   * Check to see if facets are enabled for the view.
223   *
224   * @param viewName string
225   *
226   * return bool
227   *   Whether the view has facets.
228   */
229  var hasFacets = function (viewName) {
230    var hasFacets = false;
231    if (drupalSettings.facets_views_ajax) {
232      // Loop through the facets.
233      $.each(drupalSettings.facets_views_ajax, function (facetId, facetSettings) {
234        if (facetSettings.view_id === viewName) {
235          // Yes, facets are enabled for this view.
236          hasFacets = true;
237        }
238      });
239    }
240
241    return hasFacets;
242  }
243
244  /**
245   * Override beforeSerialize to handle click on pager links.
246   *
247   * @param $element
248   *   jQuery DOM element
249   * @param options
250   */
251  Drupal.Ajax.prototype.beforeSerialize = function (element, options) {
252    // Check that we handle a click on a link, not a form submission.
253    if (options.data.view_name && element && $(element).is('a')) {
254      let params = new URLSearchParams($(element).attr('href'));
255      if (!$.isEmptyObject(drupalSettings.viewsAjaxHistory.excludeArgs)) {
256        var keysToRemove = [];
257        $.each(drupalSettings.viewsAjaxHistory.excludeArgs, function (index, pathToExclude) {
258          params.forEach(function (value, key, parent) {
259            if (key.startsWith(pathToExclude)) {
260              keysToRemove.push(key);
261            }
262          });
263        });
264        keysToRemove.forEach(function (key) {
265          params.delete(key)
266        });
267      }
268      addState(options, '?' + params.toString());
269    }
270
271    // Call the original Drupal method with the right context.
272    Drupal.ViewsAjaxHistory.beforeSerialize.apply(this, arguments);
273  };
274
275  /**
276   * Override beforeSubmit to handle exposed form submissions.
277   *
278   * @param form_values
279   *   Object with all field values.
280   * @param element
281   *   jQuery DOM form element.
282   * @param options
283   *   Object containing AJAX options.
284   */
285  Drupal.Ajax.prototype.beforeSubmit = function (form_values, element, options) {
286    if (options && options.data && options.data.view_name) {
287      var url = original.path + '?' + new URLSearchParams(new FormData(element.get(0))).toString();
288      var currentQuery = parseQueryString(window.location.href);
289
290      // Prepare ajax url
291      var ajaxUrl = options.url.split('?')[0];
292      var ajaxQuery = parseQueryString(options.url);
293
294      // Remove the page number from the query string, as a new filter has been
295      // applied and should return new results.
296      if ($.inArray("page", Object.keys(currentQuery)) !== -1) {
297        delete currentQuery.page;
298        delete ajaxQuery.page;
299      }
300
301      // Copy selected values in history state.
302      $.each(form_values, function () {
303        // Field name ending with [] is a multi value field.
304        if (/\[\]$/.test(this.name)) {
305          if (!options.data[this.name]) {
306            options.data[this.name] = [];
307          }
308          options.data[this.name].push(this.value);
309        }
310        // Regular field.
311        else {
312          options.data[this.name] = this.value;
313        }
314      });
315      // Remove exposed data from the current query to leave behind any
316      // non exposed form related query vars.
317      element.find('[name]').each(function () {
318        if (currentQuery[this.name]) {
319          delete currentQuery[this.name];
320          delete ajaxQuery[this.name];
321        }
322      });
323
324      // If the exposed form has checkboxes, we need to check if these are
325      // unchecked and if so, remove them from the url
326      element.find('input[type="checkbox"]').each(function (key, value) {
327        if (!form_values[this.name]) {
328          if (currentQuery[this.name]) {
329            delete currentQuery[this.name];
330          }
331          else if (options.data[this.name]) {
332            delete options.data[this.name];
333          }
334          if (ajaxQuery[this.name]) {
335            delete ajaxQuery[this.name];
336          }
337        }
338      });
339
340      // Helper function to remove multi-value query keys that match a given
341      // name
342      let removeMultiValueQueryKeys = function (multiValueParamToRemove, queryParams) {
343        Object.getOwnPropertyNames(queryParams).forEach(function (queryKey) {
344          let queryKeyWithoutBracket = queryKey.replace(/\[\d+]$/, '');
345          if (multiValueParamToRemove === queryKeyWithoutBracket) {
346            delete queryParams[queryKey];
347          }
348        });
349        return queryParams;
350      };
351
352      // If the exposed form has a multiple select.
353      element.find('select[multiple]').each(function (key, value) {
354        if ($(value).val().length === 0) {
355          delete options.data[this.name];
356          delete currentQuery[this.name];
357          delete ajaxQuery[this.name];
358        }
359        // Pagers creates query params that are indexed like this:
360        // ?someparam[0]=123,someparam[1]=456
361        // instead of this:
362        // ?someparam[]=123&someparam[]=456
363        // We need to clear them out. The submitted form values will use the
364        // non-indexed versions, and we can't have the indexed versions creating
365        // a conflict.
366        let nameWithoutBracket = this.name.replace(/\[]$/, '');
367        currentQuery = removeMultiValueQueryKeys(nameWithoutBracket, currentQuery);
368        ajaxQuery = removeMultiValueQueryKeys(nameWithoutBracket, ajaxQuery);
369      });
370
371      url += (/\?/.test(url) ? '&' : '?') + $.param(currentQuery);
372      // Update options with updated ajax url.
373      options.url = ajaxUrl + '?' + $.param(ajaxQuery);
374      addState(options, url);
375    }
376    // Call the original Drupal method with the right context.
377    Drupal.ViewsAjaxHistory.beforeSubmit.apply(this, arguments);
378  };
379
380  /**
381   * Override beforeSend to clean up the Ajax submission URL.
382   *
383   * @param {XMLHttpRequest} xmlhttprequest
384   *   Native Ajax object.
385   * @param {object} options
386   *   jQuery.ajax options.
387   */
388  Drupal.Ajax.prototype.beforeSend = function (xmlhttprequest, options) {
389    var data = (typeof options.data === 'string') ? parseQuery(options.data) : {};
390
391    if (data.view_name && options.type !== 'GET') {
392      if (hasFacets(data.view_name) === true) {
393        var currentQuery = parseQueryString(window.location.href);
394        options.url = drupalSettings.views.ajax_path + '?' + $.param(currentQuery) + '&' + Drupal.ajax.WRAPPER_FORMAT + '=drupal_ajax';
395      }
396      else {
397        // Override the URL to not contain any fields that were submitted.
398        var delimiter = drupalSettings.views.ajax_path.indexOf('?') === -1 ? '?' : '&';
399        options.url = drupalSettings.views.ajax_path + delimiter + Drupal.ajax.WRAPPER_FORMAT + '=drupal_ajax';
400      }
401    }
402    // Call the original Drupal method with the right context.
403    Drupal.ViewsAjaxHistory.beforeSend.apply(this, arguments);
404  }
405
406})(jQuery, Drupal, drupalSettings);

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.