diff --git a/Rakefile b/Rakefile
index 0c9efcf949d5..e71fd0cf288b 100644
--- a/Rakefile
+++ b/Rakefile
@@ -77,66 +77,8 @@ task :compile_jstd_scenario_adapter => :init do
end
-desc 'Generate IE css js patch'
-task :generate_ie_compat => :init do
- css = File.open('css/angular.css', 'r') {|f| f.read }
-
- # finds all css rules that contain backround images and extracts the rule name(s), content type of
- # the image and base64 encoded image data
- r = /\n([^\{\n]+)\s*\{[^\}]*background-image:\s*url\("data:([^;]+);base64,([^"]+)"\);[^\}]*\}/
-
- images = css.scan(r)
-
- # create a js file with multipart header containing the extracted images. the entire file *must*
- # be CRLF (\r\n) delimited
- File.open(path_to('angular-ie-compat.js'), 'w') do |f|
- f.write("/*\r\n" +
- "Content-Type: multipart/related; boundary=\"_\"\r\n" +
- "\r\n")
-
- images.each_index do |idx|
- f.write("--_\r\n" +
- "Content-Location:img#{idx}\r\n" +
- "Content-Transfer-Encoding:base64\r\n" +
- "\r\n" +
- images[idx][2] + "\r\n")
- end
-
- f.write("--_--\r\n" +
- "*/\r\n")
-
- # generate a css string containing *background-image rules for IE that point to the mime type
- # images in the header
- cssString = ''
- images.each_index do |idx|
- cssString += "#{images[idx][0]}{*background-image:url(\"mhtml:' + jsUri + '!img#{idx}\")}"
- end
-
- # generate a javascript closure that contains a function which will append the generated css
- # string as a stylesheet to the current html document
- jsString = "(function(){ \r\n" +
- " var jsUri = document.location.href.replace(/\\/[^\\\/]+(#.*)?$/, '/') + \r\n" +
- " document.getElementById('ng-ie-compat').src,\r\n" +
- " css = '#{cssString}',\r\n" +
- " s = document.createElement('style'); \r\n" +
- "\r\n" +
- " s.setAttribute('type', 'text/css'); \r\n" +
- "\r\n" +
- " if (s.styleSheet) { \r\n" +
- " s.styleSheet.cssText = css; \r\n" +
- " } else { \r\n" +
- " s.appendChild(document.createTextNode(css)); \r\n" +
- " } \r\n" +
- " document.getElementsByTagName('head')[0].appendChild(s); \r\n" +
- "})();\r\n"
-
- f.write(jsString)
- end
-end
-
-
desc 'Compile JavaScript'
-task :compile => [:init, :compile_scenario, :compile_jstd_scenario_adapter, :generate_ie_compat] do
+task :compile => [:init, :compile_scenario, :compile_jstd_scenario_adapter] do
deps = [
'src/angular.prefix',
@@ -193,7 +135,6 @@ task :package => [:clean, :compile, :docs] do
['src/angular-mocks.js',
path_to('angular.js'),
path_to('angular.min.js'),
- path_to('angular-ie-compat.js'),
path_to('angular-scenario.js'),
path_to('jstd-scenario-adapter.js'),
path_to('jstd-scenario-adapter-config.js'),
diff --git a/angularFiles.js b/angularFiles.js
index 8e52731bd4ea..906c3f98fc38 100644
--- a/angularFiles.js
+++ b/angularFiles.js
@@ -12,14 +12,12 @@ angularFiles = {
'src/jqLite.js',
'src/apis.js',
'src/filters.js',
- 'src/formatters.js',
- 'src/validators.js',
'src/service/cookieStore.js',
'src/service/cookies.js',
'src/service/defer.js',
'src/service/document.js',
'src/service/exceptionHandler.js',
- 'src/service/invalidWidgets.js',
+ 'src/service/formFactory.js',
'src/service/location.js',
'src/service/log.js',
'src/service/resource.js',
@@ -35,6 +33,9 @@ angularFiles = {
'src/directives.js',
'src/markups.js',
'src/widgets.js',
+ 'src/widget/form.js',
+ 'src/widget/input.js',
+ 'src/widget/select.js',
'src/AngularPublic.js',
],
@@ -74,6 +75,7 @@ angularFiles = {
'test/jstd-scenario-adapter/*.js',
'test/*.js',
'test/service/*.js',
+ 'test/widget/*.js',
'example/personalLog/test/*.js'
],
diff --git a/css/angular.css b/css/angular.css
index 89519da60ab2..d11462157091 100644
--- a/css/angular.css
+++ b/css/angular.css
@@ -7,12 +7,3 @@
.ng-format-negative {
color: red;
}
-
-/*****************
- * indicators
- *****************/
-.ng-input-indicator-wait {
- background-image: url("data:image/png;base64,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");
- background-position: right;
- background-repeat: no-repeat;
-}
diff --git a/docs/content/api/angular.inputType.ngdoc b/docs/content/api/angular.inputType.ngdoc
new file mode 100644
index 000000000000..434fe6c2235e
--- /dev/null
+++ b/docs/content/api/angular.inputType.ngdoc
@@ -0,0 +1,92 @@
+@ngdoc overview
+@name angular.inputType
+@description
+
+Angular {@link guide/dev_guide.forms forms} allow you to build complex widgets. However for
+simple widget which are based on HTML input text element a simpler way of providing the validation
+and parsing is also provided. `angular.inputType` is a short hand for creating a widget which
+already has the DOM listeners and `$render` method supplied. The only thing which needs to
+be provided by the developer are the optional `$validate` listener and
+`$parseModel` or `$parseModel` methods.
+
+All `inputType` widgets support:
+
+ - CSS classes:
+ - **`ng-valid`**: when widget is valid.
+ - **`ng-invalid`**: when widget is invalid.
+ - **`ng-pristine`**: when widget has not been modified by user action.
+ - **`ng-dirty`**: when has been modified do to user action.
+
+ - Widget properties:
+ - **`$valid`**: When widget is valid.
+ - **`$invalid`**: When widget is invalid.
+ - **`$pristine`**: When widget has not been modified by user interaction.
+ - **`$dirty`**: When user has been modified do to user interaction.
+ - **`$required`**: When the `` element has `required` attribute. This means that the
+ widget will have `REQUIRED` validation error if empty.
+ - **`$disabled`**: When the `` element has `disabled` attribute.
+ - **`$readonly`**: When the `` element has `readonly` attribute.
+
+ - Widget Attribute Validators:
+ - **`required`**: Sets `REQUIRED` validation error key if the input is empty
+ - **`ng:pattern`** Sets `PATTERN` validation error key if the value does not match the
+ RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
+ patterns defined as scope expressions.
+
+
+
+# Example
+
+
+
+
+
+
+
+
+
+ it('should invalidate on wrong input', function(){
+ expect(element('form[name=myForm]').prop('className')).toMatch('ng-valid');
+ input('data').enter('{}');
+ expect(binding('data')).toEqual('data={\n }');
+ input('data').enter('{');
+ expect(element('form[name=myForm]').prop('className')).toMatch('ng-invalid');
+ });
+
+
diff --git a/docs/content/api/angular.service.ngdoc b/docs/content/api/angular.service.ngdoc
index 874fe4bb5b51..50fe156056ce 100644
--- a/docs/content/api/angular.service.ngdoc
+++ b/docs/content/api/angular.service.ngdoc
@@ -14,8 +14,6 @@ session cookies
* {@link angular.service.$document $document } - Provides reference to `window.document` element
* {@link angular.service.$exceptionHandler $exceptionHandler } - Receives uncaught angular
exceptions
-* {@link angular.service.$hover $hover } -
-* {@link angular.service.$invalidWidgets $invalidWidgets } - Holds references to invalid widgets
* {@link angular.service.$location $location } - Parses the browser location URL
* {@link angular.service.$log $log } - Provides logging service
* {@link angular.service.$resource $resource } - Creates objects for interacting with RESTful
diff --git a/docs/content/api/index.ngdoc b/docs/content/api/index.ngdoc
index 05928ab46a79..2ec86346425a 100644
--- a/docs/content/api/index.ngdoc
+++ b/docs/content/api/index.ngdoc
@@ -8,8 +8,6 @@
* {@link angular.directive Directives} - Angular DOM element attributes
* {@link angular.markup Markup} and {@link angular.attrMarkup Attribute Markup}
* {@link angular.filter Filters} - Angular output filters
-* {@link angular.formatter Formatters} - Angular converters for form elements
-* {@link angular.validator Validators} - Angular input validators
* {@link angular.compile angular.compile()} - Template compiler
## Angular Scope API
diff --git a/docs/content/cookbook/advancedform.ngdoc b/docs/content/cookbook/advancedform.ngdoc
index 585c66a6c213..d38008f29d57 100644
--- a/docs/content/cookbook/advancedform.ngdoc
+++ b/docs/content/cookbook/advancedform.ngdoc
@@ -9,9 +9,7 @@ detection, and preventing invalid form submission.
diff --git a/docs/content/cookbook/form.ngdoc b/docs/content/cookbook/form.ngdoc
index 2aeafc4d2d2b..c74b203b5204 100644
--- a/docs/content/cookbook/form.ngdoc
+++ b/docs/content/cookbook/form.ngdoc
@@ -24,25 +24,26 @@ allow a user to enter data.
});
it('should validate zip', function(){
- expect(using('.example').element(':input[name="user.address.zip"]').prop('className'))
- .not().toMatch(/ng-validation-error/);
+ expect(using('.example').
+ element(':input[ng\\:model="user.address.zip"]').
+ prop('className')).not().toMatch(/ng-invalid/);
using('.example').input('user.address.zip').enter('abc');
- expect(using('.example').element(':input[name="user.address.zip"]').prop('className'))
- .toMatch(/ng-validation-error/);
+ expect(using('.example').
+ element(':input[ng\\:model="user.address.zip"]').
+ prop('className')).toMatch(/ng-invalid/);
});
it('should validate state', function(){
- expect(using('.example').element(':input[name="user.address.state"]').prop('className'))
- .not().toMatch(/ng-validation-error/);
+ expect(using('.example').element(':input[ng\\:model="user.address.state"]').prop('className'))
+ .not().toMatch(/ng-invalid/);
using('.example').input('user.address.state').enter('XXX');
- expect(using('.example').element(':input[name="user.address.state"]').prop('className'))
- .toMatch(/ng-validation-error/);
+ expect(using('.example').element(':input[ng\\:model="user.address.state"]').prop('className'))
+ .toMatch(/ng-invalid/);
});
@@ -94,7 +97,7 @@ available in
* For debugging purposes we have included a debug view of the model to better understand what
is going on.
* The {@link api/angular.widget.HTML input widgets} simply refer to the model and are auto bound.
-* The inputs {@link api/angular.validator validate}. (Try leaving them blank or entering non digits
+* The inputs {@link guide/dev_guide.forms validate}. (Try leaving them blank or entering non digits
in the zip field)
* In your application you can simply read from or write to the model and the form will be updated.
* By clicking the 'add' link you are adding new items into the `user.contacts` array which are then
diff --git a/docs/content/cookbook/helloworld.ngdoc b/docs/content/cookbook/helloworld.ngdoc
index 8018a399ece3..9562aaff7cda 100644
--- a/docs/content/cookbook/helloworld.ngdoc
+++ b/docs/content/cookbook/helloworld.ngdoc
@@ -5,9 +5,16 @@
- Your name:
-
- Hello {{name}}!
+
+
+ Your name:
+
+ Hello {{name}}!
+
it('should change the binding when user enters text', function(){
diff --git a/docs/content/guide/dev_guide.compiler.directives.ngdoc b/docs/content/guide/dev_guide.compiler.directives.ngdoc
index 0f99e46b035d..3b233551f6cf 100644
--- a/docs/content/guide/dev_guide.compiler.directives.ngdoc
+++ b/docs/content/guide/dev_guide.compiler.directives.ngdoc
@@ -16,7 +16,7 @@ directives per element.
You add angular directives to a standard HTML tag as in the following example, in which we have
added the {@link api/angular.directive.ng:click ng:click} directive to a button tag:
-
+
In the example above, `name` is the standard HTML attribute, and `ng:click` is the angular
directive. The `ng:click` directive lets you implement custom behavior in an associated controller
diff --git a/docs/content/guide/dev_guide.expressions.ngdoc b/docs/content/guide/dev_guide.expressions.ngdoc
index 177a5e87a051..ab5a897bc1c3 100644
--- a/docs/content/guide/dev_guide.expressions.ngdoc
+++ b/docs/content/guide/dev_guide.expressions.ngdoc
@@ -51,9 +51,15 @@ You can try evaluating different expressions here:
-
+
+
Expression:
-
+
@@ -84,9 +90,18 @@ the global state (a common source of subtle bugs).
-
- Name:
-
+
+
+ Name:
+
@@ -158,7 +173,7 @@ Extensions: You can further extend the expression vocabulary by adding new metho
{name:'Mike', phone:'555-4321'},
{name:'Adam', phone:'555-5678'},
{name:'Julie', phone:'555-8765'}]">
- Search:
+ Search:
Name
Phone
diff --git a/docs/content/guide/dev_guide.forms.ngdoc b/docs/content/guide/dev_guide.forms.ngdoc
new file mode 100644
index 000000000000..6849ff4eba01
--- /dev/null
+++ b/docs/content/guide/dev_guide.forms.ngdoc
@@ -0,0 +1,610 @@
+@ngdoc overview
+@name Developer Guide: Forms
+@description
+
+# Overview
+
+Forms allow users to enter data into your application. Forms represent the bidirectional data
+bindings in Angular.
+
+Forms consist of all of the following:
+
+ - the individual widgets with which users interact
+ - the validation rules for widgets
+ - the form, a collection of widgets that contains aggregated validation information
+
+
+# Form
+
+A form groups a set of widgets together into a single logical data-set. A form is created using
+the {@link api/angular.widget.form <form>} element that calls the
+{@link api/angular.service.$formFactory $formFactory} service. The form is responsible for managing
+the widgets and for tracking validation information.
+
+A form is:
+
+- The collection which contains widgets or other forms.
+- Responsible for marshaling data from the model into a widget. This is
+ triggered by {@link api/angular.scope.$watch $watch} of the model expression.
+- Responsible for marshaling data from the widget into the model. This is
+ triggered by the widget emitting the `$viewChange` event.
+- Responsible for updating the validation state of the widget, when the widget emits
+ `$valid` / `$invalid` event. The validation state is useful for controlling the validation
+ errors shown to the user in it consist of:
+
+ - `$valid` / `$invalid`: Complementary set of booleans which show if a widget is valid / invalid.
+ - `$error`: an object which has a property for each validation key emited by the widget.
+ The value of the key is always true. If widget is valid, then the `$error`
+ object has no properties. For example if the widget emits
+ `$invalid` event with `REQUIRED` key. The internal state of the `$error` would be
+ updated to `$error.REQUIRED == true`.
+
+- Responsible for aggregating widget validation information into the form.
+
+ - `$valid` / `$invalid`: Complementary set of booleans which show if all the child widgets
+ (or forms) are valid or if any are invalid.
+ - `$error`: an object which has a property for each validation key emited by the
+ child widget. The value of the key is an array of widgets which fired the invalid
+ event. If all child widgets are valid then, then the `$error` object has no
+ properties. For example if a child widget emits
+ `$invalid` event with `REQUIRED` key. The internal state of the `$error` would be
+ updated to `$error.REQUIRED == [ widgetWhichEmitedInvalid ]`.
+
+
+# Widgets
+
+In Angular, a widget is the term used for the UI with which the user input. Examples of
+bult-in Angular widgets are {@link api/angular.widget.input input} and
+{@link api/angular.widget.select select}. Widgets provide the rendering and the user
+interaction logic. Widgets should be declared inside a form, if no form is provided an implicit
+form {@link api/angular.service.$formFactory $formFactory.rootForm} form is used.
+
+Widgets are implemented as Angular controllers. A widget controller:
+
+- implements methods:
+
+ - `$render` - Updates the DOM from the internal state as represented by `$viewValue`.
+ - `$parseView` - Translate `$viewValue` to `$modelValue`. (`$modelValue` will be assigned to
+ the model scope by the form)
+ - `$parseModel` - Translate `$modelValue` to `$viewValue`. (`$viewValue` will be assigned to
+ the DOM inside the `$render` method)
+
+- responds to events:
+
+ - `$validate` - Emitted by the form when the form determines that the widget needs to validate
+ itself. There may be more then one listener on the `$validate` event. The widget responds
+ by emitting `$valid` / `$invalid` event of its own.
+
+- emits events:
+
+ - `$viewChange` - Emitted when the user interacts with the widget and it is necessary to update
+ the model.
+ - `$valid` - Emitted when the widget determines that it is valid (usually as a response to
+ `$validate` event or inside `$parseView()` or `$parseModel()` method).
+ - `$invalid` - Emitted when the widget determines that it is invalid (usually as a response to
+ `$validate` event or inside `$parseView()` or `$parseModel()` method).
+ - `$destroy` - Emitted when the widget element is removed from the DOM.
+
+
+# CSS
+
+Angular-defined widgets and forms set `ng-valid` and `ng-invalid` classes on themselves to allow
+the web-designer a way to style them. If you write your own widgets, then their `$render()`
+methods must set the appropriate CSS classes to allow styling.
+(See {@link dev_guide.templates.css-styling CSS})
+
+
+# Example
+
+The following example demonstrates:
+
+ - How an error is displayed when a required field is empty.
+ - Error highlighting.
+ - How form submission is disabled when the form is invalid.
+ - The internal state of the widget and form in the the 'Debug View' area.
+
+
+
+
+
+
+