From 0c42eb9909d554807549cd3394e0ea0c715cc2d1 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Wed, 24 Mar 2010 16:13:42 -0700 Subject: input[type=text] now works with binding, validation, formatter, required --- src/Angular.js | 9 +++---- src/Compiler.js | 4 +--- src/Validators.js | 24 ++++++++++--------- src/jqLite.js | 34 +++++++++++++++----------- src/widgets2.js | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 108 insertions(+), 34 deletions(-) (limited to 'src') 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 -- cgit v1.2.3