diff --git a/Gulpfile.js b/Gulpfile.js
new file mode 100644
index 0000000..a9fe738
--- /dev/null
+++ b/Gulpfile.js
@@ -0,0 +1,24 @@
+/* jshint strict: false */
+var gulp = require('gulp');
+var uglify = require('gulp-uglify');
+var concat = require('gulp-concat');
+
+var PATH = {
+ src: [
+ 'src/autodisable.module.js',
+ 'src/autodisable.directive.js',
+ 'src/autodisable.factory.js'
+ ]
+};
+
+gulp.task('build', function() {
+ return gulp.src(PATH.src)
+ .pipe(concat('autodisable.js'))
+ .pipe(gulp.dest('.'))
+ .pipe(uglify())
+ .pipe(gulp.dest('dist'));
+});
+
+gulp.task('watch', function () {
+ gulp.watch(PATH.src, ['build']);
+});
\ No newline at end of file
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..157fe56
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,11 @@
+install:
+ npm install
+
+build:
+ ./node_modules/gulp/bin/gulp.js build
+
+test:
+ ./node_modules/karma/bin/karma start karma.conf.js --single-run
+
+tdd:
+ ./node_modules/karma/bin/karma start karma.conf.js
\ No newline at end of file
diff --git a/autodisable.js b/autodisable.js
new file mode 100644
index 0000000..9fd3b21
--- /dev/null
+++ b/autodisable.js
@@ -0,0 +1,298 @@
+angular.module('angular-autodisable', []);
+/* global angular */
+(function(module) {
+ 'use strict';
+
+ /**
+ * @directive [autodisable]
+ * @example
+ *
+ */
+ function autodisableDirective(AutoDisable) {
+ return {
+ restrict: 'A',
+ compile: compiler,
+ require: ['?form', '?^form']
+ };
+
+ function compiler(element, attrs) {
+ var autoDisable = new AutoDisable(element, attrs, attrs.autodisable);
+ return autoDisable.link.bind(autoDisable);
+ }
+ }
+
+ module.directive('autodisable', ['AutoDisable', autodisableDirective]);
+
+})(angular.module('angular-autodisable'));
+
+/* global angular */
+(function(module) {
+ 'use strict';
+
+ var TAG_INPUT = /^(input|textarea)$/i,
+ TAG_BUTTON = /^button$/i,
+ TAG_FORM = /^form$/i,
+
+ CLS_AUTODISABLE = 'autodisable',
+ CLS_LOCKED = 'autodisable-locked',
+ CLS_BUSY = 'autodisable-busy',
+
+ baseConfig = {
+ lockOnComplete: true
+ };
+
+ /**
+ * @factory
+ */
+ function AutoDisableFactory($parse, $q) {
+ /* jshint validthis: true */
+ function AutoDisable(element, attrs, options) {
+ var tagName = String(element && element[0] && element[0].tagName || ''),
+ type = attrs.type || '',
+ isSubmit = type === 'submit',
+ isInput = TAG_INPUT.test(tagName),
+ isButton = TAG_BUTTON.test(tagName),
+ isForm = TAG_FORM.test(tagName);
+
+ this.options = options;
+ this.type = type;
+
+ this.isSubmit = isSubmit && (isButton || isInput);
+ this.isForm = isForm;
+ this.isInput = !isForm;
+
+ this.onClick = !!attrs.ngClick;
+ this.onSubmit = !!attrs.ngSubmit;
+ }
+
+ AutoDisable.prototype = {
+ constructor: AutoDisable,
+ link: link,
+ lock: lock,
+ unlock: unlock,
+ busyLock: busyLock,
+ busyUnlock: busyUnlock,
+ initialize: initialize
+ };
+
+ function initialize() {
+ // listen to ng-submit
+ if (this.isForm && this.onSubmit) {
+ bindEvent(this, 'submit');
+ }
+
+ // listen to ng-click on submit button
+ if (this.isSubmit && this.onClick) {
+ bindEvent(this, 'click');
+ }
+
+ if (this.isForm) {
+ bindFormState(this);
+ } else {
+ bindChildState(this);
+ }
+ }
+
+ function link($scope, $element, $attrs, controllers) {
+ var form = this.isForm ? controllers[0] : controllers[1],
+ options = this.options;
+
+ form.$busy = form.$disabled = false;
+
+ angular.extend(this, {
+ scope: $scope,
+ element: $element,
+ attrs: $attrs,
+ form: form,
+ options: angular.isString(options) && $scope.$eval(options) || {}
+ });
+
+ $element.addClass(CLS_AUTODISABLE);
+
+ this.initialize();
+ }
+
+ function busyLock(promise) {
+ var self = this;
+
+ if (self.promise) return;
+
+ self.element.addClass(CLS_BUSY);
+
+ // at form or submit button, bind to promise
+ // otherwise, just lock the field
+ if (promise) {
+ self.promise = promise.then(function(response) {
+ self.busyUnlock(true);
+ return response;
+ }, function(error) {
+ self.busyUnlock(false);
+ return $q.reject(error);
+ });
+ }
+
+ if (this.isForm) {
+ setFormBusy(self, true);
+ } else {
+ setInputDisable(self, true);
+ }
+ }
+
+ function busyUnlock(success) {
+ this.element.removeClass(CLS_BUSY);
+ this.promise = null;
+
+ if (this.isForm) {
+ setFormBusy(this, false);
+
+ if (success && baseConfig.lockOnComplete) {
+ this.form.$setPristine();
+ }
+ } else {
+ setInputDisable(this, false);
+ }
+ }
+
+ function setFormBusy(self, value) {
+ self.form.$busy = value;
+ setFormDisable(self, value);
+ }
+
+ function lock() {
+ if (this.locked) return;
+
+ this.locked = true;
+ this.element.addClass(CLS_LOCKED);
+
+ if (this.isForm) {
+ setFormDisable(this, true);
+ } else if (this.isSubmit) {
+ setInputDisable(this, true);
+ } else {
+ setInputDisable(this, false);
+ }
+ }
+
+ function unlock() {
+ if (!this.locked) return;
+
+ this.locked = false;
+ this.element.removeClass(CLS_LOCKED);
+
+ if (this.isForm) {
+ setFormDisable(this, false);
+ } else {
+ setInputDisable(this, false);
+ }
+ }
+
+ function setFormDisable(self, value) {
+ self.form.$disabled = value;
+ }
+
+ function setInputDisable(self, value) {
+ self.attrs.$set('disabled', value);
+ }
+
+ // helper functions
+ function bindEvent(self, eventName) {
+ var attributeName = 'ng' + eventName.charAt(0).toUpperCase() + eventName.slice(1),
+ fn = $parse(self.attrs[attributeName], /* interceptorFn */ null, /* expensiveChecks */ true);
+
+ self.element.unbind(eventName).bind(eventName, handler);
+
+ function handler($event) {
+ if (self.locked || self.promise) return;
+
+ var result = fn(self.scope, {
+ $event: $event
+ });
+
+ if (isPromise(result)) {
+ self.busyLock(result);
+ }
+
+ self.scope.$apply();
+ }
+ }
+
+ function bindStateTrigger(self, watcher, trigger) {
+ var form = self.form || false;
+ if (!form) return;
+ self.scope.$watch(watcher, trigger);
+ }
+
+ /**
+ * Lock/unlock the form
+ */
+ function bindFormState(self) {
+ var form = self.form;
+ bindStateTrigger(self, isInvalid, updateLockOnForm);
+
+ function isInvalid() {
+ return form.$pristine + '' + form.$invalid;
+ }
+
+ function updateLockOnForm() {
+ if (
+ (form.$pristine && self.options.pristine !== false) ||
+ (form.$invalid && self.options.invalid !== false)
+ ) {
+ self.lock();
+ return;
+ }
+
+ self.unlock();
+ }
+ }
+
+ function bindChildState(self) {
+ var form = self.form;
+
+ bindStateTrigger(self, isFormDisabled, updateChildDisabled);
+ bindStateTrigger(self, isFormBusy, updateChildBusy);
+
+ function isFormDisabled() {
+ return form.$disabled;
+ }
+
+ function isFormBusy() {
+ return form.$busy;
+ }
+
+ function updateChildDisabled() {
+ if (form.$disabled && !form.$busy) {
+ self.lock();
+ } else {
+ self.unlock();
+ }
+ }
+
+ function updateChildBusy() {
+ if (form.$busy) {
+ self.busyLock();
+ } else {
+ self.busyUnlock();
+ }
+ }
+ }
+
+ function isPromise(value) {
+ return Boolean(value && typeof value.then === 'function' &&
+ typeof value.finally === 'function');
+ }
+
+ return AutoDisable;
+ }
+
+ module.provider('AutoDisable', function() {
+ this.$get = ['$parse', '$q', AutoDisableFactory];
+ this.config = function(config) {
+ angular.extend(baseConfig, config);
+ };
+ });
+
+})(angular.module('angular-autodisable'));
diff --git a/bower.json b/bower.json
new file mode 100644
index 0000000..49dfd86
--- /dev/null
+++ b/bower.json
@@ -0,0 +1,25 @@
+{
+ "name": "ng-autodisable",
+ "main": "autodisable.js",
+ "version": "0.2.0",
+ "homepage": "https://github.com/darlanalves/angular-autodisable",
+ "authors": [
+ "Darlan Alves "
+ ],
+ "description": "Autodisable form fields and buttons while a promise is in progress",
+ "keywords": [
+ "angularjs"
+ ],
+ "license": "ISC",
+ "ignore": [
+ "**/.*",
+ "node_modules",
+ "bower_components",
+ "test",
+ "tests",
+ "Gruntfile.js",
+ "karma.conf.js",
+ "Makefile",
+ "src/**.*"
+ ]
+}
diff --git a/karma.conf.js b/karma.conf.js
new file mode 100644
index 0000000..a8b1f7a
--- /dev/null
+++ b/karma.conf.js
@@ -0,0 +1,76 @@
+// Karma configuration
+// Generated on Sun Jul 05 2015 01:20:52 GMT-0300 (BRT)
+var path = require('path');
+
+module.exports = function(config) {
+ 'use strict';
+
+ var angular = path.join(path.dirname(require.resolve('angular')), 'angular.js');
+ var angularMocks = path.join(path.dirname(require.resolve('angular-mocks/ngMock')), 'angular-mocks.js');
+ var es5Shim = path.join(path.dirname(require.resolve('es5-shim')), 'es5-shim.js');
+
+ config.set({
+
+ // base path that will be used to resolve all patterns (eg. files, exclude)
+ basePath: '',
+
+
+ // frameworks to use
+ // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
+ frameworks: ['jasmine'],
+
+
+ // list of files / patterns to load in the browser
+ files: [
+ angular,
+ angularMocks,
+ es5Shim,
+ 'src/autodisable.module.js',
+ 'src/autodisable.directive.js',
+ 'src/autodisable.factory.js',
+ 'src/*.spec.js'
+ ],
+
+
+ // list of files to exclude
+ exclude: [],
+
+
+ // preprocess matching files before serving them to the browser
+ // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
+ preprocessors: {},
+
+
+ // test results reporter to use
+ // possible values: 'dots', 'progress'
+ // available reporters: https://npmjs.org/browse/keyword/karma-reporter
+ reporters: ['progress'],
+
+
+ // web server port
+ port: 9876,
+
+
+ // enable / disable colors in the output (reporters and logs)
+ colors: true,
+
+
+ // level of logging
+ // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
+ logLevel: config.LOG_INFO,
+
+
+ // enable / disable watching file and executing tests whenever any file changes
+ autoWatch: true,
+
+
+ // start these browsers
+ // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
+ browsers: ['PhantomJS'],
+
+
+ // Continuous Integration mode
+ // if true, Karma captures browsers, runs the tests and exits
+ singleRun: false
+ })
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..0c7c13a
--- /dev/null
+++ b/package.json
@@ -0,0 +1,50 @@
+{
+ "_args": [
+ [
+ "git://github.com/contentools/angular-autodisable.git#v0.3.1",
+ "/Users/davi/Projetos/contentools/frontend"
+ ]
+ ],
+ "_from": "git://github.com/contentools/angular-autodisable.git#v0.3.1",
+ "_id": "autodisable@git://github.com/contentools/angular-autodisable.git#cccaf2fa1f2409a320e932d1e996de206ff271b1",
+ "_inBundle": false,
+ "_integrity": "",
+ "_location": "/autodisable",
+ "_phantomChildren": {},
+ "_requested": {
+ "type": "git",
+ "raw": "git://github.com/contentools/angular-autodisable.git#v0.3.1",
+ "rawSpec": "git://github.com/contentools/angular-autodisable.git#v0.3.1",
+ "saveSpec": "git://github.com/contentools/angular-autodisable.git#v0.3.1",
+ "fetchSpec": "git://github.com/contentools/angular-autodisable.git",
+ "gitCommittish": "v0.3.1"
+ },
+ "_requiredBy": [
+ "/"
+ ],
+ "_resolved": "git://github.com/contentools/angular-autodisable.git#cccaf2fa1f2409a320e932d1e996de206ff271b1",
+ "_spec": "git://github.com/contentools/angular-autodisable.git#v0.3.1",
+ "_where": "/Users/davi/Projetos/contentools/frontend",
+ "author": "",
+ "description": "Autodisable buttons and form fields on submit",
+ "devDependencies": {
+ "angular": "^1.3.16",
+ "angular-mocks": "^1.3.16",
+ "es5-shim": "^4.1.7",
+ "gulp": "^3.9.0",
+ "gulp-concat": "^2.6.0",
+ "gulp-uglify": "^1.2.0",
+ "jasmine-core": "^2.3.4",
+ "karma": "^0.12.37",
+ "karma-jasmine": "^0.3.6",
+ "karma-phantomjs-launcher": "^0.2.0",
+ "phantomjs": "^1.9.17"
+ },
+ "license": "ISC",
+ "main": "autodisable.js",
+ "name": "autodisable",
+ "scripts": {
+ "test": "make test"
+ },
+ "version": "0.3.1"
+}
diff --git a/src/autodisable.directive.js b/src/autodisable.directive.js
new file mode 100644
index 0000000..73eb259
--- /dev/null
+++ b/src/autodisable.directive.js
@@ -0,0 +1,28 @@
+/* global angular */
+(function(module) {
+ 'use strict';
+
+ /**
+ * @directive [autodisable]
+ * @example
+ *
+ */
+ function autodisableDirective(AutoDisable) {
+ return {
+ restrict: 'A',
+ compile: compiler,
+ require: ['?form', '?^form']
+ };
+
+ function compiler(element, attrs) {
+ var autoDisable = new AutoDisable(element, attrs, attrs.autodisable);
+ return autoDisable.link.bind(autoDisable);
+ }
+ }
+
+ module.directive('autodisable', ['AutoDisable', autodisableDirective]);
+
+})(angular.module('angular-autodisable'));
diff --git a/src/autodisable.directive.spec.js b/src/autodisable.directive.spec.js
new file mode 100644
index 0000000..65afedf
--- /dev/null
+++ b/src/autodisable.directive.spec.js
@@ -0,0 +1,163 @@
+'use strict';
+
+describe('autodisable directive', function() {
+
+ beforeEach(module('angular-autodisable'));
+
+ var compile, flush;
+
+ beforeEach(inject(function($rootScope, $compile) {
+ compile = function(template) {
+ var el = $compile(template)($rootScope);
+ $rootScope.$digest();
+
+ return el;
+ };
+
+ flush = function() {
+ $rootScope.$digest();
+ };
+ }));
+
+ describe('disable form submission if there is a promise waiting in the submit event handler', function() {
+ it('should not fire the handler twice while the promise is waiting', inject(function($rootScope, $q) {
+ var template = '',
+
+ count = 0,
+ element = compile(template),
+ deferred = $q.defer(),
+ form = element.data('$formController'),
+ button = element.find('button'),
+ input = element.find('input');
+
+ expect(count).toBe(0);
+
+ // autolock
+ expect(element.hasClass('autodisable')).toBe(true);
+ expect(element.hasClass('autodisable-locked')).toBe(true);
+ expect(element.hasClass('autodisable-busy')).toBe(false);
+
+ // auto lock children on startup
+ expect(button.attr('disabled')).toBe('disabled');
+ expect(button.hasClass('autodisable-locked')).toBe(true);
+ expect(button.hasClass('autodisable-busy')).toBe(false);
+
+ expect(input.attr('disabled')).not.toBe('disabled');
+ expect(input.hasClass('autodisable-locked')).toBe(true);
+ expect(input.hasClass('autodisable-busy')).toBe(false);
+
+
+ $rootScope.onsubmit = function() {
+ count++;
+ return deferred.promise;
+ };
+
+ form.$setDirty();
+ flush();
+
+ /**
+ * Submit the form
+ */
+ element.triggerHandler('submit');
+ expect(count).toBe(1);
+
+ expect(element.hasClass('autodisable')).toBe(true);
+ expect(element.hasClass('autodisable-locked')).toBe(false);
+ expect(element.hasClass('autodisable-busy')).toBe(true);
+
+ // auto locked on submit
+ expect(button.attr('disabled')).toBe('disabled');
+ expect(button.hasClass('autodisable-locked')).toBe(false);
+ expect(button.hasClass('autodisable-busy')).toBe(true);
+
+ // inputs are locked as well
+ expect(input.attr('disabled')).toBe('disabled');
+ expect(input.hasClass('autodisable-locked')).toBe(false);
+ expect(input.hasClass('autodisable-busy')).toBe(true);
+
+ /**
+ * Another submission must be ignored
+ */
+ element.triggerHandler('submit');
+ expect(count).toBe(1);
+
+ deferred.resolve();
+ flush();
+
+ /**
+ * The promise is now resolved. Unlock the form
+ */
+ expect(element.hasClass('autodisable')).toBe(true);
+ expect(element.hasClass('autodisable-locked')).toBe(false);
+ expect(element.hasClass('autodisable-busy')).toBe(false);
+
+ // unlocked when the submission ends
+ expect(button.attr('disabled')).not.toBe('disabled');
+ expect(button.hasClass('autodisable-locked')).toBe(false);
+ expect(button.hasClass('autodisable-busy')).toBe(false);
+
+ // inputs are unlocked as well
+ expect(input.attr('disabled')).not.toBe('disabled');
+ expect(input.hasClass('autodisable-locked')).toBe(false);
+ expect(input.hasClass('autodisable-busy')).toBe(false);
+
+ element.triggerHandler('submit');
+ expect(count).toBe(2);
+ }));
+
+ it('should autolock if the form is invalid or pristine', inject(function($rootScope) {
+ var template = '',
+ element = compile(template),
+ count = 0;
+
+ $rootScope.onsubmit = function() {
+ count++;
+ };
+
+ element.triggerHandler('submit');
+ expect(count).toBe(0);
+ }));
+
+ it('should lock the child nodes when the parent is locked', inject(function() {
+ var template = '',
+
+ element = compile(template);
+
+ var button = element.find('button'),
+ input = element.find('input');
+
+ // auto locked due to form state being pristine
+ expect(button.attr('disabled')).toBe('disabled');
+ expect(button.hasClass('autodisable-locked')).toBe(true);
+ expect(button.hasClass('autodisable-busy')).toBe(false);
+
+ // has the class but won't be disabled, otherwise the form
+ // would be unusable
+ expect(input.attr('disabled')).not.toBe('disabled');
+ expect(input.hasClass('autodisable-locked')).toBe(true);
+ expect(input.hasClass('autodisable-busy')).toBe(false);
+ }));
+
+ it('should unlock the child nodes when the parent is unlocked', inject(function() {
+ var template = '',
+
+ element = compile(template),
+ form = element.data('$formController');
+
+ form.$setDirty();
+ flush();
+
+ expect(element.find('button').attr('disabled')).not.toBe('disabled');
+ expect(element.find('input').attr('disabled')).not.toBe('disabled');
+ }));
+ });
+});
diff --git a/src/autodisable.factory.js b/src/autodisable.factory.js
new file mode 100644
index 0000000..4290196
--- /dev/null
+++ b/src/autodisable.factory.js
@@ -0,0 +1,268 @@
+/* global angular */
+(function(module) {
+ 'use strict';
+
+ var TAG_INPUT = /^(input|textarea)$/i,
+ TAG_BUTTON = /^button$/i,
+ TAG_FORM = /^form$/i,
+
+ CLS_AUTODISABLE = 'autodisable',
+ CLS_LOCKED = 'autodisable-locked',
+ CLS_BUSY = 'autodisable-busy',
+
+ baseConfig = {
+ lockOnComplete: true
+ };
+
+ /**
+ * @factory
+ */
+ function AutoDisableFactory($parse, $q) {
+ /* jshint validthis: true */
+ function AutoDisable(element, attrs, options) {
+ var tagName = String(element && element[0] && element[0].tagName || ''),
+ type = attrs.type || '',
+ isSubmit = type === 'submit',
+ isInput = TAG_INPUT.test(tagName),
+ isButton = TAG_BUTTON.test(tagName),
+ isForm = TAG_FORM.test(tagName);
+
+ this.options = options;
+ this.type = type;
+
+ this.isSubmit = isSubmit && (isButton || isInput);
+ this.isForm = isForm;
+ this.isInput = !isForm;
+
+ this.onClick = !!attrs.ngClick;
+ this.onSubmit = !!attrs.ngSubmit;
+ }
+
+ AutoDisable.prototype = {
+ constructor: AutoDisable,
+ link: link,
+ lock: lock,
+ unlock: unlock,
+ busyLock: busyLock,
+ busyUnlock: busyUnlock,
+ initialize: initialize
+ };
+
+ function initialize() {
+ // listen to ng-submit
+ if (this.isForm && this.onSubmit) {
+ bindEvent(this, 'submit');
+ }
+
+ // listen to ng-click on submit button
+ if (this.isSubmit && this.onClick) {
+ bindEvent(this, 'click');
+ }
+
+ if (this.isForm) {
+ bindFormState(this);
+ } else {
+ bindChildState(this);
+ }
+ }
+
+ function link($scope, $element, $attrs, controllers) {
+ var form = this.isForm ? controllers[0] : controllers[1],
+ options = this.options;
+
+ form.$busy = form.$disabled = false;
+
+ angular.extend(this, {
+ scope: $scope,
+ element: $element,
+ attrs: $attrs,
+ form: form,
+ options: angular.isString(options) && $scope.$eval(options) || {}
+ });
+
+ $element.addClass(CLS_AUTODISABLE);
+
+ this.initialize();
+ }
+
+ function busyLock(promise) {
+ var self = this;
+
+ if (self.promise) return;
+
+ self.element.addClass(CLS_BUSY);
+
+ // at form or submit button, bind to promise
+ // otherwise, just lock the field
+ if (promise) {
+ self.promise = promise.then(function(response) {
+ self.busyUnlock(true);
+ return response;
+ }, function(error) {
+ self.busyUnlock(false);
+ return $q.reject(error);
+ });
+ }
+
+ if (this.isForm) {
+ setFormBusy(self, true);
+ } else {
+ setInputDisable(self, true);
+ }
+ }
+
+ function busyUnlock(success) {
+ this.element.removeClass(CLS_BUSY);
+ this.promise = null;
+
+ if (this.isForm) {
+ setFormBusy(this, false);
+
+ if (success && baseConfig.lockOnComplete) {
+ this.form.$setPristine();
+ }
+ } else {
+ setInputDisable(this, false);
+ }
+ }
+
+ function setFormBusy(self, value) {
+ self.form.$busy = value;
+ setFormDisable(self, value);
+ }
+
+ function lock() {
+ if (this.locked) return;
+
+ this.locked = true;
+ this.element.addClass(CLS_LOCKED);
+
+ if (this.isForm) {
+ setFormDisable(this, true);
+ } else if (this.isSubmit) {
+ setInputDisable(this, true);
+ } else {
+ setInputDisable(this, false);
+ }
+ }
+
+ function unlock() {
+ if (!this.locked) return;
+
+ this.locked = false;
+ this.element.removeClass(CLS_LOCKED);
+
+ if (this.isForm) {
+ setFormDisable(this, false);
+ } else {
+ setInputDisable(this, false);
+ }
+ }
+
+ function setFormDisable(self, value) {
+ self.form.$disabled = value;
+ }
+
+ function setInputDisable(self, value) {
+ self.attrs.$set('disabled', value);
+ }
+
+ // helper functions
+ function bindEvent(self, eventName) {
+ var attributeName = 'ng' + eventName.charAt(0).toUpperCase() + eventName.slice(1),
+ fn = $parse(self.attrs[attributeName], /* interceptorFn */ null, /* expensiveChecks */ true);
+
+ self.element.unbind(eventName).bind(eventName, handler);
+
+ function handler($event) {
+ if (self.locked || self.promise) return;
+
+ var result = fn(self.scope, {
+ $event: $event
+ });
+
+ if (isPromise(result)) {
+ self.busyLock(result);
+ }
+
+ self.scope.$apply();
+ }
+ }
+
+ function bindStateTrigger(self, watcher, trigger) {
+ var form = self.form || false;
+ if (!form) return;
+ self.scope.$watch(watcher, trigger);
+ }
+
+ /**
+ * Lock/unlock the form
+ */
+ function bindFormState(self) {
+ var form = self.form;
+ bindStateTrigger(self, isInvalid, updateLockOnForm);
+
+ function isInvalid() {
+ return form.$pristine + '' + form.$invalid;
+ }
+
+ function updateLockOnForm() {
+ if (
+ (form.$pristine && self.options.pristine !== false) ||
+ (form.$invalid && self.options.invalid !== false)
+ ) {
+ self.lock();
+ return;
+ }
+
+ self.unlock();
+ }
+ }
+
+ function bindChildState(self) {
+ var form = self.form;
+
+ bindStateTrigger(self, isFormDisabled, updateChildDisabled);
+ bindStateTrigger(self, isFormBusy, updateChildBusy);
+
+ function isFormDisabled() {
+ return form.$disabled;
+ }
+
+ function isFormBusy() {
+ return form.$busy;
+ }
+
+ function updateChildDisabled() {
+ if (form.$disabled && !form.$busy) {
+ self.lock();
+ } else {
+ self.unlock();
+ }
+ }
+
+ function updateChildBusy() {
+ if (form.$busy) {
+ self.busyLock();
+ } else {
+ self.busyUnlock();
+ }
+ }
+ }
+
+ function isPromise(value) {
+ return Boolean(value && typeof value.then === 'function' &&
+ typeof value.finally === 'function');
+ }
+
+ return AutoDisable;
+ }
+
+ module.provider('AutoDisable', function() {
+ this.$get = ['$parse', '$q', AutoDisableFactory];
+ this.config = function(config) {
+ angular.extend(baseConfig, config);
+ };
+ });
+
+})(angular.module('angular-autodisable'));
diff --git a/src/autodisable.module.js b/src/autodisable.module.js
new file mode 100644
index 0000000..cceab42
--- /dev/null
+++ b/src/autodisable.module.js
@@ -0,0 +1 @@
+angular.module('angular-autodisable', []);
\ No newline at end of file