From 851b4a5f942a7628db4248d74afaa4603742cd72 Mon Sep 17 00:00:00 2001
From: Chris Hallberg <crhallberg@gmail.com>
Date: Tue, 13 Sep 2016 11:02:12 -0400
Subject: [PATCH] Config to load record tabs in background (#758)

---
 module/VuFind/config/module.config.php        |  3 +++
 .../src/VuFind/Controller/AbstractBase.php    | 11 ++++++++
 .../src/VuFind/Controller/AbstractRecord.php  | 26 ++++++++++++++++---
 .../src/VuFind/Controller/AjaxController.php  |  4 +++
 .../Controller/CollectionController.php       |  2 +-
 .../src/VuFind/RecordTab/PluginManager.php    | 15 +++++++++++
 themes/bootstrap3/js/embedded_record.js       |  3 +++
 themes/bootstrap3/js/record.js                | 19 +++++++++++++-
 .../templates/collection/view.phtml           | 14 +++++-----
 .../templates/record/ajaxview-accordion.phtml |  2 +-
 .../templates/record/ajaxview-tabs.phtml      |  2 +-
 themes/bootstrap3/templates/record/view.phtml | 14 +++++-----
 12 files changed, 91 insertions(+), 24 deletions(-)

diff --git a/module/VuFind/config/module.config.php b/module/VuFind/config/module.config.php
index 12c50d8f86f..a375d807c25 100644
--- a/module/VuFind/config/module.config.php
+++ b/module/VuFind/config/module.config.php
@@ -610,6 +610,8 @@ $config = [
         // driver is not defined here, it will inherit configuration from a configured
         // parent class.  The defaultTab setting may be used to specify the default
         // active tab; if null, the value from the relevant .ini file will be used.
+        // You can also specify which tabs are loaded in the background when arriving
+        // at a record tabs view with backgroundLoadedTabs as a list of tab indexes.
         'recorddriver_tabs' => [
             'VuFind\RecordDriver\EDS' => [
                 'tabs' => [
@@ -654,6 +656,7 @@ $config = [
                     'Details' => 'StaffViewArray',
                 ],
                 'defaultTab' => null,
+                // 'backgroundLoadedTabs' => ['UserComments', 'Details']
             ],
             'VuFind\RecordDriver\SolrMarc' => [
                 'tabs' => [
diff --git a/module/VuFind/src/VuFind/Controller/AbstractBase.php b/module/VuFind/src/VuFind/Controller/AbstractBase.php
index 16b6701f572..5c9900cd4d9 100644
--- a/module/VuFind/src/VuFind/Controller/AbstractBase.php
+++ b/module/VuFind/src/VuFind/Controller/AbstractBase.php
@@ -637,4 +637,15 @@ class AbstractBase extends AbstractActionController
     {
         $this->followup()->clear('url');
     }
+
+    /**
+     * Get the tab configuration for this controller.
+     *
+     * @return array
+     */
+    protected function getRecordTabConfig()
+    {
+        $cfg = $this->getServiceLocator()->get('Config');
+        return $cfg['vufind']['recorddriver_tabs'];
+    }
 }
diff --git a/module/VuFind/src/VuFind/Controller/AbstractRecord.php b/module/VuFind/src/VuFind/Controller/AbstractRecord.php
index 317c6fd6657..7ea90a10dba 100644
--- a/module/VuFind/src/VuFind/Controller/AbstractRecord.php
+++ b/module/VuFind/src/VuFind/Controller/AbstractRecord.php
@@ -614,14 +614,15 @@ class AbstractRecord extends AbstractBase
     }
 
     /**
-     * Get the tab configuration for this controller.
+     * Alias to getRecordTabConfig for backward compatibility.
+     *
+     * @deprecated use getRecordTabConfig instead
      *
      * @return array
      */
     protected function getTabConfiguration()
     {
-        $cfg = $this->getServiceLocator()->get('Config');
-        return $cfg['vufind']['recorddriver_tabs'];
+        return $this->getRecordTabConfig();
     }
 
     /**
@@ -635,11 +636,14 @@ class AbstractRecord extends AbstractBase
         $request = $this->getRequest();
         $rtpm = $this->getServiceLocator()->get('VuFind\RecordTabPluginManager');
         $details = $rtpm->getTabDetailsForRecord(
-            $driver, $this->getTabConfiguration(), $request,
+            $driver, $this->getRecordTabConfig(), $request,
             $this->fallbackDefaultTab
         );
         $this->allTabs = $details['tabs'];
         $this->defaultTab = $details['default'] ? $details['default'] : false;
+        $this->backgroundTabs = $rtpm->getBackgroundTabNames(
+            $driver, $this->getRecordTabConfig()
+        );
     }
 
     /**
@@ -669,6 +673,19 @@ class AbstractRecord extends AbstractBase
         return $this->allTabs;
     }
 
+    /**
+     * Get names of tabs to be loaded in the background.
+     *
+     * @return array
+     */
+    protected function getBackgroundTabs()
+    {
+        if (null === $this->backgroundTabs) {
+            $this->loadTabDetails();
+        }
+        return $this->backgroundTabs;
+    }
+
     /**
      * Is the result scroller active?
      *
@@ -709,6 +726,7 @@ class AbstractRecord extends AbstractBase
         $view->tabs = $this->getAllTabs();
         $view->activeTab = strtolower($tab);
         $view->defaultTab = strtolower($this->getDefaultTab());
+        $view->backgroundTabs = $this->getBackgroundTabs();
         $view->loadInitialTabWithAjax
             = isset($config->Site->loadInitialTabWithAjax)
             ? (bool) $config->Site->loadInitialTabWithAjax : false;
diff --git a/module/VuFind/src/VuFind/Controller/AjaxController.php b/module/VuFind/src/VuFind/Controller/AjaxController.php
index 98c7b7f50d4..af4244d91d1 100644
--- a/module/VuFind/src/VuFind/Controller/AjaxController.php
+++ b/module/VuFind/src/VuFind/Controller/AjaxController.php
@@ -796,6 +796,7 @@ class AjaxController extends AbstractBase
                 'Information'
             );
 
+        $rtpm = $this->getServiceLocator()->get('VuFind\RecordTabPluginManager');
         $html = $this->getViewRenderer()
             ->render(
                 "record/ajaxview-" . $viewtype . ".phtml",
@@ -803,6 +804,9 @@ class AjaxController extends AbstractBase
                     'defaultTab' => $details['default'],
                     'driver' => $driver,
                     'tabs' => $details['tabs'],
+                    'backgroundTabs' => $rtpm->getBackgroundTabNames(
+                        $driver, $this->getRecordTabConfig()
+                    )
                 ]
             );
         return $this->output($html, self::STATUS_OK);
diff --git a/module/VuFind/src/VuFind/Controller/CollectionController.php b/module/VuFind/src/VuFind/Controller/CollectionController.php
index 7b3e42f4ed9..a0233529ab0 100644
--- a/module/VuFind/src/VuFind/Controller/CollectionController.php
+++ b/module/VuFind/src/VuFind/Controller/CollectionController.php
@@ -59,7 +59,7 @@ class CollectionController extends AbstractRecord
      *
      * @return array
      */
-    protected function getTabConfiguration()
+    protected function getRecordTabConfig()
     {
         $cfg = $this->getServiceLocator()->get('Config');
         return $cfg['vufind']['recorddriver_collection_tabs'];
diff --git a/module/VuFind/src/VuFind/RecordTab/PluginManager.php b/module/VuFind/src/VuFind/RecordTab/PluginManager.php
index 514710b40bb..28792dafdbf 100644
--- a/module/VuFind/src/VuFind/RecordTab/PluginManager.php
+++ b/module/VuFind/src/VuFind/RecordTab/PluginManager.php
@@ -95,6 +95,21 @@ class PluginManager extends \VuFind\ServiceManager\AbstractPluginManager
         return $this->getConfigByClass($driver, $config, 'tabs', []);
     }
 
+    /**
+     * Get an array of tabs names configured to load via AJAX in the background
+     *
+     * @param AbstractRecordDriver $driver Record driver
+     * @param array                $config Tab configuration (associative array
+     * including 'tabs' array mapping driver class => tab service name)
+     *
+     * @return array
+     */
+    public function getBackgroundTabNames(AbstractRecordDriver $driver,
+        array $config
+    ) {
+        return $this->getConfigByClass($driver, $config, 'backgroundLoadedTabs', []);
+    }
+
     /**
      * Get a default tab by looking up the provided record driver in the tab
      * configuration array.
diff --git a/themes/bootstrap3/js/embedded_record.js b/themes/bootstrap3/js/embedded_record.js
index 51e7f0cb04b..0d4eeb028eb 100644
--- a/themes/bootstrap3/js/embedded_record.js
+++ b/themes/bootstrap3/js/embedded_record.js
@@ -157,6 +157,9 @@ VuFind.register('embedded', function embedded() {
                   );
                 }
               );
+              longNode.find('[data-background]').each(function setupEmbeddedBackgroundTabs(index, el) {
+                ajaxLoadTab(el.id, false);
+              });
               // Add events to record toolbar
               VuFind.lightbox.bind(longNode);
               if (typeof checkSaveStatuses == 'function') {
diff --git a/themes/bootstrap3/js/record.js b/themes/bootstrap3/js/record.js
index 863968bc504..5df87a2e8cd 100644
--- a/themes/bootstrap3/js/record.js
+++ b/themes/bootstrap3/js/record.js
@@ -231,6 +231,19 @@ function ajaxTagUpdate(_link, tag, _remove) {
   });
 }
 
+function getNewRecordTab(tabid) {
+  return $('<div class="tab-pane ' + tabid + '-tab"><i class="fa fa-spinner fa-spin" aria-hidden="true"></i> ' + VuFind.translate('loading') + '...</div>');
+}
+
+function backgroundLoadTab(tabid) {
+  if ($('.' + tabid + '-tab').length > 0) {
+    return;
+  }
+  var newTab = getNewRecordTab(tabid);
+  $('.nav-tabs a.'+tabid).closest('.result,.record').find('.tab-content').append(newTab);
+  return ajaxLoadTab(newTab, tabid, false);
+}
+
 function applyRecordTabHash() {
   var activeTab = $('.record-tabs li.active a').attr('class');
   var $initiallyActiveTab = $('.record-tabs li.initiallyActive a');
@@ -277,12 +290,16 @@ function recordDocReady() {
       window.location.hash = tabid;
       return false;
     } else {
-      var newTab = $('<div class="tab-pane active ' + tabid + '-tab"><i class="fa fa-spinner fa-spin" aria-hidden="true"></i> ' + VuFind.translate('loading') + '...</div>');
+      var newTab = getNewRecordTab(tabid).addClass('active');
       $top.find('.tab-content').append(newTab);
       return ajaxLoadTab(newTab, tabid, !$(this).parent().hasClass('initiallyActive'));
     }
   });
 
+  $('[data-background]').each(function setupBackgroundTabs(index, el) {
+    backgroundLoadTab(el.className);
+  });
+
   registerTabEvents();
   applyRecordTabHash();
 }
diff --git a/themes/bootstrap3/templates/collection/view.phtml b/themes/bootstrap3/templates/collection/view.phtml
index 640a86b4126..08b328f6ff6 100644
--- a/themes/bootstrap3/templates/collection/view.phtml
+++ b/themes/bootstrap3/templates/collection/view.phtml
@@ -62,14 +62,12 @@
 
 <?=$this->record($this->driver)->getToolbar()?>
 
-<div class="row">
+<div class="record row">
   <div class="<?=$tree ? 'col-sm-12' : $this->layoutClass('mainbody') ?>">
-    <div class="record">
-      <input type="hidden" value="<?=$this->escapeHtmlAttr($this->driver->getUniqueId())?>" class="hiddenId" id="record_id" />
-      <input type="hidden" value="<?=$this->escapeHtmlAttr($this->driver->getSourceIdentifier())?>" class="hiddenSource" />
-      <?=$this->flashmessages()?>
-      <?=$this->record($this->driver)->getCollectionMetadata()?>
-    </div>
+    <input type="hidden" value="<?=$this->escapeHtmlAttr($this->driver->getUniqueId())?>" class="hiddenId" id="record_id" />
+    <input type="hidden" value="<?=$this->escapeHtmlAttr($this->driver->getSourceIdentifier())?>" class="hiddenSource" />
+    <?=$this->flashmessages()?>
+    <?=$this->record($this->driver)->getCollectionMetadata()?>
 
     <? if (count($this->tabs) > 0): ?>
       <a name="tabnav"></a>
@@ -91,7 +89,7 @@
               if (!$obj->supportsAjax()) { $tab_classes[] = 'noajax'; }
             ?>
             <li<?=count($tab_classes) > 0 ? ' class="' . implode(' ', $tab_classes) . '"' : ''?>>
-              <a class="<?=strtolower($tab) ?>" href="<?=$this->recordLink()->getTabUrl($this->driver, $tab)?>#tabnav"><?=$this->transEsc($desc)?></a>
+              <a class="<?=strtolower($tab) ?>" href="<?=$this->recordLink()->getTabUrl($this->driver, $tab)?>#tabnav"<? if ($obj->supportsAjax() && in_array($tab, $this->backgroundTabs)):?> data-background<? endif ?>><?=$this->transEsc($desc)?></a>
             </li>
           <? endforeach; ?>
         </ul>
diff --git a/themes/bootstrap3/templates/record/ajaxview-accordion.phtml b/themes/bootstrap3/templates/record/ajaxview-accordion.phtml
index 71916a3a676..150886392be 100644
--- a/themes/bootstrap3/templates/record/ajaxview-accordion.phtml
+++ b/themes/bootstrap3/templates/record/ajaxview-accordion.phtml
@@ -75,7 +75,7 @@
         if (!$obj->supportsAjax()) { $tab_classes[] = 'noajax'; }
       ?>
       <div class="panel panel-default <?=implode(' ', $tab_classes) ?>">
-        <div id="<?=strtolower($tab)?>_<?=$idSuffix?>" class="list-tab-toggle panel-heading" data-toggle="collapse" data-parent="#accordion_<?=$idSuffix?>" data-target="#<?=strtolower($tab)?>_<?=$idSuffix?>-content">
+        <div id="<?=strtolower($tab)?>_<?=$idSuffix?>" class="list-tab-toggle panel-heading" data-toggle="collapse" data-parent="#accordion_<?=$idSuffix?>" data-target="#<?=strtolower($tab)?>_<?=$idSuffix?>-content"<? if ($obj->supportsAjax() && in_array($tab, $this->backgroundTabs)):?> data-background<? endif ?>>
           <h4 class="panel-title">
             <a class="accordion-toggle" data-href="<?=$this->recordLink()->getTabUrl($this->driver, $tab)?>#tabnav"><?=$desc ?></a>
           </h4>
diff --git a/themes/bootstrap3/templates/record/ajaxview-tabs.phtml b/themes/bootstrap3/templates/record/ajaxview-tabs.phtml
index 955095841b3..a995b431515 100644
--- a/themes/bootstrap3/templates/record/ajaxview-tabs.phtml
+++ b/themes/bootstrap3/templates/record/ajaxview-tabs.phtml
@@ -43,7 +43,7 @@
       if (!$obj->supportsAjax()) { $tab_classes[] = 'noajax'; }
     ?>
     <li<?=count($tab_classes) > 0 ? ' class="' . implode(' ', $tab_classes) . '"' : ''?>>
-      <a id="<?=strtolower($tab)?>_<?=$idSuffix?>" class="list-tab-toggle" href="<?=$this->recordLink()->getTabUrl($this->driver, $tab)?>#tabnav" data-toggle="tab" data-target="#<?=strtolower($tab)?>_<?=$idSuffix?>-content"><?=$this->transEsc($desc)?></a>
+      <a id="<?=strtolower($tab)?>_<?=$idSuffix?>" class="list-tab-toggle" href="<?=$this->recordLink()->getTabUrl($this->driver, $tab)?>#tabnav" data-toggle="tab" data-target="#<?=strtolower($tab)?>_<?=$idSuffix?>-content"<? if ($obj->supportsAjax() && in_array($tab, $this->backgroundTabs)):?> data-background<? endif ?>><?=$this->transEsc($desc)?></a>
     </li>
   <? endforeach; ?>
 </ul>
diff --git a/themes/bootstrap3/templates/record/view.phtml b/themes/bootstrap3/templates/record/view.phtml
index 180eaeee6a8..d17786fd5b4 100644
--- a/themes/bootstrap3/templates/record/view.phtml
+++ b/themes/bootstrap3/templates/record/view.phtml
@@ -56,14 +56,12 @@
 
 <?=$this->record($this->driver)->getToolbar()?>
 
-<div class="row">
+<div class="record source<?=$this->escapeHtmlAttr($this->driver->getSourceIdentifier())?> row">
   <div class="<?=$this->layoutClass('mainbody')?>">
-    <div class="record source<?=$this->escapeHtmlAttr($this->driver->getSourceIdentifier())?>">
-      <input type="hidden" value="<?=$this->escapeHtmlAttr($this->driver->getUniqueId())?>" class="hiddenId" />
-      <input type="hidden" value="<?=$this->escapeHtmlAttr($this->driver->getSourceIdentifier()) ?>" class="hiddenSource" />
-      <?=$this->flashmessages()?>
-      <?=$this->record($this->driver)->getCoreMetadata()?>
-    </div>
+    <input type="hidden" value="<?=$this->escapeHtmlAttr($this->driver->getUniqueId())?>" class="hiddenId" />
+    <input type="hidden" value="<?=$this->escapeHtmlAttr($this->driver->getSourceIdentifier()) ?>" class="hiddenSource" />
+    <?=$this->flashmessages()?>
+    <?=$this->record($this->driver)->getCoreMetadata()?>
 
     <? if (count($this->tabs) > 0): ?>
       <a name="tabnav"></a>
@@ -85,7 +83,7 @@
               if (!$obj->supportsAjax()) { $tab_classes[] = 'noajax'; }
             ?>
             <li<?=count($tab_classes) > 0 ? ' class="' . implode(' ', $tab_classes) . '"' : ''?>>
-              <a class="<?=strtolower($tab) ?>" href="<?=$this->recordLink()->getTabUrl($this->driver, $tab)?>#tabnav"><?=$this->transEsc($desc)?></a>
+              <a class="<?=strtolower($tab) ?>" href="<?=$this->recordLink()->getTabUrl($this->driver, $tab)?>#tabnav"<? if ($obj->supportsAjax() && in_array($tab, $this->backgroundTabs)):?> data-background<? endif ?>><?=$this->transEsc($desc)?></a>
             </li>
           <? endforeach; ?>
         </ul>
-- 
GitLab