Further Drupal 8.6.4 changes. Some core files were not committed before a commit...
[yaffs-website] / web / core / modules / views / js / ajax_view.es6.js
1 /**
2  * @file
3  * Handles AJAX fetching of views, including filter submission and response.
4  */
5
6 (function($, Drupal, drupalSettings) {
7   /**
8    * Attaches the AJAX behavior to exposed filters forms and key View links.
9    *
10    * @type {Drupal~behavior}
11    *
12    * @prop {Drupal~behaviorAttach} attach
13    *   Attaches ajaxView functionality to relevant elements.
14    */
15   Drupal.behaviors.ViewsAjaxView = {};
16   Drupal.behaviors.ViewsAjaxView.attach = function(context, settings) {
17     if (settings && settings.views && settings.views.ajaxViews) {
18       const {
19         views: { ajaxViews },
20       } = settings;
21       Object.keys(ajaxViews || {}).forEach(i => {
22         Drupal.views.instances[i] = new Drupal.views.ajaxView(ajaxViews[i]);
23       });
24     }
25   };
26   Drupal.behaviors.ViewsAjaxView.detach = (context, settings, trigger) => {
27     if (trigger === 'unload') {
28       if (settings && settings.views && settings.views.ajaxViews) {
29         const {
30           views: { ajaxViews },
31         } = settings;
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];
37           }
38         });
39       }
40     }
41   };
42
43   /**
44    * @namespace
45    */
46   Drupal.views = {};
47
48   /**
49    * @type {object.<string, Drupal.views.ajaxView>}
50    */
51   Drupal.views.instances = {};
52
53   /**
54    * Javascript object for a certain view.
55    *
56    * @constructor
57    *
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.
62    */
63   Drupal.views.ajaxView = function(settings) {
64     const selector = `.js-view-dom-id-${settings.view_dom_id}`;
65     this.$view = $(selector);
66
67     // Retrieve the path to use for views' ajax.
68     let ajaxPath = drupalSettings.views.ajax_path;
69
70     // If there are multiple views this might've ended up showing up multiple
71     // times.
72     if (ajaxPath.constructor.toString().indexOf('Array') !== -1) {
73       ajaxPath = ajaxPath[0];
74     }
75
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
81         .slice(1)
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;
87       }
88     }
89
90     this.element_settings = {
91       url: ajaxPath + queryString,
92       submit: settings,
93       setClick: true,
94       event: 'click',
95       selector,
96       progress: { type: 'fullscreen' },
97     };
98
99     this.settings = settings;
100
101     // Add the ajax to exposed forms.
102     this.$exposed_form = $(
103       `form#views-exposed-form-${settings.view_name.replace(
104         /_/g,
105         '-',
106       )}-${settings.view_display_id.replace(/_/g, '-')}`,
107     );
108     this.$exposed_form
109       .once('exposed-form')
110       .each($.proxy(this.attachExposedFormAjax, this));
111
112     // Add the ajax to pagers.
113     this.$view
114       // Don't attach to nested views. Doing so would attach multiple behaviors
115       // to a given element.
116       .filter($.proxy(this.filterNestedViews, this))
117       .once('ajax-pager')
118       .each($.proxy(this.attachPagerAjax, this));
119
120     // Add a trigger to update this view specifically. In order to trigger a
121     // refresh use the following code.
122     //
123     // @code
124     // $('.view-name').trigger('RefreshView');
125     // @endcode
126     const selfSettings = $.extend({}, this.element_settings, {
127       event: 'RefreshView',
128       base: this.selector,
129       element: this.$view.get(0),
130     });
131     this.refreshViewAjax = Drupal.ajax(selfSettings);
132   };
133
134   /**
135    * @method
136    */
137   Drupal.views.ajaxView.prototype.attachExposedFormAjax = function() {
138     const that = this;
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'),
147           element: this,
148         });
149         that.exposedFormAjax[index] = Drupal.ajax(selfSettings);
150       });
151   };
152
153   /**
154    * @return {bool}
155    *   If there is at least one parent with a view class return false.
156    */
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;
161   };
162
163   /**
164    * Attach the ajax behavior to each link.
165    */
166   Drupal.views.ajaxView.prototype.attachPagerAjax = function() {
167     this.$view
168       .find(
169         'ul.js-pager__items > li > a, th.views-field a, .attachment .views-summary a',
170       )
171       .each($.proxy(this.attachPagerLinkAjax, this));
172   };
173
174   /**
175    * Attach the ajax behavior to a singe link.
176    *
177    * @param {string} [id]
178    *   The ID of the link.
179    * @param {HTMLElement} link
180    *   The link element.
181    */
182   Drupal.views.ajaxView.prototype.attachPagerLinkAjax = function(id, link) {
183     const $link = $(link);
184     const viewData = {};
185     const href = $link.attr('href');
186     // Construct an object using the settings defaults and then overriding
187     // with data specific to the link.
188     $.extend(
189       viewData,
190       this.settings,
191       Drupal.Views.parseQueryString(href),
192       // Extract argument data from the URL.
193       Drupal.Views.parseViewArgs(href, this.settings.view_base_path),
194     );
195
196     const selfSettings = $.extend({}, this.element_settings, {
197       submit: viewData,
198       base: false,
199       element: link,
200     });
201     this.pagerAjax = Drupal.ajax(selfSettings);
202   };
203
204   /**
205    * Views scroll to top ajax command.
206    *
207    * @param {Drupal.Ajax} [ajax]
208    *   A {@link Drupal.ajax} object.
209    * @param {object} response
210    *   Ajax response.
211    * @param {string} response.selector
212    *   Selector to use.
213    */
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
217     // link.
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();
226     }
227     // Only scroll upward.
228     if (offset.top - 10 < $(scrollTarget).scrollTop()) {
229       $(scrollTarget).animate({ scrollTop: offset.top - 10 }, 500);
230     }
231   };
232 })(jQuery, Drupal, drupalSettings);