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