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('');      });    });  | 
