diff options
Diffstat (limited to 'src/widget/input.js')
| -rw-r--r-- | src/widget/input.js | 1007 | 
1 files changed, 581 insertions, 426 deletions
diff --git a/src/widget/input.js b/src/widget/input.js index 05390b38..6c95327c 100644 --- a/src/widget/input.js +++ b/src/widget/input.js @@ -4,7 +4,6 @@  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*$/;  /** @@ -36,37 +35,36 @@ var INTEGER_REGEXP = /^\s*(\-|\+)?\d+\s*$/;             $scope.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> +       <form name="myForm" ng:controller="Ctrl"> +         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> +           <tt>text = {{text}}</tt><br/> -         <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> -         <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> -         <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> -         <tt>myForm.$error.REQUIRED = {{!!myForm.$error.REQUIRED}}</tt><br/> -       </div> +         <tt>myForm.input.valid = {{myForm.input.valid}}</tt><br/> +         <tt>myForm.input.error = {{myForm.input.error}}</tt><br/> +         <tt>myForm.valid = {{myForm.valid}}</tt><br/> +         <tt>myForm.error.REQUIRED = {{!!myForm.error.REQUIRED}}</tt><br/> +        </form>        </doc:source>        <doc:scenario>          it('should initialize to model', function() {            expect(binding('text')).toEqual('guest'); -          expect(binding('myForm.input.$valid')).toEqual('true'); +          expect(binding('myForm.input.valid')).toEqual('true');          });          it('should be invalid if empty', function() {            input('text').enter('');            expect(binding('text')).toEqual(''); -          expect(binding('myForm.input.$valid')).toEqual('false'); +          expect(binding('myForm.input.valid')).toEqual('false');          });          it('should be invalid if multi word', function() {            input('text').enter('hello world'); -          expect(binding('myForm.input.$valid')).toEqual('false'); +          expect(binding('myForm.input.valid')).toEqual('false');          });        </doc:scenario>      </doc:example> @@ -100,48 +98,39 @@ var INTEGER_REGEXP = /^\s*(\-|\+)?\d+\s*$/;             $scope.text = 'me@example.com';           }         </script> -       <div ng:controller="Ctrl"> -         <form name="myForm"> +         <form name="myForm" ng:controller="Ctrl">             Email: <input type="email" name="input" ng:model="text" required> -           <span class="error" ng:show="myForm.input.$error.REQUIRED"> +           <span class="error" ng:show="myForm.input.error.REQUIRED">               Required!</span> -           <span class="error" ng:show="myForm.input.$error.EMAIL"> +           <span class="error" ng:show="myForm.input.error.EMAIL">               Not valid email!</span> +           <tt>text = {{text}}</tt><br/> +           <tt>myForm.input.valid = {{myForm.input.valid}}</tt><br/> +           <tt>myForm.input.error = {{myForm.input.error}}</tt><br/> +           <tt>myForm.valid = {{myForm.valid}}</tt><br/> +           <tt>myForm.error.REQUIRED = {{!!myForm.error.REQUIRED}}</tt><br/> +           <tt>myForm.error.EMAIL = {{!!myForm.error.EMAIL}}</tt><br/>           </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'); +          expect(binding('myForm.input.valid')).toEqual('true');          });          it('should be invalid if empty', function() {            input('text').enter('');            expect(binding('text')).toEqual(''); -          expect(binding('myForm.input.$valid')).toEqual('false'); +          expect(binding('myForm.input.valid')).toEqual('false');          });          it('should be invalid if not email', function() {            input('text').enter('xxx'); -          expect(binding('text')).toEqual('xxx'); -          expect(binding('myForm.input.$valid')).toEqual('false'); +          expect(binding('myForm.input.valid')).toEqual('false');          });        </doc:scenario>      </doc:example>   */ -angularInputType('email', function(element, widget) { -  widget.$on('$validate', function(event) { -    var value = widget.$viewValue; -    widget.$emit(!value || value.match(EMAIL_REGEXP) ? "$valid" : "$invalid", "EMAIL"); -  }); -});  /** @@ -173,48 +162,39 @@ angularInputType('email', function(element, widget) {             $scope.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> +       <form name="myForm" ng:controller="Ctrl"> +         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>           <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> +         <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/> +        </form>        </doc:source>        <doc:scenario>          it('should initialize to model', function() {            expect(binding('text')).toEqual('http://google.com'); -          expect(binding('myForm.input.$valid')).toEqual('true'); +          expect(binding('myForm.input.valid')).toEqual('true');          });          it('should be invalid if empty', function() {            input('text').enter('');            expect(binding('text')).toEqual(''); -          expect(binding('myForm.input.$valid')).toEqual('false'); +          expect(binding('myForm.input.valid')).toEqual('false');          });          it('should be invalid if not url', function() {            input('text').enter('xxx'); -          expect(binding('text')).toEqual('xxx'); -          expect(binding('myForm.input.$valid')).toEqual('false'); +          expect(binding('myForm.input.valid')).toEqual('false');          });        </doc:scenario>      </doc:example>   */ -angularInputType('url', function(element, widget) { -  widget.$on('$validate', function(event) { -    var value = widget.$viewValue; -    widget.$emit(!value || value.match(URL_REGEXP) ? "$valid" : "$invalid", "URL"); -  }); -});  /** @@ -241,53 +221,58 @@ angularInputType('url', function(element, widget) {             $scope.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> +       <form name="myForm" ng:controller="Ctrl"> +         List: <input type="list" name="input" ng:model="names" required> +         <span class="error" ng:show="myForm.list.error.REQUIRED"> +           Required!</span>           <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> +         <tt>myForm.input.valid = {{myForm.input.valid}}</tt><br/> +         <tt>myForm.input.error = {{myForm.input.error}}</tt><br/> +         <tt>myForm.valid = {{myForm.valid}}</tt><br/> +         <tt>myForm.error.REQUIRED = {{!!myForm.error.REQUIRED}}</tt><br/> +        </form>        </doc:source>        <doc:scenario>          it('should initialize to model', function() {            expect(binding('names')).toEqual('["igor","misko","vojta"]'); -          expect(binding('myForm.input.$valid')).toEqual('true'); +          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'); +          expect(binding('names')).toEqual(''); +          expect(binding('myForm.input.valid')).toEqual('false');          });        </doc:scenario>      </doc:example>   */ -angularInputType('list', function(element, widget) { -  function parse(viewValue) { -    var list = []; -    forEach(viewValue.split(/\s*,\s*/), function(value){ -      if (value) list.push(trim(value)); -    }); -    return list; -  } -  widget.$parseView = function() { -    isString(widget.$viewValue) && (widget.$modelValue = parse(widget.$viewValue)); -  }; -  widget.$parseModel = function() { -    var modelValue = widget.$modelValue; -    if (isArray(modelValue) -        && (!isString(widget.$viewValue) || !equals(parse(widget.$viewValue), modelValue))) { -      widget.$viewValue =  modelValue.join(', '); +var ngListDirective = function() { +  return { +    require: 'ngModel', +    link: function(scope, element, attr, ctrl) { +      var parse = function(viewValue) { +        var list = []; + +        if (viewValue) { +          forEach(viewValue.split(/\s*,\s*/), function(value) { +            if (value) list.push(value); +          }); +        } + +        return list; +      }; + +      ctrl.parsers.push(parse); +      ctrl.formatters.push(function(value) { +        if (isArray(value) && !equals(parse(ctrl.viewValue), value)) { +          return value.join(', '); +        } + +        return undefined; +      });      }    }; -}); - +};  /**   * @ngdoc inputType @@ -320,113 +305,40 @@ angularInputType('list', function(element, widget) {             $scope.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> +       <form name="myForm" ng:controller="Ctrl"> +         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>           <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> +         <tt>myForm.input.valid = {{myForm.input.valid}}</tt><br/> +         <tt>myForm.input.error = {{myForm.input.error}}</tt><br/> +         <tt>myForm.valid = {{myForm.valid}}</tt><br/> +         <tt>myForm.error.REQUIRED = {{!!myForm.error.REQUIRED}}</tt><br/> +        </form>        </doc:source>        <doc:scenario>          it('should initialize to model', function() {           expect(binding('value')).toEqual('12'); -         expect(binding('myForm.input.$valid')).toEqual('true'); +         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'); +         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')); - - -/** - * @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 {number=} ng:minlength Sets `MINLENGTH` validation error key if the value is shorter than - *    minlength. - * @param {number=} ng:maxlength Sets `MAXLENGTH` validation error key if the value is longer than - *    maxlength. - * @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. - * @param {string=} ng:change Angular expression to be executed when input changes due to user - *    interaction with the input element. - * - * @example -    <doc:example> -      <doc:source> -       <script> -         function Ctrl($scope) { -           $scope.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'); +         expect(binding('value')).toEqual('12'); +         expect(binding('myForm.input.valid')).toEqual('false');          });        </doc:scenario>      </doc:example>   */ -angularInputType('integer', numericRegexpInputType(INTEGER_REGEXP, 'INTEGER'));  /** @@ -452,15 +364,13 @@ angularInputType('integer', numericRegexpInputType(INTEGER_REGEXP, 'INTEGER'));             $scope.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" -                          ng:true-value="YES" ng:false-value="NO"> <br/> -         </form> +       <form name="myForm" ng:controller="Ctrl"> +         Value1: <input type="checkbox" ng:model="value1"> <br/> +         Value2: <input type="checkbox" ng:model="value2" +                        ng:true-value="YES" ng:false-value="NO"> <br/>           <tt>value1 = {{value1}}</tt><br/>           <tt>value2 = {{value2}}</tt><br/> -       </div> +        </form>        </doc:source>        <doc:scenario>          it('should change state', function() { @@ -475,31 +385,7 @@ angularInputType('integer', numericRegexpInputType(INTEGER_REGEXP, 'INTEGER'));        </doc:scenario>      </doc:example>   */ -angularInputType('checkbox', function(inputElement, widget) { -  var trueValue = inputElement.attr('ng:true-value'), -      falseValue = inputElement.attr('ng: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 = widget.$modelValue === trueValue; -  }; - -  widget.$parseView = function() { -    widget.$modelValue = widget.$viewValue ? trueValue : falseValue; -  }; -});  /** @@ -523,14 +409,12 @@ angularInputType('checkbox', function(inputElement, widget) {             $scope.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> +       <form name="myForm" ng:controller="Ctrl"> +         <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/>           <tt>color = {{color}}</tt><br/> -       </div> +        </form>        </doc:source>        <doc:scenario>          it('should change state', function() { @@ -542,63 +426,6 @@ angularInputType('checkbox', function(inputElement, widget) {        </doc:scenario>      </doc:example>   */ -angularInputType('radio', function(inputElement, widget, attr) { -  //correct the name -  attr.$set('name', widget.$id + '@' + attr.name); -  inputElement.bind('click', function() { -    widget.$apply(function() { -      if (inputElement[0].checked) { -        widget.$emit('$viewChange', attr.value); -      } -    }); -  }); - -  widget.$render = function() { -    inputElement[0].checked = isDefined(attr.value) && (attr.value == widget.$viewValue); -  }; - -  if (inputElement[0].checked) { -    widget.$viewValue = attr.value; -  } -}); - - -function numericRegexpInputType(regexp, error) { -  return function(inputElement, widget) { -    var 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() { -      widget.$viewValue = isNumber(widget.$modelValue) -        ? '' + 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,password");  /** @@ -641,188 +468,67 @@ var HTML5_INPUTS_TYPES =  makeMap(         <div ng:controller="Ctrl">           <form name="myForm">             User name: <input type="text" name="userName" ng:model="user.name" required> -           <span class="error" ng:show="myForm.userName.$error.REQUIRED"> +           <span class="error" ng:show="myForm.userName.error.REQUIRED">               Required!</span><br>             Last name: <input type="text" name="lastName" ng:model="user.last"               ng:minlength="3" ng:maxlength="10"> -           <span class="error" ng:show="myForm.lastName.$error.MINLENGTH"> +           <span class="error" ng:show="myForm.lastName.error.MINLENGTH">               Too short!</span> -           <span class="error" ng:show="myForm.lastName.$error.MAXLENGTH"> +           <span class="error" ng:show="myForm.lastName.error.MAXLENGTH">               Too long!</span><br>           </form>           <hr>           <tt>user = {{user}}</tt><br/> -         <tt>myForm.userName.$valid = {{myForm.userName.$valid}}</tt><br> -         <tt>myForm.userName.$error = {{myForm.userName.$error}}</tt><br> -         <tt>myForm.lastName.$valid = {{myForm.lastName.$valid}}</tt><br> -         <tt>myForm.userName.$error = {{myForm.lastName.$error}}</tt><br> -         <tt>myForm.$valid = {{myForm.$valid}}</tt><br> -         <tt>myForm.$error.REQUIRED = {{!!myForm.$error.REQUIRED}}</tt><br> -         <tt>myForm.$error.MINLENGTH = {{!!myForm.$error.MINLENGTH}}</tt><br> -         <tt>myForm.$error.MAXLENGTH = {{!!myForm.$error.MAXLENGTH}}</tt><br> +         <tt>myForm.userName.valid = {{myForm.userName.valid}}</tt><br> +         <tt>myForm.userName.error = {{myForm.userName.error}}</tt><br> +         <tt>myForm.lastName.valid = {{myForm.lastName.valid}}</tt><br> +         <tt>myForm.userName.error = {{myForm.lastName.error}}</tt><br> +         <tt>myForm.valid = {{myForm.valid}}</tt><br> +         <tt>myForm.error.REQUIRED = {{!!myForm.error.REQUIRED}}</tt><br> +         <tt>myForm.error.MINLENGTH = {{!!myForm.error.MINLENGTH}}</tt><br> +         <tt>myForm.error.MAXLENGTH = {{!!myForm.error.MAXLENGTH}}</tt><br>         </div>        </doc:source>        <doc:scenario>          it('should initialize to model', function() {            expect(binding('user')).toEqual('{"last":"visitor","name":"guest"}'); -          expect(binding('myForm.userName.$valid')).toEqual('true'); -          expect(binding('myForm.$valid')).toEqual('true'); +          expect(binding('myForm.userName.valid')).toEqual('true'); +          expect(binding('myForm.valid')).toEqual('true');          });          it('should be invalid if empty when required', function() {            input('user.name').enter(''); -          expect(binding('user')).toEqual('{"last":"visitor","name":""}'); -          expect(binding('myForm.userName.$valid')).toEqual('false'); -          expect(binding('myForm.$valid')).toEqual('false'); +          expect(binding('user')).toEqual('{"last":"visitor","name":null}'); +          expect(binding('myForm.userName.valid')).toEqual('false'); +          expect(binding('myForm.valid')).toEqual('false');          });          it('should be valid if empty when min length is set', function() {            input('user.last').enter('');            expect(binding('user')).toEqual('{"last":"","name":"guest"}'); -          expect(binding('myForm.lastName.$valid')).toEqual('true'); -          expect(binding('myForm.$valid')).toEqual('true'); +          expect(binding('myForm.lastName.valid')).toEqual('true'); +          expect(binding('myForm.valid')).toEqual('true');          });          it('should be invalid if less than required min length', function() {            input('user.last').enter('xx'); -          expect(binding('user')).toEqual('{"last":"xx","name":"guest"}'); -          expect(binding('myForm.lastName.$valid')).toEqual('false'); -          expect(binding('myForm.lastName.$error')).toMatch(/MINLENGTH/); -          expect(binding('myForm.$valid')).toEqual('false'); +          expect(binding('user')).toEqual('{"last":"visitor","name":"guest"}'); +          expect(binding('myForm.lastName.valid')).toEqual('false'); +          expect(binding('myForm.lastName.error')).toMatch(/MINLENGTH/); +          expect(binding('myForm.valid')).toEqual('false');          });          it('should be valid if longer than max length', function() {            input('user.last').enter('some ridiculously long name');            expect(binding('user')) -            .toEqual('{"last":"some ridiculously long name","name":"guest"}'); -          expect(binding('myForm.lastName.$valid')).toEqual('false'); -          expect(binding('myForm.lastName.$error')).toMatch(/MAXLENGTH/); -          expect(binding('myForm.$valid')).toEqual('false'); +            .toEqual('{"last":"visitor","name":"guest"}'); +          expect(binding('myForm.lastName.valid')).toEqual('false'); +          expect(binding('myForm.lastName.error')).toMatch(/MAXLENGTH/); +          expect(binding('myForm.valid')).toEqual('false');          });        </doc:scenario>      </doc:example>   */ -var inputDirective = ['$defer', '$formFactory', function($defer, $formFactory) { -  return { -    restrict: 'E', -    link: function(modelScope, inputElement, attr) { -      if (!attr.ngModel) return; - -      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 = attr.type || 'text', -          TypeController, -          patternMatch, widget, -          pattern = attr.ngPattern, -          modelExp = attr.ngModel, -          minlength = parseInt(attr.ngMinlength, 10), -          maxlength = parseInt(attr.ngMaxlength, 10), -          loadFromScope = type.match(/^\s*\@\s*(.*)/); - -       if (!pattern) { -         patternMatch = valueFn(true); -       } else { -         if (pattern.match(/^\/(.*)\/$/)) { -           pattern = new RegExp(pattern.substr(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(modelScope.$eval(loadFromScope[1]), loadFromScope[1]) -              : 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. -        } -      } - -      //TODO(misko): setting $inject is a hack -      !TypeController.$inject && (TypeController.$inject = ['$element', '$scope', '$attr']); -      widget = form.$createWidget({ -          scope: modelScope, -          model: modelExp, -          onChange: attr.ngChange, -          alias: attr.name, -          controller: TypeController, -          controllerArgs: {$element: inputElement, $attr: attr} -      }); - -      widget.$pristine = !(widget.$dirty = false); - -      widget.$on('$validate', function() { -        var $viewValue = trim(widget.$viewValue), -            inValid = attr.required && !$viewValue, -            tooLong = maxlength && $viewValue && $viewValue.length > maxlength, -            tooShort = minlength && $viewValue && $viewValue.length < minlength, -            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'); -        } -        if (widget.$error.MINLENGTH != tooShort){ -          widget.$emit(tooShort ? '$invalid' : '$valid', 'MINLENGTH'); -        } -        if (widget.$error.MAXLENGTH != tooLong){ -          widget.$emit(tooLong ? '$invalid' : '$valid', 'MAXLENGTH'); -        } -      }); - -      forEach(['valid', 'invalid', 'pristine', 'dirty'], function(name) { -        widget.$watch('$' + name, function(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 input', 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); -              } -            }); -          } -        }); -      } -    } -  }; -}];  /** @@ -850,3 +556,452 @@ var inputDirective = ['$defer', '$formFactory', function($defer, $formFactory) {   * @param {string=} ng:change Angular expression to be executed when input changes due to user   *    interaction with the input element.   */ +var inputType = { +  'text': textInputType, +  'number': numberInputType, +  'url': urlInputType, +  'email': emailInputType, + +  'radio': radioInputType, +  'checkbox': checkboxInputType, + +  'hidden': noop, +  'button': noop, +  'submit': noop, +  'reset': noop +}; + + +function isEmpty(value) { +  return isUndefined(value) || value === '' || value === null || value !== value; +} + + +function textInputType(scope, element, attr, ctrl) { +  element.bind('blur', function() { +    var touched = ctrl.touch(), +        value = trim(element.val()); + +    if (ctrl.viewValue !== value) { +      scope.$apply(function() { +        ctrl.read(value); +      }); +    } else if (touched) { +      scope.$apply(); +    } +  }); + +  ctrl.render = function() { +    element.val(ctrl.viewValue || ''); +  }; + +  // pattern validator +  var pattern = attr.ngPattern, +      patternValidator; + +  var emit = function(regexp, value) { +    if (isEmpty(value) || regexp.test(value)) { +      ctrl.emitValidity('PATTERN', true); +      return value; +    } else { +      ctrl.emitValidity('PATTERN', false); +      return undefined; +    } +  }; + +  if (pattern) { +    if (pattern.match(/^\/(.*)\/$/)) { +      pattern = new RegExp(pattern.substr(1, pattern.length - 2)); +      patternValidator = function(value) { +        return emit(pattern, value) +      }; +    } else { +      patternValidator = function(value) { +        var patternObj = scope.$eval(pattern); + +        if (!patternObj || !patternObj.test) { +          throw new Error('Expected ' + pattern + ' to be a RegExp but was ' + patternObj); +        } +        return emit(patternObj, value); +      }; +    } + +    ctrl.formatters.push(patternValidator); +    ctrl.parsers.push(patternValidator); +  } + +  // min length validator +  if (attr.ngMinlength) { +    var minlength = parseInt(attr.ngMinlength, 10); +    var minLengthValidator = function(value) { +      if (!isEmpty(value) && value.length < minlength) { +        ctrl.emitValidity('MINLENGTH', false); +        return undefined; +      } else { +        ctrl.emitValidity('MINLENGTH', true); +        return value; +      } +    }; + +    ctrl.parsers.push(minLengthValidator); +    ctrl.formatters.push(minLengthValidator); +  } + +  // max length validator +  if (attr.ngMaxlength) { +    var maxlength = parseInt(attr.ngMaxlength, 10); +    var maxLengthValidator = function(value) { +      if (!isEmpty(value) && value.length > maxlength) { +        ctrl.emitValidity('MAXLENGTH', false); +        return undefined; +      } else { +        ctrl.emitValidity('MAXLENGTH', true); +        return value; +      } +    }; + +    ctrl.parsers.push(maxLengthValidator); +    ctrl.formatters.push(maxLengthValidator); +  } +}; + +function numberInputType(scope, element, attr, ctrl) { +  textInputType(scope, element, attr, ctrl); + +  ctrl.parsers.push(function(value) { +    var empty = isEmpty(value); +    if (empty || NUMBER_REGEXP.test(value)) { +      ctrl.emitValidity('NUMBER', true); +      return value === '' ? null : (empty ? value : parseFloat(value)); +    } else { +      ctrl.emitValidity('NUMBER', false); +      return undefined; +    } +  }); + +  ctrl.formatters.push(function(value) { +    return isEmpty(value) ? '' : '' + value; +  }); + +  if (attr.min) { +    var min = parseFloat(attr.min); +    var minValidator = function(value) { +      if (!isEmpty(value) && value < min) { +        ctrl.emitValidity('MIN', false); +        return undefined; +      } else { +        ctrl.emitValidity('MIN', true); +        return value; +      } +    }; + +    ctrl.parsers.push(minValidator); +    ctrl.formatters.push(minValidator); +  } + +  if (attr.max) { +    var max = parseFloat(attr.max); +    var maxValidator = function(value) { +      if (!isEmpty(value) && value > max) { +        ctrl.emitValidity('MAX', false); +        return undefined; +      } else { +        ctrl.emitValidity('MAX', true); +        return value; +      } +    }; + +    ctrl.parsers.push(maxValidator); +    ctrl.formatters.push(maxValidator); +  } + +  ctrl.formatters.push(function(value) { + +    if (isEmpty(value) || isNumber(value)) { +      ctrl.emitValidity('NUMBER', true); +      return value; +    } else { +      ctrl.emitValidity('NUMBER', false); +      return undefined; +    } +  }); +} + +function urlInputType(scope, element, attr, ctrl) { +  textInputType(scope, element, attr, ctrl); + +  var urlValidator = function(value) { +    if (isEmpty(value) || URL_REGEXP.test(value)) { +      ctrl.emitValidity('URL', true); +      return value; +    } else { +      ctrl.emitValidity('URL', false); +      return undefined; +    } +  }; + +  ctrl.formatters.push(urlValidator); +  ctrl.parsers.push(urlValidator); +} + +function emailInputType(scope, element, attr, ctrl) { +  textInputType(scope, element, attr, ctrl); + +  var emailValidator = function(value) { +    if (isEmpty(value) || EMAIL_REGEXP.test(value)) { +      ctrl.emitValidity('EMAIL', true); +      return value; +    } else { +      ctrl.emitValidity('EMAIL', false); +      return undefined; +    } +  }; + +  ctrl.formatters.push(emailValidator); +  ctrl.parsers.push(emailValidator); +} + +function radioInputType(scope, element, attr, ctrl) { +  // correct the name +  element.attr('name', attr.id + '@' + attr.name); + +  element.bind('click', function() { +    if (element[0].checked) { +      scope.$apply(function() { +        ctrl.touch(); +        ctrl.read(attr.value); +      }); +    }; +  }); + +  ctrl.render = function() { +    var value = attr.value; +    element[0].checked = isDefined(value) && (value == ctrl.viewValue); +  }; +} + +function checkboxInputType(scope, element, attr, ctrl) { +  var trueValue = attr.ngTrueValue, +      falseValue = attr.ngFalseValue; + +  if (!isString(trueValue)) trueValue = true; +  if (!isString(falseValue)) falseValue = false; + +  element.bind('click', function() { +    scope.$apply(function() { +      ctrl.touch(); +      ctrl.read(element[0].checked); +    }); +  }); + +  ctrl.render = function() { +    element[0].checked = ctrl.viewValue; +  }; + +  ctrl.formatters.push(function(value) { +    return value === trueValue; +  }); + +  ctrl.parsers.push(function(value) { +    return value ? trueValue : falseValue; +  }); +} + + +var inputDirective = [function() { +  return { +    restrict: 'E', +    require: '?ngModel', +    link: function(scope, element, attr, ctrl) { +      if (ctrl) { +        (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrl); +      } +    } +  }; +}]; + + +var NgModelController = ['$scope', '$exceptionHandler', 'ngModel', +    function($scope, $exceptionHandler, ngModel) { +  this.viewValue = Number.NaN; +  this.modelValue = Number.NaN; +  this.parsers = []; +  this.formatters = []; +  this.error = {}; +  this.pristine = true; +  this.dirty = false; +  this.valid = true; +  this.invalid = false; +  this.render = noop; + +  this.touch = function() { +    if (this.dirty) return false; + +    this.dirty = true; +    this.pristine = false; +    try { +      $scope.$emit('$viewTouch'); +    } catch (e) { +      $exceptionHandler(e); +    } +    return true; +  }; + +  // don't $emit valid if already valid, the same for $invalid +  // not sure about this method name, should the argument be reversed ? emitError ? +  this.emitValidity = function(name, isValid) { + +    if (!isValid && this.error[name]) return; +    if (isValid && !this.error[name]) return; + +    if (!isValid) { +      this.error[name] = true; +      this.invalid = true; +      this.valid = false; +    } + +    if (isValid) { +      delete this.error[name]; +      if (equals(this.error, {})) { +        this.valid = true; +        this.invalid = false; +      } +    } + +    return $scope.$emit(isValid ? '$valid' : '$invalid', name, this); +  }; + +  // view -> model +  this.read = function(value) { +    this.viewValue = value; + +    forEach(this.parsers, function(fn) { +      value = fn(value); +    }); + +    if (isDefined(value) && this.model !== value) { +      this.modelValue = value; +      ngModel(value); +      $scope.$emit('$viewChange', value, this); +    } +  }; + +  // model -> value +  var ctrl = this; +  $scope.$watch(function() { +    return ngModel(); +  }, function(value) { + +    // ignore change from view +    if (ctrl.modelValue === value) return; + +    var formatters = ctrl.formatters, +        idx = formatters.length; + +    ctrl.modelValue = value; +    while(idx--) { +      value = formatters[idx](value); +    } + +    if (isDefined(value) && ctrl.viewValue !== value) { +      ctrl.viewValue = value; +      ctrl.render(); +    } +  }); +}]; + + +var ngModelDirective = [function() { +  return { +    inject: { +      ngModel: 'accessor' +    }, +    require: ['ngModel', '^?form'], +    controller: NgModelController, +    link: function(scope, element, attr, controllers) { +      var modelController = controllers[0], +          formController = controllers[1]; + +      if (formController) { +        formController.registerWidget(modelController, attr.name); +      } + +      forEach(['valid', 'invalid', 'pristine', 'dirty'], function(name) { +        scope.$watch(function() { +          return modelController[name]; +        }, function(value) { +          element[value ? 'addClass' : 'removeClass']('ng-' + name); +        }); +      }); + +      element.bind('$destroy', function() { +        scope.$emit('$destroy', modelController); +      }); +    } +  }; +}]; + + +var ngChangeDirective = valueFn({ +  require: 'ngModel', +  link: function(scope, element, attr, ctrl) { +    scope.$on('$viewChange', function(event, value, widget) { +      if (ctrl === widget) scope.$eval(attr.ngChange); +    }); +  } +}); + + +var ngBindImmediateDirective = ['$browser', function($browser) { +  return { +    require: 'ngModel', +    link: function(scope, element, attr, ctrl) { +      element.bind('keydown change input', function(event) { +        var key = event.keyCode; + +        //    command            modifiers                   arrows +        if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return; + +        $browser.defer(function() { +          var touched = ctrl.touch(), +              value = trim(element.val()); + +          if (ctrl.viewValue !== value) { +            scope.$apply(function() { +              ctrl.read(value); +            }); +          } else if (touched) { +            scope.$apply(); +          } +        }); +      }); +    } +  }; +}]; + + +var requiredDirective = [function() { +  return { +    require: '?ngModel', +    link: function(scope, elm, attr, ctrl) { +      if (!ctrl) return; + +      var validator = function(value) { +        if (attr.required && isEmpty(value)) { +          ctrl.emitValidity('REQUIRED', false); +          return null; +        } else { +          ctrl.emitValidity('REQUIRED', true); +          return value; +        } +      }; + +      ctrl.formatters.push(validator); +      ctrl.parsers.unshift(validator); + +      attr.$observe('required', function() { +        validator(ctrl.viewValue); +      }); +    } +  }; +}];  | 
