annotate core/modules/views/js/ajax_view.es6.js @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents 129ea1e6d783
children
rev   line source
Chris@0 1 /**
Chris@0 2 * @file
Chris@0 3 * Handles AJAX fetching of views, including filter submission and response.
Chris@0 4 */
Chris@0 5
Chris@17 6 (function($, Drupal, drupalSettings) {
Chris@0 7 /**
Chris@0 8 * Attaches the AJAX behavior to exposed filters forms and key View links.
Chris@0 9 *
Chris@0 10 * @type {Drupal~behavior}
Chris@0 11 *
Chris@0 12 * @prop {Drupal~behaviorAttach} attach
Chris@0 13 * Attaches ajaxView functionality to relevant elements.
Chris@0 14 */
Chris@0 15 Drupal.behaviors.ViewsAjaxView = {};
Chris@17 16 Drupal.behaviors.ViewsAjaxView.attach = function(context, settings) {
Chris@17 17 if (settings && settings.views && settings.views.ajaxViews) {
Chris@17 18 const {
Chris@17 19 views: { ajaxViews },
Chris@17 20 } = settings;
Chris@17 21 Object.keys(ajaxViews || {}).forEach(i => {
Chris@14 22 Drupal.views.instances[i] = new Drupal.views.ajaxView(ajaxViews[i]);
Chris@14 23 });
Chris@0 24 }
Chris@0 25 };
Chris@17 26 Drupal.behaviors.ViewsAjaxView.detach = (context, settings, trigger) => {
Chris@17 27 if (trigger === 'unload') {
Chris@17 28 if (settings && settings.views && settings.views.ajaxViews) {
Chris@17 29 const {
Chris@17 30 views: { ajaxViews },
Chris@17 31 } = settings;
Chris@17 32 Object.keys(ajaxViews || {}).forEach(i => {
Chris@17 33 const selector = `.js-view-dom-id-${ajaxViews[i].view_dom_id}`;
Chris@17 34 if ($(selector, context).length) {
Chris@17 35 delete Drupal.views.instances[i];
Chris@17 36 delete settings.views.ajaxViews[i];
Chris@17 37 }
Chris@17 38 });
Chris@17 39 }
Chris@17 40 }
Chris@17 41 };
Chris@0 42
Chris@0 43 /**
Chris@0 44 * @namespace
Chris@0 45 */
Chris@0 46 Drupal.views = {};
Chris@0 47
Chris@0 48 /**
Chris@0 49 * @type {object.<string, Drupal.views.ajaxView>}
Chris@0 50 */
Chris@0 51 Drupal.views.instances = {};
Chris@0 52
Chris@0 53 /**
Chris@0 54 * Javascript object for a certain view.
Chris@0 55 *
Chris@0 56 * @constructor
Chris@0 57 *
Chris@0 58 * @param {object} settings
Chris@0 59 * Settings object for the ajax view.
Chris@0 60 * @param {string} settings.view_dom_id
Chris@0 61 * The DOM id of the view.
Chris@0 62 */
Chris@17 63 Drupal.views.ajaxView = function(settings) {
Chris@0 64 const selector = `.js-view-dom-id-${settings.view_dom_id}`;
Chris@0 65 this.$view = $(selector);
Chris@0 66
Chris@0 67 // Retrieve the path to use for views' ajax.
Chris@14 68 let ajaxPath = drupalSettings.views.ajax_path;
Chris@0 69
Chris@0 70 // If there are multiple views this might've ended up showing up multiple
Chris@0 71 // times.
Chris@14 72 if (ajaxPath.constructor.toString().indexOf('Array') !== -1) {
Chris@14 73 ajaxPath = ajaxPath[0];
Chris@0 74 }
Chris@0 75
Chris@0 76 // Check if there are any GET parameters to send to views.
Chris@0 77 let queryString = window.location.search || '';
Chris@0 78 if (queryString !== '') {
Chris@0 79 // Remove the question mark and Drupal path component if any.
Chris@17 80 queryString = queryString
Chris@17 81 .slice(1)
Chris@17 82 .replace(/q=[^&]+&?|&?render=[^&]+/, '');
Chris@0 83 if (queryString !== '') {
Chris@14 84 // If there is a '?' in ajaxPath, clean url are on and & should be
Chris@0 85 // used to add parameters.
Chris@17 86 queryString = (/\?/.test(ajaxPath) ? '&' : '?') + queryString;
Chris@0 87 }
Chris@0 88 }
Chris@0 89
Chris@0 90 this.element_settings = {
Chris@14 91 url: ajaxPath + queryString,
Chris@0 92 submit: settings,
Chris@0 93 setClick: true,
Chris@0 94 event: 'click',
Chris@0 95 selector,
Chris@0 96 progress: { type: 'fullscreen' },
Chris@0 97 };
Chris@0 98
Chris@0 99 this.settings = settings;
Chris@0 100
Chris@0 101 // Add the ajax to exposed forms.
Chris@17 102 this.$exposed_form = $(
Chris@17 103 `form#views-exposed-form-${settings.view_name.replace(
Chris@17 104 /_/g,
Chris@17 105 '-',
Chris@17 106 )}-${settings.view_display_id.replace(/_/g, '-')}`,
Chris@17 107 );
Chris@17 108 this.$exposed_form
Chris@17 109 .once('exposed-form')
Chris@17 110 .each($.proxy(this.attachExposedFormAjax, this));
Chris@0 111
Chris@0 112 // Add the ajax to pagers.
Chris@0 113 this.$view
Chris@0 114 // Don't attach to nested views. Doing so would attach multiple behaviors
Chris@0 115 // to a given element.
Chris@0 116 .filter($.proxy(this.filterNestedViews, this))
Chris@17 117 .once('ajax-pager')
Chris@17 118 .each($.proxy(this.attachPagerAjax, this));
Chris@0 119
Chris@0 120 // Add a trigger to update this view specifically. In order to trigger a
Chris@0 121 // refresh use the following code.
Chris@0 122 //
Chris@0 123 // @code
Chris@0 124 // $('.view-name').trigger('RefreshView');
Chris@0 125 // @endcode
Chris@14 126 const selfSettings = $.extend({}, this.element_settings, {
Chris@0 127 event: 'RefreshView',
Chris@0 128 base: this.selector,
Chris@0 129 element: this.$view.get(0),
Chris@0 130 });
Chris@14 131 this.refreshViewAjax = Drupal.ajax(selfSettings);
Chris@0 132 };
Chris@0 133
Chris@0 134 /**
Chris@0 135 * @method
Chris@0 136 */
Chris@17 137 Drupal.views.ajaxView.prototype.attachExposedFormAjax = function() {
Chris@0 138 const that = this;
Chris@0 139 this.exposedFormAjax = [];
Chris@0 140 // Exclude the reset buttons so no AJAX behaviours are bound. Many things
Chris@0 141 // break during the form reset phase if using AJAX.
Chris@17 142 $('input[type=submit], input[type=image]', this.$exposed_form)
Chris@17 143 .not('[data-drupal-selector=edit-reset]')
Chris@17 144 .each(function(index) {
Chris@17 145 const selfSettings = $.extend({}, that.element_settings, {
Chris@17 146 base: $(this).attr('id'),
Chris@17 147 element: this,
Chris@17 148 });
Chris@17 149 that.exposedFormAjax[index] = Drupal.ajax(selfSettings);
Chris@0 150 });
Chris@0 151 };
Chris@0 152
Chris@0 153 /**
Chris@0 154 * @return {bool}
Chris@0 155 * If there is at least one parent with a view class return false.
Chris@0 156 */
Chris@17 157 Drupal.views.ajaxView.prototype.filterNestedViews = function() {
Chris@0 158 // If there is at least one parent with a view class, this view
Chris@0 159 // is nested (e.g., an attachment). Bail.
Chris@0 160 return !this.$view.parents('.view').length;
Chris@0 161 };
Chris@0 162
Chris@0 163 /**
Chris@0 164 * Attach the ajax behavior to each link.
Chris@0 165 */
Chris@17 166 Drupal.views.ajaxView.prototype.attachPagerAjax = function() {
Chris@17 167 this.$view
Chris@17 168 .find(
Chris@17 169 'ul.js-pager__items > li > a, th.views-field a, .attachment .views-summary a',
Chris@17 170 )
Chris@0 171 .each($.proxy(this.attachPagerLinkAjax, this));
Chris@0 172 };
Chris@0 173
Chris@0 174 /**
Chris@0 175 * Attach the ajax behavior to a singe link.
Chris@0 176 *
Chris@0 177 * @param {string} [id]
Chris@0 178 * The ID of the link.
Chris@0 179 * @param {HTMLElement} link
Chris@0 180 * The link element.
Chris@0 181 */
Chris@17 182 Drupal.views.ajaxView.prototype.attachPagerLinkAjax = function(id, link) {
Chris@0 183 const $link = $(link);
Chris@0 184 const viewData = {};
Chris@0 185 const href = $link.attr('href');
Chris@0 186 // Construct an object using the settings defaults and then overriding
Chris@0 187 // with data specific to the link.
Chris@0 188 $.extend(
Chris@0 189 viewData,
Chris@0 190 this.settings,
Chris@0 191 Drupal.Views.parseQueryString(href),
Chris@0 192 // Extract argument data from the URL.
Chris@0 193 Drupal.Views.parseViewArgs(href, this.settings.view_base_path),
Chris@0 194 );
Chris@0 195
Chris@14 196 const selfSettings = $.extend({}, this.element_settings, {
Chris@0 197 submit: viewData,
Chris@0 198 base: false,
Chris@0 199 element: link,
Chris@0 200 });
Chris@14 201 this.pagerAjax = Drupal.ajax(selfSettings);
Chris@0 202 };
Chris@0 203
Chris@0 204 /**
Chris@0 205 * Views scroll to top ajax command.
Chris@0 206 *
Chris@0 207 * @param {Drupal.Ajax} [ajax]
Chris@0 208 * A {@link Drupal.ajax} object.
Chris@0 209 * @param {object} response
Chris@0 210 * Ajax response.
Chris@0 211 * @param {string} response.selector
Chris@0 212 * Selector to use.
Chris@0 213 */
Chris@17 214 Drupal.AjaxCommands.prototype.viewsScrollTop = function(ajax, response) {
Chris@0 215 // Scroll to the top of the view. This will allow users
Chris@0 216 // to browse newly loaded content after e.g. clicking a pager
Chris@0 217 // link.
Chris@0 218 const offset = $(response.selector).offset();
Chris@0 219 // We can't guarantee that the scrollable object should be
Chris@0 220 // the body, as the view could be embedded in something
Chris@0 221 // more complex such as a modal popup. Recurse up the DOM
Chris@0 222 // and scroll the first element that has a non-zero top.
Chris@0 223 let scrollTarget = response.selector;
Chris@0 224 while ($(scrollTarget).scrollTop() === 0 && $(scrollTarget).parent()) {
Chris@0 225 scrollTarget = $(scrollTarget).parent();
Chris@0 226 }
Chris@0 227 // Only scroll upward.
Chris@0 228 if (offset.top - 10 < $(scrollTarget).scrollTop()) {
Chris@17 229 $(scrollTarget).animate({ scrollTop: offset.top - 10 }, 500);
Chris@0 230 }
Chris@0 231 };
Chris@17 232 })(jQuery, Drupal, drupalSettings);