From 2430f52bb97fa9d682e5f028c977c5bf94c5ec38 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Fri, 23 Mar 2012 14:03:24 -0700 Subject: chore(module): move files around in preparation for more modules --- src/ng/directive/form.js | 267 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 267 insertions(+) create mode 100644 src/ng/directive/form.js (limited to 'src/ng/directive/form.js') diff --git a/src/ng/directive/form.js b/src/ng/directive/form.js new file mode 100644 index 00000000..b6d3f4be --- /dev/null +++ b/src/ng/directive/form.js @@ -0,0 +1,267 @@ +'use strict'; + + +var nullFormCtrl = { + $addControl: noop, + $removeControl: noop, + $setValidity: noop, + $setDirty: noop +} + +/** + * @ngdoc object + * @name angular.module.ng.$compileProvider.directive.form.FormController + * + * @property {boolean} $pristine True if user has not interacted with the form yet. + * @property {boolean} $dirty True if user has already interacted with the form. + * @property {boolean} $valid True if all of the containg forms and controls are valid. + * @property {boolean} $invalid True if at least one containing control or form is invalid. + * + * @property {Object} $error Is an object hash, containing references to all invalid controls or + * forms, where: + * + * - keys are validation tokens (error names) — such as `REQUIRED`, `URL` or `EMAIL`), + * - values are arrays of controls or forms that are invalid with given error. + * + * @description + * `FormController` keeps track of all its controls and nested forms as well as state of them, + * such as being valid/invalid or dirty/pristine. + * + * Each {@link angular.module.ng.$compileProvider.directive.form form} directive creates an instance + * of `FormController`. + * + */ +FormController.$inject = ['$element', '$attrs']; +function FormController(element, attrs) { + var form = this, + parentForm = element.parent().controller('form') || nullFormCtrl, + invalidCount = 0, // used to easily determine if we are valid + errors = form.$error = {}; + + // init state + form.$name = attrs.name; + form.$dirty = false; + form.$pristine = true; + form.$valid = true; + form.$invalid = false; + + parentForm.$addControl(form); + + // Setup initial state of the control + element.addClass(PRISTINE_CLASS); + toggleValidCss(true); + + // convenience method for easy toggling of classes + function toggleValidCss(isValid, validationErrorKey) { + validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; + element. + removeClass((isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey). + addClass((isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey); + } + + form.$addControl = function(control) { + if (control.$name && !form.hasOwnProperty(control.$name)) { + form[control.$name] = control; + } + }; + + form.$removeControl = function(control) { + if (control.$name && form[control.$name] === control) { + delete form[control.$name]; + } + forEach(errors, cleanupControlErrors, control); + }; + + form.$setValidity = function(validationToken, isValid, control) { + if (isValid) { + cleanupControlErrors(errors[validationToken], validationToken, control); + + if (!invalidCount) { + toggleValidCss(isValid); + form.$valid = true; + form.$invalid = false; + } + } else { + if (!invalidCount) { + toggleValidCss(isValid); + } + addControlError(validationToken, control); + + form.$valid = false; + form.$invalid = true; + } + }; + + form.$setDirty = function() { + element.removeClass(PRISTINE_CLASS).addClass(DIRTY_CLASS); + form.$dirty = true; + form.$pristine = false; + }; + + function cleanupControlErrors(queue, validationToken, control) { + if (queue) { + control = control || this; // so that we can be used in forEach; + arrayRemove(queue, control); + if (!queue.length) { + invalidCount--; + errors[validationToken] = false; + toggleValidCss(true, validationToken); + parentForm.$setValidity(validationToken, true, form); + } + } + } + + function addControlError(validationToken, control) { + var queue = errors[validationToken]; + if (queue) { + if (includes(queue, control)) return; + } else { + errors[validationToken] = queue = []; + invalidCount++; + toggleValidCss(false, validationToken); + parentForm.$setValidity(validationToken, false, form); + } + queue.push(control); + } +} + + +/** + * @ngdoc directive + * @name angular.module.ng.$compileProvider.directive.ng-form + * @restrict EAC + * + * @description + * Nestable alias of {@link angular.module.ng.$compileProvider.directive.form `form`} directive. HTML + * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a + * sub-group of controls needs to be determined. + * + * @param {string=} ng-form|name Name of the form. If specified, the form controller will be published into + * related scope, under this name. + * + */ + + /** + * @ngdoc directive + * @name angular.module.ng.$compileProvider.directive.form + * @restrict E + * + * @description + * Directive that instantiates + * {@link angular.module.ng.$compileProvider.directive.form.FormController FormController}. + * + * If `name` attribute is specified, the form controller is published onto the current scope under + * this name. + * + * # Alias: {@link angular.module.ng.$compileProvider.directive.ng-form `ng-form`} + * + * In angular forms can be nested. This means that the outer form is valid when all of the child + * forms are valid as well. However browsers do not allow nesting of `