3 * Handles AJAX fetching of views, including filter submission and response.
6 (function($, Drupal, drupalSettings) {
8 * Attaches the AJAX behavior to exposed filters forms and key View links.
10 * @type {Drupal~behavior}
12 * @prop {Drupal~behaviorAttach} attach
13 * Attaches ajaxView functionality to relevant elements.
15 Drupal.behaviors.ViewsAjaxView = {};
16 Drupal.behaviors.ViewsAjaxView.attach = function(context, settings) {
17 if (settings && settings.views && settings.views.ajaxViews) {
21 Object.keys(ajaxViews || {}).forEach(i => {
22 Drupal.views.instances[i] = new Drupal.views.ajaxView(ajaxViews[i]);
26 Drupal.behaviors.ViewsAjaxView.detach = (context, settings, trigger) => {
27 if (trigger === 'unload') {
28 if (settings && settings.views && settings.views.ajaxViews) {
32 Object.keys(ajaxViews || {}).forEach(i => {
33 const selector = `.js-view-dom-id-${ajaxViews[i].view_dom_id}`;
34 if ($(selector, context).length) {
35 delete Drupal.views.instances[i];
36 delete settings.views.ajaxViews[i];
49 * @type {object.<string, Drupal.views.ajaxView>}
51 Drupal.views.instances = {};
54 * Javascript object for a certain view.
58 * @param {object} settings
59 * Settings object for the ajax view.
60 * @param {string} settings.view_dom_id
61 * The DOM id of the view.
63 Drupal.views.ajaxView = function(settings) {
64 const selector = `.js-view-dom-id-${settings.view_dom_id}`;
65 this.$view = $(selector);
67 // Retrieve the path to use for views' ajax.
68 let ajaxPath = drupalSettings.views.ajax_path;
70 // If there are multiple views this might've ended up showing up multiple
72 if (ajaxPath.constructor.toString().indexOf('Array') !== -1) {
73 ajaxPath = ajaxPath[0];
76 // Check if there are any GET parameters to send to views.
77 let queryString = window.location.search || '';
78 if (queryString !== '') {
79 // Remove the question mark and Drupal path component if any.
80 queryString = queryString
82 .replace(/q=[^&]+&?|&?render=[^&]+/, '');
83 if (queryString !== '') {
84 // If there is a '?' in ajaxPath, clean url are on and & should be
85 // used to add parameters.
86 queryString = (/\?/.test(ajaxPath) ? '&' : '?') + queryString;
90 this.element_settings = {
91 url: ajaxPath + queryString,
96 progress: { type: 'fullscreen' },
99 this.settings = settings;
101 // Add the ajax to exposed forms.
102 this.$exposed_form = $(
103 `form#views-exposed-form-${settings.view_name.replace(
106 )}-${settings.view_display_id.replace(/_/g, '-')}`,
109 .once('exposed-form')
110 .each($.proxy(this.attachExposedFormAjax, this));
112 // Add the ajax to pagers.
114 // Don't attach to nested views. Doing so would attach multiple behaviors
115 // to a given element.
116 .filter($.proxy(this.filterNestedViews, this))
118 .each($.proxy(this.attachPagerAjax, this));
120 // Add a trigger to update this view specifically. In order to trigger a
121 // refresh use the following code.
124 // $('.view-name').trigger('RefreshView');
126 const selfSettings = $.extend({}, this.element_settings, {
127 event: 'RefreshView',
129 element: this.$view.get(0),
131 this.refreshViewAjax = Drupal.ajax(selfSettings);
137 Drupal.views.ajaxView.prototype.attachExposedFormAjax = function() {
139 this.exposedFormAjax = [];
140 // Exclude the reset buttons so no AJAX behaviours are bound. Many things
141 // break during the form reset phase if using AJAX.
142 $('input[type=submit], input[type=image]', this.$exposed_form)
143 .not('[data-drupal-selector=edit-reset]')
144 .each(function(index) {
145 const selfSettings = $.extend({}, that.element_settings, {
146 base: $(this).attr('id'),
149 that.exposedFormAjax[index] = Drupal.ajax(selfSettings);
155 * If there is at least one parent with a view class return false.
157 Drupal.views.ajaxView.prototype.filterNestedViews = function() {
158 // If there is at least one parent with a view class, this view
159 // is nested (e.g., an attachment). Bail.
160 return !this.$view.parents('.view').length;
164 * Attach the ajax behavior to each link.
166 Drupal.views.ajaxView.prototype.attachPagerAjax = function() {
169 'ul.js-pager__items > li > a, th.views-field a, .attachment .views-summary a',
171 .each($.proxy(this.attachPagerLinkAjax, this));
175 * Attach the ajax behavior to a singe link.
177 * @param {string} [id]
178 * The ID of the link.
179 * @param {HTMLElement} link
182 Drupal.views.ajaxView.prototype.attachPagerLinkAjax = function(id, link) {
183 const $link = $(link);
185 const href = $link.attr('href');
186 // Construct an object using the settings defaults and then overriding
187 // with data specific to the link.
191 Drupal.Views.parseQueryString(href),
192 // Extract argument data from the URL.
193 Drupal.Views.parseViewArgs(href, this.settings.view_base_path),
196 const selfSettings = $.extend({}, this.element_settings, {
201 this.pagerAjax = Drupal.ajax(selfSettings);
205 * Views scroll to top ajax command.
207 * @param {Drupal.Ajax} [ajax]
208 * A {@link Drupal.ajax} object.
209 * @param {object} response
211 * @param {string} response.selector
214 Drupal.AjaxCommands.prototype.viewsScrollTop = function(ajax, response) {
215 // Scroll to the top of the view. This will allow users
216 // to browse newly loaded content after e.g. clicking a pager
218 const offset = $(response.selector).offset();
219 // We can't guarantee that the scrollable object should be
220 // the body, as the view could be embedded in something
221 // more complex such as a modal popup. Recurse up the DOM
222 // and scroll the first element that has a non-zero top.
223 let scrollTarget = response.selector;
224 while ($(scrollTarget).scrollTop() === 0 && $(scrollTarget).parent()) {
225 scrollTarget = $(scrollTarget).parent();
227 // Only scroll upward.
228 if (offset.top - 10 < $(scrollTarget).scrollTop()) {
229 $(scrollTarget).animate({ scrollTop: offset.top - 10 }, 500);
232 })(jQuery, Drupal, drupalSettings);