diff options
Diffstat (limited to 'docs')
36 files changed, 916 insertions, 554 deletions
| diff --git a/docs/content/api/angular.inputType.ngdoc b/docs/content/api/angular.inputType.ngdoc new file mode 100644 index 00000000..434fe6c2 --- /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 `<input>` element has `required` attribute. This means that the +       widget will have `REQUIRED` validation error if empty. +    - **`$disabled`**: When the `<input>` element has `disabled` attribute. +    - **`$readonly`**: When the `<input>` 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 + +<doc:example> +<doc:source> +   <script> +     angular.inputType('json', function(){ +       this.$parseView = function(){ +         try { +           this.$modelValue = angular.fromJson(this.$viewValue); +           if (this.$error.JSON) { +             this.$emit('$valid', 'JSON'); +           } +         } catch (e) { +             this.$emit('$invalid', 'JSON'); +         } +       } + +       this.$parseModel = function(){ +         this.$viewValue = angular.toJson(this.$modelValue); +       } +     }); + +     function Ctrl(){ +       this.data = { +         framework:'angular', +         codenames:'supper-powers' +       } +       this.required = false; +       this.disabled = false; +       this.readonly = false; +     } +   </script> +   <div ng:controller="Ctrl"> +     <form name="myForm"> +       <input type="json" ng:model="data" size="80" +              ng:required="{{required}}" ng:disabled="{{disabled}}" +              ng:readonly="{{readonly}}"/><br/> +       Required: <input type="checkbox" ng:model="required"> <br/> +       Disabled: <input type="checkbox" ng:model="disabled"> <br/> +       Readonly: <input type="checkbox" ng:model="readonly"> <br/> +      <pre>data={{data}}</pre> +       <pre>myForm={{myForm}}</pre> +     </form> +   </div> +</doc:source> +<doc:scenario> +  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'); +  }); +</doc:scenario> +</doc:example> diff --git a/docs/content/api/angular.service.ngdoc b/docs/content/api/angular.service.ngdoc index 874fe4bb..50fe1560 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 05928ab4..2ec86346 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 585c66a6..d38008f2 100644 --- a/docs/content/cookbook/advancedform.ngdoc +++ b/docs/content/cookbook/advancedform.ngdoc @@ -9,9 +9,7 @@ detection, and preventing invalid form submission.  <doc:example>  <doc:source>     <script> -   UserForm.$inject = ['$invalidWidgets']; -   function UserForm($invalidWidgets){ -     this.$invalidWidgets = $invalidWidgets; +   function UserForm(){       this.state = /^\w\w$/;       this.zip = /^\d\d\d\d\d$/;       this.master = { @@ -42,31 +40,34 @@ detection, and preventing invalid form submission.     </script>     <div ng:controller="UserForm"> -     <label>Name:</label><br/> -     <input type="text" name="form.name" ng:required/> <br/><br/> +     <form name="myForm"> -     <label>Address:</label><br/> -     <input type="text" name="form.address.line1" size="33" ng:required/> <br/> -     <input type="text" name="form.address.city" size="12" ng:required/>, -     <input type="text" name="form.address.state" size="2" ng:required ng:validate="regexp:state"/> -     <input type="text" name="form.address.zip" size="5" ng:required -ng:validate="regexp:zip"/><br/><br/> +       <label>Name:</label><br/> +       <input type="text" ng:model="form.name" required/> <br/><br/> -     <label>Contacts:</label> -     [ <a href="" ng:click="form.contacts.$add()">add</a> ] -     <div ng:repeat="contact in form.contacts"> -       <select name="contact.type"> -         <option>email</option> -         <option>phone</option> -         <option>pager</option> -         <option>IM</option> -       </select> -       <input type="text" name="contact.value" ng:required/> -        [ <a href="" ng:click="form.contacts.$remove(contact)">X</a> ] -     </div> -   <button ng:click="cancel()" ng:disabled="{{master.$equals(form)}}">Cancel</button> -   <button ng:click="save()" ng:disabled="{{$invalidWidgets.visible() || -master.$equals(form)}}">Save</button> +       <label>Address:</label> <br/> +       <input type="text" ng:model="form.address.line1" size="33" required/> <br/> +       <input type="text" ng:model="form.address.city" size="12" required/>, +       <input type="text" ng:model="form.address.state" size="2" +              ng:pattern="state" required/> +       <input type="text" ng:model="form.address.zip" size="5" +              ng:pattern="zip" required/><br/><br/> + +       <label>Contacts:</label> +       [ <a href="" ng:click="form.contacts.$add()">add</a> ] +       <div ng:repeat="contact in form.contacts"> +         <select ng:model="contact.type"> +           <option>email</option> +           <option>phone</option> +           <option>pager</option> +           <option>IM</option> +         </select> +         <input type="text" ng:model="contact.value" required/> +          [ <a href="" ng:click="form.contacts.$remove(contact)">X</a> ] +       </div> +     <button ng:click="cancel()" ng:disabled="{{master.$equals(form)}}">Cancel</button> +     <button ng:click="save()" ng:disabled="{{myForm.$invalid || master.$equals(form)}}">Save</button> +   </form>     <hr/>     Debug View: @@ -90,7 +91,7 @@ master.$equals(form)}}">Save</button>      expect(element(':button:contains(Cancel)').attr('disabled')).toBeFalsy();      element(':button:contains(Cancel)').click();      expect(element(':button:contains(Cancel)').attr('disabled')).toBeTruthy(); -    expect(element(':input[name="form.name"]').val()).toEqual('John Smith'); +    expect(element(':input[ng\\:model="form.name"]').val()).toEqual('John Smith');    });  </doc:scenario>  </doc:example> diff --git a/docs/content/cookbook/buzz.ngdoc b/docs/content/cookbook/buzz.ngdoc index a1e4a8b2..fad4c1ff 100644 --- a/docs/content/cookbook/buzz.ngdoc +++ b/docs/content/cookbook/buzz.ngdoc @@ -15,6 +15,7 @@ to retrieve Buzz activity and comments.      <script>      BuzzController.$inject = ['$resource'];      function BuzzController($resource) { +     this.userId = 'googlebuzz';       this.Activity = $resource(        'https://www.googleapis.com/buzz/v1/activities/:userId/:visibility/:activityId/:comments',        {alt: 'json', callback: 'JSON_CALLBACK'}, @@ -32,7 +33,7 @@ to retrieve Buzz activity and comments.      };      </script>      <div ng:controller="BuzzController"> -     <input name="userId" value="googlebuzz"/> +     <input ng:model="userId"/>       <button ng:click="fetch()">fetch</button>       <hr/>       <div class="buzz" ng:repeat="item in activities.data.items"> diff --git a/docs/content/cookbook/form.ngdoc b/docs/content/cookbook/form.ngdoc index 2aeafc4d..c74b203b 100644 --- a/docs/content/cookbook/form.ngdoc +++ b/docs/content/cookbook/form.ngdoc @@ -24,25 +24,26 @@ allow a user to enter data.    <div ng:controller="FormController" class="example">      <label>Name:</label><br/> -    <input type="text" name="user.name" ng:required/> <br/><br/> +    <input type="text" ng:model="user.name" required/> <br/><br/>      <label>Address:</label><br/> -    <input type="text" name="user.address.line1" size="33" ng:required/> <br/> -    <input type="text" name="user.address.city" size="12" ng:required/>, -    <input type="text" name="user.address.state" size="2" ng:required ng:validate="regexp:state"/> -    <input type="text" name="user.address.zip" size="5" ng:required -ng:validate="regexp:zip"/><br/><br/> +    <input type="text" ng:model="user.address.line1" size="33" required> <br/> +    <input type="text" ng:model="user.address.city" size="12" required>, +    <input type="text" ng:model="user.address.state" size="2" +           ng:pattern="state" required> +    <input type="text" ng:model="user.address.zip" size="5" +           ng:pattern="zip"  required><br/><br/>      <label>Phone:</label>      [ <a href="" ng:click="user.contacts.$add()">add</a> ]      <div ng:repeat="contact in user.contacts"> -      <select name="contact.type"> +      <select ng:model="contact.type">          <option>email</option>          <option>phone</option>          <option>pager</option>          <option>IM</option>        </select> -      <input type="text" name="contact.value" ng:required/> +      <input type="text" ng:model="contact.value" required/>         [ <a href="" ng:click="user.contacts.$remove(contact)">X</a> ]      </div>      <hr/> @@ -68,19 +69,21 @@ ng:validate="regexp:zip"/><br/><br/>    });    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/);    });   </doc:scenario>  </doc:example> @@ -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 8018a399..9562aaff 100644 --- a/docs/content/cookbook/helloworld.ngdoc +++ b/docs/content/cookbook/helloworld.ngdoc @@ -5,9 +5,16 @@  <doc:example>   <doc:source> -  Your name: <input type="text" name="name" value="World"/> -  <hr/> -  Hello {{name}}! +  <script> +    function HelloCntl(){ +      this.name = 'World'; +    } +  </script> +  <div ng:controller="HelloCntl"> +    Your name: <input type="text" ng:model="name" value="World"/> +    <hr/> +    Hello {{name}}! +  </div>   </doc:source>   <doc:scenario>     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 0f99e46b..3b233551 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: -        <button name="button1" ng:click="foo()">Click This</button> +        <button ng:model="button1" ng:click="foo()">Click This</button>  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 177a5e87..ab5a897b 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:  <doc:example>  <doc:source> - <div ng:init="exprs=[]" class="expressions"> + <script> +   function Cntl2(){ +     this.exprs = []; +     this.expr = '3*10|currency'; +   } + </script> + <div ng:controller="Cntl2" class="expressions">     Expression: -   <input type='text' name="expr" value="3*10|currency" size="80"/> +   <input type='text' ng:model="expr" size="80"/>     <button ng:click="exprs.$add(expr)">Evaluate</button>     <ul>      <li ng:repeat="expr in exprs"> @@ -84,9 +90,18 @@ the global state (a common source of subtle bugs).  <doc:example>  <doc:source> - <div class="example2" ng:init="$window = $service('$window')"> -   Name: <input name="name" type="text" value="World"/> -   <button ng:click="($window.mockWindow || $window).alert('Hello ' + name)">Greet</button> + <script> +   function Cntl1($window){ +     this.name = 'World'; + +     this.greet = function() { +       ($window.mockWindow || $window).alert('Hello ' + this.name); +     } +   } + </script> + <div class="example2" ng:controller="Cntl1"> +   Name: <input ng:model="name" type="text"/> +   <button ng:click="greet()">Greet</button>   </div>  </doc:source>  <doc:scenario> @@ -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'}]"></div> - Search: <input name="searchText"/> + Search: <input ng:model="searchText"/>   <table class="example3">     <tr><th>Name</th><th>Phone</th><tr>     <tr ng:repeat="friend in friends.$filter(searchText)"> diff --git a/docs/content/guide/dev_guide.forms.ngdoc b/docs/content/guide/dev_guide.forms.ngdoc new file mode 100644 index 00000000..6849ff4e --- /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. + + +<doc:example> +<doc:source> +   <style> +     .ng-invalid { border: solid 1px red; } +     .ng-form {display: block;} +   </style> +   <script> +   function UserFormCntl(){ +     this.state = /^\w\w$/; +     this.zip = /^\d\d\d\d\d$/; +     this.master = { +       customer: 'John Smith', +       address:{ +         line1: '123 Main St.', +         city:'Anytown', +         state:'AA', +         zip:'12345' +       } +     }; +     this.cancel(); +   } + +   UserFormCntl.prototype = { +     cancel: function(){ +       this.form = angular.copy(this.master); +     }, + +     save: function(){ +       this.master = this.form; +       this.cancel(); +     } +   }; +   </script> +   <div ng:controller="UserFormCntl"> + +     <form name="userForm"> + +       <label>Name:</label><br/> +       <input type="text" name="customer" ng:model="form.customer" required/> +       <span class="error" ng:show="userForm.customer.$error.REQUIRED"> +         Customer name is required!</span> +       <br/><br/> + +       <ng:form name="addressForm"> +         <label>Address:</label> <br/> +         <input type="text" name="line1" size="33" required +                ng:model="form.address.line1"/> <br/> +         <input type="text" name="city" size="12" required +                ng:model="form.address.city"/>, +         <input type="text" name="state" ng:pattern="state" size="2" required +                ng:model="form.address.state"/> +         <input type="text" name="zip" ng:pattern="zip" size="5" required +                ng:model="form.address.zip"/><br/><br/> + +         <span class="error" ng:show="addressForm.$invalid"> +           Incomplete address: +           <div class="error" ng:show="addressForm.state.$error.REQUIRED"> +             Missing state!</span> +           <div class="error" ng:show="addressForm.state.$error.PATTERN"> +             Invalid state!</span> +           <div class="error" ng:show="addressForm.zip.$error.REQUIRED"> +             Missing zip!</span> +           <div class="error" ng:show="addressForm.zip.$error.PATTERN"> +             Invalid zip!</span> +         </span> +       </ng:form> + +       <button ng:click="cancel()" +               ng:disabled="{{master.$equals(form)}}">Cancel</button> +       <button ng:click="save()" +               ng:disabled="{{userForm.$invalid || master.$equals(form)}}"> +          Save</button> +     </form> + +     <hr/> +     Debug View: +     <pre>form={{form}}</pre> +     <pre>master={{master}}</pre> +     <pre>userForm={{userForm}}</pre> +     <pre>addressForm={{addressForm}}</pre> +   </div> +</doc:source> +<doc:scenario> +  it('should enable save button', function(){ +    expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy(); +    input('form.customer').enter(''); +    expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy(); +    input('form.customer').enter('change'); +    expect(element(':button:contains(Save)').attr('disabled')).toBeFalsy(); +    element(':button:contains(Save)').click(); +    expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy(); +  }); +  it('should enable cancel button', function(){ +    expect(element(':button:contains(Cancel)').attr('disabled')).toBeTruthy(); +    input('form.customer').enter('change'); +    expect(element(':button:contains(Cancel)').attr('disabled')).toBeFalsy(); +    element(':button:contains(Cancel)').click(); +    expect(element(':button:contains(Cancel)').attr('disabled')).toBeTruthy(); +    expect(element(':input[ng\\:model="form.customer"]').val()).toEqual('John Smith'); +  }); +</doc:scenario> +</doc:example> + +# Life-cycle + +- The `<form>` element triggers creation of a new form {@link dev_guide.scopes scope} using the +  {@link api/angular.service.$formFactory $formfactory}. The new form scope is added to the +  `<form>` element using the jQuery `.data()` method for later retrieval under the key `$form`. +  The form also sets up these listeners: + +  - `$destroy` - This event is emitted by nested widget when it is removed from the view. It gives +     the form a chance to clean up any validation references to the destroyed widget. +  - `$valid` / `$invalid` - This event is emitted by the widget on validation state change. + +- `<input>` element triggers the creation of the widget using the +  {@link api/angular.service.$formFactory $formfactory.$createWidget()} method. The `$createWidget()` +  creates new widget instance by calling the current scope {@link api/angular.scope.$new .$new()} and +  registers these listeners: + +  - `$watch` on the model scope. +  - `$viewChange` event on the widget scope. +  - `$validate` event on the widget scope. +  - Element `change` event when the user enters data. + +<img class="center" src="img/form_data_flow.png" border="1" /> + + +- When the user interacts with the widget: + +  1. The DOM element fires the `change` event which the widget intercepts. Widget then emits +     a `$viewChange` event which includes the new user-entered value. (Remember that the DOM events +     are outside of the Angular environment so the widget must emit its event within the +     {@link api/angular.scope.$apply $apply} method). +  2. The form's `$viewChange` listener copies the user-entered value to the widget's `$viewValue` +     property. Since the `$viewValue` is the raw value as entered by user, it may need to be +     translated to a different format/type (for example, translating a string to a number). +     If you need your widget to translate between the internal `$viewValue` and the external +     `$modelValue` state, you must declare a `$parseView()` method. The `$parseView()` method +     will copy `$viewValue` to `$modelValue` and perform any necessary translations. +  3. The `$modelValue` is written into the application model. +  4. The form then emits a `$validate` event, giving the widget's validators chance to validate the +     input. There can be any number of validators registered. Each validator may in turn +     emit a `$valid` / `$invalid` event with the validator's validation key. For example `REQUIRED`. +  5. Form listens to `$valid`/`$invalid` events and updates both the form as well as the widget +     scope with the validation state. The validation updates the `$valid` and `$invalid`, property +     as well as `$error` object. The widget's `$error` object is updated with the validation key +     such that `$error.REQUIRED == true` when the validation emits `$invalid` with `REQUIRED` +     validation key. Similarly the form's `$error` object gets updated, but instead of boolean +     `true` it contains an array of invalid widgets (widgets which fired `$invalid` event with +     `REQUIRED` validation key). + +- When the model is updated: + +  1. The model `$watch` listener assigns the model value to `$modelValue` on the widget. +  2. The form then calls `$parseModel` method on widget if present. The method converts the +     value to renderable format and assigns it to `$viewValue` (for example converting number to a +     string.) +  3. The form then emits a `$validate` which behaves as described above. +  4. The form then calls `$render` method on the widget to update the DOM structure from the +     `$viewValue`. + + + +# Writing Your Own Widget + +This example shows how to implement a custom HTML editor widget in Angular. + +    <doc:example> +      <doc:source> +        <script> +          function EditorCntl(){ +            this.htmlContent = '<b>Hello</b> <i>World</i>!'; +          } + +          function HTMLEditorWidget(element) { +            var self = this; +            var htmlFilter = angular.filter('html'); + +            this.$parseModel = function(){ +              // need to protect for script injection +              try { +                this.$viewValue = htmlFilter( +                  this.$modelValue || '').get(); +                if (this.$error.HTML) { +                  // we were invalid, but now we are OK. +                  this.$emit('$valid', 'HTML'); +                } +              } catch (e) { +                // if HTML not parsable invalidate form. +                this.$emit('$invalid', 'HTML'); +              } +            } + +            this.$render = function(){ +              element.html(this.$viewValue); +            } + +            element.bind('keyup', function(){ +              self.$apply(function(){ +                self.$emit('$viewChange', element.html()); +              }); +            }); +          } + +          angular.directive('ng:html-editor-model', function(){ +            function linkFn($formFactory, element) { +              var exp = element.attr('ng:html-editor-model'), +                  form = $formFactory.forElement(element), +                  widget; +              element.attr('contentEditable', true); +              widget = form.$createWidget({ +                scope: this, +                model: exp, +                controller: HTMLEditorWidget, +                controllerArgs: [element]}); +              // if the element is destroyed, then we need to +              // notify the form. +              element.bind('$destroy', function(){ +                widget.$destroy(); +              }); +            } +            linkFn.$inject = ['$formFactory']; +            return linkFn; +          }); +        </script> +        <form name='editorForm' ng:controller="EditorCntl"> +          <div ng:html-editor-model="htmlContent"></div> +          <hr/> +          HTML: <br/> +          <textarea ng:model="htmlContent" cols="80"></textarea> +          <hr/> +          <pre>editorForm = {{editorForm}}</pre> +        </form> +      </doc:source> +      <doc:scenario> +        it('should enter invalid HTML', function(){ +          expect(element('form[name=editorForm]').prop('className')).toMatch(/ng-valid/); +          input('htmlContent').enter('<'); +          expect(element('form[name=editorForm]').prop('className')).toMatch(/ng-invalid/); +        }); +      </doc:scenario> +    </doc:example> + + + +# HTML Inputs + +The most common widgets you will use will be in the form of the +standard HTML set. These widgets are bound using the `name` attribute +to an expression. In addition, they can have `required` attribute to further control their +validation. +<doc:example> +  <doc:source> +     <script> +       function Ctrl(){ +         this.input1 = ''; +         this.input2 = ''; +         this.input3 = 'A'; +         this.input4 = false; +         this.input5 = 'c'; +         this.input6 = []; +       } +     </script> +    <table style="font-size:.9em;" ng:controller="Ctrl"> +      <tr> +        <th>Name</th> +        <th>Format</th> +        <th>HTML</th> +        <th>UI</th> +        <th ng:non-bindable>{{input#}}</th> +      </tr> +      <tr> +        <th>text</th> +        <td>String</td> +        <td><tt><input type="text" ng:model="input1"></tt></td> +        <td><input type="text" ng:model="input1" size="4"></td> +        <td><tt>{{input1|json}}</tt></td> +      </tr> +      <tr> +        <th>textarea</th> +        <td>String</td> +        <td><tt><textarea ng:model="input2"></textarea></tt></td> +        <td><textarea ng:model="input2" cols='6'></textarea></td> +        <td><tt>{{input2|json}}</tt></td> +      </tr> +      <tr> +        <th>radio</th> +        <td>String</td> +        <td><tt> +          <input type="radio" ng:model="input3" value="A"><br> +          <input type="radio" ng:model="input3" value="B"> +        </tt></td> +        <td> +          <input type="radio" ng:model="input3" value="A"> +          <input type="radio" ng:model="input3" value="B"> +        </td> +        <td><tt>{{input3|json}}</tt></td> +      </tr> +      <tr> +        <th>checkbox</th> +        <td>Boolean</td> +        <td><tt><input type="checkbox" ng:model="input4"></tt></td> +        <td><input type="checkbox" ng:model="input4"></td> +        <td><tt>{{input4|json}}</tt></td> +      </tr> +      <tr> +        <th>pulldown</th> +        <td>String</td> +        <td><tt> +          <select ng:model="input5"><br> +            <option value="c">C</option><br> +            <option value="d">D</option><br> +          </select><br> +        </tt></td> +        <td> +          <select ng:model="input5"> +            <option value="c">C</option> +            <option value="d">D</option> +          </select> +        </td> +        <td><tt>{{input5|json}}</tt></td> +      </tr> +      <tr> +        <th>multiselect</th> +        <td>Array</td> +        <td><tt> +          <select ng:model="input6" multiple size="4"><br> +            <option value="e">E</option><br> +            <option value="f">F</option><br> +          </select><br> +        </tt></td> +        <td> +          <select ng:model="input6" multiple size="4"> +            <option value="e">E</option> +            <option value="f">F</option> +          </select> +        </td> +        <td><tt>{{input6|json}}</tt></td> +      </tr> +    </table> +  </doc:source> +  <doc:scenario> + +    it('should exercise text', function(){ +     input('input1').enter('Carlos'); +     expect(binding('input1')).toEqual('"Carlos"'); +    }); +    it('should exercise textarea', function(){ +     input('input2').enter('Carlos'); +     expect(binding('input2')).toEqual('"Carlos"'); +    }); +    it('should exercise radio', function(){ +     expect(binding('input3')).toEqual('"A"'); +     input('input3').select('B'); +     expect(binding('input3')).toEqual('"B"'); +     input('input3').select('A'); +     expect(binding('input3')).toEqual('"A"'); +    }); +    it('should exercise checkbox', function(){ +     expect(binding('input4')).toEqual('false'); +     input('input4').check(); +     expect(binding('input4')).toEqual('true'); +    }); +    it('should exercise pulldown', function(){ +     expect(binding('input5')).toEqual('"c"'); +     select('input5').option('d'); +     expect(binding('input5')).toEqual('"d"'); +    }); +    it('should exercise multiselect', function(){ +     expect(binding('input6')).toEqual('[]'); +     select('input6').options('e'); +     expect(binding('input6')).toEqual('["e"]'); +     select('input6').options('e', 'f'); +     expect(binding('input6')).toEqual('["e","f"]'); +    }); +  </doc:scenario> +</doc:example> + +#Testing + +When unit-testing a controller it may be desirable to have a reference to form and to simulate +different form validation states. + +This example demonstrates a login form, where the login button is enabled only when the form is +properly filled out. +<pre> +  <div ng:controller="LoginController"> +    <form name="loginForm"> +      <input type="text" ng:model="username" required/> +      <input type="password" ng:model="password" required/> +      <button ng:disabled="{{!disableLogin()}}" ng:click="login()">Login</login> +    </form> +  </div> +</pre> + +In the unit tests we do not have access to the DOM, and therefore the `loginForm` reference does +not get set on the controller. This example shows how it can be unit-tested, by creating a mock +form. +<pre> +function LoginController() { +  this.disableLogin = function() { +    return this.loginForm.$invalid; +  }; +} + +describe('LoginController', function() { +  it('should disable login button when form is invalid', function() { +    var scope = angular.scope(); +    var loginController = scope.$new(LoginController); + +    // In production the 'loginForm' form instance gets set from the view, +    // but in unit-test we have to set it manually. +    loginController.loginForm = scope.$service('$formFactory')(); + +    expect(loginController.disableLogin()).toBe(false); + +    // Now simulate an invalid form +    loginController.loginForm.$emit('$invalid', 'MyReason'); +    expect(loginController.disableLogin()).toBe(true); + +    // Now simulate a valid form +    loginController.loginForm.$emit('$valid', 'MyReason'); +    expect(loginController.disableLogin()).toBe(false); +  }); +}); +</pre> + +## Custom widgets + +This example demonstrates a login form, where the password has custom validation rules. +<pre> +  <div ng:controller="LoginController"> +    <form name="loginForm"> +      <input type="text" ng:model="username" required/> +      <input type="@StrongPassword" ng:model="password" required/> +      <button ng:disabled="{{!disableLogin()}}" ng:click="login()">Login</login> +    </form> +  </div> +</pre> + +In the unit tests we do not have access to the DOM, and therefore the `loginForm` and custom +input type reference does not get set on the controller. This example shows how it can be +unit-tested, by creating a mock form and a mock custom input type. +<pre> +function LoginController(){ +  this.disableLogin = function() { +    return this.loginForm.$invalid; +  }; + +  this.StrongPassword = function(element) { +    var widget = this; +    element.attr('type', 'password'); // act as password. +    this.$on('$validate', function(){ +      widget.$emit(widget.$viewValue.length > 5 ? '$valid' : '$invalid', 'PASSWORD'); +    }); +  }; +} + +describe('LoginController', function() { +  it('should disable login button when form is invalid', function() { +    var scope = angular.scope(); +    var loginController = scope.$new(LoginController); +    var input = angular.element('<input>'); + +    // In production the 'loginForm' form instance gets set from the view, +    // but in unit-test we have to set it manually. +    loginController.loginForm = scope.$service('$formFactory')(); + +    // now instantiate a custom input type +    loginController.loginForm.$createWidget({ +      scope: loginController, +      model: 'password', +      alias: 'password', +      controller: loginController.StrongPassword, +      controllerArgs: [input] +    }); + +    // Verify that the custom password input type sets the input type to password +    expect(input.attr('type')).toEqual('password'); + +    expect(loginController.disableLogin()).toBe(false); + +    // Now simulate an invalid form +    loginController.loginForm.password.$emit('$invalid', 'PASSWORD'); +    expect(loginController.disableLogin()).toBe(true); + +    // Now simulate a valid form +    loginController.loginForm.password.$emit('$valid', 'PASSWORD'); +    expect(loginController.disableLogin()).toBe(false); + +    // Changing model state, should also influence the form validity +    loginController.password = 'abc'; // too short so it should be invalid +    scope.$digest(); +    expect(loginController.loginForm.password.$invalid).toBe(true); + +    // Changeing model state, should also influence the form validity +    loginController.password = 'abcdef'; // should be valid +    scope.$digest(); +    expect(loginController.loginForm.password.$valid).toBe(true); +  }); +}); +</pre> + + diff --git a/docs/content/guide/dev_guide.mvc.understanding_controller.ngdoc b/docs/content/guide/dev_guide.mvc.understanding_controller.ngdoc index 15ae3b34..7a6653e9 100644 --- a/docs/content/guide/dev_guide.mvc.understanding_controller.ngdoc +++ b/docs/content/guide/dev_guide.mvc.understanding_controller.ngdoc @@ -68,7 +68,7 @@ Putting any presentation logic into controllers significantly affects testabilit  logic. Angular offers {@link dev_guide.templates.databinding} for automatic DOM manipulation. If  you have to perform your own manual DOM manipulation, encapsulate the presentation logic in {@link  dev_guide.compiler.widgets widgets} and {@link dev_guide.compiler.directives directives}. -- Input formatting — Use {@link dev_guide.templates.formatters angular formatters} instead. +- Input formatting — Use {@link dev_guide.forms angular form widgets} instead.  - Output filtering — Use {@link dev_guide.templates.filters angular filters} instead.  - Run stateless or stateful code shared across controllers — Use {@link dev_guide.services angular  services} instead. @@ -139,7 +139,7 @@ previous example.  <pre>  <body ng:controller="SpicyCtrl"> - <input name="customSpice" value="wasabi"> + <input ng:model="customSpice" value="wasabi">   <button ng:click="spicy('chili')">Chili</button>   <button ng:click="spicy(customSpice)">Custom spice</button>   <p>The food is {{spice}} spicy!</p> diff --git a/docs/content/guide/dev_guide.mvc.understanding_model.ngdoc b/docs/content/guide/dev_guide.mvc.understanding_model.ngdoc index a35541d0..b4659b0c 100644 --- a/docs/content/guide/dev_guide.mvc.understanding_model.ngdoc +++ b/docs/content/guide/dev_guide.mvc.understanding_model.ngdoc @@ -41,7 +41,7 @@ when processing the following template constructs:  * Form input, select, textarea and other form elements: -         <input name="query" value="fluffy cloud"> +         <input ng:model="query" value="fluffy cloud">     The code above creates a model called "query" on the current scope with the value set to "fluffy  cloud". diff --git a/docs/content/guide/dev_guide.overview.ngdoc b/docs/content/guide/dev_guide.overview.ngdoc index f5db7f94..fcf15044 100644 --- a/docs/content/guide/dev_guide.overview.ngdoc +++ b/docs/content/guide/dev_guide.overview.ngdoc @@ -42,19 +42,27 @@ easier a web developer's life can if they're using angular:  <doc:example>  <doc:source> - <b>Invoice:</b> - <br /> - <br /> - <table> -  <tr><td> </td><td> </td> -  <tr><td>Quantity</td><td>Cost</td></tr> -  <tr> -    <td><input name="qty" value="1" ng:validate="integer:0" ng:required /></td> -    <td><input name="cost" value="19.95" ng:validate="number" ng:required /></td> -  </tr> - </table> - <hr /> - <b>Total:</b> {{qty * cost | currency}} + <script> +   function InvoiceCntl(){ +     this.qty = 1; +     this.cost = 19.95; +   } + </script> + <div ng:controller="InvoiceCntl"> +   <b>Invoice:</b> +   <br /> +   <br /> +   <table> +    <tr><td> </td><td> </td> +    <tr><td>Quantity</td><td>Cost</td></tr> +    <tr> +      <td><input type="integer" min="0" ng:model="qty" required ></td> +      <td><input type="number" ng:model="cost" required ></td> +    </tr> +   </table> +   <hr /> +   <b>Total:</b> {{qty * cost | currency}} + </div>  </doc:source>  <!--  <doc:scenario> @@ -89,18 +97,18 @@ In the `<script>` tag we do two angular setup tasks:  From the `name` attribute of the `<input>` tags, angular automatically sets up two-way data  binding, and we also demonstrate some easy input validation: -        Quantity: <input name="qty" value="1" ng:validate="integer:0" ng:required/> -        Cost: <input name="cost" value="199.95" ng:validate="number" ng:required/> +        Quantity: <input type="integer" min="0" ng:model="qty" required > +        Cost: <input type="number" ng:model="cost" required >  These input widgets look normal enough, but consider these points:  * When this page loaded, angular bound the names of the input widgets (`qty` and `cost`) to  variables of the same name. Think of those variables as the "Model" component of the  Model-View-Controller design pattern. -* Note the angular directives, {@link api/angular.widget.@ng:validate ng:validate} and {@link -api/angular.widget.@ng:required ng:required}. You may have noticed that when you enter invalid data +* Note the angular/HTML widget, {@link api/angular.widget.input input}. +You may have noticed that when you enter invalid data  or leave the the input fields blank, the borders turn red color, and the display value disappears. -These `ng:` directives make it easier to implement field validators than coding them in JavaScript, +These widgets make it easier to implement field validation than coding them in JavaScript,  no?  Yes.  And finally, the mysterious `{{ double curly braces }}`: diff --git a/docs/content/guide/dev_guide.services.$location.ngdoc b/docs/content/guide/dev_guide.services.$location.ngdoc index 4e0e8548..c0f35c96 100644 --- a/docs/content/guide/dev_guide.services.$location.ngdoc +++ b/docs/content/guide/dev_guide.services.$location.ngdoc @@ -612,7 +612,7 @@ https://github.com/angular/angular.js/issues/404 issue}).  If you should require  you will need to specify an extra property that has two watchers. For example:  <pre>  <!-- html --> -<input type="text" name="locationPath" /> +<input type="text" ng:model="locationPath" />  </pre>  <pre>  // js - controller diff --git a/docs/content/guide/dev_guide.services.injecting_controllers.ngdoc b/docs/content/guide/dev_guide.services.injecting_controllers.ngdoc index 0046dd7f..44206f7c 100644 --- a/docs/content/guide/dev_guide.services.injecting_controllers.ngdoc +++ b/docs/content/guide/dev_guide.services.injecting_controllers.ngdoc @@ -54,13 +54,13 @@ myController.$inject = ['notify'];  <div ng:controller="myController">  <p>Let's try this simple notify service, injected into the controller...</p> -<input ng:init="message='test'" type="text" name="message" /> +<input ng:init="message='test'" type="text" ng:model="message" />  <button ng:click="callNotify(message);">NOTIFY</button>  </div>  </doc:source>  <doc:scenario>  it('should test service', function(){ -  expect(element(':input[name=message]').val()).toEqual('test'); +  expect(element(':input[ng\\:model="message"]').val()).toEqual('test');  });  </doc:scenario>  </doc:example> diff --git a/docs/content/guide/dev_guide.templates.css-styling.ngdoc b/docs/content/guide/dev_guide.templates.css-styling.ngdoc index 4a4b2d65..4bd3f1b2 100644 --- a/docs/content/guide/dev_guide.templates.css-styling.ngdoc +++ b/docs/content/guide/dev_guide.templates.css-styling.ngdoc @@ -4,48 +4,32 @@  @description -Angular includes built-in CSS classes, which in turn have predefined CSS styles. +Angular sets these CSS classes. It is up to your application to provide useful styling. -# Built-in CSS classes +# CSS classes used by angular -* `ng-exception` +* `ng-invalid`, `ng-valid` +  - **Usage:** angular applies this class to an input widget element if that element's input does +    notpass validation. (see {@link api/angular.widget.input input} widget). -**Usage:** angular applies this class to a DOM element if that element contains an Expression that -threw an exception when evaluated. +* `ng-pristine`, `ng-dirty` +  - **Usage:** angular {@link api/angular.widget.input input} widget applies `ng-pristine` class +    to a new input widget element which did not have user interaction. Once the user interacts with +    the input widget the class is changed to `ng-dirty`. -**Styling:** The built-in styling of the ng-exception class displays an error message surrounded -by a solid red border, for example: +# Marking CSS classes - <div class="ng-exception">Error message</div> +* `ng-widget`, `ng-directive` +  -  **Usage:** angular sets these class on elements where {@link api/angular.widget widget} or +     {@link api/angular.directive directive} has bound to. -You can try to evaluate malformed expressions in {@link dev_guide.expressions expressions} to see -the `ng-exception` class' styling. - -* `ng-validation-error` - -**Usage:** angular applies this class to an input widget element if that element's input does not -pass validation. Note that you set the validation criteria on the input widget element using the -Ng:validate or Ng:required directives. - -**Styling:** The built-in styling of the ng-validation-error class turns the border of the input -box red and includes a hovering UI element that includes more details of the validation error. You -can see an example in {@link api/angular.widget.@ng:validate ng:validate example}. - -## Overriding Styles for Angular CSS Classes - -To override the styles for angular's built-in CSS classes, you can do any of the following: - -* Download the source code, edit angular.css, and host the source on your own server. -* Create a local CSS file, overriding any styles that you'd like, and link to it from your HTML file -as you normally would: - -<pre> -<link href="yourfile.css" rel="stylesheet" type="text/css"> -</pre> +* Old browser support +  - Pre v9, IE browsers could not select `ng:include` elements in CSS, because of the `:` +    character. For this reason angular also sets `ng-include` class on any element which has `:` +    character in the name by replacing `:` with `-`.  ## Related Topics  * {@link dev_guide.templates Angular Templates} -* {@link dev_guide.templates.formatters Angular Formatters} -* {@link dev_guide.templates.formatters.creating_formatters Creating Angular Formatters} +* {@link dev_guide.forms Angular Forms} diff --git a/docs/content/guide/dev_guide.templates.filters.creating_filters.ngdoc b/docs/content/guide/dev_guide.templates.filters.creating_filters.ngdoc index ebb7d923..27daec9f 100644 --- a/docs/content/guide/dev_guide.templates.filters.creating_filters.ngdoc +++ b/docs/content/guide/dev_guide.templates.filters.creating_filters.ngdoc @@ -35,20 +35,26 @@ text upper-case and assigns color.     }     return out;   }); + + function Ctrl(){ +   this.greeting = 'hello'; + }  </script> -<input name="text" type="text" value="hello" /><br> -No filter: {{text}}<br> -Reverse: {{text|reverse}}<br> -Reverse + uppercase: {{text|reverse:true}}<br> -Reverse + uppercase + blue:  {{text|reverse:true:"blue"}} +<div ng:controller="Ctrl"> +  <input ng:model="greeting" type="greeting"><br> +  No filter: {{greeting}}<br> +  Reverse: {{greeting|reverse}}<br> +  Reverse + uppercase: {{greeting|reverse:true}}<br> +  Reverse + uppercase + blue:  {{greeting|reverse:true:"blue"}} +</div>  </doc:source>  <doc:scenario> -it('should reverse text', function(){ -expect(binding('text|reverse')).toEqual('olleh'); -input('text').enter('ABC'); -expect(binding('text|reverse')).toEqual('CBA'); -}); +  it('should reverse greeting', function(){ +    expect(binding('greeting|reverse')).toEqual('olleh'); +    input('greeting').enter('ABC'); +    expect(binding('greeting|reverse')).toEqual('CBA'); +  });  </doc:scenario>  </doc:example> diff --git a/docs/content/guide/dev_guide.templates.formatters.creating_formatters.ngdoc b/docs/content/guide/dev_guide.templates.formatters.creating_formatters.ngdoc deleted file mode 100644 index 2ecd8f19..00000000 --- a/docs/content/guide/dev_guide.templates.formatters.creating_formatters.ngdoc +++ /dev/null @@ -1,55 +0,0 @@ -@workInProgress -@ngdoc overview -@name Developer Guide: Templates: Angular Formatters: Creating Angular Formatters -@description - -To create your own formatter, you can simply register a pair of JavaScript functions with -`angular.formatter`. One of your functions is used to parse text from the input widget into the -data storage format; the other function is used to format stored data into user-readable text. - -The following example demonstrates a "reverse" formatter. Data is stored in uppercase and in -reverse, but it is displayed in lower case and non-reversed. When a user edits the data model via -the input widget, the input is automatically parsed into the internal data storage format, and when -the data changes in the model, it is automatically formatted to the user-readable form for display -in the view. - -<pre> -function reverse(text) { -var reversed = []; -for (var i = 0; i < text.length; i++) { -reversed.unshift(text.charAt(i)); -} -return reversed.join(''); -} - -angular.formatter('reverse', { -parse: function(value){ -return reverse(value||'').toUpperCase(); -}, -format: function(value){ -return reverse(value||'').toLowerCase(); -} -}); -</pre> - -<doc:example> -<doc:source> -<script type="text/javascript"> -function reverse(text) { -var reversed = []; -for (var i = 0; i < text.length; i++) { -  reversed.unshift(text.charAt(i)); -} -return reversed.join(''); -} - -angular.formatter('reverse', { -parse: function(value){ -  return reverse(value||'').toUpperCase(); -}, -format: function(value){ -  return reverse(value||'').toLowerCase(); -} -}); -</script> - diff --git a/docs/content/guide/dev_guide.templates.formatters.ngdoc b/docs/content/guide/dev_guide.templates.formatters.ngdoc deleted file mode 100644 index 82a14fb4..00000000 --- a/docs/content/guide/dev_guide.templates.formatters.ngdoc +++ /dev/null @@ -1,20 +0,0 @@ -@workInProgress -@ngdoc overview -@name Developer Guide: Templates: Angular Formatters -@description - -In angular, formatters are responsible for translating user-readable text entered in an {@link -api/angular.widget.HTML input widget} to a JavaScript object in the data model that the application -can manipulate. - -You can use formatters in a template, and also in JavaScript.  Angular provides built-in -formatters, and of course you can create your own formatters. - -## Related Topics - -* {@link dev_guide.templates.formatters.using_formatters Using Angular Formatters} -* {@link dev_guide.templates.formatters.creating_formatters Creating Angular Formatters} - -## Related API - -* {@link api/angular.formatter Angular Formatter API} diff --git a/docs/content/guide/dev_guide.templates.formatters.using_formatters.ngdoc b/docs/content/guide/dev_guide.templates.formatters.using_formatters.ngdoc deleted file mode 100644 index bf983cd5..00000000 --- a/docs/content/guide/dev_guide.templates.formatters.using_formatters.ngdoc +++ /dev/null @@ -1,9 +0,0 @@ -@workInProgress -@ngdoc overview -@name Developer Guide: Templates: Angular Formatters: Using Angular Formatters -@description - -The following snippet shows how to use a formatter in a template. The formatter below is -`ng:format="reverse"`, added as an attribute to an `<input>` tag. - -<pre> diff --git a/docs/content/guide/dev_guide.templates.ngdoc b/docs/content/guide/dev_guide.templates.ngdoc index ca0ca99a..32514eb9 100644 --- a/docs/content/guide/dev_guide.templates.ngdoc +++ b/docs/content/guide/dev_guide.templates.ngdoc @@ -18,9 +18,7 @@ is {@link api/angular.widget.@ng:repeat ng:repeat}.  * {@link dev_guide.compiler.markup  Markup} — Shorthand for a widget or a directive. The double  curly brace notation `{{ }}` to bind expressions to elements is built-in angular markup.  * {@link dev_guide.templates.filters Filter} — Formats your data for display to the user. -* {@link dev_guide.templates.validators Validator} — Lets you validate user input. -* {@link dev_guide.templates.formatters Formatter} — Lets you format the input object into a user -readable view. +* {@link dev_guide.forms Form widgets} — Lets you validate user input.  Note:  In addition to declaring the elements above in templates, you can also access these elements  in JavaScript code. @@ -33,7 +31,7 @@ and {@link dev_guide.expressions expressions}:  <html>   <!-- Body tag augmented with ng:controller directive  -->   <body ng:controller="MyController"> -   <input name="foo" value="bar"> +   <input ng:model="foo" value="bar">     <!-- Button tag with ng:click directive, and            string expression 'buttonText'            wrapped in "{{ }}" markup --> @@ -55,8 +53,7 @@ eight.  ## Related Topics  * {@link dev_guide.templates.filters Angular Filters} -* {@link dev_guide.templates.formatters Angular Formatters} -* {@link dev_guide.templates.validators Angular Validators} +* {@link dev_guide.forms Angular Forms}  ## Related API diff --git a/docs/content/guide/dev_guide.templates.validators.creating_validators.ngdoc b/docs/content/guide/dev_guide.templates.validators.creating_validators.ngdoc deleted file mode 100644 index 835b0b51..00000000 --- a/docs/content/guide/dev_guide.templates.validators.creating_validators.ngdoc +++ /dev/null @@ -1,82 +0,0 @@ -@workInProgress -@ngdoc overview -@name Developer Guide: Validators: Creating Angular Validators -@description - - -To create a custom validator, you simply add your validator code as a method onto the -`angular.validator` object and provide input(s) for the validator function. Each input provided is -treated as an argument to the validator function.  Any additional inputs should be separated by -commas. - -The following bit of pseudo-code shows how to set up a custom validator: - -<pre> -angular.validator('your_validator', function(input [,additional params]) { -        [your validation code]; -        if ( [validation succeeds] ) { -                return false; -        } else { -                return true; // No error message specified -                         } -} -</pre> - -Note that this validator returns "true" when the user's input is incorrect, as in "Yes, it's true, -there was a problem with that input". If you prefer to provide more information when a validator -detects a problem with input, you can specify an error message in the validator that angular will -display when the user hovers over the input widget. - -To specify an error message, replace "`return true;`" with an error string, for example: - -     return "Must be a value between 1 and 5!"; - -Following is a sample UPS Tracking Number validator: - -<doc:example> -<doc:source> -<script> -angular.validator('upsTrackingNo', function(input, format) { - var regexp = new RegExp("^" + format.replace(/9/g, '\\d') + "$"); - return input.match(regexp)?"":"The format must match " + format; -}); -</script> -<input type="text" name="trackNo" size="40" -    ng:validate="upsTrackingNo:'1Z 999 999 99 9999 999 9'" -    value="1Z 123 456 78 9012 345 6"/> -</doc:source> -<doc:scenario> -it('should validate correct UPS tracking number', function() { -expect(element('input[name=trackNo]').attr('class')). -   not().toMatch(/ng-validation-error/); -}); - -it('should not validate in correct UPS tracking number', function() { -input('trackNo').enter('foo'); -expect(element('input[name=trackNo]').attr('class')). -   toMatch(/ng-validation-error/); -}); -</doc:scenario> -</doc:example> - -In this sample validator, we specify a regular expression against which to test the user's input. -Note that when the user's input matches `regexp`, the function returns "false" (""); otherwise it -returns the specified error message ("true"). - -Note: you can also access the current angular scope and DOM element objects in your validator -functions as follows: - -* `this` ===  The current angular scope. -* `this.$element` ===  The DOM element that contains the binding. This allows the filter to -manipulate the DOM in addition to transforming the input. - - -## Related Topics - -* {@link dev_guide.templates Angular Templates} -* {@link dev_guide.templates.filters Angular Filters} -* {@link dev_guide.templates.formatters Angular Formatters} - -## Related API - -* {@link api/angular.validator API Validator Reference} diff --git a/docs/content/guide/dev_guide.templates.validators.ngdoc b/docs/content/guide/dev_guide.templates.validators.ngdoc deleted file mode 100644 index 76df92b5..00000000 --- a/docs/content/guide/dev_guide.templates.validators.ngdoc +++ /dev/null @@ -1,131 +0,0 @@ -@workInProgress -@ngdoc overview -@name Developer Guide: Templates: Understanding Angular Validators -@description - -Angular validators are attributes that test the validity of different types of user input. Angular -provides a set of built-in input validators: - -* {@link api/angular.validator.phone phone number} -* {@link api/angular.validator.number number} -* {@link api/angular.validator.integer integer} -* {@link api/angular.validator.date date} -* {@link api/angular.validator.email email address} -* {@link api/angular.validator.json JSON} -* {@link api/angular.validator.regexp regular expressions} -* {@link api/angular.validator.url URLs} -* {@link api/angular.validator.asynchronous asynchronous} - -You can also create your own custom validators. - -# Using Angular Validators - -You can use angular validators in HTML template bindings, and in JavaScript: - -* Validators in HTML Template Bindings - -<pre> -<input ng:validator="validator_type:parameters" [...]> -</pre> - -* Validators in JavaScript - -<pre> -angular.validator.[validator_type](parameters) -</pre> - -The following example shows how to use the built-in angular integer validator: - -<doc:example> -<doc:source> - Change me: <input type="text" name="number" ng:validate="integer" value="123"> -</doc:source> -<doc:scenario> - it('should validate the default number string', function() { -   expect(element('input[name=number]').attr('class')). -      not().toMatch(/ng-validation-error/); - }); - it('should not validate "foo"', function() { -   input('number').enter('foo'); -   expect(element('input[name=number]').attr('class')). -      toMatch(/ng-validation-error/); - }); -</doc:scenario> -</doc:example> - -# Creating an Angular Validator - -To create a custom validator, you simply add your validator code as a method onto the -`angular.validator` object and provide input(s) for the validator function. Each input provided is -treated as an argument to the validator function.  Any additional inputs should be separated by -commas. - -The following bit of pseudo-code shows how to set up a custom validator: - -<pre> -angular.validator('your_validator', function(input [,additional params]) { -        [your validation code]; -        if ( [validation succeeds] ) { -                return false; -        } else { -                return true; // No error message specified -                          } -} -</pre> - -Note that this validator returns "true" when the user's input is incorrect, as in "Yes, it's true, -there was a problem with that input". If you prefer to provide more information when a validator -detects a problem with input, you can specify an error message in the validator that angular will -display when the user hovers over the input widget. - -To specify an error message, replace "`return true;`" with an error string, for example: - -      return "Must be a value between 1 and 5!"; - -Following is a sample UPS Tracking Number validator: - -<doc:example> -<doc:source> -<script> -angular.validator('upsTrackingNo', function(input, format) { -  var regexp = new RegExp("^" + format.replace(/9/g, '\\d') + "$"); -  return input.match(regexp)?"":"The format must match " + format; -}); -</script> -<input type="text" name="trackNo" size="40" -     ng:validate="upsTrackingNo:'1Z 999 999 99 9999 999 9'" -     value="1Z 123 456 78 9012 345 6"/> -</doc:source> -<doc:scenario> -it('should validate correct UPS tracking number', function() { - expect(element('input[name=trackNo]').attr('class')). -    not().toMatch(/ng-validation-error/); -}); - -it('should not validate in correct UPS tracking number', function() { - input('trackNo').enter('foo'); - expect(element('input[name=trackNo]').attr('class')). -    toMatch(/ng-validation-error/); -}); -</doc:scenario> -</doc:example> - -In this sample validator, we specify a regular expression against which to test the user's input. -Note that when the user's input matches `regexp`, the function returns "false" (""); otherwise it -returns the specified error message ("true"). - -Note: you can also access the current angular scope and DOM element objects in your validator -functions as follows: - -* `this` ===  The current angular scope. -* `this.$element` ===  The DOM element that contains the binding. This allows the filter to -manipulate the DOM in addition to transforming the input. - - -## Related Topics - -* {@link dev_guide.templates Angular Templates} - -## Related API - -* {@link api/angular.validator Validator API} diff --git a/docs/content/guide/index.ngdoc b/docs/content/guide/index.ngdoc index b2aab161..8d609afa 100644 --- a/docs/content/guide/index.ngdoc +++ b/docs/content/guide/index.ngdoc @@ -42,8 +42,7 @@ of the following documents before returning here to the Developer Guide:  ## {@link dev_guide.templates Angular Templates}  * {@link dev_guide.templates.filters Understanding Angular Filters} -* {@link dev_guide.templates.formatters Understanding Angular Formatters} -* {@link dev_guide.templates.validators Understanding Angular Validators} +* {@link dev_guide.forms Understanding Angular Forms}  ## {@link dev_guide.services Angular Services} diff --git a/docs/content/misc/started.ngdoc b/docs/content/misc/started.ngdoc index 3bf71cf1..591fb859 100644 --- a/docs/content/misc/started.ngdoc +++ b/docs/content/misc/started.ngdoc @@ -67,7 +67,7 @@ This example demonstrates angular's two-way data binding:  <doc:example>   <doc:source> -  Your name: <input type="text" name="yourname" value="World"/> +  Your name: <input type="text" ng:model="yourname" value="World"/>    <hr/>    Hello {{yourname}}!   </doc:source> diff --git a/docs/content/tutorial/step_03.ngdoc b/docs/content/tutorial/step_03.ngdoc index ec546956..89a1b0cb 100644 --- a/docs/content/tutorial/step_03.ngdoc +++ b/docs/content/tutorial/step_03.ngdoc @@ -32,7 +32,7 @@ We made no changes to the controller.  __`app/index.html`:__  <pre>  ... -   Fulltext Search: <input name="query"/> +   Fulltext Search: <input ng:model="query"/>    <ul class="phones">      <li ng:repeat="phone in phones.$filter(query)"> diff --git a/docs/content/tutorial/step_04.ngdoc b/docs/content/tutorial/step_04.ngdoc index 72aa26c9..d05a8e7c 100644 --- a/docs/content/tutorial/step_04.ngdoc +++ b/docs/content/tutorial/step_04.ngdoc @@ -27,11 +27,11 @@ __`app/index.html`:__  ...    <ul class="controls">      <li> -      Search: <input type="text" name="query"/> +      Search: <input type="text" ng:model="query"/>      </li>      <li>        Sort by: -      <select name="orderProp"> +      <select ng:model="orderProp">          <option value="name">Alphabetical</option>          <option value="age">Newest</option>        </select> diff --git a/docs/content/tutorial/step_07.ngdoc b/docs/content/tutorial/step_07.ngdoc index fa0c1e1f..eaf7f4ab 100644 --- a/docs/content/tutorial/step_07.ngdoc +++ b/docs/content/tutorial/step_07.ngdoc @@ -122,11 +122,11 @@ __`app/partials/phone-list.html`:__  <pre>  <ul class="predicates">    <li> -    Search: <input type="text" name="query"/> +    Search: <input type="text" ng:model="query"/>    </li>    <li>      Sort by: -    <select name="orderProp"> +    <select ng:model="orderProp">        <option value="name">Alphabetical</option>        <option value="age">Newest</option>      </select> diff --git a/docs/content/tutorial/step_09.ngdoc b/docs/content/tutorial/step_09.ngdoc index 80b10f65..7d8e3430 100644 --- a/docs/content/tutorial/step_09.ngdoc +++ b/docs/content/tutorial/step_09.ngdoc @@ -109,7 +109,7 @@ following bindings to `index.html`:  *  We can also create a model with an input element, and combine it with a filtered binding. Add  the following to index.html: -        <input name="userInput"> Uppercased: {{ userInput | uppercase }} +        <input ng:model="userInput"> Uppercased: {{ userInput | uppercase }}  # Summary diff --git a/docs/examples/settings.html b/docs/examples/settings.html index 2fa5dca8..74500b35 100644 --- a/docs/examples/settings.html +++ b/docs/examples/settings.html @@ -1,13 +1,13 @@  <label>Name:</label> -<input type="text" name="form.name" ng:required> +<input type="text" ng:model="form.name" required>  <div ng:repeat="contact in form.contacts"> -  <select name="contact.type"> +  <select ng:model="contact.type">      <option>url</option>      <option>email</option>      <option>phone</option>    </select> -  <input type="text" name="contact.url"> +  <input type="text" ng:model="contact.url">    [ <a href="" ng:click="form.contacts.$remove(contact)">X</a> ]  </div>  <div> @@ -15,4 +15,4 @@  </div>  <button ng:click="cancel()">Cancel</button> -<button ng:click="save()">Save</button>
\ No newline at end of file +<button ng:click="save()">Save</button> diff --git a/docs/img/form_data_flow.png b/docs/img/form_data_flow.pngBinary files differ new file mode 100644 index 00000000..60e947a5 --- /dev/null +++ b/docs/img/form_data_flow.png diff --git a/docs/spec/ngdocSpec.js b/docs/spec/ngdocSpec.js index 106fd22b..2afcc3d4 100644 --- a/docs/spec/ngdocSpec.js +++ b/docs/spec/ngdocSpec.js @@ -194,12 +194,12 @@ describe('ngdoc', function(){      it('should ignore nested doc widgets', function() {        expect(new Doc().markdown(          'before<doc:tutorial-instructions>\n' + -          '<doc:tutorial-instruction id="git-mac" name="Git on Mac/Linux">' + +          '<doc:tutorial-instruction id="git-mac" ng:model="Git on Mac/Linux">' +            '\ngit bla bla\n</doc:tutorial-instruction>\n' +          '</doc:tutorial-instructions>')).toEqual(          '<p>before</p><doc:tutorial-instructions>\n' + -          '<doc:tutorial-instruction id="git-mac" name="Git on Mac/Linux">\n' + +          '<doc:tutorial-instruction id="git-mac" ng:model="Git on Mac/Linux">\n' +            'git bla bla\n' +            '</doc:tutorial-instruction>\n' +          '</doc:tutorial-instructions>'); @@ -543,38 +543,6 @@ describe('ngdoc', function(){        });      }); -    describe('validator', function(){ -      it('should format', function(){ -        var doc = new Doc({ -          ngdoc:'validator', -          shortName:'myValidator', -          param: [ -            {name:'a'}, -            {name:'b'} -          ] -        }); -        doc.html_usage_validator(dom); -        expect(dom).toContain('ng:validate="myValidator:b"'); -        expect(dom).toContain('angular.validator.myValidator(a, b)'); -      }); -    }); - -    describe('formatter', function(){ -      it('should format', function(){ -        var doc = new Doc({ -          ngdoc:'formatter', -          shortName:'myFormatter', -          param: [ -            {name:'a'}, -          ] -        }); -        doc.html_usage_formatter(dom); -        expect(dom).toContain('ng:format="myFormatter:a"'); -        expect(dom).toContain('var userInputString = angular.formatter.myFormatter.format(modelValue, a);'); -        expect(dom).toContain('var modelValue = angular.formatter.myFormatter.parse(userInputString, a);'); -      }); -    }); -      describe('property', function(){        it('should format', function(){          var doc = new Doc({ diff --git a/docs/src/ngdoc.js b/docs/src/ngdoc.js index 8a20e64a..1a4f5d25 100644 --- a/docs/src/ngdoc.js +++ b/docs/src/ngdoc.js @@ -13,6 +13,11 @@ exports.scenarios = scenarios;  exports.merge = merge;  exports.Doc = Doc; +var BOOLEAN_ATTR = {}; +['multiple', 'selected', 'checked', 'disabled', 'readOnly', 'required'].forEach(function(value, key) { +  BOOLEAN_ATTR[value] = true; +}); +  //////////////////////////////////////////////////////////  function Doc(text, file, line) {    if (typeof text == 'object') { @@ -385,69 +390,21 @@ Doc.prototype = {      });    }, -  html_usage_formatter: function(dom){ -    var self = this; -    dom.h('Usage', function(){ -      dom.h('In HTML Template Binding', function(){ -        dom.code(function(){ -          if (self.inputType=='select') -            dom.text('<select name="bindExpression"'); -          else -            dom.text('<input type="text" name="bindExpression"'); -          dom.text(' ng:format="'); -          dom.text(self.shortName); -          self.parameters(dom, ':', false, true); -          dom.text('">'); -        }); -      }); - -      dom.h('In JavaScript', function(){ -        dom.code(function(){ -          dom.text('var userInputString = angular.formatter.'); -          dom.text(self.shortName); -          dom.text('.format(modelValue'); -          self.parameters(dom, ', ', false, true); -          dom.text(');'); -          dom.text('\n'); -          dom.text('var modelValue = angular.formatter.'); -          dom.text(self.shortName); -          dom.text('.parse(userInputString'); -          self.parameters(dom, ', ', false, true); -          dom.text(');'); -        }); -      }); - -      self.html_usage_parameters(dom); -      self.html_usage_this(dom); -      self.html_usage_returns(dom); -    }); -  }, - -  html_usage_validator: function(dom){ +  html_usage_inputType: function(dom){      var self = this;      dom.h('Usage', function(){ -      dom.h('In HTML Template Binding', function(){ -        dom.code(function(){ -          dom.text('<input type="text" ng:validate="'); -          dom.text(self.shortName); -          self.parameters(dom, ':', true); -          dom.text('"/>'); -        }); -      }); - -      dom.h('In JavaScript', function(){ -        dom.code(function(){ -          dom.text('angular.validator.'); -          dom.text(self.shortName); -          dom.text('('); -          self.parameters(dom, ', '); -          dom.text(')'); +      dom.code(function(){ +        dom.text('<input type="' + self.shortName + '"'); +        (self.param||[]).forEach(function(param){ +          dom.text('\n      '); +          dom.text(param.optional ? ' [' : ' '); +          dom.text(param.name); +          dom.text(BOOLEAN_ATTR[param.name] ? '' : '="..."'); +          dom.text(param.optional ? ']' : '');          }); +        dom.text('>');        }); -        self.html_usage_parameters(dom); -      self.html_usage_this(dom); -      self.html_usage_returns(dom);      });    }, @@ -473,11 +430,11 @@ Doc.prototype = {              dom.text('<');              dom.text(self.shortName);              (self.param||[]).forEach(function(param){ -              if (param.optional) { -                dom.text(' [' + param.name + '="..."]'); -              } else { -                dom.text(' ' + param.name + '="..."'); -              } +              dom.text('\n      '); +              dom.text(param.optional ? ' [' : ' '); +              dom.text(param.name); +              dom.text(BOOLEAN_ATTR[param.name] ? '' : '="..."'); +              dom.text(param.optional ? ']' : '');              });              dom.text('></');              dom.text(self.shortName); @@ -533,12 +490,18 @@ Doc.prototype = {      dom.h('Events', this.events, function(event){      dom.h(event.shortName, event, function(){          dom.html(event.description); -        dom.tag('div', {class:'inline'}, function(){ -          dom.h('Type:', event.type); -        }); -        dom.tag('div', {class:'inline'}, function(){ -          dom.h('Target:', event.target); -        }); +        if (event.type == 'listen') { +          dom.tag('div', {class:'inline'}, function(){ +            dom.h('Listen on:', event.target); +          }); +        } else { +          dom.tag('div', {class:'inline'}, function(){ +            dom.h('Type:', event.type); +          }); +          dom.tag('div', {class:'inline'}, function(){ +            dom.h('Target:', event.target); +          }); +        }          event.html_usage_parameters(dom);          self.html_usage_this(dom); @@ -632,10 +595,9 @@ var KEYWORD_PRIORITY = {    '.angular.Object': 7,    '.angular.directive': 7,    '.angular.filter': 7, -  '.angular.formatter': 7,    '.angular.scope': 7,    '.angular.service': 7, -  '.angular.validator': 7, +  '.angular.inputType': 7,    '.angular.widget': 7,    '.angular.mock': 8,    '.dev_guide.overview': 1, diff --git a/docs/src/templates/doc_widgets.js b/docs/src/templates/doc_widgets.js index 17284a1d..72f59f74 100644 --- a/docs/src/templates/doc_widgets.js +++ b/docs/src/templates/doc_widgets.js @@ -81,14 +81,16 @@            fiddleSrc = fiddleSrc.replace(new RegExp('^\\s{' + stripIndent + '}', 'gm'), '');            return '<form class="jsfiddle" method="post" action="' + fiddleUrl + '" target="_blank">' + -                    '<textarea name="css">' + +                    '<textarea ng:model="css">' + +                      '.ng-invalid { border: 1px solid red; } \n' +                        'body { font-family: Arial,Helvetica,sans-serif; }\n' +                        'body, td, th { font-size: 14px; margin: 0; }\n' +                        'table { border-collapse: separate; border-spacing: 2px; display: table; margin-bottom: 0; margin-top: 0; -moz-box-sizing: border-box; text-indent: 0; }\n' +                        'a:link, a:visited, a:hover { color: #5D6DB6; text-decoration: none; }\n' + +                      '.error { color: red; }\n' +                      '</textarea>' + -                    '<input type="text" name="title" value="AngularJS Live Example">' + -                    '<textarea name="html">' + +                    '<input type="text" ng:model="title" value="AngularJS Live Example">' + +                    '<textarea ng:model="html">' +                        '<script src="' + angularJsUrl + '" ng:autobind></script>\n\n' +                        '<!-- AngularJS Example Code: -->\n\n' +                        fiddleSrc + diff --git a/docs/src/templates/docs.css b/docs/src/templates/docs.css index 99ea7454..c38252ff 100644 --- a/docs/src/templates/docs.css +++ b/docs/src/templates/docs.css @@ -49,6 +49,10 @@ li {    margin: 0.3em 0 0.3em 0;  } +.ng-invalid { +  border: 1px solid red; +} +  /*----- Upgrade IE Prompt -----*/ @@ -426,7 +430,7 @@ li {  }  table { -	border-collapse: collapse; +  border-collapse: collapse;  }  td { @@ -448,7 +452,7 @@ td.empty-corner-lt {  .html5-hashbang-example {    height: 255px;    margin-left: -40px; -  padding-left: 30px;  +  padding-left: 30px;  }  .html5-hashbang-example div { @@ -459,3 +463,7 @@ td.empty-corner-lt {  .html5-hashbang-example div input {    width: 360px;  } + +.error { +   color: red; +} diff --git a/docs/src/templates/index.html b/docs/src/templates/index.html index a2def7a6..87c27ac0 100644 --- a/docs/src/templates/index.html +++ b/docs/src/templates/index.html @@ -99,7 +99,7 @@      </ul>      <div id="sidebar"> -      <input type="text" name="search" id="search-box" placeholder="search the docs" +      <input type="text" ng:model="search" id="search-box" placeholder="search the docs"               tabindex="1" accesskey="s">        <ul id="content-list" ng:class="sectionId" ng:cloak> | 
