CRM-17663 - Implement localStorage cache
authorColeman Watts <coleman@civicrm.org>
Sat, 6 Aug 2016 05:30:49 +0000 (01:30 -0400)
committerColeman Watts <coleman@civicrm.org>
Fri, 12 Aug 2016 03:32:35 +0000 (23:32 -0400)
CRM/Core/BAO/Dashboard.php
js/jquery/jquery.dashboard.js
templates/CRM/Contact/Page/DashBoardDashlet.tpl
templates/CRM/Contact/Page/Dashlet.tpl
templates/CRM/common/dashboard.tpl

index 209b309874d591e92a307fe5bc5be2c2ae9b76de..1b14cdd2ca89f9d02910e61231c2fc297a658388 100644 (file)
@@ -155,7 +155,6 @@ class CRM_Core_BAO_Dashboard extends CRM_Core_DAO_Dashboard {
     foreach (self::getContactDashlets() as $item) {
       $data[$item['column_no']][] = array(
         'id' => (int) $item['dashboard_id'],
-        'minimized' => (bool) $item['is_minimized'],
         'name' => $item['name'],
         'title' => $item['label'],
         'url' => self::parseUrl($item['url']),
index b26f88d96d117867f309be1f34ebe14f5f283c20..3561922369a15d4ffeb32e9acec27f65499fd7c7 100644 (file)
@@ -1,5 +1,6 @@
 // https://civicrm.org/licensing
 /* global CRM, ts */
+/*jshint loopfunc: true */
 (function($) {
   'use strict';
   // Constructor for dashboard object.
@@ -9,8 +10,7 @@
     dashboard.element = this.empty();
     dashboard.ready = false;
     dashboard.columns = [];
-    dashboard.widgets = [];
-    // End of public properties of dashboard.
+    dashboard.widgets = {};
 
     /**
      * Public methods of dashboard.
     // Saves the order of widgets for all columns including the widget.minimized status to options.ajaxCallbacks.saveColumns.
     dashboard.saveColumns = function(showStatus) {
       // Update the display status of the empty placeholders.
-      for (var c in dashboard.columns) {
-        var col = dashboard.columns[c];
-       if ( typeof col == 'object' ) {
-                       // Are there any visible children of the column (excluding the empty placeholder)?
-                       if (col.element.children(':visible').not(col.emptyPlaceholder).length > 0) {
-                         col.emptyPlaceholder.hide();
-                       }
-                       else {
-                         col.emptyPlaceholder.show();
-                       }
-               }
-         }
+      $.each(dashboard.columns, function(c, col) {
+        if ( typeof col == 'object' ) {
+          // Are there any visible children of the column (excluding the empty placeholder)?
+          if (col.element.children(':visible').not(col.emptyPlaceholder).length > 0) {
+            col.emptyPlaceholder.hide();
+          }
+          else {
+            col.emptyPlaceholder.show();
+          }
+        }
+      });
 
       // Don't save any changes to the server unless the dashboard has finished initiating.
       if (!dashboard.ready) {
       var params = {};
 
       // For each column...
-      for (var c2 in dashboard.columns) {
+      $.each(dashboard.columns, function(c, col) {
 
         // IDs of the sortable elements in this column.
-        var ids = (typeof dashboard.columns[c2] == 'object') ? dashboard.columns[c2].element.sortable('toArray') : undefined;
+        var ids = (typeof col == 'object') ? col.element.sortable('toArray') : [];
 
         // For each id...
-        for (var w in ids) {
-          // Chop 'widget-' off of the front so that we have the real widget id.
-          var id = (typeof ids[w] == 'string') ? ids[w].substring('widget-'.length) : undefined;
-
-          // Add one flat property to the params object that will look like an array element to the PHP server.
-          // Unfortunately jQuery doesn't do this for us.
-          if ( typeof dashboard.widgets[id] == 'object' ) params['columns[' + c2 + '][' + id + ']'] = (dashboard.widgets[id].minimized ? '1' : '0');
-        }
-      }
+        $.each(ids, function(w, id) {
+          if (typeof id == 'string') {
+            // Chop 'widget-' off of the front so that we have the real widget id.
+            id = id.substring('widget-'.length);
+            // Add one flat property to the params object that will look like an array element to the PHP server.
+            // Unfortunately jQuery doesn't do this for us.
+            if (typeof dashboard.widgets[id] == 'object') params['columns[' + c + '][' + id + ']'] = (dashboard.widgets[id].minimized ? '1' : '0');
+          }
+        });
+      });
 
       // The ajaxCallback settings overwrite any duplicate properties.
       $.extend(params, opts.ajaxCallbacks.saveColumns.data);
-      var post = $.post(opts.ajaxCallbacks.saveColumns.url, params, function(response, status) {
+      var post = $.post(opts.ajaxCallbacks.saveColumns.url, params, function() {
         invokeCallback(opts.callbacks.saveColumns, dashboard);
       });
       if (showStatus !== false) {
         CRM.status({}, post);
       }
     };
-    // End of public methods of dashboard.
 
     /**
      * Private properties of dashboard.
      */
 
-    // Used to determine whether there are any incomplete ajax requests pending initialization of the dashboard.
-    var asynchronousRequestCounter = 0;
-
     // Used to determine whether two resort events are resulting from the same UI event.
     var currentReSortEvent = null;
 
     // Merge in the caller's options with the defaults.
     var opts = $.extend({}, $.fn.dashboard.defaults, options);
 
+    var localCache = window.localStorage && localStorage.dashboard ? JSON.parse(localStorage.dashboard) : {};
+
     init(opts.widgetsByColumn);
 
     return dashboard;
-    // End of constructor and private properties for dashboard object.
 
     /**
      * Private methods of dashboard.
         $("#empty-message").show( );
     }
 
+    function saveLocalCache() {
+      localCache = {};
+      $.each(dashboard.widgets, function(id, widget) {
+        localCache[id] = {
+          content: widget.content,
+          expires: widget.expires,
+          minimized: widget.minimized
+        };
+      });
+      if (window.localStorage) {
+        localStorage.dashboard = JSON.stringify(localCache);
+      }
+    }
+
     // Contructors for each widget call this when initialization has finished so that dashboard can complete it's intitialization.
     function completeInit() {
-      // Don't do anything if any widgets are waiting for ajax requests to complete in order to finish initialization.
-      if (asynchronousRequestCounter > 0 || dashboard.ready) {
+      // Only do this once.
+      if (dashboard.ready) {
           return;
       }
 
      */
     function widget(widget) {
       // Merge default options with the options defined for this widget.
-      widget = $.extend({}, $.fn.dashboard.widget.defaults, widget);
+      widget = $.extend({}, $.fn.dashboard.widget.defaults, localCache[widget.id] || {}, widget);
 
       /**
        * Public methods of widget.
         }
 
         widget.hideSettings();
-        dashboard.saveColumns();
       };
       widget.minimize = function() {
         $('.widget-content', widget.element).slideUp(opts.animationSpeed);
           .removeClass('fa-caret-down')
           .attr('title', ts('Expand'));
         widget.minimized = true;
+        saveLocalCache();
       };
       widget.maximize = function() {
-        $('.widget-content', widget.element).slideDown(opts.animationSpeed);
         $(widget.controls.minimize.element)
           .removeClass( 'fa-caret-right' )
           .addClass( 'fa-caret-down' )
           .attr('title', ts('Collapse'));
         widget.minimized = false;
+        saveLocalCache();
         if (!widget.contentLoaded) {
           loadContent();
         }
+        $('.widget-content', widget.element).slideDown(opts.animationSpeed);
       };
 
       // Toggles whether the widget is in settings-display mode or not.
         var params = {};
         // serializeArray() returns an array of objects.  Process it.
         var fields = widget.settings.element.serializeArray();
-        for (var i in fields) {
-            var field = fields[i];
+        $.each(fields, function(i, field) {
             // Put the values into flat object properties that PHP will parse into an array server-side.
             // (Unfortunately jQuery doesn't do this)
             params['settings[' + field.name + ']'] = field.value;
-        }
+        });
 
         // Things get messy here.
         // @todo Refactor to use currentState and targetedState properties to determine what needs
           control.element = $(markup).prependTo($('.widget-controls', widget.element)).click(control.callback);
       };
 
-      // An external method used only by and from external scripts to reload content.  Not invoked or used internally.
-      // The widget must provide the script that executes this, as well as the script that invokes it.
+      // Fetch remote content.
       widget.reloadContent = function() {
-          getJavascript(widget.reloadContentScript);
-          invokeCallback(opts.widgetCallbacks.reloadContent, widget);
+        // If minimized, we'll reload later
+        if (widget.minimized) {
+          widget.contentLoaded = false;
+          widget.expires = 0;
+        } else {
+          CRM.loadPage(widget.url, {target: widget.contentElement});
+        }
       };
 
       // Removes the widget from the dashboard, and saves columns.
         invokeCallback(opts.widgetCallbacks.remove, widget);
         widget.element.fadeOut(opts.animationSpeed, function() {
           $(this).remove();
+          delete(dashboard.widgets[widget.id]);
+          dashboard.saveColumns(false);
         });
-        dashboard.saveColumns(false);
         CRM.alert(
           ts('You can re-add it by clicking the "Configure Your Dashboard" button.'),
           ts('"%1" Removed', {1: widget.title}),
           'success'
         );
       };
-      // End public methods of widget.
 
       /**
        * Public properties of widget.
       };
       widget.contentLoaded = false;
 
-      // End public properties of widget.
-
       init();
       return widget;
 
        */
 
       function loadContent() {
-        asynchronousRequestCounter++;
-        widget.contentLoaded = true;
-        CRM.loadPage(widget.url, {target: widget.contentElement})
-          .one('crmLoad', function() {
-            asynchronousRequestCounter--;
+        var loadFromCache = (widget.expires > $.now() && widget.content);
+        if (loadFromCache) {
+          widget.contentElement.html(widget.content).trigger('crmLoad', widget);
+        }
+        widget.contentElement.off('crmLoad').on('crmLoad', function(event, data) {
+          if ($(event.target).is(widget.contentElement)) {
+            widget.content = data.content;
+            // Cache for one day
+            widget.expires = $.now() + 86400000;
+            saveLocalCache();
             invokeCallback(opts.widgetCallbacks.get, widget);
-            completeInit();
-          });
+          }
+        });
+        if (!loadFromCache) {
+          widget.reloadContent();
+        }
+        widget.contentLoaded = true;
       }
 
       // Build widget & load content.
   $.fn.dashboard.widget = {
     defaults: {
       minimized: false,
+      content: null,
+      expires: 0,
       settings: false
-      // url, fullscreenUrl, content, title, name
+      // url, fullscreenUrl, title, name
     }
   };
 })(jQuery);
index b67f50e8285619f504b79a2a54baafea60f0861b..277f6d4b6c736f12b52abcc0c9148d8bb99d8830 100644 (file)
 {include file="CRM/common/openFlashChart.tpl"}
 {* Alerts for critical configuration settings. *}
 {$communityMessages}
-<div class="crm-submit-buttons">
+<div class="crm-submit-buttons crm-dashboard-controls">
 <a href="#" id="crm-dashboard-configure" class="crm-hover-button show-add">
   <i class="crm-i fa-wrench"></i> {ts}Configure Your Dashboard{/ts}
 </a>
 
-<a style="display:none;" href="{crmURL p="civicrm/dashboard" q="reset=1"}" class="button show-done" style="margin-left: 6px;">
-  <span><i class="crm-i fa-check"></i> {ts}Done{/ts}</span>
-</a>
-
-<a style="float:right;" href="{crmURL p="civicrm/dashboard" q="reset=1&resetCache=1"}" class="crm-hover-button show-refresh" style="margin-left: 6px;">
+<a style="float:right;" href="#" class="crm-hover-button show-refresh" style="margin-left: 6px;">
   <i class="crm-i fa-refresh"></i> {ts}Refresh Dashboard Data{/ts}
 </a>
 
@@ -53,7 +49,7 @@
     </div>
 </div>
 
-<div id="configure-dashlet" class='hiddenElement'></div>
+<div id="configure-dashlet" class='hiddenElement' style="min-height: 20em;"></div>
 <div id="civicrm-dashboard">
   {* You can put anything you like here.  jQuery.dashboard() will remove it. *}
   <noscript>{ts}Javascript must be enabled in your browser in order to use the dashboard features.{/ts}</noscript>
 {literal}
 <script type="text/javascript">
   CRM.$(function($) {
-    $('#crm-dashboard-configure').click(function() {
-      $.ajax({
-         url: CRM.url('civicrm/dashlet', 'reset=1&snippet=1'),
-         success: function( content ) {
-           $("#civicrm-dashboard, #crm-dashboard-configure, .show-refresh, #empty-message").hide();
-           $('.show-done').show();
-           $("#configure-dashlet").show().html(content).trigger('crmLoad');
-         }
+    $('#crm-dashboard-configure').click(function(e) {
+      e.preventDefault();
+      $(this).hide();
+      if ($("#empty-message").is(':visible')) {
+        $("#empty-message").fadeOut(400);
+      }
+      $("#civicrm-dashboard").fadeOut(400, function() {
+        $(".crm-dashboard-controls").hide();
+        $("#configure-dashlet").fadeIn(400);
       });
-      return false;
+      CRM.loadPage(CRM.url('civicrm/dashlet', 'reset=1'), {target: $("#configure-dashlet")});
     });
   });
 </script>
index 8ddc57d66cbd0482a160c40a2b8cb97bbf047384..e13b0a5c88fd11b86925384499425888d33e38f9 100644 (file)
@@ -23,6 +23,7 @@
  | see the CiviCRM license FAQ at http://civicrm.org/licensing        |
  +--------------------------------------------------------------------+
 *}
+    <div class="crm-submit-buttons">{crmButton p="civicrm/dashboard" q="reset=1" icon="check"}{ts}Done{/ts}{/crmButton}</div>
     <div id="help" style="padding: 1em;">
         {ts}Available dashboard elements - dashlets - are displayed in the dark gray top bar. Drag and drop dashlets onto the left or right columns below to add them to your dashboard. Changes are automatically saved. Click 'Done' to return to the normal dashboard view.{/ts}
         {help id="id-dash_configure" file="CRM/Contact/Page/Dashboard.hlp" admin=$admin}
index 0c682d2e53c27fc28a6a96ab515ed1e4c4ab0074..378fbed1df2db38cdd6bceb39a16a860c1895664 100644 (file)
@@ -87,7 +87,7 @@ CRM.$(function($) {
 
   };
 
-  $('#civicrm-dashboard')
+  var dashboard = $('#civicrm-dashboard')
     .on('mouseover', '.widget-header', function() {
       $(this).closest('.widget-wrapper').addClass('db-hover-handle');
     })
@@ -96,6 +96,14 @@ CRM.$(function($) {
     })
     .dashboard(options);
 
+
+  $('.crm-hover-button.show-refresh').click(function(e) {
+    e.preventDefault();
+    $.each(dashboard.widgets, function(id, widget) {
+      widget.reloadContent();
+    });
+  });
+
 });
 
 </script>