From ac310db40b288778205108f25e38cda5bd77b9af Mon Sep 17 00:00:00 2001
From: Lee Rowlands <lee.rowlands@previousnext.com.au>
Date: Sat, 14 Jul 2018 08:09:39 +1000
Subject: [PATCH] Issue #2861860 by samuel.mortenson, phenaproxima, drpal,
 Daniel Korte, mogio_hh, tim.plunkett, alexpott: View inside core modal
 doesn't refresh correctly when reopened

---
 core/modules/views/js/ajax_view.es6.js        | 20 +++++-
 core/modules/views/js/ajax_view.js            | 22 ++++++-
 .../src/Controller/TestController.php         | 41 +++++++++++++
 .../views_test_modal.info.yml                 |  9 +++
 .../views_test_modal.routing.yml              |  6 ++
 .../ExposedFilterAJAXTest.php                 | 61 ++++++++++++++++++-
 6 files changed, 150 insertions(+), 9 deletions(-)
 create mode 100644 core/modules/views/tests/modules/views_test_modal/src/Controller/TestController.php
 create mode 100644 core/modules/views/tests/modules/views_test_modal/views_test_modal.info.yml
 create mode 100644 core/modules/views/tests/modules/views_test_modal/views_test_modal.routing.yml

diff --git a/core/modules/views/js/ajax_view.es6.js b/core/modules/views/js/ajax_view.es6.js
index 0c8535890b5a..e4de7b3d3a2b 100644
--- a/core/modules/views/js/ajax_view.es6.js
+++ b/core/modules/views/js/ajax_view.es6.js
@@ -13,14 +13,28 @@
    *   Attaches ajaxView functionality to relevant elements.
    */
   Drupal.behaviors.ViewsAjaxView = {};
-  Drupal.behaviors.ViewsAjaxView.attach = function () {
-    if (drupalSettings && drupalSettings.views && drupalSettings.views.ajaxViews) {
-      const ajaxViews = drupalSettings.views.ajaxViews;
+  Drupal.behaviors.ViewsAjaxView.attach = function (context, settings) {
+    if (settings && settings.views && settings.views.ajaxViews) {
+      const { views: { ajaxViews } } = settings;
       Object.keys(ajaxViews || {}).forEach((i) => {
         Drupal.views.instances[i] = new Drupal.views.ajaxView(ajaxViews[i]);
       });
     }
   };
+  Drupal.behaviors.ViewsAjaxView.detach = (context, settings, trigger) => {
+    if (trigger === 'unload') {
+      if (settings && settings.views && settings.views.ajaxViews) {
+        const { views: { ajaxViews } } = settings;
+        Object.keys(ajaxViews || {}).forEach((i) => {
+          const selector = `.js-view-dom-id-${ajaxViews[i].view_dom_id}`;
+          if ($(selector, context).length) {
+            delete Drupal.views.instances[i];
+            delete settings.views.ajaxViews[i];
+          }
+        });
+      }
+    }
+  };
 
   /**
    * @namespace
diff --git a/core/modules/views/js/ajax_view.js b/core/modules/views/js/ajax_view.js
index a10eb837eb97..95a803d7fec8 100644
--- a/core/modules/views/js/ajax_view.js
+++ b/core/modules/views/js/ajax_view.js
@@ -7,14 +7,30 @@
 
 (function ($, Drupal, drupalSettings) {
   Drupal.behaviors.ViewsAjaxView = {};
-  Drupal.behaviors.ViewsAjaxView.attach = function () {
-    if (drupalSettings && drupalSettings.views && drupalSettings.views.ajaxViews) {
-      var ajaxViews = drupalSettings.views.ajaxViews;
+  Drupal.behaviors.ViewsAjaxView.attach = function (context, settings) {
+    if (settings && settings.views && settings.views.ajaxViews) {
+      var ajaxViews = settings.views.ajaxViews;
+
       Object.keys(ajaxViews || {}).forEach(function (i) {
         Drupal.views.instances[i] = new Drupal.views.ajaxView(ajaxViews[i]);
       });
     }
   };
+  Drupal.behaviors.ViewsAjaxView.detach = function (context, settings, trigger) {
+    if (trigger === 'unload') {
+      if (settings && settings.views && settings.views.ajaxViews) {
+        var ajaxViews = settings.views.ajaxViews;
+
+        Object.keys(ajaxViews || {}).forEach(function (i) {
+          var selector = '.js-view-dom-id-' + ajaxViews[i].view_dom_id;
+          if ($(selector, context).length) {
+            delete Drupal.views.instances[i];
+            delete settings.views.ajaxViews[i];
+          }
+        });
+      }
+    }
+  };
 
   Drupal.views = {};
 
diff --git a/core/modules/views/tests/modules/views_test_modal/src/Controller/TestController.php b/core/modules/views/tests/modules/views_test_modal/src/Controller/TestController.php
new file mode 100644
index 000000000000..b89cf4081d73
--- /dev/null
+++ b/core/modules/views/tests/modules/views_test_modal/src/Controller/TestController.php
@@ -0,0 +1,41 @@
+<?php
+
+namespace Drupal\views_test_modal\Controller;
+
+use Drupal\Component\Serialization\Json;
+use Drupal\Core\Controller\ControllerBase;
+use Drupal\Core\Url;
+
+class TestController extends ControllerBase {
+
+  /**
+   * Renders a link to open the /admin/content view in a modal dialog.
+   */
+  public function modal() {
+    $build = [];
+
+    $build['open_admin_content'] = [
+      '#type' => 'link',
+      '#title' => $this->t('Administer content'),
+      '#url' => Url::fromUserInput('/admin/content'),
+      '#attributes' => [
+        'class' => ['use-ajax'],
+        'data-dialog-type' => 'modal',
+        'data-dialog-options' => Json::encode([
+          'dialogClass' => 'views-test-modal',
+          'height' => '50%',
+          'width' => '50%',
+          'title' => $this->t('Administer content'),
+        ]),
+      ],
+      '#attached' => [
+        'library' => [
+          'core/drupal.dialog.ajax',
+        ],
+      ],
+    ];
+
+    return $build;
+  }
+
+}
diff --git a/core/modules/views/tests/modules/views_test_modal/views_test_modal.info.yml b/core/modules/views/tests/modules/views_test_modal/views_test_modal.info.yml
new file mode 100644
index 000000000000..59be6d4e51fb
--- /dev/null
+++ b/core/modules/views/tests/modules/views_test_modal/views_test_modal.info.yml
@@ -0,0 +1,9 @@
+name: 'Views Test Modal'
+type: module
+description: 'Provides a test page that renders a View in a modal.'
+package: Testing
+version: VERSION
+core: 8.x
+dependencies:
+  - drupal:node
+  - drupal:views
diff --git a/core/modules/views/tests/modules/views_test_modal/views_test_modal.routing.yml b/core/modules/views/tests/modules/views_test_modal/views_test_modal.routing.yml
new file mode 100644
index 000000000000..430bac18e523
--- /dev/null
+++ b/core/modules/views/tests/modules/views_test_modal/views_test_modal.routing.yml
@@ -0,0 +1,6 @@
+views_test_modal.modal:
+  path: '/views-test-modal/modal'
+  defaults:
+    _controller: '\Drupal\views_test_modal\Controller\TestController::modal'
+  requirements:
+    _access: 'TRUE'
diff --git a/core/modules/views/tests/src/FunctionalJavascript/ExposedFilterAJAXTest.php b/core/modules/views/tests/src/FunctionalJavascript/ExposedFilterAJAXTest.php
index 6aa52bcd12c1..c6cbaf576412 100644
--- a/core/modules/views/tests/src/FunctionalJavascript/ExposedFilterAJAXTest.php
+++ b/core/modules/views/tests/src/FunctionalJavascript/ExposedFilterAJAXTest.php
@@ -19,12 +19,14 @@ class ExposedFilterAJAXTest extends WebDriverTestBase {
   /**
    * {@inheritdoc}
    */
-  public static $modules = ['node', 'views'];
+  public static $modules = ['node', 'views', 'views_test_modal'];
 
   /**
-   * Tests if exposed filtering via AJAX works for the "Content" View.
+   * {@inheritdoc}
    */
-  public function testExposedFiltering() {
+  protected function setUp() {
+    parent::setUp();
+
     // Enable AJAX on the /admin/content View.
     \Drupal::configFactory()->getEditable('views.view.content')
       ->set('display.default.display_options.use_ajax', TRUE)
@@ -43,7 +45,12 @@ public function testExposedFiltering() {
       'edit any page content',
     ]);
     $this->drupalLogin($user);
+  }
 
+  /**
+   * Tests if exposed filtering via AJAX works for the "Content" View.
+   */
+  public function testExposedFiltering() {
     // Visit the View page.
     $this->drupalGet('admin/content');
 
@@ -91,4 +98,52 @@ public function testExposedFiltering() {
     $this->assertFalse($session->getPage()->hasButton('Reset'));
   }
 
+  /**
+   * Tests if exposed filtering via AJAX works in a modal.
+   */
+  public function testExposedFiltersInModal() {
+    $this->drupalGet('views-test-modal/modal');
+
+    $assert = $this->assertSession();
+
+    $assert->elementExists('named', ['link', 'Administer content'])->click();
+    $dialog = $assert->waitForElementVisible('css', '.views-test-modal');
+
+    $session = $this->getSession();
+    // Ensure that the Content we're testing for is present.
+    $html = $session->getPage()->getHtml();
+    $this->assertContains('Page One', $html);
+    $this->assertContains('Page Two', $html);
+
+    // Search for "Page One".
+    $session->getPage()->fillField('title', 'Page One');
+    $assert->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Filter');
+    $this->assertSession()->assertWaitOnAjaxRequest();
+
+    // Verify that only the "Page One" Node is present.
+    $html = $session->getPage()->getHtml();
+    $this->assertContains('Page One', $html);
+    $this->assertNotContains('Page Two', $html);
+
+    // Close and re-open the modal.
+    $assert->buttonExists('Close', $dialog)->press();
+    $assert->elementExists('named', ['link', 'Administer content'])->click();
+    $assert->waitForElementVisible('css', '.views-test-modal');
+
+    // Ensure that the Content we're testing for is present.
+    $html = $session->getPage()->getHtml();
+    $this->assertContains('Page One', $html);
+    $this->assertContains('Page Two', $html);
+
+    // Search for "Page One".
+    $session->getPage()->fillField('title', 'Page One');
+    $assert->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Filter');
+    $this->assertSession()->assertWaitOnAjaxRequest();
+
+    // Verify that only the "Page One" Node is present.
+    $html = $session->getPage()->getHtml();
+    $this->assertContains('Page One', $html);
+    $this->assertNotContains('Page Two', $html);
+  }
+
 }
-- 
GitLab