Skip to content

Commit 3f9a2ab

Browse files
committed
added asynchronous validator
1 parent 7c8a2cc commit 3f9a2ab

File tree

7 files changed

+217
-91
lines changed

7 files changed

+217
-91
lines changed

angular-minified.js

Lines changed: 88 additions & 87 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

angular.js

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2468,11 +2468,11 @@ Parser.prototype = {
24682468
},
24692469

24702470
filter: function(){
2471-
return this._pipeFunction(angular['filter']);
2471+
return this._pipeFunction(angularFilter);
24722472
},
24732473

24742474
validator: function(){
2475-
return this._pipeFunction(angular['validator']);
2475+
return this._pipeFunction(angularValidator);
24762476
},
24772477

24782478
_pipeFunction: function(fnScope){
@@ -3240,7 +3240,36 @@ foreach({
32403240
} catch (e) {
32413241
return e.toString();
32423242
}
3243+
},
3244+
3245+
'asynchronous': function(text, asynchronousFn) {
3246+
var stateKey = '$validateState';
3247+
var lastKey = '$lastKey';
3248+
var obj = this['$element'];
3249+
var stateCache = obj[stateKey] = obj[stateKey] || {};
3250+
var state = stateCache[text];
3251+
var updateView = this['$updateView'];
3252+
obj[lastKey] = text;
3253+
if (state === undefined) {
3254+
// we have never seen this before, Request it
3255+
jQuery(obj).addClass('ng-input-indicator-wait');
3256+
state = stateCache[text] = null;
3257+
asynchronousFn(text, function(error){
3258+
state = stateCache[text] = error ? error : false;
3259+
if (stateCache[obj[lastKey]] !== null) {
3260+
jQuery(obj).removeClass('ng-input-indicator-wait');
3261+
}
3262+
updateView();
3263+
});
3264+
}
3265+
3266+
if (state === null){
3267+
// request in flight, mark widget invalid, but don't show it to user
3268+
this['$invalidWidgets'].push(this.$element);
3269+
}
3270+
return state;
32433271
}
3272+
32443273
}, function(v,k) {angularValidator[k] = v;});
32453274
function WidgetFactory(serverUrl, database) {
32463275
this.nextUploadId = 0;

css/angular.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,3 +182,8 @@ div.ui-widget {
182182
background-image: url("angular_images/indicator-wait.png");
183183
}
184184

185+
.ng-input-indicator-wait {
186+
background-image: url("angular_images/indicator-wait.png");
187+
background-position: right;
188+
background-repeat: no-repeat;
189+
}

example/widgets.html

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@
88
$(document).ready(function(){
99
angular.compile(document).init();
1010
});
11+
function asyncValidate(value, callback){
12+
var x = value.length % 2 ? null: "even";
13+
//callback(x);
14+
_(callback).delay(1000, x);
15+
}
1116
</script>
1217
<link rel="StyleSheet" type="text/css" href="../css/angular.css"/>
1318
</head>
@@ -20,6 +25,8 @@
2025
<hr/>
2126
<input type="checkbox" name="form.boolean" ng-format="boolean" value="true" checked="checked" />
2227
<input type="checkbox" name="form.boolean" ng-format="boolean" value="true" />
28+
<hr/>
29+
<input type="text" name="form.async" ng-validate="asynchronous:$window.asyncValidate" />
2330
<pre>
2431
form={{form}}
2532
$invalidWidgets.length={{$invalidWidgets.length}}

src/Parser.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -349,11 +349,11 @@ Parser.prototype = {
349349
},
350350

351351
filter: function(){
352-
return this._pipeFunction(angular['filter']);
352+
return this._pipeFunction(angularFilter);
353353
},
354354

355355
validator: function(){
356-
return this._pipeFunction(angular['validator']);
356+
return this._pipeFunction(angularValidator);
357357
},
358358

359359
_pipeFunction: function(fnScope){

src/Validators.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,5 +77,34 @@ foreach({
7777
} catch (e) {
7878
return e.toString();
7979
}
80+
},
81+
82+
'asynchronous': function(text, asynchronousFn) {
83+
var stateKey = '$validateState';
84+
var lastKey = '$lastKey';
85+
var obj = this['$element'];
86+
var stateCache = obj[stateKey] = obj[stateKey] || {};
87+
var state = stateCache[text];
88+
var updateView = this['$updateView'];
89+
obj[lastKey] = text;
90+
if (state === undefined) {
91+
// we have never seen this before, Request it
92+
jQuery(obj).addClass('ng-input-indicator-wait');
93+
state = stateCache[text] = null;
94+
asynchronousFn(text, function(error){
95+
state = stateCache[text] = error ? error : false;
96+
if (stateCache[obj[lastKey]] !== null) {
97+
jQuery(obj).removeClass('ng-input-indicator-wait');
98+
}
99+
updateView();
100+
});
101+
}
102+
103+
if (state === null){
104+
// request in flight, mark widget invalid, but don't show it to user
105+
this['$invalidWidgets'].push(this.$element);
106+
}
107+
return state;
80108
}
109+
81110
}, function(v,k) {angularValidator[k] = v;});

test/ValidatorsTest.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,58 @@ ValidatorTest.prototype.testJson = function() {
8181
assertNotNull(angular.validator.json("''X"));
8282
assertNull(angular.validator.json("{}"));
8383
};
84+
85+
describe('Validator:asynchronous', function(){
86+
var asynchronous = angular.validator.asynchronous;
87+
var self;
88+
var value, fn;
89+
90+
beforeEach(function(){
91+
value = null;
92+
fn = null;
93+
self = {
94+
$element:$('<input />')[0],
95+
$invalidWidgets:[],
96+
$updateView: noop
97+
};
98+
});
99+
100+
it('should make a request and show spinner', function(){
101+
var x = compile('<input name="name" ng-validate="asynchronous:asyncFn"/>')
102+
var asyncFn = function(v,f){value=v; fn=f};
103+
var input = x.node.find(":input");
104+
x.scope.set("asyncFn", asyncFn);
105+
x.scope.set("name", "misko");
106+
x.binder.updateView();
107+
expect(value).toEqual('misko');
108+
expect(input.hasClass('ng-input-indicator-wait')).toBeTruthy();
109+
fn("myError");
110+
expect(input.hasClass('ng-input-indicator-wait')).toBeFalsy();
111+
expect(input.attr('ng-error')).toEqual("myError");
112+
});
113+
114+
it("should not make second request to same value", function(){
115+
asynchronous.call(self, "kai", function(v,f){value=v; fn=f;});
116+
expect(value).toEqual('kai');
117+
expect(self.$invalidWidgets).toEqual([self.$element]);
118+
119+
var spy = jasmine.createSpy();
120+
asynchronous.call(self, "kai", spy);
121+
expect(spy).wasNotCalled();
122+
123+
asynchronous.call(self, "misko", spy);
124+
expect(spy).wasCalled();
125+
});
126+
127+
it("should ignore old callbacks, and not remove spinner", function(){
128+
var firstCb, secondCb;
129+
asynchronous.call(self, "first", function(v,f){value=v; firstCb=f;});
130+
asynchronous.call(self, "second", function(v,f){value=v; secondCb=f;});
131+
132+
firstCb();
133+
expect($(self.$element).hasClass('ng-input-indicator-wait')).toBeTruthy();
134+
135+
secondCb();
136+
expect($(self.$element).hasClass('ng-input-indicator-wait')).toBeFalsy();
137+
});
138+
});

0 commit comments

Comments
 (0)