diff --git a/core/modules/views/js/ajax_view.js b/core/modules/views/js/ajax_view.js index 67601a121..e855950ce 100644 --- a/core/modules/views/js/ajax_view.js +++ b/core/modules/views/js/ajax_view.js @@ -9,12 +9,9 @@ Drupal.behaviors.ViewsAjaxView = {}; Drupal.behaviors.ViewsAjaxView.attach = function () { if (drupalSettings && drupalSettings.views && drupalSettings.views.ajaxViews) { - var ajaxViews = drupalSettings.views.ajaxViews; - for (var i in ajaxViews) { - if (ajaxViews.hasOwnProperty(i)) { - Drupal.views.instances[i] = new Drupal.views.ajaxView(ajaxViews[i]); - } - } + Drupal.views.sortByNestingLevel(drupalSettings.views.ajaxViews).forEach(function (item) { + Drupal.views.instances[item.key] = new Drupal.views.ajaxView(item.value); + }); } }; @@ -22,9 +19,39 @@ Drupal.views.instances = {}; + /** + * Helper function to sort views by nesting level. + * + * @param {object} ajaxViews + * Object containing ajax view. + * @return {Array} + * Array of views sorted by nesting level. + */ + Drupal.views.sortByNestingLevel = function (ajaxViews) { + var ajaxViewsArray = []; + + for (var i in ajaxViews) { + if (ajaxViews.hasOwnProperty(i)) { + ajaxViews[i].selector = '.js-view-dom-id-' + ajaxViews[i].view_dom_id; + + ajaxViewsArray.push({ + key: i, + value: ajaxViews[i], + nestingLevel: $(ajaxViews[i].selector).parents('.view').length + }); + } + } + + // Sort views by nesting level descending. The goal is to start with the + // innermost. + return ajaxViewsArray.sort(function (a, b) { + return b.nestingLevel - a.nestingLevel; + }); + }; + + Drupal.views.ajaxView = function (settings) { - var selector = '.js-view-dom-id-' + settings.view_dom_id; - this.$view = $(selector); + this.$view = $(settings.selector); var ajax_path = drupalSettings.views.ajax_path; @@ -45,7 +72,7 @@ submit: settings, setClick: true, event: 'click', - selector: selector, + selector: settings.selector, progress: { type: 'fullscreen' } }; @@ -54,7 +81,7 @@ this.$exposed_form = $('form#views-exposed-form-' + settings.view_name.replace(/_/g, '-') + '-' + settings.view_display_id.replace(/_/g, '-')); this.$exposed_form.once('exposed-form').each($.proxy(this.attachExposedFormAjax, this)); - this.$view.filter($.proxy(this.filterNestedViews, this)).once('ajax-pager').each($.proxy(this.attachPagerAjax, this)); + this.$view.once('ajax-pager').each($.proxy(this.attachPagerAjax, this)); var self_settings = $.extend({}, this.element_settings, { event: 'RefreshView', @@ -77,12 +104,8 @@ }); }; - Drupal.views.ajaxView.prototype.filterNestedViews = function () { - return !this.$view.parents('.view').length; - }; - Drupal.views.ajaxView.prototype.attachPagerAjax = function () { - this.$view.find('ul.js-pager__items > li > a, th.views-field a, .attachment .views-summary a').each($.proxy(this.attachPagerLinkAjax, this)); + this.$view.find('ul.js-pager__items > li > a, th.views-field a, .attachment .views-summary a').once('attach-pager-ajax').each($.proxy(this.attachPagerLinkAjax, this)); }; Drupal.views.ajaxView.prototype.attachPagerLinkAjax = function (id, link) { diff --git a/core/modules/views/tests/modules/views_test_ajax_subscriber/src/EventSubscriber/ViewsTestAjaxSubscriberEvent.php b/core/modules/views/tests/modules/views_test_ajax_subscriber/src/EventSubscriber/ViewsTestAjaxSubscriberEvent.php new file mode 100644 index 000000000..3920a20c1 --- /dev/null +++ b/core/modules/views/tests/modules/views_test_ajax_subscriber/src/EventSubscriber/ViewsTestAjaxSubscriberEvent.php @@ -0,0 +1,48 @@ + [['onResponse']]]; + } + + /** + * This method is called whenever the kernel.response event is + * dispatched. + * + * @param Event $event + * @throws \Drupal\views_test_ajax_subscriber\EventSubscriber\UnexpectedViewAjaxException + */ + public function onResponse(Event $event) { + /** @var ViewAjaxResponse $response */ + $response = $event->getResponse(); + + // Only alter views ajax responses. + if (!($response instanceof ViewAjaxResponse)) { + return; + } + + $expectedView = \Drupal::state()->get('views_test_ajax_subscriber'); + if ($expectedView && $response->getView()->id() != $expectedView) { + throw new UnexpectedViewAjaxException("Expected: {$expectedView}. Got: {$response->getView()->id()}."); + } + } + +} + +class UnexpectedViewAjaxException extends \Exception {} diff --git a/core/modules/views/tests/modules/views_test_ajax_subscriber/views_test_ajax_subscriber.info.yml b/core/modules/views/tests/modules/views_test_ajax_subscriber/views_test_ajax_subscriber.info.yml new file mode 100644 index 000000000..38041e49d --- /dev/null +++ b/core/modules/views/tests/modules/views_test_ajax_subscriber/views_test_ajax_subscriber.info.yml @@ -0,0 +1,7 @@ +name: views_test_ajax_subscriber +type: module +description: Tests views ajax responses. +core: 8.x +package: Testing +dependencies: + - views diff --git a/core/modules/views/tests/modules/views_test_ajax_subscriber/views_test_ajax_subscriber.services.yml b/core/modules/views/tests/modules/views_test_ajax_subscriber/views_test_ajax_subscriber.services.yml new file mode 100644 index 000000000..83c71871e --- /dev/null +++ b/core/modules/views/tests/modules/views_test_ajax_subscriber/views_test_ajax_subscriber.services.yml @@ -0,0 +1,7 @@ +services: + views_test_ajax_subscriber.event_subscriber: + class: Drupal\views_test_ajax_subscriber\EventSubscriber\ViewsTestAjaxSubscriberEvent + arguments: [] + tags: + - { name: event_subscriber } + diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_area_ajax.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_area_ajax.yml new file mode 100644 index 000000000..c93f5ebea --- /dev/null +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_area_ajax.yml @@ -0,0 +1,194 @@ +langcode: en +status: true +dependencies: + config: + - core.entity_view_mode.node.teaser + - views.view.test_content_ajax + module: + - node + - user +id: test_view_area_ajax +label: 'Test view area ajax' +module: views +description: '' +tag: '' +base_table: node_field_data +base_field: nid +core: 8.x +display: + default: + display_plugin: default + id: default + display_title: Master + position: 0 + display_options: + use_ajax: true + access: + type: perm + options: + perm: 'access content' + cache: + type: tag + options: { } + query: + type: views_query + options: + disable_sql_rewrite: false + distinct: false + replica: false + query_comment: '' + query_tags: { } + exposed_form: + type: basic + options: + submit_button: Apply + reset_button: false + reset_button_label: Reset + exposed_sorts_label: 'Sort by' + expose_sort_order: true + sort_asc_label: Asc + sort_desc_label: Desc + pager: + type: mini + options: + items_per_page: 10 + offset: 0 + id: 0 + total_pages: null + expose: + items_per_page: false + items_per_page_label: 'Items per page' + items_per_page_options: '5, 10, 25, 50' + items_per_page_options_all: false + items_per_page_options_all_label: '- All -' + offset: false + offset_label: Offset + tags: + previous: ‹‹ + next: ›› + style: + type: default + row: + type: 'entity:node' + options: + view_mode: teaser + fields: + title: + id: title + table: node_field_data + field: title + entity_type: node + entity_field: title + label: '' + alter: + alter_text: false + make_link: false + absolute: false + trim: false + word_boundary: false + ellipsis: false + strip_tags: false + html: false + hide_empty: false + empty_zero: false + settings: + link_to_entity: true + plugin_id: field + relationship: none + group_type: group + admin_label: '' + exclude: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_alter_empty: true + click_sort_column: value + type: string + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + filters: + status: + value: '1' + table: node_field_data + field: status + plugin_id: boolean + entity_type: node + entity_field: status + id: status + expose: + operator: '' + group: 1 + sorts: + created: + id: created + table: node_field_data + field: created + order: DESC + entity_type: node + entity_field: created + plugin_id: date + relationship: none + group_type: group + admin_label: '' + exposed: false + expose: + label: '' + granularity: second + title: '' + header: + view: + id: view + table: views + field: view + relationship: none + group_type: group + admin_label: '' + empty: false + view_to_insert: 'test_content_ajax:page_1' + inherit_arguments: false + plugin_id: view + footer: { } + empty: { } + relationships: { } + arguments: { } + display_extenders: { } + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url.query_args + - 'user.node_grants:view' + - user.permissions + tags: { } + page_1: + display_plugin: page + id: page_1 + display_title: Page + position: 1 + display_options: + display_extenders: { } + path: test-view-area-ajax + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url.query_args + - 'user.node_grants:view' + - user.permissions + tags: { } diff --git a/core/modules/views/tests/src/FunctionalJavascript/EmbeddedViewPaginationAJAXTest.php b/core/modules/views/tests/src/FunctionalJavascript/EmbeddedViewPaginationAJAXTest.php new file mode 100644 index 000000000..169d27b44 --- /dev/null +++ b/core/modules/views/tests/src/FunctionalJavascript/EmbeddedViewPaginationAJAXTest.php @@ -0,0 +1,73 @@ +createContentType(['type' => 'page']); + for ($i = 1; $i <= 55; $i++) { + $this->createNode(['title' => 'Node ' . $i . ' content', 'changed' => $i * 1000]); + } + + // Create a user privileged enough to view content. + $user = $this->drupalCreateUser([ + 'administer site configuration', + 'access content', + 'access content overview', + ]); + $this->drupalLogin($user); + } + + /** + * Checks if embedded views send pager requests as themselves and not as + * enclosing view. + */ + public function testPaginationInEmbeddedAjaxView() { + $this->drupalGet('test-view-area-ajax'); + + $session_assert = $this->assertSession(); + + /** + * Tell the event subscriber to expect a pager request sent by the + * test_content_ajax view. + * + * @see \Drupal\views_test_ajax_subscriber\EventSubscriberViewsTestAjaxSubscriberEvent + */ + \Drupal::state()->set('views_test_ajax_subscriber', 'test_content_ajax'); + $session_assert->waitForLink('Next ›')->click(); + } + +}