Commit | Line | Data |
---|---|---|
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._); |