mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-05-12 08:53:10 +00:00
input[type=text] now works with binding, validation, formatter, required
This commit is contained in:
parent
3d36942400
commit
0c42eb9909
7 changed files with 190 additions and 60 deletions
|
|
@ -44,6 +44,8 @@ function extensionList(angular, name) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var consoleNode, msie,
|
var consoleNode, msie,
|
||||||
|
VALUE = 'value',
|
||||||
|
NOOP = 'noop',
|
||||||
jQuery = window['jQuery'] || window['$'], // weirdness to make IE happy
|
jQuery = window['jQuery'] || window['$'], // weirdness to make IE happy
|
||||||
slice = Array.prototype.slice,
|
slice = Array.prototype.slice,
|
||||||
angular = window['angular'] || (window['angular'] = {}),
|
angular = window['angular'] || (window['angular'] = {}),
|
||||||
|
|
@ -92,6 +94,9 @@ function isObject(value){ return typeof value == 'object';}
|
||||||
function isString(value){ return typeof value == 'string';}
|
function isString(value){ return typeof value == 'string';}
|
||||||
function isArray(value) { return value instanceof Array; }
|
function isArray(value) { return value instanceof Array; }
|
||||||
function isFunction(value){ return typeof value == 'function';}
|
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){
|
function log(a, b, c){
|
||||||
var console = window['console'];
|
var console = window['console'];
|
||||||
|
|
@ -244,10 +249,6 @@ function outerHTML(node) {
|
||||||
return outerHTML;
|
return outerHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
function trim(str) {
|
|
||||||
return str.replace(/^ */, '').replace(/ *$/, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
function toBoolean(value) {
|
function toBoolean(value) {
|
||||||
var v = ("" + value).toLowerCase();
|
var v = ("" + value).toLowerCase();
|
||||||
if (v == 'f' || v == '0' || v == 'false' || v == 'no')
|
if (v == 'f' || v == '0' || v == 'false' || v == 'no')
|
||||||
|
|
|
||||||
|
|
@ -105,9 +105,7 @@ Compiler.prototype = {
|
||||||
|
|
||||||
templatize: function(element){
|
templatize: function(element){
|
||||||
var self = this,
|
var self = this,
|
||||||
elementName = element[0].nodeName,
|
widget = self.widgets[element[0].nodeName],
|
||||||
widgets = self.widgets,
|
|
||||||
widget = widgets[elementName],
|
|
||||||
directives = self.directives,
|
directives = self.directives,
|
||||||
descend = true,
|
descend = true,
|
||||||
exclusive = false,
|
exclusive = false,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
foreach({
|
foreach({
|
||||||
|
'noop': noop,
|
||||||
|
|
||||||
'regexp': function(value, regexp, msg) {
|
'regexp': function(value, regexp, msg) {
|
||||||
if (!value.match(regexp)) {
|
if (!value.match(regexp)) {
|
||||||
return msg ||
|
return msg ||
|
||||||
|
|
@ -19,7 +21,7 @@ foreach({
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
return "Value is not a number.";
|
return "Not a number";
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -27,7 +29,7 @@ foreach({
|
||||||
var numberError = angularValidator['number'](value, min, max);
|
var numberError = angularValidator['number'](value, min, max);
|
||||||
if (numberError) return numberError;
|
if (numberError) return numberError;
|
||||||
if (!("" + value).match(/^\s*[\d+]*\s*$/) || value != Math.round(value)) {
|
if (!("" + value).match(/^\s*[\d+]*\s*$/) || value != Math.round(value)) {
|
||||||
return "Value is not a whole number.";
|
return "Not a whole number";
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -77,22 +77,24 @@ JQLite.prototype = {
|
||||||
},
|
},
|
||||||
|
|
||||||
bind: function(type, fn){
|
bind: function(type, fn){
|
||||||
var element = this[0],
|
var self = this,
|
||||||
bind = this.data('bind'),
|
element = self[0],
|
||||||
|
bind = self.data('bind'),
|
||||||
eventHandler;
|
eventHandler;
|
||||||
if (!bind) this.data('bind', bind = {});
|
if (!bind) this.data('bind', bind = {});
|
||||||
eventHandler = bind[type];
|
foreach(type.split(' '), function(type){
|
||||||
if (!eventHandler) {
|
eventHandler = bind[type];
|
||||||
bind[type] = eventHandler = function() {
|
if (!eventHandler) {
|
||||||
var self = this;
|
bind[type] = eventHandler = function() {
|
||||||
foreach(eventHandler.fns, function(fn){
|
foreach(eventHandler.fns, function(fn){
|
||||||
fn.apply(self, arguments);
|
fn.apply(self, arguments);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
eventHandler.fns = [];
|
eventHandler.fns = [];
|
||||||
addEventListener(element, type, eventHandler);
|
addEventListener(element, type, eventHandler);
|
||||||
}
|
}
|
||||||
eventHandler.fns.push(fn);
|
eventHandler.fns.push(fn);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
trigger: function(type) {
|
trigger: function(type) {
|
||||||
|
|
@ -134,6 +136,10 @@ JQLite.prototype = {
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
removeClass: function(selector) {
|
||||||
|
this[0].className = trim((" " + this[0].className + " ").replace(/[\n\t]/g, " ").replace(" " + selector + " ", ""));
|
||||||
|
},
|
||||||
|
|
||||||
addClass: function( selector ) {
|
addClass: function( selector ) {
|
||||||
if (!this.hasClass(selector)) {
|
if (!this.hasClass(selector)) {
|
||||||
this[0].className += ' ' + selector;
|
this[0].className += ' ' + selector;
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,79 @@
|
||||||
/////////////////////////////////////////
|
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;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/////////////////////////////////////////
|
||||||
|
/////////////////////////////////////////
|
||||||
|
/////////////////////////////////////////
|
||||||
|
/////////////////////////////////////////
|
||||||
|
/////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//widget related
|
//widget related
|
||||||
//ng-validate, ng-required, ng-formatter
|
//ng-validate, ng-required, ng-formatter
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ ValidatorTest.prototype.testRegexp = function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
ValidatorTest.prototype.testNumber = 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("-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("10.1",0,10), "Value can not be greater than 10.");
|
||||||
assertEquals(angular.validator.number("1.2"), null);
|
assertEquals(angular.validator.number("1.2"), null);
|
||||||
|
|
@ -34,10 +34,10 @@ ValidatorTest.prototype.testNumber = function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
ValidatorTest.prototype.testInteger = function() {
|
ValidatorTest.prototype.testInteger = function() {
|
||||||
assertEquals(angular.validator.integer("ab"), "Value is not a number.");
|
assertEquals(angular.validator.integer("ab"), "Not a number");
|
||||||
assertEquals(angular.validator.integer("1.1"), "Value is not a whole number.");
|
assertEquals(angular.validator.integer("1.1"), "Not a whole number");
|
||||||
assertEquals(angular.validator.integer("1.0"), "Value is not a whole number.");
|
assertEquals(angular.validator.integer("1.0"), "Not a whole number");
|
||||||
assertEquals(angular.validator.integer("1."), "Value is 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("-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("11",0,10), "Value can not be greater than 10.");
|
||||||
assertEquals(angular.validator.integer("1"), null);
|
assertEquals(angular.validator.integer("1"), null);
|
||||||
|
|
@ -98,8 +98,8 @@ describe('Validator:asynchronous', function(){
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should make a request and show spinner', function(){
|
it('should make a request and show spinner', function(){
|
||||||
var x = compile('<input name="name" ng-validate="asynchronous:asyncFn"/>')
|
var x = compile('<input name="name" ng-validate="asynchronous:asyncFn"/>');
|
||||||
var asyncFn = function(v,f){value=v; fn=f};
|
var asyncFn = function(v,f){value=v; fn=f;};
|
||||||
var input = x.node.find(":input");
|
var input = x.node.find(":input");
|
||||||
x.scope.set("asyncFn", asyncFn);
|
x.scope.set("asyncFn", asyncFn);
|
||||||
x.scope.set("name", "misko");
|
x.scope.set("name", "misko");
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
describe("widgets", function(){
|
describe("input widget", function(){
|
||||||
|
|
||||||
var compile, element, scope;
|
var compile, element, scope;
|
||||||
|
|
||||||
|
|
@ -15,14 +15,70 @@ describe("widgets", function(){
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function(){
|
afterEach(function(){
|
||||||
if (element) {
|
if (element) element.remove();
|
||||||
element.remove();
|
|
||||||
}
|
|
||||||
expect(_(jqCache).size()).toEqual(0);
|
expect(_(jqCache).size()).toEqual(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fail', function(){
|
it('should input-text auto init and handle keyup/change events', function(){
|
||||||
fail('iueoi');
|
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');
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue