diff options
| author | Misko Hevery | 2010-03-24 16:13:42 -0700 | 
|---|---|---|
| committer | Misko Hevery | 2010-03-24 16:13:42 -0700 | 
| commit | 0c42eb9909d554807549cd3394e0ea0c715cc2d1 (patch) | |
| tree | 331c637a5c86b87970166a0936874fec9c6920a6 | |
| parent | 3d3694240034b6841c9fdf2e38a2a7955cb592c7 (diff) | |
| download | angular.js-0c42eb9909d554807549cd3394e0ea0c715cc2d1.tar.bz2 | |
input[type=text] now works with binding, validation, formatter, required
| -rw-r--r-- | src/Angular.js | 9 | ||||
| -rw-r--r-- | src/Compiler.js | 4 | ||||
| -rw-r--r-- | src/Validators.js | 24 | ||||
| -rw-r--r-- | src/jqLite.js | 34 | ||||
| -rw-r--r-- | src/widgets2.js | 71 | ||||
| -rw-r--r-- | test/ValidatorsTest.js | 34 | ||||
| -rw-r--r-- | test/widgetsSpec.js | 68 | 
7 files changed, 187 insertions, 57 deletions
diff --git a/src/Angular.js b/src/Angular.js index 95f7325a..b76926b9 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -44,6 +44,8 @@ function extensionList(angular, name) {  }  var consoleNode, msie, +    VALUE             = 'value', +    NOOP              = 'noop',      jQuery            = window['jQuery'] || window['$'], // weirdness to make IE happy      slice             = Array.prototype.slice,      angular           = window['angular']    || (window['angular']    = {}), @@ -92,6 +94,9 @@ function isObject(value){ return typeof value == 'object';}  function isString(value){ return typeof value == 'string';}  function isArray(value) { return value instanceof Array; }  function isFunction(value){ return typeof value == 'function';} +function lowercase(value){ return isString(value) ? value.toLowerCase() : value; } +function uppercase(value){ return isString(value) ? value.toUpperCase() : value; } +function trim(value) { return isString(value) ? value.replace(/^\s*/, '').replace(/\s*$/, '') : value; };  function log(a, b, c){    var console = window['console']; @@ -244,10 +249,6 @@ function outerHTML(node) {    return outerHTML;  } -function trim(str) { -  return str.replace(/^ */, '').replace(/ *$/, ''); -} -  function toBoolean(value) {    var v = ("" + value).toLowerCase();    if (v == 'f' || v == '0' || v == 'false' || v == 'no') diff --git a/src/Compiler.js b/src/Compiler.js index ba598a43..4423fcef 100644 --- a/src/Compiler.js +++ b/src/Compiler.js @@ -105,9 +105,7 @@ Compiler.prototype = {    templatize: function(element){      var self = this, -        elementName = element[0].nodeName, -        widgets = self.widgets, -        widget = widgets[elementName], +        widget = self.widgets[element[0].nodeName],          directives = self.directives,          descend = true,          exclusive = false, diff --git a/src/Validators.js b/src/Validators.js index b7efcb4a..cdff5e1a 100644 --- a/src/Validators.js +++ b/src/Validators.js @@ -1,4 +1,6 @@  foreach({ +  'noop': noop, +    'regexp': function(value, regexp, msg) {      if (!value.match(regexp)) {        return msg || @@ -7,7 +9,7 @@ foreach({        return null;      }    }, -   +    'number': function(value, min, max) {      var num = 1 * value;      if (num == value) { @@ -19,40 +21,40 @@ foreach({        }        return null;      } else { -      return "Value is not a number."; +      return "Not a number";      }    }, -   +    'integer': function(value, min, max) {      var numberError = angularValidator['number'](value, min, max);      if (numberError) return numberError;      if (!("" + value).match(/^\s*[\d+]*\s*$/) || value != Math.round(value)) { -      return "Value is not a whole number."; +      return "Not a whole number";      }      return null;    }, -   +    'date': function(value, min, max) {      if (value.match(/^\d\d?\/\d\d?\/\d\d\d\d$/)) {        return null;      }      return "Value is not a date. (Expecting format: 12/31/2009).";    }, -   +    'ssn': function(value) {      if (value.match(/^\d\d\d-\d\d-\d\d\d\d$/)) {        return null;      }      return "SSN needs to be in 999-99-9999 format.";    }, -   +    'email': function(value) {      if (value.match(/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/)) {        return null;      }      return "Email needs to be in username@host.com format.";    }, -   +    'phone': function(value) {      if (value.match(/^1\(\d\d\d\)\d\d\d-\d\d\d\d$/)) {        return null; @@ -62,14 +64,14 @@ foreach({      }      return "Phone number needs to be in 1(987)654-3210 format in North America or +999 (123) 45678 906 internationaly.";    }, -   +    'url': function(value) {      if (value.match(/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/)) {        return null;      }      return "URL needs to be in http://server[:port]/path format.";    }, -   +    'json': function(value) {      try {        fromJson(value); @@ -78,7 +80,7 @@ foreach({        return e.toString();      }    }, -   +    'asynchronous': function(text, asynchronousFn) {      var stateKey = '$validateState';      var lastKey = '$lastKey'; diff --git a/src/jqLite.js b/src/jqLite.js index 035a7a1b..2ac3f6c9 100644 --- a/src/jqLite.js +++ b/src/jqLite.js @@ -77,22 +77,24 @@ JQLite.prototype = {    },    bind: function(type, fn){ -    var element = this[0], -        bind = this.data('bind'), +    var self = this, +        element = self[0], +        bind = self.data('bind'),          eventHandler;      if (!bind) this.data('bind', bind = {}); -    eventHandler = bind[type]; -    if (!eventHandler) { -      bind[type] = eventHandler = function() { -        var self = this; -        foreach(eventHandler.fns, function(fn){ -          fn.apply(self, arguments); -        }); -      }; -      eventHandler.fns = []; -      addEventListener(element, type, eventHandler); -    } -    eventHandler.fns.push(fn); +    foreach(type.split(' '), function(type){ +      eventHandler = bind[type]; +      if (!eventHandler) { +        bind[type] = eventHandler = function() { +          foreach(eventHandler.fns, function(fn){ +            fn.apply(self, arguments); +          }); +        }; +        eventHandler.fns = []; +        addEventListener(element, type, eventHandler); +      } +      eventHandler.fns.push(fn); +    });    },    trigger: function(type) { @@ -134,6 +136,10 @@ JQLite.prototype = {      return false;    }, +  removeClass: function(selector) { +    this[0].className = trim((" " + this[0].className + " ").replace(/[\n\t]/g, " ").replace(" " + selector + " ", "")); +  }, +    addClass: function( selector ) {      if (!this.hasClass(selector)) {        this[0].className += ' ' + selector; diff --git a/src/widgets2.js b/src/widgets2.js index b0f467d4..cee0e49e 100644 --- a/src/widgets2.js +++ b/src/widgets2.js @@ -1,3 +1,72 @@ +function scopeAccessor(scope, element) { +  var expr = element.attr('name'), +      farmatterName = element.attr('ng-format') || NOOP, +      formatter = angularFormatter(farmatterName); +  if (!expr) throw "Required field 'name' not found."; +  if (!formatter) throw "Formatter named '" + farmatterName + "' not found."; +  return { +    get: function() { +      return formatter['format'](scope.$eval(expr)); +    }, +    set: function(value) { +      scope.$eval(expr + '=' + toJson(formatter['parse'](value))); +    } +  }; +} + +function domAccessor(element) { +  var validatorName = element.attr('ng-validate') || NOOP, +      validator = angularValidator(validatorName), +      required = element.attr('ng-required'), +      lastError; +  required = required || required == ''; +  if (!validator) throw "Validator named '" + validatorName + "' not found."; +  function validate(value) { +    var error = required && !trim(value) ? "Required" : validator(value); +    if (error !== lastError) { +      if (error) { +        element.addClass(NG_VALIDATION_ERROR); +        element.attr(NG_ERROR, error); +      } else { +        element.removeClass(NG_VALIDATION_ERROR); +        element.removeAttr(NG_ERROR); +      } +      lastError = error; +    } +    return value; +  } +  return { +    get: function(){ +      return validate(element.attr(VALUE)); +    }, +    set: function(value){ +      element.attr(VALUE, validate(value)); +    } +  }; +} + +var NG_ERROR = 'ng-error', +    NG_VALIDATION_ERROR = 'ng-validation-error', +    INPUT_META = { +  'text': ["", 'keyup change'] +}; + +angularWidget('INPUT', function input(element){ +  var meta = INPUT_META[lowercase(element.attr('type'))]; +  return meta ? function(element) { +    var scope = scopeAccessor(this, element), +        dom = domAccessor(element); +    scope.set(dom.get() || meta[0]); +    element.bind(meta[1], function(){ +      scope.set(dom.get()); +    }); +    this.$watch(scope.get, dom.set); +  } : 0; +}); + + + +  /////////////////////////////////////////  /////////////////////////////////////////  ///////////////////////////////////////// @@ -6,8 +75,6 @@ - -  //widget related  //ng-validate, ng-required, ng-formatter  //ng-error diff --git a/test/ValidatorsTest.js b/test/ValidatorsTest.js index 5449ebb0..6b61c273 100644 --- a/test/ValidatorsTest.js +++ b/test/ValidatorsTest.js @@ -26,7 +26,7 @@ ValidatorTest.prototype.testRegexp = function() {  };  ValidatorTest.prototype.testNumber = function() { -  assertEquals(angular.validator.number("ab"), "Value is not a number."); +  assertEquals(angular.validator.number("ab"), "Not a number");    assertEquals(angular.validator.number("-0.1",0), "Value can not be less than 0.");    assertEquals(angular.validator.number("10.1",0,10), "Value can not be greater than 10.");    assertEquals(angular.validator.number("1.2"), null); @@ -34,10 +34,10 @@ ValidatorTest.prototype.testNumber = function() {  };  ValidatorTest.prototype.testInteger = function() { -  assertEquals(angular.validator.integer("ab"), "Value is not a number."); -  assertEquals(angular.validator.integer("1.1"), "Value is not a whole number."); -  assertEquals(angular.validator.integer("1.0"), "Value is not a whole number."); -  assertEquals(angular.validator.integer("1."), "Value is not a whole number."); +  assertEquals(angular.validator.integer("ab"), "Not a number"); +  assertEquals(angular.validator.integer("1.1"), "Not a whole number"); +  assertEquals(angular.validator.integer("1.0"), "Not a whole number"); +  assertEquals(angular.validator.integer("1."), "Not a whole number");    assertEquals(angular.validator.integer("-1",0), "Value can not be less than 0.");    assertEquals(angular.validator.integer("11",0,10), "Value can not be greater than 10.");    assertEquals(angular.validator.integer("1"), null); @@ -86,7 +86,7 @@ describe('Validator:asynchronous', function(){    var asynchronous = angular.validator.asynchronous;    var self;    var value, fn; -   +    beforeEach(function(){      value = null;      fn = null; @@ -96,10 +96,10 @@ describe('Validator:asynchronous', function(){          $updateView: noop      };    }); -   +    it('should make a request and show spinner', function(){ -    var x = compile('<input name="name" ng-validate="asynchronous:asyncFn"/>') -    var asyncFn = function(v,f){value=v; fn=f}; +    var x = compile('<input name="name" ng-validate="asynchronous:asyncFn"/>'); +    var asyncFn = function(v,f){value=v; fn=f;};      var input = x.node.find(":input");      x.scope.set("asyncFn", asyncFn);      x.scope.set("name", "misko"); @@ -110,29 +110,29 @@ describe('Validator:asynchronous', function(){      expect(input.hasClass('ng-input-indicator-wait')).toBeFalsy();      expect(input.attr('ng-error')).toEqual("myError");    }); -   +    it("should not make second request to same value", function(){      asynchronous.call(self, "kai", function(v,f){value=v; fn=f;});      expect(value).toEqual('kai');      expect(self.$invalidWidgets).toEqual([self.$element]); -     +      var spy = jasmine.createSpy();      asynchronous.call(self, "kai", spy);      expect(spy).wasNotCalled(); -     +      asynchronous.call(self, "misko", spy); -    expect(spy).wasCalled();     +    expect(spy).wasCalled();    }); -   +    it("should ignore old callbacks, and not remove spinner", function(){      var firstCb, secondCb;      asynchronous.call(self, "first", function(v,f){value=v; firstCb=f;});      asynchronous.call(self, "second", function(v,f){value=v; secondCb=f;}); -     +      firstCb();      expect($(self.$element).hasClass('ng-input-indicator-wait')).toBeTruthy(); -     +      secondCb();      expect($(self.$element).hasClass('ng-input-indicator-wait')).toBeFalsy();    }); -});
\ No newline at end of file +}); diff --git a/test/widgetsSpec.js b/test/widgetsSpec.js index 3b6be8ec..da9d5f9b 100644 --- a/test/widgetsSpec.js +++ b/test/widgetsSpec.js @@ -1,4 +1,4 @@ -describe("widgets", function(){ +describe("input widget", function(){    var compile, element, scope; @@ -15,14 +15,70 @@ describe("widgets", function(){    });    afterEach(function(){ -    if (element) { -      element.remove(); -    } +    if (element) element.remove();      expect(_(jqCache).size()).toEqual(0);    }); -  it('should fail', function(){ -    fail('iueoi'); +  it('should input-text auto init and handle keyup/change events', function(){ +    compile('<input type="Text" name="name" value="Misko"/>'); +    expect(scope.get('name')).toEqual("Misko"); + +    scope.set('name', 'Adam'); +    scope.updateView(); +    expect(element.attr('value')).toEqual("Adam"); + +    element.attr('value', 'Shyam'); +    element.trigger('keyup'); +    expect(scope.get('name')).toEqual('Shyam'); + +    element.attr('value', 'Kai'); +    element.trigger('change'); +    expect(scope.get('name')).toEqual('Kai'); +  }); + +  it("should process ng-format", function(){ +    compile('<input type="Text" name="list" value="a,b,c" ng-format="list"/>'); +    expect(scope.get('list')).toEqual(['a', 'b', 'c']); + +    scope.set('list', ['x', 'y', 'z']); +    scope.updateView(); +    expect(element.attr('value')).toEqual("x, y, z"); + +    element.attr('value', '1, 2, 3'); +    element.trigger('keyup'); +    expect(scope.get('list')).toEqual(['1', '2', '3']); +  }); + +  it("should process ng-validation", function(){ +    compile('<input type="text" name="price" value="abc" ng-validate="number"/>'); +    expect(element.hasClass('ng-validation-error')).toBeTruthy(); +    expect(element.attr('ng-error')).toEqual('Not a number'); + +    scope.set('price', '123'); +    scope.updateView(); +    expect(element.hasClass('ng-validation-error')).toBeFalsy(); +    expect(element.attr('ng-error')).toBeFalsy(); + +    element.attr('value', 'x'); +    element.trigger('keyup'); +    expect(element.hasClass('ng-validation-error')).toBeTruthy(); +    expect(element.attr('ng-error')).toEqual('Not a number'); +  }); + +  it("should process ng-required", function(){ +    compile('<input type="text" name="price" ng-required/>'); +    expect(element.hasClass('ng-validation-error')).toBeTruthy(); +    expect(element.attr('ng-error')).toEqual('Required'); + +    scope.set('price', 'xxx'); +    scope.updateView(); +    expect(element.hasClass('ng-validation-error')).toBeFalsy(); +    expect(element.attr('ng-error')).toBeFalsy(); + +    element.attr('value', ''); +    element.trigger('keyup'); +    expect(element.hasClass('ng-validation-error')).toBeTruthy(); +    expect(element.attr('ng-error')).toEqual('Required');    });  });  | 
