Chris@0: /** Chris@0: * @file Chris@0: * Handles AJAX fetching of views, including filter submission and response. Chris@0: */ Chris@0: Chris@17: (function($, Drupal, drupalSettings) { Chris@0: /** Chris@0: * Attaches the AJAX behavior to exposed filters forms and key View links. Chris@0: * Chris@0: * @type {Drupal~behavior} Chris@0: * Chris@0: * @prop {Drupal~behaviorAttach} attach Chris@0: * Attaches ajaxView functionality to relevant elements. Chris@0: */ Chris@0: Drupal.behaviors.ViewsAjaxView = {}; Chris@17: Drupal.behaviors.ViewsAjaxView.attach = function(context, settings) { Chris@17: if (settings && settings.views && settings.views.ajaxViews) { Chris@17: const { Chris@17: views: { ajaxViews }, Chris@17: } = settings; Chris@17: Object.keys(ajaxViews || {}).forEach(i => { Chris@14: Drupal.views.instances[i] = new Drupal.views.ajaxView(ajaxViews[i]); Chris@14: }); Chris@0: } Chris@0: }; Chris@17: Drupal.behaviors.ViewsAjaxView.detach = (context, settings, trigger) => { Chris@17: if (trigger === 'unload') { Chris@17: if (settings && settings.views && settings.views.ajaxViews) { Chris@17: const { Chris@17: views: { ajaxViews }, Chris@17: } = settings; Chris@17: Object.keys(ajaxViews || {}).forEach(i => { Chris@17: const selector = `.js-view-dom-id-${ajaxViews[i].view_dom_id}`; Chris@17: if ($(selector, context).length) { Chris@17: delete Drupal.views.instances[i]; Chris@17: delete settings.views.ajaxViews[i]; Chris@17: } Chris@17: }); Chris@17: } Chris@17: } Chris@17: }; Chris@0: Chris@0: /** Chris@0: * @namespace Chris@0: */ Chris@0: Drupal.views = {}; Chris@0: Chris@0: /** Chris@0: * @type {object.} Chris@0: */ Chris@0: Drupal.views.instances = {}; Chris@0: Chris@0: /** Chris@0: * Javascript object for a certain view. Chris@0: * Chris@0: * @constructor Chris@0: * Chris@0: * @param {object} settings Chris@0: * Settings object for the ajax view. Chris@0: * @param {string} settings.view_dom_id Chris@0: * The DOM id of the view. Chris@0: */ Chris@17: Drupal.views.ajaxView = function(settings) { Chris@0: const selector = `.js-view-dom-id-${settings.view_dom_id}`; Chris@0: this.$view = $(selector); Chris@0: Chris@0: // Retrieve the path to use for views' ajax. Chris@14: let ajaxPath = drupalSettings.views.ajax_path; Chris@0: Chris@0: // If there are multiple views this might've ended up showing up multiple Chris@0: // times. Chris@14: if (ajaxPath.constructor.toString().indexOf('Array') !== -1) { Chris@14: ajaxPath = ajaxPath[0]; Chris@0: } Chris@0: Chris@0: // Check if there are any GET parameters to send to views. Chris@0: let queryString = window.location.search || ''; Chris@0: if (queryString !== '') { Chris@0: // Remove the question mark and Drupal path component if any. Chris@17: queryString = queryString Chris@17: .slice(1) Chris@17: .replace(/q=[^&]+&?|&?render=[^&]+/, ''); Chris@0: if (queryString !== '') { Chris@14: // If there is a '?' in ajaxPath, clean url are on and & should be Chris@0: // used to add parameters. Chris@17: queryString = (/\?/.test(ajaxPath) ? '&' : '?') + queryString; Chris@0: } Chris@0: } Chris@0: Chris@0: this.element_settings = { Chris@14: url: ajaxPath + queryString, Chris@0: submit: settings, Chris@0: setClick: true, Chris@0: event: 'click', Chris@0: selector, Chris@0: progress: { type: 'fullscreen' }, Chris@0: }; Chris@0: Chris@0: this.settings = settings; Chris@0: Chris@0: // Add the ajax to exposed forms. Chris@17: this.$exposed_form = $( Chris@17: `form#views-exposed-form-${settings.view_name.replace( Chris@17: /_/g, Chris@17: '-', Chris@17: )}-${settings.view_display_id.replace(/_/g, '-')}`, Chris@17: ); Chris@17: this.$exposed_form Chris@17: .once('exposed-form') Chris@17: .each($.proxy(this.attachExposedFormAjax, this)); Chris@0: Chris@0: // Add the ajax to pagers. Chris@0: this.$view Chris@0: // Don't attach to nested views. Doing so would attach multiple behaviors Chris@0: // to a given element. Chris@0: .filter($.proxy(this.filterNestedViews, this)) Chris@17: .once('ajax-pager') Chris@17: .each($.proxy(this.attachPagerAjax, this)); Chris@0: Chris@0: // Add a trigger to update this view specifically. In order to trigger a Chris@0: // refresh use the following code. Chris@0: // Chris@0: // @code Chris@0: // $('.view-name').trigger('RefreshView'); Chris@0: // @endcode Chris@14: const selfSettings = $.extend({}, this.element_settings, { Chris@0: event: 'RefreshView', Chris@0: base: this.selector, Chris@0: element: this.$view.get(0), Chris@0: }); Chris@14: this.refreshViewAjax = Drupal.ajax(selfSettings); Chris@0: }; Chris@0: Chris@0: /** Chris@0: * @method Chris@0: */ Chris@17: Drupal.views.ajaxView.prototype.attachExposedFormAjax = function() { Chris@0: const that = this; Chris@0: this.exposedFormAjax = []; Chris@0: // Exclude the reset buttons so no AJAX behaviours are bound. Many things Chris@0: // break during the form reset phase if using AJAX. Chris@17: $('input[type=submit], input[type=image]', this.$exposed_form) Chris@17: .not('[data-drupal-selector=edit-reset]') Chris@17: .each(function(index) { Chris@17: const selfSettings = $.extend({}, that.element_settings, { Chris@17: base: $(this).attr('id'), Chris@17: element: this, Chris@17: }); Chris@17: that.exposedFormAjax[index] = Drupal.ajax(selfSettings); Chris@0: }); Chris@0: }; Chris@0: Chris@0: /** Chris@0: * @return {bool} Chris@0: * If there is at least one parent with a view class return false. Chris@0: */ Chris@17: Drupal.views.ajaxView.prototype.filterNestedViews = function() { Chris@0: // If there is at least one parent with a view class, this view Chris@0: // is nested (e.g., an attachment). Bail. Chris@0: return !this.$view.parents('.view').length; Chris@0: }; Chris@0: Chris@0: /** Chris@0: * Attach the ajax behavior to each link. Chris@0: */ Chris@17: Drupal.views.ajaxView.prototype.attachPagerAjax = function() { Chris@17: this.$view Chris@17: .find( Chris@17: 'ul.js-pager__items > li > a, th.views-field a, .attachment .views-summary a', Chris@17: ) Chris@0: .each($.proxy(this.attachPagerLinkAjax, this)); Chris@0: }; Chris@0: Chris@0: /** Chris@0: * Attach the ajax behavior to a singe link. Chris@0: * Chris@0: * @param {string} [id] Chris@0: * The ID of the link. Chris@0: * @param {HTMLElement} link Chris@0: * The link element. Chris@0: */ Chris@17: Drupal.views.ajaxView.prototype.attachPagerLinkAjax = function(id, link) { Chris@0: const $link = $(link); Chris@0: const viewData = {}; Chris@0: const href = $link.attr('href'); Chris@0: // Construct an object using the settings defaults and then overriding Chris@0: // with data specific to the link. Chris@0: $.extend( Chris@0: viewData, Chris@0: this.settings, Chris@0: Drupal.Views.parseQueryString(href), Chris@0: // Extract argument data from the URL. Chris@0: Drupal.Views.parseViewArgs(href, this.settings.view_base_path), Chris@0: ); Chris@0: Chris@14: const selfSettings = $.extend({}, this.element_settings, { Chris@0: submit: viewData, Chris@0: base: false, Chris@0: element: link, Chris@0: }); Chris@14: this.pagerAjax = Drupal.ajax(selfSettings); Chris@0: }; Chris@0: Chris@0: /** Chris@0: * Views scroll to top ajax command. Chris@0: * Chris@0: * @param {Drupal.Ajax} [ajax] Chris@0: * A {@link Drupal.ajax} object. Chris@0: * @param {object} response Chris@0: * Ajax response. Chris@0: * @param {string} response.selector Chris@0: * Selector to use. Chris@0: */ Chris@17: Drupal.AjaxCommands.prototype.viewsScrollTop = function(ajax, response) { Chris@0: // Scroll to the top of the view. This will allow users Chris@0: // to browse newly loaded content after e.g. clicking a pager Chris@0: // link. Chris@0: const offset = $(response.selector).offset(); Chris@0: // We can't guarantee that the scrollable object should be Chris@0: // the body, as the view could be embedded in something Chris@0: // more complex such as a modal popup. Recurse up the DOM Chris@0: // and scroll the first element that has a non-zero top. Chris@0: let scrollTarget = response.selector; Chris@0: while ($(scrollTarget).scrollTop() === 0 && $(scrollTarget).parent()) { Chris@0: scrollTarget = $(scrollTarget).parent(); Chris@0: } Chris@0: // Only scroll upward. Chris@0: if (offset.top - 10 < $(scrollTarget).scrollTop()) { Chris@17: $(scrollTarget).animate({ scrollTop: offset.top - 10 }, 500); Chris@0: } Chris@0: }; Chris@17: })(jQuery, Drupal, drupalSettings);