Thu Sep 12 16:28:13 2019 +0600 95125ac (HEAD -> back_button) Fixing tests [Yuriy Shupen'ko] diff --git a/config/schema/views_infinite_scroll.schema.yml b/config/schema/views_infinite_scroll.schema.yml index 75df35b..25e3b5d 100644 --- a/config/schema/views_infinite_scroll.schema.yml +++ b/config/schema/views_infinite_scroll.schema.yml @@ -11,3 +11,6 @@ views.pager.infinite_scroll: automatically_load_content: type: boolean label: 'Automatically Load Content' + preserve_history: + type: boolean + label: 'Preserve History' diff --git a/js/infinite-scroll.behaviors.js b/js/infinite-scroll.behaviors.js new file mode 100644 index 0000000..af415f4 --- /dev/null +++ b/js/infinite-scroll.behaviors.js @@ -0,0 +1,70 @@ +(function ($, Drupal) { + 'use strict'; + + function htmlDecode(value) { + return $('
').html(value).text(); + } + + function backButtonPressed() { + return performance.navigation.type === performance.navigation.TYPE_BACK_FORWARD; + } + + function getSessionStorageId(viewId, viewDisplay, domViewId) { + return "viewPreviousData_"+viewId+"_"+viewDisplay+"_"+domViewId; + } + + function emptySessionStorage(sessionStorageId) { + sessionStorage.removeItem(sessionStorageId); + } + + function hasDataInSessionStorage(sessionStorageId) { + return (sessionStorage[sessionStorageId]); + } + + function getDataFromSessionStorage(sessionStorageId) { + var data = JSON.parse(sessionStorage.getItem(sessionStorageId)); + if(typeof data !== 'undefined') { + return data; + } + return ''; + } + + Drupal.behaviors.ViewsInfiniteScrollHistory = { + attach: function (context, settings) { + for (var key in settings.views.ajaxViews) { + var viewSettings = settings.views.ajaxViews[key]; + $('.js-view-dom-id-' + viewSettings.view_dom_id).once('vis-history').each(function () { + if (!window.performance) return; + var pagerSelector = '[data-drupal-views-infinite-scroll-pager]'; + var sessionStorageId = getSessionStorageId(viewSettings.view_name, viewSettings.view_display_id, viewSettings.view_dom_id); + + if (!hasDataInSessionStorage(sessionStorageId)) { + return; + } + + if (!backButtonPressed()) { + emptySessionStorage(sessionStorageId); + return; + } + + var view = Drupal.views.instances['views_dom_id:' + viewSettings.view_dom_id]; + var $existingPager = view.$view.find(pagerSelector); + var data = getDataFromSessionStorage(sessionStorageId); + var decodedContent = htmlDecode(data.articles); + $('.views-infinite-scroll-content-wrapper', this).append(decodedContent); + $existingPager.once('pagerReplaced').each(function(){ + var decodedPager = htmlDecode(data.pager); + $existingPager.html(decodedPager); + view.$view.removeOnce('ajax-pager'); + view.$exposed_form.removeOnce('exposed-form'); + $existingPager.removeOnce('infinite-scroll'); + + // Process each view. + Drupal.behaviors.ViewsAjaxView.attach(view.settings); + Drupal.attachBehaviors(view.$view[0]); + }); + }); + } + } + }; +})(jQuery, Drupal); diff --git a/js/infinite-scroll.js b/js/infinite-scroll.js index 942a595..e899910 100644 --- a/js/infinite-scroll.js +++ b/js/infinite-scroll.js @@ -10,6 +10,9 @@ // The selector for the automatic pager. var automaticPagerSelector = '[data-drupal-views-infinite-scroll-pager="automatic"]'; + // The selector for history pager. + var historyPagerSelector = '[data-drupal-views-infinite-scroll-history="preserve-history"]'; + // The selector for both manual load and automatic pager. var pagerSelector = '[data-drupal-views-infinite-scroll-pager]'; @@ -19,19 +22,48 @@ // The event and namespace that is bound to window for automatic scrolling. var scrollEvent = 'scroll.views_infinite_scroll'; + var entityMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '/': '/', + '`': '`', + '=': '=' + }; + /** - * Insert a views infinite scroll view into the document. - * - * @param {jQuery} $newView - * New content detached from the DOM. + * Escapes HTML for sessionStorage as JSON string + * @param string + * @returns {string} */ + function escapeHtml (string) { + return String(string).replace(/[&<>"'`=\/]/g, function (s) { + return entityMap[s]; + }); + } + + String.prototype.escapeSpecialChars = function() { + return this.replace(/\\n/g, "\\n") + .replace(/\\'/g, "\\'") + .replace(/\\"/g, '\\"') + .replace(/\\&/g, "\\&") + .replace(/\\r/g, "\\r") + .replace(/\\t/g, "\\t") + .replace(/\\b/g, "\\b") + .replace(/\\f/g, "\\f"); + }; + $.fn.infiniteScrollInsertView = function ($newView) { + var articles = []; + var previousItems = {}; // Extract the view DOM ID from the view classes. var matches = /(js-view-dom-id-\w+)/.exec(this.attr('class')); - var currentViewId = matches[1].replace('js-view-dom-id-', 'views_dom_id:'); + var currentViewId = matches[1].replace('js-view-dom-id-', ''); // Get the existing ajaxViews object. - var view = Drupal.views.instances[currentViewId]; + var view = Drupal.views.instances['views_dom_id:'+currentViewId]; // Remove once so that the exposed form and pager are processed on // behavior attach. view.$view.removeOnce('ajax-pager'); @@ -43,6 +75,41 @@ var $newRows = $newView.find(contentWrapperSelector).children(); var $newPager = $newView.find(pagerSelector); + // If preserve-history is switched on, we try to + // use sessionStorage. + $(view.$view).find(historyPagerSelector).once('infinite-scroll-history').each(function() { + var sessionStorageId = 'viewPreviousData_'+view.settings.view_name+"_"+view.settings.view_display_id+"_"+currentViewId; + previousItems.articles = []; + $newView.find(contentWrapperSelector).children().each(function () { + // var result = escapeHtml($(this).html()).escapeSpecialChars(); + var elHtml = $(this).html(); + var elClass = $(this).attr('class'); + var elType = $(this).prop('nodeName'); + var result = "<" + elType + " class='history-loaded " + elClass + "'>"+ elHtml +""; + result = escapeHtml(result).escapeSpecialChars(); + articles.push($.trim(result)); + }); + + if (articles.length > 0) { + if (sessionStorage[sessionStorageId]) { + previousItems = JSON.parse(sessionStorage.getItem(sessionStorageId)); + } + for (var i = 0; i < articles.length; i++) { + previousItems.articles.push($.trim(articles[i])); + } + } + + var pager = escapeHtml($($newPager).html()).escapeSpecialChars(); + + if (pager.length > 0) { + previousItems.pager = pager; + } + + if (previousItems.articles.length > 0 && previousItems.pager) { + sessionStorage.setItem(sessionStorageId, JSON.stringify(previousItems)); + } + }); + // Add the new rows to existing view. view.$view.find(contentWrapperSelector).append($newRows); // Replace the pager link with the new link and ajaxPageState values. @@ -87,5 +154,4 @@ } } }; - })(jQuery, Drupal, Drupal.debounce); diff --git a/src/Plugin/views/pager/InfiniteScroll.php b/src/Plugin/views/pager/InfiniteScroll.php index fa8f99b..134f79f 100644 --- a/src/Plugin/views/pager/InfiniteScroll.php +++ b/src/Plugin/views/pager/InfiniteScroll.php @@ -57,6 +57,9 @@ class InfiniteScroll extends SqlBase { 'automatically_load_content' => [ 'default' => FALSE, ], + 'preserve_history' => [ + 'default' => FALSE, + ], ], ]; return $options; @@ -105,6 +108,12 @@ class InfiniteScroll extends SqlBase { '#description' => $this->t('Automatically load subsequent pages as the user scrolls.'), '#default_value' => $options['automatically_load_content'], ], + 'preserve_history' => [ + '#type' => 'checkbox', + '#title' => $this->t('Preserve history'), + '#description' => $this->t('Preserve history of expanded content when navigating back to this view using the browser back button.'), + '#default_value' => $options['preserve_history'], + ], ]; } diff --git a/tests/src/FunctionalJavascript/InfiniteScrollTest.php b/tests/src/FunctionalJavascript/InfiniteScrollTest.php index 1a0f43d..99d475e 100644 --- a/tests/src/FunctionalJavascript/InfiniteScrollTest.php +++ b/tests/src/FunctionalJavascript/InfiniteScrollTest.php @@ -82,6 +82,42 @@ class InfiniteScrollTest extends WebDriverTestBase { $this->assertTotalNodes(11); } + /** + * Test back button behavior. + */ + public function testBackButton() { + // Test manually clicking a view. + $this->createView('click-to-load', [ + 'button_text' => 'Load More', + 'automatically_load_content' => FALSE, + 'preserve_history' => TRUE, + ]); + + $this->getSession()->resizeWindow(1200, 200); + + $this->drupalGet('click-to-load'); + $this->assertTotalElements(3, '.node--type-page'); + + $this->getSession()->getPage()->clickLink('Load More'); + $this->getSession()->wait(500); + $this->assertTotalNodes(6); + + $this->getSession()->getDriver()->executeScript("window.scrollTo(null, 500);"); + $this->getSession()->getPage()->find('css', '.views-row:nth-child(4) h2 a')->click(); + $this->getSession()->back(); + $this->assertTotalNodes(6); + $this->assertTotalElements(3, '.views-row.history-loaded'); + + $this->getSession()->getPage()->clickLink('Load More'); + $this->getSession()->wait(500); + $this->assertTotalNodes(9); + + $this->getSession()->getPage()->find('css', '.views-row:nth-child(8) h2 a')->click(); + $this->getSession()->back(); + $this->assertTotalNodes(9); + $this->assertTotalElements(6, '.views-row.history-loaded'); + } + /** * Assert how many nodes appear on the page. * @@ -89,7 +125,19 @@ class InfiniteScrollTest extends WebDriverTestBase { * The total nodes on the page. */ protected function assertTotalNodes($total) { - $this->assertEquals($total, count($this->getSession()->getPage()->findAll('css', '.node--type-page'))); + $this->assertTotalElements($total, '.node--type-page'); + } + + /** + * Assert how many elements appear on the page. + * + * @param int $total + * The total nodes on the page. + * @param $selector + * Selector to fetch elements + */ + protected function assertTotalElements($total, $selector) { + $this->assertEquals($total, count($this->getSession()->getPage()->findAll('css', $selector))); } /** diff --git a/views_infinite_scroll.libraries.yml b/views_infinite_scroll.libraries.yml index 249790b..e473113 100644 --- a/views_infinite_scroll.libraries.yml +++ b/views_infinite_scroll.libraries.yml @@ -1,10 +1,19 @@ views-infinite-scroll: version: VERSION js: - js/infinite-scroll.js : {} + js/infinite-scroll.js: {} dependencies: - core/jquery - core/jquery.once - core/drupal - core/drupal.debounce - views/views.ajax + +views-infinite-scroll-behaviors: + version: VERSION + js: + js/infinite-scroll.behaviors.js: {} + dependencies: + - core/jquery + - core/jquery.once + - core/drupal diff --git a/views_infinite_scroll.module b/views_infinite_scroll.module index c16914e..b7e75f0 100644 --- a/views_infinite_scroll.module +++ b/views_infinite_scroll.module @@ -28,6 +28,7 @@ function views_infinite_scroll_preprocess_views_infinite_scroll_pager(&$vars) { $vars['attributes'] = new Attribute([ 'class' => ['js-pager__items', 'pager'], 'data-drupal-views-infinite-scroll-pager' => $vars['options']['automatically_load_content'] ? 'automatic' : TRUE, + 'data-drupal-views-infinite-scroll-history' => $vars['options']['preserve_history'] ? 'preserve-history' : TRUE, ]); } @@ -40,6 +41,7 @@ function views_infinite_scroll_preprocess_views_view(&$vars) { if ($view->getDisplay()->isPagerEnabled() && !empty($vars['rows'])) { $pager = $view->getPager(); if ($pager && $pager instanceof InfiniteScroll) { + $vars['#attached']['library'][] = 'views_infinite_scroll/views-infinite-scroll-behaviors'; if (!isset($vars['rows']['#theme_wrappers'])) { $vars['rows']['#theme_wrappers'] = []; }