diff options
| author | Misko Hevery | 2011-09-08 13:56:29 -0700 | 
|---|---|---|
| committer | Igor Minar | 2011-10-11 11:01:45 -0700 | 
| commit | 4f78fd692c0ec51241476e6be9a4df06cd62fdd6 (patch) | |
| tree | 91f70bb89b9c095126fbc093f51cedbac5cb0c78 /src/widget/input.js | |
| parent | df6d2ba3266de405ad6c2f270f24569355706e76 (diff) | |
| download | angular.js-4f78fd692c0ec51241476e6be9a4df06cd62fdd6.tar.bz2 | |
feat(forms): new and improved forms
Diffstat (limited to 'src/widget/input.js')
| -rw-r--r-- | src/widget/input.js | 773 | 
1 files changed, 773 insertions, 0 deletions
| diff --git a/src/widget/input.js b/src/widget/input.js new file mode 100644 index 00000000..f82027f4 --- /dev/null +++ b/src/widget/input.js @@ -0,0 +1,773 @@ +'use strict'; + + +var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/; +var EMAIL_REGEXP = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/; +var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/; +var INTEGER_REGEXP = /^\s*(\-|\+)?\d+\s*$/; + +/** + * @workInProgress + * @ngdoc inputType + * @name angular.inputType.text + * + * @description + * Standard HTML text input with angular data binding. + * + * @param {string} ng:model Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the widgets is published. + * @param {string=} required Sets `REQUIRED` validation error key if the value is not entered. + * @param {string=} 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> +         function Ctrl(){ +           this.text = 'guest'; +           this.word = /^\w*$/; +         } +       </script> +       <div ng:controller="Ctrl"> +         <form name="myForm"> +           Single word: <input type="text" name="input" ng:model="text" +                               ng:pattern="word" required> +           <span class="error" ng:show="myForm.input.$error.REQUIRED"> +             Required!</span> +           <span class="error" ng:show="myForm.input.$error.PATTERN"> +             Single word only!</span> +         </form> +         <tt>text = {{text}}</tt><br/> +         <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> +         <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> +         <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> +         <tt>myForm.$error.REQUIRED = {{!!myForm.$error.REQUIRED}}</tt><br/> +       </div> +      </doc:source> +      <doc:scenario> +        it('should initialize to model', function() { +          expect(binding('text')).toEqual('guest'); +          expect(binding('myForm.input.$valid')).toEqual('true'); +        }); + +        it('should be invalid if empty', function() { +          input('text').enter(''); +          expect(binding('text')).toEqual(''); +          expect(binding('myForm.input.$valid')).toEqual('false'); +        }); + +        it('should be invalid if multi word', function() { +          input('text').enter('hello world'); +          expect(binding('myForm.input.$valid')).toEqual('false'); +        }); +      </doc:scenario> +    </doc:example> + */ + + +/** + * @workInProgress + * @ngdoc inputType + * @name angular.inputType.email + * + * @description + * Text input with email validation. Sets the `EMAIL` validation error key if not a valid email + * address. + * + * @param {string} ng:model Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the widgets is published. + * @param {string=} required Sets `REQUIRED` validation error key if the value is not entered. + * @param {string=} 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> +         function Ctrl(){ +           this.text = 'me@example.com'; +         } +       </script> +       <div ng:controller="Ctrl"> +         <form name="myForm"> +           Email: <input type="email" name="input" ng:model="text" required> +           <span class="error" ng:show="myForm.input.$error.REQUIRED"> +             Required!</span> +           <span class="error" ng:show="myForm.input.$error.EMAIL"> +             Not valid email!</span> +         </form> +         <tt>text = {{text}}</tt><br/> +         <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> +         <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> +         <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> +         <tt>myForm.$error.REQUIRED = {{!!myForm.$error.REQUIRED}}</tt><br/> +         <tt>myForm.$error.EMAIL = {{!!myForm.$error.EMAIL}}</tt><br/> +       </div> +      </doc:source> +      <doc:scenario> +        it('should initialize to model', function() { +          expect(binding('text')).toEqual('me@example.com'); +          expect(binding('myForm.input.$valid')).toEqual('true'); +        }); + +        it('should be invalid if empty', function() { +          input('text').enter(''); +          expect(binding('text')).toEqual(''); +          expect(binding('myForm.input.$valid')).toEqual('false'); +        }); + +        it('should be invalid if not email', function() { +          input('text').enter('xxx'); +          expect(binding('text')).toEqual('xxx'); +          expect(binding('myForm.input.$valid')).toEqual('false'); +        }); +      </doc:scenario> +    </doc:example> + */ +angularInputType('email', function() { +  var widget = this; +  this.$on('$validate', function(event){ +    var value = widget.$viewValue; +    widget.$emit(!value || value.match(EMAIL_REGEXP) ? "$valid" : "$invalid", "EMAIL"); +  }); +}); + +/** + * @workInProgress + * @ngdoc inputType + * @name angular.inputType.url + * + * @description + * Text input with URL validation. Sets the `URL` validation error key if the content is not a + * valid URL. + * + * @param {string} ng:model Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the widgets is published. + * @param {string=} required Sets `REQUIRED` validation error key if the value is not entered. + * @param {string=} 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> +         function Ctrl(){ +           this.text = 'http://google.com'; +         } +       </script> +       <div ng:controller="Ctrl"> +         <form name="myForm"> +           URL: <input type="url" name="input" ng:model="text" required> +           <span class="error" ng:show="myForm.input.$error.REQUIRED"> +             Required!</span> +           <span class="error" ng:show="myForm.input.$error.url"> +             Not valid url!</span> +         </form> +         <tt>text = {{text}}</tt><br/> +         <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> +         <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> +         <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> +         <tt>myForm.$error.REQUIRED = {{!!myForm.$error.REQUIRED}}</tt><br/> +         <tt>myForm.$error.url = {{!!myForm.$error.url}}</tt><br/> +       </div> +      </doc:source> +      <doc:scenario> +        it('should initialize to model', function() { +          expect(binding('text')).toEqual('http://google.com'); +          expect(binding('myForm.input.$valid')).toEqual('true'); +        }); + +        it('should be invalid if empty', function() { +          input('text').enter(''); +          expect(binding('text')).toEqual(''); +          expect(binding('myForm.input.$valid')).toEqual('false'); +        }); + +        it('should be invalid if not url', function() { +          input('text').enter('xxx'); +          expect(binding('text')).toEqual('xxx'); +          expect(binding('myForm.input.$valid')).toEqual('false'); +        }); +      </doc:scenario> +    </doc:example> + */ +angularInputType('url', function() { +  var widget = this; +  this.$on('$validate', function(event){ +    var value = widget.$viewValue; +    widget.$emit(!value || value.match(URL_REGEXP) ? "$valid" : "$invalid", "URL"); +  }); +}); + +/** + * @workInProgress + * @ngdoc inputType + * @name angular.inputType.list + * + * @description + * Text input that converts between comma-seperated string into an array of strings. + * + * @param {string} ng:model Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the widgets is published. + * @param {string=} required Sets `REQUIRED` validation error key if the value is not entered. + * @param {string=} 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> +         function Ctrl(){ +           this.names = ['igor', 'misko', 'vojta']; +         } +       </script> +       <div ng:controller="Ctrl"> +         <form name="myForm"> +           List: <input type="list" name="input" ng:model="names" required> +           <span class="error" ng:show="myForm.list.$error.REQUIRED"> +             Required!</span> +         </form> +         <tt>names = {{names}}</tt><br/> +         <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> +         <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> +         <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> +         <tt>myForm.$error.REQUIRED = {{!!myForm.$error.REQUIRED}}</tt><br/> +       </div> +      </doc:source> +      <doc:scenario> +        it('should initialize to model', function() { +          expect(binding('names')).toEqual('["igor","misko","vojta"]'); +          expect(binding('myForm.input.$valid')).toEqual('true'); +        }); + +        it('should be invalid if empty', function() { +          input('names').enter(''); +          expect(binding('names')).toEqual('[]'); +          expect(binding('myForm.input.$valid')).toEqual('false'); +        }); +      </doc:scenario> +    </doc:example> + */ +angularInputType('list', function() { +  function parse(viewValue) { +    var list = []; +    forEach(viewValue.split(/\s*,\s*/), function(value){ +      if (value) list.push(trim(value)); +    }); +    return list; +  } +  this.$parseView = function() { +    isString(this.$viewValue) && (this.$modelValue = parse(this.$viewValue)); +  }; +  this.$parseModel = function() { +    var modelValue = this.$modelValue; +    if (isArray(modelValue) +        && (!isString(this.$viewValue) || !equals(parse(this.$viewValue), modelValue))) { +      this.$viewValue =  modelValue.join(', '); +    } +  }; +}); + +/** + * @workInProgress + * @ngdoc inputType + * @name angular.inputType.number + * + * @description + * Text input with number validation and transformation. Sets the `NUMBER` validation + * error if not a valid number. + * + * @param {string} ng:model Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the widgets is published. + * @param {string=} min Sets the `MIN` validation error key if the value entered is less then `min`. + * @param {string=} max Sets the `MAX` validation error key if the value entered is greater then `min`. + * @param {string=} required Sets `REQUIRED` validation error key if the value is not entered. + * @param {string=} 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> +         function Ctrl(){ +           this.value = 12; +         } +       </script> +       <div ng:controller="Ctrl"> +         <form name="myForm"> +           Number: <input type="number" name="input" ng:model="value" +                          min="0" max="99" required> +           <span class="error" ng:show="myForm.list.$error.REQUIRED"> +             Required!</span> +           <span class="error" ng:show="myForm.list.$error.NUMBER"> +             Not valid number!</span> +         </form> +         <tt>value = {{value}}</tt><br/> +         <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> +         <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> +         <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> +         <tt>myForm.$error.REQUIRED = {{!!myForm.$error.REQUIRED}}</tt><br/> +       </div> +      </doc:source> +      <doc:scenario> +        it('should initialize to model', function() { +         expect(binding('value')).toEqual('12'); +         expect(binding('myForm.input.$valid')).toEqual('true'); +        }); + +        it('should be invalid if empty', function() { +         input('value').enter(''); +         expect(binding('value')).toEqual(''); +         expect(binding('myForm.input.$valid')).toEqual('false'); +        }); + +        it('should be invalid if over max', function() { +         input('value').enter('123'); +         expect(binding('value')).toEqual('123'); +         expect(binding('myForm.input.$valid')).toEqual('false'); +        }); +      </doc:scenario> +    </doc:example> + */ +angularInputType('number', numericRegexpInputType(NUMBER_REGEXP, 'NUMBER')); + +/** + * @workInProgress + * @ngdoc inputType + * @name angular.inputType.integer + * + * @description + * Text input with integer validation and transformation. Sets the `INTEGER` + * validation error key if not a valid integer. + * + * @param {string} ng:model Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the widgets is published. + * @param {string=} min Sets the `MIN` validation error key if the value entered is less then `min`. + * @param {string=} max Sets the `MAX` validation error key if the value entered is greater then `min`. + * @param {string=} required Sets `REQUIRED` validation error key if the value is not entered. + * @param {string=} 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> +         function Ctrl(){ +           this.value = 12; +         } +       </script> +       <div ng:controller="Ctrl"> +         <form name="myForm"> +           Integer: <input type="integer" name="input" ng:model="value" +                           min="0" max="99" required> +           <span class="error" ng:show="myForm.list.$error.REQUIRED"> +             Required!</span> +           <span class="error" ng:show="myForm.list.$error.INTEGER"> +             Not valid integer!</span> +         </form> +         <tt>value = {{value}}</tt><br/> +         <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> +         <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> +         <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> +         <tt>myForm.$error.REQUIRED = {{!!myForm.$error.REQUIRED}}</tt><br/> +       </div> +      </doc:source> +      <doc:scenario> +        it('should initialize to model', function() { +          expect(binding('value')).toEqual('12'); +          expect(binding('myForm.input.$valid')).toEqual('true'); +        }); + +        it('should be invalid if empty', function() { +          input('value').enter('1.2'); +          expect(binding('value')).toEqual('12'); +          expect(binding('myForm.input.$valid')).toEqual('false'); +        }); + +        it('should be invalid if over max', function() { +          input('value').enter('123'); +          expect(binding('value')).toEqual('123'); +          expect(binding('myForm.input.$valid')).toEqual('false'); +        }); +      </doc:scenario> +    </doc:example> + */ +angularInputType('integer', numericRegexpInputType(INTEGER_REGEXP, 'INTEGER')); + +/** + * @workInProgress + * @ngdoc inputType + * @name angular.inputType.checkbox + * + * @description + * HTML checkbox. + * + * @param {string} ng:model Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the widgets is published. + * @param {string=} true-value The value to which the expression should be set when selected. + * @param {string=} false-value The value to which the expression should be set when not selected. + * + * @example +    <doc:example> +      <doc:source> +       <script> +         function Ctrl(){ +           this.value1 = true; +           this.value2 = 'YES' +         } +       </script> +       <div ng:controller="Ctrl"> +         <form name="myForm"> +           Value1: <input type="checkbox" ng:model="value1"> <br/> +           Value2: <input type="checkbox" ng:model="value2" +                          true-value="YES" false-value="NO"> <br/> +         </form> +         <tt>value1 = {{value1}}</tt><br/> +         <tt>value2 = {{value2}}</tt><br/> +       </div> +      </doc:source> +      <doc:scenario> +        it('should change state', function() { +          expect(binding('value1')).toEqual('true'); +          expect(binding('value2')).toEqual('YES'); + +          input('value1').check(); +          input('value2').check(); +          expect(binding('value1')).toEqual('false'); +          expect(binding('value2')).toEqual('NO'); +        }); +      </doc:scenario> +    </doc:example> + */ +angularInputType('checkbox', function (inputElement) { +  var widget = this, +      trueValue = inputElement.attr('true-value'), +      falseValue = inputElement.attr('false-value'); + +  if (!isString(trueValue)) trueValue = true; +  if (!isString(falseValue)) falseValue = false; + +  inputElement.bind('click', function() { +    widget.$apply(function() { +      widget.$emit('$viewChange', inputElement[0].checked); +    }); +  }); + +  widget.$render = function() { +    inputElement[0].checked = widget.$viewValue; +  }; + +  widget.$parseModel = function() { +    widget.$viewValue = this.$modelValue === trueValue; +  }; + +  widget.$parseView = function() { +    widget.$modelValue = widget.$viewValue ? trueValue : falseValue; +  }; + +}); + +/** + * @workInProgress + * @ngdoc inputType + * @name angular.inputType.radio + * + * @description + * HTML radio. + * + * @param {string} ng:model Assignable angular expression to data-bind to. + * @param {string} value The value to which the expression should be set when selected. + * @param {string=} name Property name of the form under which the widgets is published. + * + * @example +    <doc:example> +      <doc:source> +       <script> +         function Ctrl(){ +           this.color = 'blue'; +         } +       </script> +       <div ng:controller="Ctrl"> +         <form name="myForm"> +           <input type="radio" ng:model="color" value="red">  Red <br/> +           <input type="radio" ng:model="color" value="green"> Green <br/> +           <input type="radio" ng:model="color" value="blue"> Blue <br/> +         </form> +         <tt>color = {{color}}</tt><br/> +       </div> +      </doc:source> +      <doc:scenario> +        it('should change state', function() { +          expect(binding('color')).toEqual('blue'); + +          input('color').select('red'); +          expect(binding('color')).toEqual('red'); +        }); +      </doc:scenario> +    </doc:example> + */ +angularInputType('radio', function(inputElement) { +  var widget = this, +      value = inputElement.attr('value'); + +  //correct the name +  inputElement.attr('name', widget.$id + '@' + inputElement.attr('name')); +  inputElement.bind('click', function() { +    widget.$apply(function() { +      if (inputElement[0].checked) { +        widget.$emit('$viewChange', value); +      } +    }); +  }); + +  widget.$render = function() { +    inputElement[0].checked = value == widget.$viewValue; +  }; + +  if (inputElement[0].checked) { +    widget.$viewValue = value; +  } +}); + + +function numericRegexpInputType(regexp, error) { +  return function(inputElement) { +    var widget = this, +        min = 1 * (inputElement.attr('min') || Number.MIN_VALUE), +        max = 1 * (inputElement.attr('max') || Number.MAX_VALUE); + +    widget.$on('$validate', function(event){ +      var value = widget.$viewValue, +          filled = value && trim(value) != '', +          valid = isString(value) && value.match(regexp); + +      widget.$emit(!filled || valid ? "$valid" : "$invalid", error); +      filled && (value = 1 * value); +      widget.$emit(valid && value < min ? "$invalid" : "$valid", "MIN"); +      widget.$emit(valid && value > max ? "$invalid" : "$valid", "MAX"); +    }); + +    widget.$parseView = function() { +      if (widget.$viewValue.match(regexp)) { +        widget.$modelValue = 1 * widget.$viewValue; +      } else if (widget.$viewValue == '') { +        widget.$modelValue = null; +      } +    }; + +    widget.$parseModel = function() { +      if (isNumber(widget.$modelValue)) { +        widget.$viewValue = '' + widget.$modelValue; +      } +    }; +  }; +} + + +var HTML5_INPUTS_TYPES =  makeMap( +        "search,tel,url,email,datetime,date,month,week,time,datetime-local,number,range,color," + +        "radio,checkbox,text,button,submit,reset,hidden"); + + +/** + * @workInProgress + * @ngdoc widget + * @name angular.widget.input + * + * @description + * HTML input element widget with angular data-binding. Input widget follows HTML5 input types + * and polyfills the HTML5 validation behavior for older browsers. + * + * The {@link angular.inputType custom angular.inputType}s provides a short hand for declaring new + * inputs. This is a shart hand for text-box based inputs, and there is no need to go through the + * full {@link angular.service.$formFactory $formFactory} widget lifecycle. + * + * + * @param {string} type Widget types as defined by {@link angular.inputType}. If the + *    type is in the format of `@ScopeType` then `ScopeType` is loaded from the + *    current scope, allowing quick definition of type. + * @param {string} ng:model Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the widgets is published. + * @param {string=} required Sets `REQUIRED` validation error key if the value is not entered. + * @param {string=} 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> +         function Ctrl(){ +           this.text = 'guest'; +         } +       </script> +       <div ng:controller="Ctrl"> +         <form name="myForm"> +           text: <input type="text" name="input" ng:model="text" required> +           <span class="error" ng:show="myForm.input.$error.REQUIRED"> +             Required!</span> +         </form> +         <tt>text = {{text}}</tt><br/> +         <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> +         <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> +         <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> +         <tt>myForm.$error.REQUIRED = {{!!myForm.$error.REQUIRED}}</tt><br/> +       </div> +      </doc:source> +      <doc:scenario> +        it('should initialize to model', function() { +          expect(binding('text')).toEqual('guest'); +          expect(binding('myForm.input.$valid')).toEqual('true'); +        }); + +        it('should be invalid if empty', function() { +          input('text').enter(''); +          expect(binding('text')).toEqual(''); +          expect(binding('myForm.input.$valid')).toEqual('false'); +        }); +      </doc:scenario> +    </doc:example> + */ +angularWidget('input', function (inputElement){ +  this.directives(true); +  this.descend(true); +  var modelExp = inputElement.attr('ng:model'); +  return modelExp && +    annotate('$defer', '$formFactory', function($defer, $formFactory, inputElement){ +      var form = $formFactory.forElement(inputElement), +          // We have to use .getAttribute, since jQuery tries to be smart and use the +          // type property. Trouble is some browser change unknown to text. +          type = inputElement[0].getAttribute('type') || 'text', +          TypeController, +          modelScope = this, +          patternMatch, widget, +          pattern = trim(inputElement.attr('ng:pattern')), +          loadFromScope = type.match(/^\s*\@\s*(.*)/); + + +       if (!pattern) { +         patternMatch = valueFn(true); +       } else { +         if (pattern.match(/^\/(.*)\/$/)) { +           pattern = new RegExp(pattern.substring(1, pattern.length - 2)); +           patternMatch = function(value) { +             return pattern.test(value); +           } +         } else { +           patternMatch = function(value) { +             var patternObj = modelScope.$eval(pattern); +             if (!patternObj || !patternObj.test) { +               throw new Error('Expected ' + pattern + ' to be a RegExp but was ' + patternObj); +             } +             return patternObj.test(value); +           } +         } +       } + +      type = lowercase(type); +      TypeController = (loadFromScope +              ? (assertArgFn(this.$eval(loadFromScope[1]), loadFromScope[1])).$unboundFn +              : angularInputType(type)) || noop; + +      if (!HTML5_INPUTS_TYPES[type]) { +        try { +          // jquery will not let you so we have to go to bare metal +          inputElement[0].setAttribute('type', 'text'); +        } catch(e){ +          // also turns out that ie8 will not allow changing of types, but since it is not +          // html5 anyway we can ignore the error. +        } +      } + +      !TypeController.$inject && (TypeController.$inject = []); +      widget = form.$createWidget({ +          scope: modelScope, +          model: modelExp, +          onChange: inputElement.attr('ng:change'), +          alias: inputElement.attr('name'), +          controller: TypeController, +          controllerArgs: [inputElement]}); + +      widget.$pattern = +      watchElementProperty(this, widget, 'required', inputElement); +      watchElementProperty(this, widget, 'readonly', inputElement); +      watchElementProperty(this, widget, 'disabled', inputElement); + + +      widget.$pristine = !(widget.$dirty = false); + +      widget.$on('$validate', function(event) { +        var $viewValue = trim(widget.$viewValue); +        var inValid = widget.$required && !$viewValue; +        var missMatch = $viewValue && !patternMatch($viewValue); +        if (widget.$error.REQUIRED != inValid){ +          widget.$emit(inValid ? '$invalid' : '$valid', 'REQUIRED'); +        } +        if (widget.$error.PATTERN != missMatch){ +          widget.$emit(missMatch ? '$invalid' : '$valid', 'PATTERN'); +        } +      }); + +      forEach(['valid', 'invalid', 'pristine', 'dirty'], function (name) { +        widget.$watch('$' + name, function(scope, value) { +            inputElement[value ? 'addClass' : 'removeClass']('ng-' + name); +          } +        ); +      }); + +      inputElement.bind('$destroy', function() { +        widget.$destroy(); +      }); + +      if (type != 'checkbox' && type != 'radio') { +        // TODO (misko): checkbox / radio does not really belong here, but until we can do +        // widget registration with CSS, we are hacking it this way. +        widget.$render = function() { +          inputElement.val(widget.$viewValue || ''); +        }; + +        inputElement.bind('keydown change', function(event){ +          var key = event.keyCode; +          if (/*command*/   key != 91 && +              /*modifiers*/ !(15 < key && key < 19) && +              /*arrow*/     !(37 < key && key < 40)) { +            $defer(function() { +              widget.$dirty = !(widget.$pristine = false); +              var value = trim(inputElement.val()); +              if (widget.$viewValue !== value ) { +                widget.$emit('$viewChange', value); +              } +            }); +          } +        }); +      } +    }); + +}); + +angularWidget('textarea', angularWidget('input')); + + +function watchElementProperty(modelScope, widget, name, element) { +  var bindAttr = fromJson(element.attr('ng:bind-attr') || '{}'), +      match = /\s*{{(.*)}}\s*/.exec(bindAttr[name]); +  widget['$' + name] = +    // some browsers return true some '' when required is set without value. +    isString(element.prop(name)) || !!element.prop(name) || +    // this is needed for ie9, since it will treat boolean attributes as false +    !!element[0].attributes[name]; +  if (bindAttr[name] && match) { +    modelScope.$watch(match[1], function(scope, value){ +      widget['$' + name] = !!value; +      widget.$emit('$validate'); +    }); +  } +} + | 
