Skip to content

Commit 006fd2c

Browse files
committed
HEAD is now at 10c0151 Fixes on issue when a SELECT has OPTION which are data bound (ie OPTION has repeater or OPTION.value is bound), then SELECT does not update to match the correct OPTION after the change in model (ie after the OPTION repeater unrolls or OPTION.value is changed.)
1 parent 125d725 commit 006fd2c

File tree

8 files changed

+106
-18
lines changed

8 files changed

+106
-18
lines changed

src/Angular.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,9 @@ function jqLiteWrap(element) {
124124
}
125125
function isUndefined(value){ return typeof value == $undefined; }
126126
function isDefined(value){ return typeof value != $undefined; }
127-
function isObject(value){ return value!=_null && typeof value == 'object';}
128-
function isString(value){ return typeof value == 'string';}
129-
function isNumber(value){ return typeof value == 'number';}
127+
function isObject(value){ return value!=_null && typeof value == $object;}
128+
function isString(value){ return typeof value == $string;}
129+
function isNumber(value){ return typeof value == $number;}
130130
function isArray(value) { return value instanceof Array; }
131131
function isFunction(value){ return typeof value == $function;}
132132
function isTextNode(node) { return nodeName(node) == '#text'; }

src/Compiler.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ function Template(priority) {
88
this.paths = [];
99
this.children = [];
1010
this.inits = [];
11-
this.priority = priority || 0;
11+
this.priority = priority;
1212
}
1313

1414
Template.prototype = {
@@ -130,7 +130,7 @@ Compiler.prototype = {
130130
priority = priority || 0;
131131
}
132132
if (isString(priority)) {
133-
priority = PRIORITY[uppercase(priority)] || 0;
133+
priority = PRIORITY[uppercase(priority)] || parseInt(priority);
134134
}
135135
template = new Template(priority);
136136
eachAttribute(element, function(value, name){

src/Scope.js

+32-8
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ function getter(instance, path, unboundFn) {
2222
}
2323
}
2424
}
25-
if (!unboundFn && isFunction(instance) && !instance['$$factory']) {
25+
if (!unboundFn && isFunction(instance)) {
2626
return bind(lastInstance, instance);
2727
}
2828
return instance;
@@ -113,12 +113,13 @@ function createScope(parent, services, existing) {
113113
function API(){}
114114
function Behavior(){}
115115

116-
var instance, behavior, api, evalLists = {sorted:[]}, servicesCache = extend({}, existing);
117-
118116
parent = Parent.prototype = (parent || {});
119-
api = API.prototype = new Parent();
120-
behavior = Behavior.prototype = new API();
121-
instance = new Behavior();
117+
var evalLists = {sorted:[]};
118+
var postList = [], postHash = {}, postId = 0;
119+
var servicesCache = extend({}, existing);
120+
var api = API.prototype = new Parent();
121+
var behavior = Behavior.prototype = new API();
122+
var instance = new Behavior();
122123

123124
extend(api, {
124125
'this': instance,
@@ -130,14 +131,23 @@ function createScope(parent, services, existing) {
130131

131132
$eval: function $eval(exp) {
132133
var type = typeof exp;
134+
var i, iSize;
135+
var j, jSize;
136+
var queue;
137+
var fn;
133138
if (type == $undefined) {
134-
for ( var i = 0, iSize = evalLists.sorted.length; i < iSize; i++) {
135-
for ( var queue = evalLists.sorted[i],
139+
for ( i = 0, iSize = evalLists.sorted.length; i < iSize; i++) {
140+
for ( queue = evalLists.sorted[i],
136141
jSize = queue.length,
137142
j= 0; j < jSize; j++) {
138143
instance.$tryEval(queue[j].fn, queue[j].handler);
139144
}
140145
}
146+
while(postList.length) {
147+
fn = postList.shift();
148+
delete postHash[fn.$postEvalId];
149+
instance.$tryEval(fn);
150+
}
141151
} else if (type === $function) {
142152
return exp.call(instance);
143153
} else if (type === 'string') {
@@ -202,6 +212,20 @@ function createScope(parent, services, existing) {
202212
});
203213
},
204214

215+
$postEval: function(expr) {
216+
if (expr) {
217+
var fn = expressionCompile(expr);
218+
var id = fn.$postEvalId;
219+
if (!id) {
220+
id = '$' + instance.$id + "_" + (postId++);
221+
fn.$postEvalId = id;
222+
}
223+
if (!postHash[id]) {
224+
postList.push(postHash[id] = fn);
225+
}
226+
}
227+
},
228+
205229
$become: function(Class) {
206230
// remove existing
207231
foreach(behavior, function(value, key){ delete behavior[key]; });

src/directives.js

+2
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ var REMOVE_ATTRIBUTES = {
115115
angularDirective("ng:bind-attr", function(expression){
116116
return function(element){
117117
var lastValue = {};
118+
var updateFn = element.parent().data('$update');
118119
this.$onEval(function(){
119120
var values = this.$eval(expression);
120121
for(var key in values) {
@@ -132,6 +133,7 @@ angularDirective("ng:bind-attr", function(expression){
132133
} else {
133134
element.attr(key, value);
134135
}
136+
this.$postEval(updateFn);
135137
}
136138
}
137139
}, element);

src/widgets.js

+13-1
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,11 @@ function inputWidget(events, modelAccessor, viewAccessor, initFn) {
209209
event.preventDefault();
210210
});
211211
}
212-
view.set(lastValue = model.get());
212+
function updateView(){
213+
view.set(lastValue = model.get());
214+
}
215+
updateView();
216+
element.data('$update', updateView);
213217
scope.$watch(model.get, function(value){
214218
if (lastValue !== value) {
215219
view.set(lastValue = value);
@@ -231,6 +235,14 @@ angularWidget('select', function(element){
231235
return inputWidgetSelector.call(this, element);
232236
});
233237

238+
angularWidget('option', function(){
239+
this.descend(true);
240+
this.directives(true);
241+
return function(element) {
242+
this.$postEval(element.parent().data('$update'));
243+
};
244+
});
245+
234246

235247
angularWidget('ng:include', function(element){
236248
var compiler = this,

test/ScopeSpec.js

+23
Original file line numberDiff line numberDiff line change
@@ -192,4 +192,27 @@ describe('scope/model', function(){
192192

193193
});
194194
});
195+
196+
describe('$postEval', function(){
197+
it('should eval function once and last', function(){
198+
var log = '';
199+
var scope = createScope();
200+
function onceOnly(){log+= '@';}
201+
scope.$onEval(function(){log+= '.';});
202+
scope.$postEval(function(){log+= '!';});
203+
scope.$postEval(onceOnly);
204+
scope.$postEval(onceOnly);
205+
scope.$postEval(); // ignore
206+
scope.$eval();
207+
expect(log).toEqual('.!@');
208+
scope.$eval();
209+
expect(log).toEqual('.!@.');
210+
211+
scope.$postEval(onceOnly);
212+
scope.$postEval(onceOnly);
213+
scope.$eval();
214+
expect(log).toEqual('.!@..@');
215+
});
216+
});
217+
195218
});

test/testabilityPatch.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ extend(angular, {
5959

6060
function sortedHtml(element) {
6161
var html = "";
62-
foreach(element, function toString(node) {
62+
foreach(jqLite(element), function toString(node) {
6363
if (node.nodeName == "#text") {
6464
html += escapeHtml(node.nodeValue);
6565
} else {
@@ -188,6 +188,7 @@ function click(element) {
188188
}
189189
}
190190
if (name == 'option') {
191+
element.parent().val(element.val());
191192
JQLite.prototype.trigger.call(element.parent(), 'change');
192193
} else {
193194
JQLite.prototype.trigger.call(element, 'click');

test/widgetsSpec.js

+29-3
Original file line numberDiff line numberDiff line change
@@ -355,9 +355,9 @@ describe("widget", function(){
355355
it('should honor the value field in option', function(){
356356
compile(
357357
'<select name="selection" ng:format="number">' +
358-
'<option value="{{$index}}" ng:repeat="name in [\'A\', \'B\']">{{name}}</option>' +
358+
'<option value="{{$index}}" ng:repeat="name in [\'A\', \'B\', \'C\']">{{name}}</option>' +
359359
'</select>');
360-
// childNodes[0] is repeater
360+
// childNodes[0] is repeater comment
361361
expect(scope.selection).toEqual(undefined);
362362

363363
click(element[0].childNodes[1]);
@@ -367,6 +367,32 @@ describe("widget", function(){
367367
scope.$eval();
368368
expect(element[0].childNodes[2].selected).toEqual(true);
369369
});
370+
371+
it('should unroll select options before eval', function(){
372+
compile(
373+
'<select name="selection" ng:required>' +
374+
'<option value="{{$index}}" ng:repeat="opt in options">{{opt}}</option>' +
375+
'</select>');
376+
scope.selection = 1;
377+
scope.options = ['one', 'two'];
378+
scope.$eval();
379+
expect(element[0].value).toEqual('1');
380+
expect(element.hasClass(NG_VALIDATION_ERROR)).toEqual(false);
381+
});
382+
383+
it('should update select when value changes', function(){
384+
compile(
385+
'<select name="selection">' +
386+
'<option value="...">...</option>' +
387+
'<option value="{{value}}">B</option>' +
388+
'</select>');
389+
scope.selection = 'B';
390+
scope.$eval();
391+
expect(element[0].childNodes[1].selected).toEqual(false);
392+
scope.value = 'B';
393+
scope.$eval();
394+
expect(element[0].childNodes[1].selected).toEqual(true);
395+
});
370396
});
371397

372398
it('should support type="select-multiple"', function(){
@@ -468,7 +494,7 @@ describe("widget", function(){
468494

469495
scope.url = undefined;
470496
scope.$eval();
471-
497+
472498
expect(element.text()).toEqual('');
473499
});
474500
});

0 commit comments

Comments
 (0)