diff options
| -rw-r--r-- | src/Angular.js | 6 | ||||
| -rw-r--r-- | src/Compiler.js | 4 | ||||
| -rw-r--r-- | src/Scope.js | 40 | ||||
| -rw-r--r-- | src/directives.js | 2 | ||||
| -rw-r--r-- | src/widgets.js | 14 | ||||
| -rw-r--r-- | test/ScopeSpec.js | 23 | ||||
| -rw-r--r-- | test/testabilityPatch.js | 3 | ||||
| -rw-r--r-- | test/widgetsSpec.js | 32 |
8 files changed, 106 insertions, 18 deletions
diff --git a/src/Angular.js b/src/Angular.js index 7ea43c98..ef1187f2 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -124,9 +124,9 @@ function jqLiteWrap(element) { } function isUndefined(value){ return typeof value == $undefined; } function isDefined(value){ return typeof value != $undefined; } -function isObject(value){ return value!=_null && typeof value == 'object';} -function isString(value){ return typeof value == 'string';} -function isNumber(value){ return typeof value == 'number';} +function isObject(value){ return value!=_null && typeof value == $object;} +function isString(value){ return typeof value == $string;} +function isNumber(value){ return typeof value == $number;} function isArray(value) { return value instanceof Array; } function isFunction(value){ return typeof value == $function;} function isTextNode(node) { return nodeName(node) == '#text'; } diff --git a/src/Compiler.js b/src/Compiler.js index c45dd46e..6252e0c4 100644 --- a/src/Compiler.js +++ b/src/Compiler.js @@ -8,7 +8,7 @@ function Template(priority) { this.paths = []; this.children = []; this.inits = []; - this.priority = priority || 0; + this.priority = priority; } Template.prototype = { @@ -130,7 +130,7 @@ Compiler.prototype = { priority = priority || 0; } if (isString(priority)) { - priority = PRIORITY[uppercase(priority)] || 0; + priority = PRIORITY[uppercase(priority)] || parseInt(priority); } template = new Template(priority); eachAttribute(element, function(value, name){ diff --git a/src/Scope.js b/src/Scope.js index c2a4f098..53228a0d 100644 --- a/src/Scope.js +++ b/src/Scope.js @@ -22,7 +22,7 @@ function getter(instance, path, unboundFn) { } } } - if (!unboundFn && isFunction(instance) && !instance['$$factory']) { + if (!unboundFn && isFunction(instance)) { return bind(lastInstance, instance); } return instance; @@ -113,12 +113,13 @@ function createScope(parent, services, existing) { function API(){} function Behavior(){} - var instance, behavior, api, evalLists = {sorted:[]}, servicesCache = extend({}, existing); - parent = Parent.prototype = (parent || {}); - api = API.prototype = new Parent(); - behavior = Behavior.prototype = new API(); - instance = new Behavior(); + var evalLists = {sorted:[]}; + var postList = [], postHash = {}, postId = 0; + var servicesCache = extend({}, existing); + var api = API.prototype = new Parent(); + var behavior = Behavior.prototype = new API(); + var instance = new Behavior(); extend(api, { 'this': instance, @@ -130,14 +131,23 @@ function createScope(parent, services, existing) { $eval: function $eval(exp) { var type = typeof exp; + var i, iSize; + var j, jSize; + var queue; + var fn; if (type == $undefined) { - for ( var i = 0, iSize = evalLists.sorted.length; i < iSize; i++) { - for ( var queue = evalLists.sorted[i], + for ( i = 0, iSize = evalLists.sorted.length; i < iSize; i++) { + for ( queue = evalLists.sorted[i], jSize = queue.length, j= 0; j < jSize; j++) { instance.$tryEval(queue[j].fn, queue[j].handler); } } + while(postList.length) { + fn = postList.shift(); + delete postHash[fn.$postEvalId]; + instance.$tryEval(fn); + } } else if (type === $function) { return exp.call(instance); } else if (type === 'string') { @@ -202,6 +212,20 @@ function createScope(parent, services, existing) { }); }, + $postEval: function(expr) { + if (expr) { + var fn = expressionCompile(expr); + var id = fn.$postEvalId; + if (!id) { + id = '$' + instance.$id + "_" + (postId++); + fn.$postEvalId = id; + } + if (!postHash[id]) { + postList.push(postHash[id] = fn); + } + } + }, + $become: function(Class) { // remove existing foreach(behavior, function(value, key){ delete behavior[key]; }); diff --git a/src/directives.js b/src/directives.js index 994ef90e..69648e31 100644 --- a/src/directives.js +++ b/src/directives.js @@ -115,6 +115,7 @@ var REMOVE_ATTRIBUTES = { angularDirective("ng:bind-attr", function(expression){ return function(element){ var lastValue = {}; + var updateFn = element.parent().data('$update'); this.$onEval(function(){ var values = this.$eval(expression); for(var key in values) { @@ -132,6 +133,7 @@ angularDirective("ng:bind-attr", function(expression){ } else { element.attr(key, value); } + this.$postEval(updateFn); } } }, element); diff --git a/src/widgets.js b/src/widgets.js index b70c4dcb..fbca8436 100644 --- a/src/widgets.js +++ b/src/widgets.js @@ -209,7 +209,11 @@ function inputWidget(events, modelAccessor, viewAccessor, initFn) { event.preventDefault(); }); } - view.set(lastValue = model.get()); + function updateView(){ + view.set(lastValue = model.get()); + } + updateView(); + element.data('$update', updateView); scope.$watch(model.get, function(value){ if (lastValue !== value) { view.set(lastValue = value); @@ -231,6 +235,14 @@ angularWidget('select', function(element){ return inputWidgetSelector.call(this, element); }); +angularWidget('option', function(){ + this.descend(true); + this.directives(true); + return function(element) { + this.$postEval(element.parent().data('$update')); + }; +}); + angularWidget('ng:include', function(element){ var compiler = this, diff --git a/test/ScopeSpec.js b/test/ScopeSpec.js index ea63fea4..66e9d489 100644 --- a/test/ScopeSpec.js +++ b/test/ScopeSpec.js @@ -192,4 +192,27 @@ describe('scope/model', function(){ }); }); + + describe('$postEval', function(){ + it('should eval function once and last', function(){ + var log = ''; + var scope = createScope(); + function onceOnly(){log+= '@';} + scope.$onEval(function(){log+= '.';}); + scope.$postEval(function(){log+= '!';}); + scope.$postEval(onceOnly); + scope.$postEval(onceOnly); + scope.$postEval(); // ignore + scope.$eval(); + expect(log).toEqual('.!@'); + scope.$eval(); + expect(log).toEqual('.!@.'); + + scope.$postEval(onceOnly); + scope.$postEval(onceOnly); + scope.$eval(); + expect(log).toEqual('.!@..@'); + }); + }); + }); diff --git a/test/testabilityPatch.js b/test/testabilityPatch.js index bbe2876e..955dccfa 100644 --- a/test/testabilityPatch.js +++ b/test/testabilityPatch.js @@ -59,7 +59,7 @@ extend(angular, { function sortedHtml(element) { var html = ""; - foreach(element, function toString(node) { + foreach(jqLite(element), function toString(node) { if (node.nodeName == "#text") { html += escapeHtml(node.nodeValue); } else { @@ -188,6 +188,7 @@ function click(element) { } } if (name == 'option') { + element.parent().val(element.val()); JQLite.prototype.trigger.call(element.parent(), 'change'); } else { JQLite.prototype.trigger.call(element, 'click'); diff --git a/test/widgetsSpec.js b/test/widgetsSpec.js index 3861ef4f..d113e6ee 100644 --- a/test/widgetsSpec.js +++ b/test/widgetsSpec.js @@ -355,9 +355,9 @@ describe("widget", function(){ it('should honor the value field in option', function(){ compile( '<select name="selection" ng:format="number">' + - '<option value="{{$index}}" ng:repeat="name in [\'A\', \'B\']">{{name}}</option>' + + '<option value="{{$index}}" ng:repeat="name in [\'A\', \'B\', \'C\']">{{name}}</option>' + '</select>'); - // childNodes[0] is repeater + // childNodes[0] is repeater comment expect(scope.selection).toEqual(undefined); click(element[0].childNodes[1]); @@ -367,6 +367,32 @@ describe("widget", function(){ scope.$eval(); expect(element[0].childNodes[2].selected).toEqual(true); }); + + it('should unroll select options before eval', function(){ + compile( + '<select name="selection" ng:required>' + + '<option value="{{$index}}" ng:repeat="opt in options">{{opt}}</option>' + + '</select>'); + scope.selection = 1; + scope.options = ['one', 'two']; + scope.$eval(); + expect(element[0].value).toEqual('1'); + expect(element.hasClass(NG_VALIDATION_ERROR)).toEqual(false); + }); + + it('should update select when value changes', function(){ + compile( + '<select name="selection">' + + '<option value="...">...</option>' + + '<option value="{{value}}">B</option>' + + '</select>'); + scope.selection = 'B'; + scope.$eval(); + expect(element[0].childNodes[1].selected).toEqual(false); + scope.value = 'B'; + scope.$eval(); + expect(element[0].childNodes[1].selected).toEqual(true); + }); }); it('should support type="select-multiple"', function(){ @@ -468,7 +494,7 @@ describe("widget", function(){ scope.url = undefined; scope.$eval(); - + expect(element.text()).toEqual(''); }); }); |
