Merge branch 4.5 into master
[civicrm-core.git] / js / angular-crmAutosave.js
CommitLineData
2386ec88
TO
1/// crmAutosave
2(function(angular, $, _) {
3
4 angular.module('crmAutosave', ['crmUtil']);
5
6 // usage: <form crm-autosave="myCtrl.save()" crm-autosave-model="myModel">...</form>
7 //
8 // Automatically save changes. Don't save while the user is actively updating the model -- save
9 // after a pause in user activity (e.g. after 2sec).
10 //
11 // - crm-autosave="callback" -- A function to handle saving. Should return a promise.
12 // If it's not a promise, then we'll assume that it completes successfully.
13 // - crm-autosave-interval="object" -- Interval spec. Default: {poll: 250, save: 3000}
14 // - crm-autosave-if="conditional" -- Only allow autosave when conditional returns true. Default: !form.$invalid
15 // - crm-autosave-model="object" -- (Re)schedule saves based on observed changes to object.
16 // We perform deep ispection on the model object. This could be a performance issue you had many concurrent
17 // autosave forms, but it should be fine with one.
18 //
19 // The call to the autosave function will cause the form to be marked as pristine (unless there's an error).
20 angular.module('crmAutosave').directive('crmAutosave', function($interval, $timeout) {
21 return {
22 restrict: 'AE',
23 require: '^form',
24 link: function(scope, element, attrs, form) {
25 var intervals = angular.extend({poll: 250, save: 3000}, scope.$eval(attrs.crmAutosaveInterval));
26 var jobs = {poll: null, save: null}; // job handles used ot cancel/reschedule timeouts/intervals
27 var lastSeenModel = null;
28 var saving = false;
29
30 // Determine if model has changed; (re)schedule the save.
31 // This is a bit expensive and doesn't need to be continuous, so we use polling instead of watches.
32 function checkChanges() {
33 if (saving) {
34 return;
35 }
36 var currentModel = scope.$eval(attrs.crmAutosaveModel);
37 if (lastSeenModel === null) {
38 lastSeenModel = angular.copy(currentModel);
39 }
40 else if (!angular.equals(currentModel, lastSeenModel)) {
41 lastSeenModel = angular.copy(currentModel);
42 if (jobs.save) {
43 $timeout.cancel(jobs.save);
44 }
45 jobs.save = $timeout(doAutosave, intervals.save);
46 }
47 }
48
49 function doAutosave() {
50 jobs.save = null;
51 if (saving) {
52 return;
53 }
54
55 if (attrs.crmAutosaveIf) {
56 if (!scope.$eval(attrs.crmAutosaveIf)) {
57 return;
58 }
59 }
60 else if (form.$invalid) {
61 return;
62 }
63
64 saving = true;
65
66 // Set to pristine before saving -- not after saving.
67 // If an eager user continues editing concurrent with the
68 // save process, then the form should become dirty again.
69 form.$setPristine();
70 var res = scope.$eval(attrs.crmAutosave);
71 if (res && res.then) {
72 res.then(
73 function() {
74 saving = false;
75 },
76 function() {
77 saving = false;
78 form.$setDirty();
79 }
80 );
81 }
82 else {
83 saving = false;
84 }
85 }
86
87 jobs.poll = $interval(checkChanges, intervals.poll);
88 element.on('$destroy', function() {
89 if (jobs.poll) {
90 $interval.cancel(jobs.poll);
91 jobs.poll = null;
92 }
93 if (jobs.save) {
94 $timeout.cancel(jobs.save);
95 jobs.save = null;
96 }
97 });
98
99 }
100 };
101 });
102
103})(angular, CRM.$, CRM._);