From 8024a5742c46a42ef204988ff7362a4fc14b7a2d Mon Sep 17 00:00:00 2001
From: Misko Hevery
Date: Fri, 25 May 2012 10:29:54 -0700
Subject: doc(NgModelController) add example and $render documentation
Closes#930
---
docs/content/guide/dev_guide.forms.ngdoc | 324 -------------------------------
docs/content/guide/forms.ngdoc | 324 +++++++++++++++++++++++++++++++
src/ng/directive/input.js | 99 +++++++++-
test/ng/directive/inputSpec.js | 16 +-
4 files changed, 430 insertions(+), 333 deletions(-)
delete mode 100644 docs/content/guide/dev_guide.forms.ngdoc
create mode 100644 docs/content/guide/forms.ngdoc
diff --git a/docs/content/guide/dev_guide.forms.ngdoc b/docs/content/guide/dev_guide.forms.ngdoc
deleted file mode 100644
index 6621e231..00000000
--- a/docs/content/guide/dev_guide.forms.ngdoc
+++ /dev/null
@@ -1,324 +0,0 @@
-@ngdoc overview
-@name Developer Guide: Forms
-@description
-
-Controls (`input`, `select`, `textarea`) are a way for user to enter data.
-Form is a collection of controls for the purpose of grouping related controls together.
-
-Form and controls provide validation services, so that the user can be notified of invalid input.
-This provides a better user experience, because the user gets instant feedback on how to correct the error.
-Keep in mind that while client-side validation plays an important role in providing good user experience, it can easily be circumvented and thus can not be trusted.
-Server-side validation is still necessary for a secure application.
-
-
-# Simple form
-The key directive in understanding two-way data-binding is {@link api/angular.module.ng.$compileProvider.directive.ngModel ngModel}.
-The `ngModel` directive provides the two-way data-binding by synchronizing the model to the view, as well as view to the model.
-In addition it provides {@link api/angular.module.ng.$compileProvider.directive.ngModel.NgModelController API} for other directives to augment its behavior.
-
-
-
-
-
-
form = {{user | json}}
-
master = {{master | json}}
-
-
-
-
-
-
-
-Note that `novalidate` is used to disable browser's native form validation.
-
-
-
-# Using CSS classes
-
-To allow styling of form as well as controls, `ngModel` add these CSS classes:
-
-- `ng-valid`
-- `ng-invalid`
-- `ng-pristine`
-- `ng-dirty`
-
-Following example uses the CSS to display validity of each form control.
-In the example both `user.name` and `user.email` are required, but are rendered with red background only when they are dirty.
-This ensures that the user is not distracted with an error until after interacting with the control, and failing to satisfy its validity.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-# Binding to form and control state
-
-A form is in instance of {@link api/angular.module.ng.$compileProvider.directive.form.FormController FormController}.
-The form instance can optionally be published into the scope using the `name` attribute.
-Similarly control is an instance of {@link api/angular.module.ng.$compileProvider.directive.ngModel.NgModelController NgModelController}.
-The control instance can similarly be published into the form instance using the `name` attribute.
-This implies that the internal state of both the form and the control is available for binding in the view using the standard binding primitives.
-
-This allows us to extend the above example with these features:
-
-- RESET button is enabled only if form has some changes
-- SAVE button is enabled only if form has some changes and is valid
-- custom error messages for `user.email` and `user.agree`
-
-
-
-
-
-
-
-
-
-
-
-# Custom Validation
-
-Angular provides basic implementation for most common html5 {@link api/angular.module.ng.$compileProvider.directive.input input}
-types: ({@link api/angular.module.ng.$compileProvider.directive.input.text text}, {@link api/angular.module.ng.$compileProvider.directive.input.number number}, {@link api/angular.module.ng.$compileProvider.directive.input.url url}, {@link api/angular.module.ng.$compileProvider.directive.input.email email}, {@link api/angular.module.ng.$compileProvider.directive.input.radio radio}, {@link api/angular.module.ng.$compileProvider.directive.input.checkbox checkbox}), as well as some directives for validation (`required`, `pattern`, `minlength`, `maxlength`, `min`, `max`).
-
-Defining your own validator can be done by defining your own directive which adds a custom validation function to the `ngModel` {@link api/angular.module.ng.$compileProvider.directive.ngModel.NgModelController controller}.
-To get a hold of the controller the directive specifies a dependency as shown in the example below.
-The validation can occur in two places:
-
- * **Model to View update** -
- Whenever the bound model changes, all functions in {@link api/angular.module.ng.$compileProvider.directive.ngModel.NgModelController#$formatters NgModelController#$formatters} array are pipe-lined, so that each of these functions has an opportunity to format the value and change validity state of the form control through {@link api/angular.module.ng.$compileProvider.directive.ngModel.NgModelController#$setValidity NgModelController#$setValidity}.
-
- * **View to Model update** -
- In a similar way, whenever a user interacts with a control, the controll calls {@link api/angular.module.ng.$compileProvider.directive.ngModel.NgModelController#$setViewValue NgModelController#$setViewValue}.
-This in turn pipelines all functions in {@link api/angular.module.ng.$compileProvider.directive.ngModel.NgModelController#$parsers NgModelController#$parsers} array, so that each of these functions has an opportunity to convert the value and change validity state of the form control through {@link api/angular.module.ng.$compileProvider.directive.ngModel.NgModelController#$setValidity NgModelController#$setValidity}.
-
-In the following example we create two directives.
-
- * The first one is `integer` and it validates whether the input is a valid integer.
- For example `1.23` is an invalid value, since it contains a fraction.
- Note, that we unshift the array instead of pushing.
- This is because we want to be first parser and consume the control string value, as we need to execute the validation function before a conversion to number occurs.
-
- * The second directive is a `smart-float`.
- It parses both `1.2` and `1,2` into a valid float number `1.2`.
- Note that, we can't use input type `number` here as HTML5 browsers would not allow the user to type what it would consider an invalid number such as `1,2`.
-
-
-
-
-
-
-
-
-
-
-
-# Implementing custom form control (using `ngModel`)
-Angular implements all of the basic HTML form controls ({@link api/angular.module.ng.$compileProvider.directive.input input}, {@link api/angular.module.ng.$compileProvider.directive.select select}, {@link api/angular.module.ng.$compileProvider.directive.textarea textarea}), which should be sufficient for most cases.
-However, if you need more flexibility, you can write your own form control as a directive.
-
-In order for custom control to work with `ngModel` and to achieve two-way data-binding it needs to:
-
- - implement `render` method, which is responsible for rendering the data after it passed the {@link api/angular.module.ng.$compileProvider.directive.ngModel.NgModelController#$formatters NgModelController#$formatters},
- - call `$setViewValue` method, whenever the user interacts with the control and model needs to be updated. This is usually done inside a DOM Event listener.
-
-See {@link guide/directive $compileProvider.directive} for more info.
-
-The following example shows how to add two-way data-binding to contentEditable elements.
-
-
-
-
-
-Some
-model = {{content}}
-
-
-
-
diff --git a/docs/content/guide/forms.ngdoc b/docs/content/guide/forms.ngdoc
new file mode 100644
index 00000000..c21ffc93
--- /dev/null
+++ b/docs/content/guide/forms.ngdoc
@@ -0,0 +1,324 @@
+@ngdoc overview
+@name Forms
+@description
+
+Controls (`input`, `select`, `textarea`) are a way for user to enter data.
+Form is a collection of controls for the purpose of grouping related controls together.
+
+Form and controls provide validation services, so that the user can be notified of invalid input.
+This provides a better user experience, because the user gets instant feedback on how to correct the error.
+Keep in mind that while client-side validation plays an important role in providing good user experience, it can easily be circumvented and thus can not be trusted.
+Server-side validation is still necessary for a secure application.
+
+
+# Simple form
+The key directive in understanding two-way data-binding is {@link api/angular.module.ng.$compileProvider.directive.ngModel ngModel}.
+The `ngModel` directive provides the two-way data-binding by synchronizing the model to the view, as well as view to the model.
+In addition it provides {@link api/angular.module.ng.$compileProvider.directive.ngModel.NgModelController API} for other directives to augment its behavior.
+
+
+
+
+
+
form = {{user | json}}
+
master = {{master | json}}
+
+
+
+
+
+
+
+Note that `novalidate` is used to disable browser's native form validation.
+
+
+
+# Using CSS classes
+
+To allow styling of form as well as controls, `ngModel` add these CSS classes:
+
+- `ng-valid`
+- `ng-invalid`
+- `ng-pristine`
+- `ng-dirty`
+
+Following example uses the CSS to display validity of each form control.
+In the example both `user.name` and `user.email` are required, but are rendered with red background only when they are dirty.
+This ensures that the user is not distracted with an error until after interacting with the control, and failing to satisfy its validity.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+# Binding to form and control state
+
+A form is in instance of {@link api/angular.module.ng.$compileProvider.directive.form.FormController FormController}.
+The form instance can optionally be published into the scope using the `name` attribute.
+Similarly control is an instance of {@link api/angular.module.ng.$compileProvider.directive.ngModel.NgModelController NgModelController}.
+The control instance can similarly be published into the form instance using the `name` attribute.
+This implies that the internal state of both the form and the control is available for binding in the view using the standard binding primitives.
+
+This allows us to extend the above example with these features:
+
+- RESET button is enabled only if form has some changes
+- SAVE button is enabled only if form has some changes and is valid
+- custom error messages for `user.email` and `user.agree`
+
+
+
+
+
+
+
+
+
+
+
+# Custom Validation
+
+Angular provides basic implementation for most common html5 {@link api/angular.module.ng.$compileProvider.directive.input input}
+types: ({@link api/angular.module.ng.$compileProvider.directive.input.text text}, {@link api/angular.module.ng.$compileProvider.directive.input.number number}, {@link api/angular.module.ng.$compileProvider.directive.input.url url}, {@link api/angular.module.ng.$compileProvider.directive.input.email email}, {@link api/angular.module.ng.$compileProvider.directive.input.radio radio}, {@link api/angular.module.ng.$compileProvider.directive.input.checkbox checkbox}), as well as some directives for validation (`required`, `pattern`, `minlength`, `maxlength`, `min`, `max`).
+
+Defining your own validator can be done by defining your own directive which adds a custom validation function to the `ngModel` {@link api/angular.module.ng.$compileProvider.directive.ngModel.NgModelController controller}.
+To get a hold of the controller the directive specifies a dependency as shown in the example below.
+The validation can occur in two places:
+
+ * **Model to View update** -
+ Whenever the bound model changes, all functions in {@link api/angular.module.ng.$compileProvider.directive.ngModel.NgModelController#$formatters NgModelController#$formatters} array are pipe-lined, so that each of these functions has an opportunity to format the value and change validity state of the form control through {@link api/angular.module.ng.$compileProvider.directive.ngModel.NgModelController#$setValidity NgModelController#$setValidity}.
+
+ * **View to Model update** -
+ In a similar way, whenever a user interacts with a control, the controll calls {@link api/angular.module.ng.$compileProvider.directive.ngModel.NgModelController#$setViewValue NgModelController#$setViewValue}.
+This in turn pipelines all functions in {@link api/angular.module.ng.$compileProvider.directive.ngModel.NgModelController#$parsers NgModelController#$parsers} array, so that each of these functions has an opportunity to convert the value and change validity state of the form control through {@link api/angular.module.ng.$compileProvider.directive.ngModel.NgModelController#$setValidity NgModelController#$setValidity}.
+
+In the following example we create two directives.
+
+ * The first one is `integer` and it validates whether the input is a valid integer.
+ For example `1.23` is an invalid value, since it contains a fraction.
+ Note, that we unshift the array instead of pushing.
+ This is because we want to be first parser and consume the control string value, as we need to execute the validation function before a conversion to number occurs.
+
+ * The second directive is a `smart-float`.
+ It parses both `1.2` and `1,2` into a valid float number `1.2`.
+ Note that, we can't use input type `number` here as HTML5 browsers would not allow the user to type what it would consider an invalid number such as `1,2`.
+
+
+
+
+
+
+
+
+
+
+
+# Implementing custom form control (using `ngModel`)
+Angular implements all of the basic HTML form controls ({@link api/angular.module.ng.$compileProvider.directive.input input}, {@link api/angular.module.ng.$compileProvider.directive.select select}, {@link api/angular.module.ng.$compileProvider.directive.textarea textarea}), which should be sufficient for most cases.
+However, if you need more flexibility, you can write your own form control as a directive.
+
+In order for custom control to work with `ngModel` and to achieve two-way data-binding it needs to:
+
+ - implement `render` method, which is responsible for rendering the data after it passed the {@link api/angular.module.ng.$compileProvider.directive.ngModel.NgModelController#$formatters NgModelController#$formatters},
+ - call `$setViewValue` method, whenever the user interacts with the control and model needs to be updated. This is usually done inside a DOM Event listener.
+
+See {@link guide/directive $compileProvider.directive} for more info.
+
+The following example shows how to add two-way data-binding to contentEditable elements.
+
+
+
+
+
+Some
+model = {{content}}
+
+
+
+
diff --git a/src/ng/directive/input.js b/src/ng/directive/input.js
index 6e394afd..5fe32125 100644
--- a/src/ng/directive/input.js
+++ b/src/ng/directive/input.js
@@ -628,6 +628,7 @@ function checkboxInputType(scope, element, attr, ctrl) {
/**
* @ngdoc directive
* @name angular.module.ng.$compileProvider.directive.textarea
+ * @restrict E
*
* @description
* HTML textarea element control with angular data-binding. The data-binding and validation
@@ -782,6 +783,79 @@ var VALID_CLASS = 'ng-valid',
*
* @description
*
+ * `NgModelController` provides API for the `ng-model` directive. The controller contains
+ * services for data-binding, validation, CSS update, value formatting and parsing. It
+ * specifically does not contain any logic which deals with DOM rendering or listening to
+ * DOM events. The `NgModelController` is meant to be extended by other directives where, the
+ * directive provides DOM manipulation and the `NgModelController` provides the data-binding.
+ *
+ * This example shows how to use `NgModelController` with a custom control to achieve
+ * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`)
+ * collaborate together to achieve the desired result.
+ *
+ *
+
+ [contenteditable] {
+ border: 1px solid black;
+ background-color: white;
+ min-height: 20px;
+ }
+
+ .ng-invalid {
+ border: 1px solid red;
+ }
+
+
+
+ angular.module('customControl', []).
+ directive('contenteditable', function() {
+ return {
+ restrict: 'A', // only activate on element attribute
+ require: '?ngModel', // get a hold of NgModelController
+ link: function(scope, element, attrs, ngModel) {
+ if(!ngModel) return; // do nothing if no ng-model
+
+ // Specify how UI should be updated
+ ngModel.$render = function() {
+ element.html(ngModel.$viewValue || '');
+ };
+
+ // Listen for change events to enable binding
+ element.bind('blur keyup change', function() {
+ scope.$apply(read);
+ });
+ read(); // initialize
+
+ // Write data to the model
+ function read() {
+ ngModel.$setViewValue(element.html());
+ }
+ }
+ };
+ });
+
+
+
+
+
+ it('should data-bind and become invalid', function() {
+ var contentEditable = element('[contenteditable]');
+
+ expect(contentEditable.text()).toEqual('Change me!');
+ input('userContent').enter('');
+ expect(contentEditable.text()).toEqual('');
+ expect(contentEditable.prop('className')).toMatch(/ng-invalid-required/);
+ });
+
+ *
+ *
*/
var NgModelController = ['$scope', '$exceptionHandler', '$attrs', 'ngModel', '$element',
function($scope, $exceptionHandler, $attr, ngModel, $element) {
@@ -794,9 +868,19 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', 'ngModel', '$e
this.$dirty = false;
this.$valid = true;
this.$invalid = false;
- this.$render = noop;
this.$name = $attr.name;
+ /**
+ * @ngdoc function
+ * @name angular.module.ng.$compileProvider.directive.ngModel.NgModelController#$render
+ * @methodOf angular.module.ng.$compileProvider.directive.ngModel.NgModelController
+ *
+ * @description
+ * Called when the view needs to be updated. It is expected that the user of the ng-model
+ * directive will implement this method.
+ */
+ this.$render = noop;
+
var parentForm = $element.inheritedData('$formController') || nullFormCtrl,
invalidCount = 0, // used to easily determine if we are valid
$error = this.$error = {}; // keep invalid keys here
@@ -958,7 +1042,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', 'ngModel', '$e
* - {@link angular.module.ng.$compileProvider.directive.textarea textarea}
*
*/
-var ngModelDirective = [function() {
+var ngModelDirective = function() {
return {
inject: {
ngModel: 'accessor'
@@ -978,7 +1062,7 @@ var ngModelDirective = [function() {
});
}
};
-}];
+};
/**
@@ -1039,11 +1123,12 @@ var ngChangeDirective = valueFn({
});
-var requiredDirective = [function() {
+var requiredDirective = function() {
return {
require: '?ngModel',
link: function(scope, elm, attr, ctrl) {
if (!ctrl) return;
+ attr.required = true; // force truthy in case we are on non input element
var validator = function(value) {
if (attr.required && (isEmpty(value) || value === false)) {
@@ -1063,7 +1148,7 @@ var requiredDirective = [function() {
});
}
};
-}];
+};
/**
@@ -1144,7 +1229,7 @@ var ngListDirective = function() {
var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/;
-var ngValueDirective = [function() {
+var ngValueDirective = function() {
return {
priority: 100,
compile: function(tpl, tplAttr) {
@@ -1162,4 +1247,4 @@ var ngValueDirective = [function() {
}
}
};
-}];
+};
diff --git a/test/ng/directive/inputSpec.js b/test/ng/directive/inputSpec.js
index 45e3e1bf..d7ca7aea 100644
--- a/test/ng/directive/inputSpec.js
+++ b/test/ng/directive/inputSpec.js
@@ -285,8 +285,9 @@ describe('input', function() {
var formElm, inputElm, scope, $compile, changeInputValueTo;
function compileInput(inputHtml) {
- formElm = jqLite('
');
- inputElm = formElm.find('input');
+ inputElm = jqLite(inputHtml);
+ formElm = jqLite('');
+ formElm.append(inputElm);
$compile(formElm)(scope);
}
@@ -633,6 +634,17 @@ describe('input', function() {
expect(inputElm.val()).toBe('0')
expect(scope.form.alias.$error.required).toBeFalsy();
});
+
+ it('should register required on non boolean elements', function() {
+ compileInput('');
+
+ scope.$apply(function() {
+ scope.value = '';
+ });
+
+ expect(inputElm).toBeInvalid();
+ expect(scope.form.alias.$error.required).toBeTruthy();
+ });
});
});
--
cgit v1.2.3