CRM-16142 - Variable scope fixes
[civicrm-core.git] / js / angular-crmAutosave.js
index 8b5a24c56a859748f656f0f53911398a05435787..a697880b8f115d1c0cc085fd3f37ae6aceeb5b2b 100644 (file)
 
   angular.module('crmAutosave', ['crmUtil']);
 
-  // usage: <form crm-autosave="myCtrl.save()" crm-autosave-model="myModel">...</form>
-  //
-  // Automatically save changes. Don't save while the user is actively updating the model -- save
-  // after a pause in user activity (e.g. after 2sec).
-  //
-  //  - crm-autosave="callback" -- A function to handle saving. Should return a promise.
-  //                               If it's not a promise, then we'll assume that it completes successfully.
-  //  - crm-autosave-interval="object" -- Interval spec. Default: {poll: 250, save: 5000}
-  //  - crm-autosave-if="conditional" -- Only allow autosave when conditional returns true. Default: !form.$invalid
-  //  - crm-autosave-model="object" -- (Re)schedule saves based on observed changes to object.
-  //    We perform deep ispection on the model object. This could be a performance issue you had many concurrent
-  //    autosave forms, but it should be fine with one.
-  //
-  // The call to the autosave function will cause the form to be marked as pristine (unless there's an error).
-  angular.module('crmAutosave').directive('crmAutosave', function($interval, $timeout) {
-    return {
-      restrict: 'AE',
-      require: '^form',
-      link: function(scope, element, attrs, form) {
-        var intervals = angular.extend({poll: 250, save: 5000}, scope.$eval(attrs.crmAutosaveInterval));
-        var jobs = {poll: null, save: null}; // job handles used ot cancel/reschedule timeouts/intervals
-        var lastSeenModel = null;
-        var saving = false;
+  // usage:
+  //   var autosave = new CrmAutosaveCtrl({
+  //     save: function           -- A function to handle saving. Should return a promise.
+  //                                 If it's not a promise, then we'll assume that it completes successfully.
+  //     saveIf: function         -- Only allow autosave when conditional returns true. Default: !form.$invalid
+  //     model: object|function   -- (Re)schedule saves based on observed changes to object. We perform deep
+  //                                 inspection on the model object. This could be a performance issue you
+  //                                 had many concurrent autosave forms or a particularly large model, but
+  //                                 it should be fine with typical usage.
+  //     interval: object         -- Interval spec. Default: {poll: 250, save: 5000}
+  //     form: object|function    -- FormController or its getter
+  //   });
+  //   autosave.start();
+  //   $scope.$on('$destroy', autosave.stop);
+  // Note: if the save operation itself
+  angular.module('crmAutosave').service('CrmAutosaveCtrl', function($interval, $timeout, $q) {
+    function CrmAutosaveCtrl(options) {
+      var intervals = angular.extend({poll: 250, save: 5000}, options.interval);
+      var jobs = {poll: null, save: null}; // job handles used ot cancel/reschedule timeouts/intervals
+      var lastSeenModel = null;
+      var saving = false;
 
-        // Determine if model has changed; (re)schedule the save.
-        // This is a bit expensive and doesn't need to be continuous, so we use polling instead of watches.
-        function checkChanges() {
-          if (saving) {
-            return;
-          }
-          var currentModel = scope.$eval(attrs.crmAutosaveModel);
-          if (lastSeenModel === null) {
-            lastSeenModel = angular.copy(currentModel);
-          }
-          else if (!angular.equals(currentModel, lastSeenModel)) {
-            lastSeenModel = angular.copy(currentModel);
-            if (jobs.save) {
-              $timeout.cancel(jobs.save);
-            }
-            jobs.save = $timeout(doAutosave, intervals.save);
+      // Determine if model has changed; (re)schedule the save.
+      // This is a bit expensive and doesn't need to be continuous, so we use polling instead of watches.
+      function checkChanges() {
+        if (saving) {
+          return;
+        }
+        var currentModel = _.isFunction(options.model) ? options.model() : options.model;
+        if (!angular.equals(currentModel, lastSeenModel)) {
+          lastSeenModel = angular.copy(currentModel);
+          if (jobs.save) {
+            $timeout.cancel(jobs.save);
           }
+          jobs.save = $timeout(doAutosave, intervals.save);
         }
+      }
 
-        function doAutosave() {
-          jobs.save = null;
-          if (saving) {
-            return;
-          }
+      function doAutosave() {
+        jobs.save = null;
+        if (saving) {
+          return;
+        }
 
-          if (attrs.crmAutosaveIf) {
-            if (!scope.$eval(attrs.crmAutosaveIf)) {
-              return;
-            }
-          }
-          else if (form.$invalid) {
+        var form = _.isFunction(options.form) ? options.form() : options.form;
+
+        if (options.saveIf) {
+          if (!options.saveIf()) {
             return;
           }
+        }
+        else if (form && form.$invalid) {
+          return;
+        }
 
-          saving = true;
-          lastSeenModel = angular.copy(scope.$eval(attrs.crmAutosaveModel));
+        saving = true;
+        lastSeenModel = angular.copy(_.isFunction(options.model) ? options.model() : options.model);
 
-          // Set to pristine before saving -- not after saving.
-          // If an eager user continues editing concurrent with the
-          // save process, then the form should become dirty again.
+        // Set to pristine before saving -- not after saving.
+        // If an eager user continues editing concurrent with the
+        // save process, then the form should become dirty again.
+        if (form) {
           form.$setPristine();
-          var res = scope.$eval(attrs.crmAutosave);
-          if (res && res.then) {
-            res.then(
-              function() {
-                saving = false;
-              },
-              function() {
-                saving = false;
+        }
+        var res = options.save();
+        if (res && res.then) {
+          res.then(
+            function() {
+              saving = false;
+            },
+            function() {
+              saving = false;
+              if (form) {
                 form.$setDirty();
               }
-            );
-          }
-          else {
-            saving = false;
-          }
+            }
+          );
         }
+        else {
+          saving = false;
+        }
+      }
 
-        jobs.poll = $interval(checkChanges, intervals.poll);
-        element.on('$destroy', function() {
-          if (jobs.poll) {
-            $interval.cancel(jobs.poll);
-            jobs.poll = null;
-          }
-          if (jobs.save) {
-            $timeout.cancel(jobs.save);
-            jobs.save = null;
-          }
-        });
+      var self = this;
 
-      }
-    };
+      this.start = function() {
+        if (!jobs.poll) {
+          lastSeenModel = angular.copy(_.isFunction(options.model) ? options.model() : options.model);
+          jobs.poll = $interval(checkChanges, intervals.poll);
+        }
+      };
+
+      this.stop = function() {
+        if (jobs.poll) {
+          $interval.cancel(jobs.poll);
+          jobs.poll = null;
+        }
+        if (jobs.save) {
+          $timeout.cancel(jobs.save);
+          jobs.save = null;
+        }
+      };
+
+      this.suspend = function(p) {
+        self.stop();
+        return p.finally(self.start);
+      };
+    }
+
+    return CrmAutosaveCtrl;
   });
 
 })(angular, CRM.$, CRM._);