Merge pull request #24238 from demeritcowboy/boo
[civicrm-core.git] / ang / crmAutosave.js
1 /// crmAutosave
2 (function(angular, $, _) {
3
4 angular.module('crmAutosave', CRM.angRequires('crmAutosave'));
5
6 // usage:
7 // var autosave = new CrmAutosaveCtrl({
8 // save: function -- A function to handle saving. Should return a promise.
9 // If it's not a promise, then we'll assume that it completes successfully.
10 // saveIf: function -- Only allow autosave when conditional returns true. Default: !form.$invalid
11 // model: object|function -- (Re)schedule saves based on observed changes to object. We perform deep
12 // inspection on the model object. This could be a performance issue you
13 // had many concurrent autosave forms or a particularly large model, but
14 // it should be fine with typical usage.
15 // interval: object -- Interval spec. Default: {poll: 250, save: 5000}
16 // form: object|function -- FormController or its getter
17 // });
18 // autosave.start();
19 // $scope.$on('$destroy', autosave.stop);
20 // Note: if the save operation itself
21 angular.module('crmAutosave').service('CrmAutosaveCtrl', function($interval, $timeout, $q) {
22 function CrmAutosaveCtrl(options) {
23 var intervals = angular.extend({poll: 250, save: 5000}, options.interval);
24 var jobs = {poll: null, save: null}; // job handles used ot cancel/reschedule timeouts/intervals
25 var lastSeenModel = null;
26 var saving = false;
27
28 // Determine if model has changed; (re)schedule the save.
29 // This is a bit expensive and doesn't need to be continuous, so we use polling instead of watches.
30 function checkChanges() {
31 if (saving) {
32 return;
33 }
34 var currentModel = _.isFunction(options.model) ? options.model() : options.model;
35 if (!angular.equals(currentModel, lastSeenModel)) {
36 lastSeenModel = angular.copy(currentModel);
37 if (jobs.save) {
38 $timeout.cancel(jobs.save);
39 }
40 jobs.save = $timeout(doAutosave, intervals.save);
41 }
42 }
43
44 function doAutosave() {
45 jobs.save = null;
46 if (saving) {
47 return;
48 }
49
50 var form = _.isFunction(options.form) ? options.form() : options.form;
51
52 if (options.saveIf) {
53 if (!options.saveIf()) {
54 return;
55 }
56 }
57 else if (form && form.$invalid) {
58 return;
59 }
60
61 saving = true;
62 lastSeenModel = angular.copy(_.isFunction(options.model) ? options.model() : options.model);
63
64 // Set to pristine before saving -- not after saving.
65 // If an eager user continues editing concurrent with the
66 // save process, then the form should become dirty again.
67 if (form) {
68 form.$setPristine();
69 }
70 var res = options.save();
71 if (res && res.then) {
72 res.then(
73 function() {
74 saving = false;
75 },
76 function() {
77 saving = false;
78 if (form) {
79 form.$setDirty();
80 }
81 }
82 );
83 }
84 else {
85 saving = false;
86 }
87 }
88
89 var self = this;
90
91 this.start = function() {
92 if (!jobs.poll) {
93 lastSeenModel = angular.copy(_.isFunction(options.model) ? options.model() : options.model);
94 jobs.poll = $interval(checkChanges, intervals.poll);
95 }
96 };
97
98 this.stop = function() {
99 if (jobs.poll) {
100 $interval.cancel(jobs.poll);
101 jobs.poll = null;
102 }
103 if (jobs.save) {
104 $timeout.cancel(jobs.save);
105 jobs.save = null;
106 }
107 };
108
109 this.suspend = function(p) {
110 self.stop();
111 return p.finally(self.start);
112 };
113 }
114
115 return CrmAutosaveCtrl;
116 });
117
118 })(angular, CRM.$, CRM._);