diff options
| author | Vojta Jina | 2012-02-15 17:16:02 -0800 |
|---|---|---|
| committer | Vojta Jina | 2012-02-28 17:46:58 -0800 |
| commit | 21c725f1a12d1de758cab6e4c4fafc5c420eb565 (patch) | |
| tree | 4d1b362387de2c41748a63b5baee0f18c3c8e5ec /src/widget/form.js | |
| parent | e23fa768aaf6d1d966c335979fe8316330c2fe28 (diff) | |
| download | angular.js-21c725f1a12d1de758cab6e4c4fafc5c420eb565.tar.bz2 | |
refactor(forms): Even better forms
- remove $formFactory completely
- remove parallel scope hierarchy (forms, widgets)
- use new compiler features (widgets, forms are controllers)
- any directive can add formatter/parser (validators, convertors)
Breaks no custom input types
Breaks removed integer input type
Breaks remove list input type (ng-list directive instead)
Breaks inputs bind only blur event by default (added ng:bind-change directive)
Diffstat (limited to 'src/widget/form.js')
| -rw-r--r-- | src/widget/form.js | 134 |
1 files changed, 107 insertions, 27 deletions
diff --git a/src/widget/form.js b/src/widget/form.js index deaf38d5..23b07107 100644 --- a/src/widget/form.js +++ b/src/widget/form.js @@ -1,5 +1,86 @@ 'use strict'; +FormController.$inject = ['$scope', 'name']; +function FormController($scope, name) { + var form = this, + errors = form.error = {}; + + // publish the form into scope + name(this); + + $scope.$on('$destroy', function(event, widget) { + if (!widget) return; + + if (widget.widgetId) { + delete form[widget.widgetId]; + } + forEach(errors, removeWidget, widget); + }); + + $scope.$on('$valid', function(event, error, widget) { + removeWidget(errors[error], error, widget); + + if (equals(errors, {})) { + form.valid = true; + form.invalid = false; + } + }); + + $scope.$on('$invalid', function(event, error, widget) { + addWidget(error, widget); + + form.valid = false; + form.invalid = true; + }); + + $scope.$on('$viewTouch', function() { + form.dirty = true; + form.pristine = false; + }); + + // init state + form.dirty = false; + form.pristine = true; + form.valid = true; + form.invalid = false; + + function removeWidget(queue, errorKey, widget) { + if (queue) { + widget = widget || this; // so that we can be used in forEach; + for (var i = 0, length = queue.length; i < length; i++) { + if (queue[i] === widget) { + queue.splice(i, 1); + if (!queue.length) { + delete errors[errorKey]; + } + } + } + } + } + + function addWidget(errorKey, widget) { + var queue = errors[errorKey]; + if (queue) { + for (var i = 0, length = queue.length; i < length; i++) { + if (queue[i] === widget) { + return; + } + } + } else { + errors[errorKey] = queue = []; + } + queue.push(widget); + } +} + +FormController.prototype.registerWidget = function(widget, alias) { + if (alias && !this.hasOwnProperty(alias)) { + widget.widgetId = alias; + this[alias] = widget; + } +}; + + /** * @ngdoc directive * @name angular.module.ng.$compileProvider.directive.form @@ -57,55 +138,54 @@ $scope.text = 'guest'; } </script> - <div ng:controller="Ctrl"> - <form name="myForm"> - text: <input type="text" name="input" ng:model="text" required> - <span class="error" ng:show="myForm.text.$error.REQUIRED">Required!</span> - </form> + <form name="myForm" ng:controller="Ctrl"> + text: <input type="text" name="input" ng:model="text" required> + <span class="error" ng:show="myForm.input.error.REQUIRED">Required!</span> <tt>text = {{text}}</tt><br/> - <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> - <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> - <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> - <tt>myForm.$error.REQUIRED = {{!!myForm.$error.REQUIRED}}</tt><br/> - </div> + <tt>myForm.input.valid = {{myForm.input.valid}}</tt><br/> + <tt>myForm.input.error = {{myForm.input.error}}</tt><br/> + <tt>myForm.valid = {{myForm.valid}}</tt><br/> + <tt>myForm.error.REQUIRED = {{!!myForm.error.REQUIRED}}</tt><br/> + </form> </doc:source> <doc:scenario> it('should initialize to model', function() { expect(binding('text')).toEqual('guest'); - expect(binding('myForm.input.$valid')).toEqual('true'); + expect(binding('myForm.input.valid')).toEqual('true'); }); it('should be invalid if empty', function() { input('text').enter(''); expect(binding('text')).toEqual(''); - expect(binding('myForm.input.$valid')).toEqual('false'); + expect(binding('myForm.input.valid')).toEqual('false'); }); </doc:scenario> </doc:example> */ -var ngFormDirective = ['$formFactory', function($formFactory) { +var ngFormDirective = [function() { return { + name: 'form', restrict: 'E', + scope: true, + inject: { + name: 'accessor' + }, + controller: FormController, compile: function() { return { - pre: function(scope, formElement, attr) { - var name = attr.name, - parentForm = $formFactory.forElement(formElement), - form = $formFactory(parentForm); - formElement.data('$form', form); - formElement.bind('submit', function(event){ + pre: function(scope, formElement, attr, controller) { + formElement.data('$form', controller); + formElement.bind('submit', function(event) { if (!attr.action) event.preventDefault(); }); - if (name) { - scope[name] = form; - } - watch('valid'); - watch('invalid'); - function watch(name) { - form.$watch('$' + name, function(value) { + + forEach(['valid', 'invalid', 'dirty', 'pristine'], function(name) { + scope.$watch(function() { + return controller[name]; + }, function(value) { formElement[value ? 'addClass' : 'removeClass']('ng-' + name); }); - } + }); } }; } |
