forked from kirstein/angular-autodisable
-
Notifications
You must be signed in to change notification settings - Fork 0
/
angular-autodisable.js
190 lines (167 loc) · 6.32 KB
/
angular-autodisable.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
/*
* angular-autodisable 0.1.1
* http://github.com/kirstein/angular-autodisable
*
* Licensed under the MIT license
*/
(function (angular) {
'use strict';
return angular.module('ngAutodisable', [])
/**
* Overwrites ngClick directive if the `ngClick` and `ngAutodisable` directives are both on the same element
* then will evaluate the click response and check if its a promise or not
* if it happens to be a promise then will set `disabled` as true for as long as the promise is fulfilled
*
* @throws error if the `ngAutodisable` is on the element without the `ngClick` directive.
*/
.directive('ngAutodisable', [ '$parse', function($parse) {
var DISABLED = 'disabled', // Disabled attribute
ATTRNAME = 'ngAutodisable', // The attribute name to which we store the handlers ids
CLICK_EVENT = 'click',
CLICK_ATTR = 'ngClick',
SUBMIT_EVENT = 'submit',
SUBMIT_ATTR = 'ngSubmit',
LOADING_CLASS_ATTR = 'ngAutodisableClass';
/**
* Validates if the given promise is really a promise that we can use.
* Out promises must have at least `then` and `finally` functions
*
* @param {Object} promise promise to test
* @return {Boolean} true if its a promise, otherwise false
*/
function isPromise(promise) {
return promise &&
angular.isFunction(promise.then) &&
angular.isFunction(promise['finally']);
}
/**
* Trigger the defined handler.
*
* @param {Object} scope scope of the element
* @param {Object} attrs attributes
* @param {Function} fn function to trigger
*/
function triggerHandler(handler, scope, fn) {
var result = fn(scope, { $event : handler.eventName });
// If the function result happens to be a promise
// then handle the `disabled` state of the element.
// registers the result handler as an attribute
if (isPromise(result)) {
handler.handlePromise(result);
}
}
/**
* The link function for this directive.
* Contains a prepended function that represents the ngClick handler.
*
* @param {Array} handlers array of click handler
* @param {Object} scope scope
* @param {Angular Element} element directive element
* @param {Object} attrs attributes
*/
function linkFn(handler, scope, element, attrs) {
// Remove the click handler and replace it with our new one
// with this move we completely disable the original ngClick functionality
element.unbind(handler.eventName).bind(handler.eventName, function() {
// Make sure we run the $digest cycle
scope.$apply(function() {
handler.callbacks.forEach(triggerHandler.bind(null, handler, scope));
});
});
}
function getCallbacks(expression) {
return expression.split(';').map(function(callback) {
return $parse(callback, /* interceptorFn */ null, /* expensiveChecks */ true);
});
}
function getLoadingClass(attrs) {
return attrs.hasOwnProperty(LOADING_CLASS_ATTR) ? attrs[LOADING_CLASS_ATTR] : false;
}
/**
* Returns a new instance that can handle the promises returned by the callbacks.
* It will disable the given element when the first promise is triggered. And will
* re-enable the element, when the last promise is finished.
*
* @param {Element} elementToDisable DOM element that should be enabled and disabled.
* @param {String} eventName Name of the event ('click' or 'submit')
* @param {String|Boolean} loadingClass Class(es) to toggle to the element or false not disired.
* @param {Array} callbacks Array of callback functions to trigger.
* @return {Object} Object that handles the promises.
*/
function handlerInstance(elementToDisable, eventName, loadingClass, callbacks) {
var instance = {},
promisesTriggered = 0;
instance.eventName = eventName;
instance.callbacks = callbacks;
/**
* This should be called everytime a callback returns a promise.
*
* Disables the element for the first promise. And re-enables it when
* the last promise is done.
*
* @param {Promise} promise promise returned by a callback.
*/
instance.handlePromise = function(promise) {
if (promisesTriggered === 0) {
disableElement();
}
promisesTriggered++;
promise['finally'](function() {
promiseDone();
});
};
/**
* This is called every time a promise is done.
*
* Re-enables the element when the last promise is done.
*/
function promiseDone() {
promisesTriggered--;
if (promisesTriggered === 0) {
enableElement();
}
}
/**
* Disables the element. It can also add the classes listed by
* loadingClass.
*/
function disableElement() {
elementToDisable.attr(DISABLED, true);
if (loadingClass) {
elementToDisable.addClass(loadingClass);
}
}
/**
* Enables the element. It can also remove the classes listed by
* loadingClass.
*/
function enableElement() {
elementToDisable.attr(DISABLED, false);
if (loadingClass) {
elementToDisable.removeClass(loadingClass);
}
}
return instance;
}
return {
restrict : 'A',
compile : function(element, attrs) {
var handler;
if( attrs.hasOwnProperty(CLICK_ATTR) ) {
handler = handlerInstance(element,
CLICK_EVENT,
getLoadingClass(attrs),
getCallbacks(attrs[CLICK_ATTR]));
} else if ( attrs.hasOwnProperty(SUBMIT_ATTR) ) {
handler = handlerInstance(element.find('button[type=submit]'),
SUBMIT_EVENT,
getLoadingClass(attrs),
getCallbacks(attrs[SUBMIT_ATTR]));
} else {
throw new Error('ngAutodisable requires ngClick or ngSubmit attribute in order to work');
}
return linkFn.bind(null, handler);
}
};
}]);
})(angular);