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?.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?.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 this.$exposed_form = $( 67 `form#views-exposed-form-${settings.view_name.replace( 68 /_/g,
69 '-', 70 )}-${settings.view_display_id.replace(/_/g, '-')}`, 71 ); 72 73 // Retrieve the path to use for views' ajax. 74 let ajaxPath = drupalSettings.views.ajax_path; 75 76 // If there are multiple views this might've ended up showing up multiple 77 // times. 78 if (ajaxPath.constructor.toString().includes('Array')) { 79 ajaxPath = ajaxPath[0]; 80 } 81 82 // Check if there are any GET parameters to send to views. 83 let queryString = window.location.search || ''; 84 if (queryString !== '') { 85 // Remove the question mark and Drupal path component if any. 86 queryString = queryString.slice(1); 87 88 // Remove current exposed filters. 89 const params = decodeURI(queryString) 90 .split('&') 91 .filter((param) => { 92 const [name, value] = param.split('='); 93 return ( 94 this.$exposed_form.find(`input[name="${name}"]`).length === 0 && 95 /* 96 Submitting filters should reset paging and sorting 97 because that is what happens without AJAX. 98 */ 99 !['page', 'reset', 'sort', 'order', 'q', 'render'].includes(name) 100 ); 101 }); 102 queryString = encodeURI(params.join('&')); 103 // If there is a '?' in ajaxPath, clean URL are on and & should be 104 // used to add parameters. 105 if (queryString !== '') { 106 queryString = (ajaxPath.includes('?') ? '&' : '?') + queryString; 107 } 108 } 109 110 this.element_settings = { 111 url: ajaxPath + queryString, 112 submit: settings, 113 httpMethod: 'GET', 114 setClick: true, 115 event: 'click', 116 selector, 117 progress: { type: 'fullscreen' }, 118 }; 119 120 this.settings = settings; 121 122 // Add the ajax to exposed forms. 123 once('exposed-form', this.$exposed_form).forEach( 124 this.attachExposedFormAjax.bind(this), 125 ); 126 127 // Add the ajax to pagers. 128 once( 129 'ajax-pager', 130 this.$view 131 // Don't attach to nested views. Doing so would attach multiple behaviors 132 // to a given element. 133 .filter(this.filterNestedViews.bind(this)), 134 ).forEach(this.attachPagerAjax.bind(this)); 135 136 // Add a trigger to update this view specifically. In order to trigger a 137 // refresh use the following code. 138 // 139 // @code 140 // $('.view-name').trigger('RefreshView'); 141 // @endcode 142 const selfSettings = $.extend({}, this.element_settings, { 143 event: 'RefreshView', 144 base: this.selector, 145 httpMethod: 'GET', 146 element: this.$view.get(0), 147 }); 148 this.refreshViewAjax = Drupal.ajax(selfSettings); 149 }; 150 151 /** 152 * @method 153 */ 154 Drupal.views.ajaxView.prototype.attachExposedFormAjax = function () { 155 const that = this; 156 this.exposedFormAjax = []; 157 // Exclude the reset buttons so no AJAX behaviors are bound. Many things 158 // break during the form reset phase if using AJAX. 159 $( 160 'input[type=submit], button[type=submit], input[type=image]', 161 this.$exposed_form, 162 ) 163 .not('[data-drupal-selector=edit-reset]') 164 .each(function (index) { 165 const selfSettings = $.extend({}, that.element_settings, { 166 base: $(this).attr('id'), 167 element: this, 168 }); 169 that.exposedFormAjax[index] = Drupal.ajax(selfSettings); 170 }); 171 }; 172 173 /** 174 * @return {boolean} 175 * If there is at least one parent with a view class return false. 176 */ 177 Drupal.views.ajaxView.prototype.filterNestedViews = function () { 178 // If there is at least one parent with a view class, this view 179 // is nested (e.g., an attachment). Bail. 180 return !this.$view.parents('.view').length; 181 }; 182 183 /** 184 * Attach the ajax behavior to each link. 185 */ 186 Drupal.views.ajaxView.prototype.attachPagerAjax = function () { 187 this.$view 188 .find( 189 '.js-pager__items a, th.views-field a, .attachment .views-summary a', 190 ) 191 .each(this.attachPagerLinkAjax.bind(this)); 192 }; 193 194 /** 195 * Attach the ajax behavior to a singe link. 196 * 197 * @param {string} [id] 198 * The ID of the link. 199 * @param {HTMLElement} link 200 * The link element. 201 */ 202 Drupal.views.ajaxView.prototype.attachPagerLinkAjax = function (id, link) { 203 const $link = $(link); 204 const viewData = {}; 205 const href = $link.attr('href'); 206 // Construct an object using the settings defaults and then overriding 207 // with data specific to the link. 208 $.extend( 209 viewData, 210 this.settings, 211 Drupal.Views.parseQueryString(href), 212 // Extract argument data from the URL. 213 Drupal.Views.parseViewArgs(href, this.settings.view_base_path), 214 ); 215 216 const selfSettings = $.extend({}, this.element_settings, {
217 submit: viewData, 218 base: false, 219 element: link, 220 httpMethod: 'GET', 221 }); 222 this.pagerAjax = Drupal.ajax(selfSettings); 223 }; 224 225 /** 226 * Sets the browser URL ajax command. 227 * 228 * @param {Drupal.Ajax} [ajax] 229 * A {@link Drupal.ajax} object. 230 * @param {object} response 231 * Ajax response. 232 * @param {string} response.url 233 * URL to be set. 234 */ 235 Drupal.AjaxCommands.prototype.setBrowserUrl = (ajax, response) => { 236 // Do not change browser URL if we are in a dialog wrapper. 237 if (ajax.element && !ajax.element.closest('.ui-dialog-content')) { 238 window.history.replaceState(null, '', response.url); 239 } 240 }; 241})(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.