'use strict'; var nullFormCtrl = { $addControl: noop, $removeControl: noop, $setValidity: noop, $setDirty: noop, $setPristine: noop }; /** * @ngdoc object * @name ng.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 containing 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 ng.directive:form form} directive creates an instance * of `FormController`. * */ //asks for $scope to fool the BC controller module FormController.$inject = ['$element', '$attrs', '$scope']; 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 = {}, controls = []; // init state form.$name = attrs.name || attrs.ngForm; 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); } /** * @ngdoc function * @name ng.directive:form.FormController#$addControl * @methodOf ng.directive:form.FormController * * @description * Register a control with the form. * * Input elements using ngModelController do this automatically when they are linked. */ form.$addControl = function(control) { controls.push(control); if (control.$name && !form.hasOwnProperty(control.$name)) { form[control.$name] = control; } }; /** * @ngdoc function * @name ng.directive:form.FormController#$removeControl * @methodOf ng.directive:form.FormController * * @description * Deregister a control from the form. * * Input elements using ngModelController do this automatically when they are destroyed. */ form.$removeControl = function(control) { if (control.$name && form[control.$name] === control) { delete form[control.$name]; } forEach(errors, function(queue, validationToken) { form.$setValidity(validationToken, true, control); }); arrayRemove(controls, control); }; /** * @ngdoc function * @name ng.directive:form.FormController#$setValidity * @methodOf ng.directive:form.FormController * * @description * Sets the validity of a form control. * * This method will also propagate to parent forms. */ form.$setValidity = function(validationToken, isValid, control) { var queue = errors[validationToken]; if (isValid) { if (queue) { arrayRemove(queue, control); if (!queue.length) { invalidCount--; if (!invalidCount) { toggleValidCss(isValid); form.$valid = true; form.$invalid = false; } errors[validationToken] = false; toggleValidCss(true, validationToken); parentForm.$setValidity(validationToken, true, form); } } } else { if (!invalidCount) { toggleValidCss(isValid); } if (queue) { if (includes(queue, control)) return; } else { errors[validationToken] = queue = []; invalidCount++; toggleValidCss(false, validationToken); parentForm.$setValidity(validationToken, false, form); } queue.push(control); form.$valid = false; form.$invalid = true; } }; /** * @ngdoc function * @name ng.directive:form.FormController#$setDirty * @methodOf ng.directive:form.FormController * * @description * Sets the form to a dirty state. * * This method can be called to add the 'ng-dirty' class and set the form to a dirty * state (ng-dirty class). This method will also propagate to parent forms. */ form.$setDirty = function() { element.removeClass(PRISTINE_CLASS).addClass(DIRTY_CLASS); form.$dirty = true; form.$pristine = false; parentForm.$setDirty(); }; /** * @ngdoc function * @name ng.directive:form.FormController#$setPristine * @methodOf ng.directive:form.FormController * * @description * Sets the form to its pristine state. * * This method can be called to remove the 'ng-dirty' class and set the form to its pristine * state (ng-pristine class). This method will also propagate to all the controls contained * in this form. * * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after * saving or resetting it. */ form.$setPristine = function () { element.removeClass(DIRTY_CLASS).addClass(PRISTINE_CLASS); form.$dirty = false; form.$pristine = true; forEach(controls, function(control) { control.$setPristine(); }); }; } /** * @ngdoc directive * @name ng.directive:ngForm * @restrict EAC * * @description * Nestable alias of {@link ng.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=} name|ngForm Name of the form. If specified, the form controller will be published into * related scope, under this name. * */ /** * @ngdoc directive * @name ng.directive:form * @restrict E * * @description * Directive that instantiates * {@link ng.directive:form.FormController FormController}. * * If `name` attribute is specified, the form controller is published onto the current scope under * this name. * * # Alias: {@link ng.directive:ngForm `ngForm`} * * 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 `