diff options
| author | Misko Hevery | 2010-03-18 12:20:06 -0700 | 
|---|---|---|
| committer | Misko Hevery | 2010-03-18 12:20:06 -0700 | 
| commit | 7634a3ed5227f8bc2a2ba83752d0e2c78adb6051 (patch) | |
| tree | ef94843f25eae83d0492b18149a084868829693c | |
| parent | f1b50b92ac69f5c58984f5e88015507552d29df2 (diff) | |
| download | angular.js-7634a3ed5227f8bc2a2ba83752d0e2c78adb6051.tar.bz2 | |
initial revision of new plugable compiler
| -rw-r--r-- | src/Angular.js | 10 | ||||
| -rw-r--r-- | src/DataStore.js | 32 | ||||
| -rw-r--r-- | src/Resource.js | 4 | ||||
| -rw-r--r-- | src/Scope.js | 5 | ||||
| -rw-r--r-- | src/directives.js | 14 | ||||
| -rw-r--r-- | test/CompilerSpec.js | 186 | 
6 files changed, 224 insertions, 27 deletions
| diff --git a/src/Angular.js b/src/Angular.js index 9b3634be..39a6e91d 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -24,9 +24,14 @@ var consoleNode, msie,      jQuery           = window['jQuery'] || window['$'], // weirdness to make IE happy      foreach          = _.each,      extend           = _.extend, +    slice            = Array.prototype.slice,      identity         = _.identity,      angular          = window['angular']    || (window['angular']    = {}),      angularValidator = angular['validator'] || (angular['validator'] = {}), +    angularDirective = angular['directive'] || (angular['directive'] = function(name, fn){ +      if (fn) {angularDirective[name] = fn;}; +      return angularDirective[name]; +    }),      angularFilter    = angular['filter']    || (angular['filter']    = {}),      angularFormatter = angular['formatter'] || (angular['formatter'] = {}),      angularCallbacks = angular['callbacks'] || (angular['callbacks'] = {}), @@ -37,7 +42,7 @@ angular['copy'] = copy;  var isVisible = isVisible || function (element) {    return jQuery(element).is(":visible"); -} +};  function log(a, b, c){    var console = window['console']; @@ -154,12 +159,13 @@ function escapeAttr(html) {  }  function bind(_this, _function) { +  var curryArgs = slice.call(arguments, 2, arguments.length);    if (!_this)      throw "Missing this";    if (!_.isFunction(_function))      throw "Missing function";    return function() { -    return _function.apply(_this, arguments); +    return _function.apply(_this, curryArgs.concat(slice.call(arguments, 0, arguments.length)));    };  } diff --git a/src/DataStore.js b/src/DataStore.js index 789b8f71..70bcc623 100644 --- a/src/DataStore.js +++ b/src/DataStore.js @@ -29,7 +29,7 @@ DataStore.prototype = {      }      return cachedDocument;    }, -   +    load: function(instance, id, callback, failure) {      if (id && id !== '*') {        var self = this; @@ -43,7 +43,7 @@ DataStore.prototype = {      }      return instance;    }, -   +    loadMany: function(entity, ids, callback) {      var self=this;      var list = []; @@ -58,7 +58,7 @@ DataStore.prototype = {      });      return list;    }, -   +    loadOrCreate: function(instance, id, callback) {      var self=this;      return this.load(instance, id, callback, function(response){ @@ -70,7 +70,7 @@ DataStore.prototype = {        }      });    }, -   +    loadAll: function(entity, callback) {      var self = this;      var list = []; @@ -89,7 +89,7 @@ DataStore.prototype = {      });      return list;    }, -   +    save: function(document, callback) {      var self = this;      var data = {}; @@ -109,7 +109,7 @@ DataStore.prototype = {          callback(document);      });    }, -   +    remove: function(document, callback) {      var self = this;      var data = {}; @@ -127,7 +127,7 @@ DataStore.prototype = {        (callback||noop)(response);      });    }, -   +    _jsonRequest: function(request, callback, failure) {      request['$$callback'] = callback;      request['$$failure'] = failure||function(response){ @@ -135,7 +135,7 @@ DataStore.prototype = {      };      this.bulkRequest.push(request);    }, -   +    flush: function() {      if (this.bulkRequest.length === 0) return;      var self = this; @@ -169,7 +169,7 @@ DataStore.prototype = {      }      this.post(bulkRequest, callback);    }, -   +    saveScope: function(scope, callback) {      var saveCounter = 1;      function onSaveDone() { @@ -186,7 +186,7 @@ DataStore.prototype = {      }      onSaveDone();    }, -   +    query: function(type, query, arg, callback){      var self = this;      var queryList = []; @@ -205,7 +205,7 @@ DataStore.prototype = {      });      return queryList;    }, -   +    entities: function(callback) {      var entities = [];      var self = this; @@ -218,7 +218,7 @@ DataStore.prototype = {      });      return entities;    }, -   +    documentCountsByUser: function(){      var counts = {};      var self = this; @@ -227,7 +227,7 @@ DataStore.prototype = {      });      return counts;    }, -   +    userDocumentIdsByEntity: function(user){      var ids = {};      var self = this; @@ -236,7 +236,7 @@ DataStore.prototype = {      });      return ids;    }, -   +    entity: function(name, defaults){      if (!name) {        return DataStore.NullEntity; @@ -271,7 +271,7 @@ DataStore.prototype = {      });      return entity;    }, -   +    join: function(join){      function fn(){        throw "Joined entities can not be instantiated into a document."; @@ -327,4 +327,4 @@ DataStore.prototype = {      };      return fn;    } -};
\ No newline at end of file +}; diff --git a/src/Resource.js b/src/Resource.js index 587c331e..971ad6e5 100644 --- a/src/Resource.js +++ b/src/Resource.js @@ -79,7 +79,7 @@ ResourceFactory.prototype = {          case 1: if (isPost) data = a1; else params = a1; break;          case 0: break;          default: -          throw "Expected between 0-3 arguments [params, data, callback], got " + arguments.length + " arguments." +          throw "Expected between 0-3 arguments [params, data, callback], got " + arguments.length + " arguments.";          }          var value = action.isArray ? [] : new Resource(data); @@ -109,7 +109,7 @@ ResourceFactory.prototype = {            case 1: if (typeof a1 == 'function') callback = a1; else params = a1;            case 0: break;            default: -            throw "Expected between 1-3 arguments [params, data, callback], got " + arguments.length + " arguments." +            throw "Expected between 1-3 arguments [params, data, callback], got " + arguments.length + " arguments.";            }            var self = this;            Resource[name](params, this, function(response){ diff --git a/src/Scope.js b/src/Scope.js index daf4b36c..442477b4 100644 --- a/src/Scope.js +++ b/src/Scope.js @@ -10,6 +10,7 @@ function Scope(initialState, name) {    if (name == "ROOT") {      this.state['$root'] = this.state;    } +  this.set('$watch', bind(this, this.addWatchListener));  };  Scope.expressionCache = {}; @@ -202,5 +203,9 @@ Scope.prototype = {        }      });      return fired; +  }, + +  apply: function(fn) { +    fn.apply(this.state, slice(arguments, 0, arguments.length));    }  }; diff --git a/src/directives.js b/src/directives.js index 0e99d633..7c5cc257 100644 --- a/src/directives.js +++ b/src/directives.js @@ -4,7 +4,7 @@ angular.directive("auth", function(expression, element){      if(expression == "eager") {        this.$users.fetchCurrent();      } -  } +  };  }); @@ -30,7 +30,7 @@ angular.directive("entity", function(expression, element){  angular.directive("init", function(expression, element){    return function(){      this.$eval(expresssion); -  } +  };  }); @@ -49,8 +49,8 @@ angular.directive("bind", function(expression, element){  // becomes  // <a href="" ng-bind-attr="{href:'http://example.com?id={{book.$id}}', alt:'{{book.$name}}'}">link</a>  angular.directive("bind-attr", function(expression, element){ -  var jElement = jQuery(element); -  return function(){ +  return function(expression, element){ +    var jElement = jQuery(element);      this.$watch(expression, _(jElement.attr).bind(jElement));    };  }); @@ -58,7 +58,7 @@ angular.directive("bind-attr", function(expression, element){  angular.directive("repeat", function(expression, element){    var anchor = document.createComment(expression);    jQuery(element).replace(anchor); -  var template = this.compile(element); +  var template = this.templetize(element);    var lhs = "item";    var rhs = "items";    var children = []; @@ -103,7 +103,7 @@ angular.directive("action", function(expression, element){  angular.directive("eval", function(expression, element){    return function(){      this.$onUpdate( expression); -  } +  };  });  //ng-watch  // <div ng-watch="$anchor.book: book=Book.get();"/> @@ -113,7 +113,7 @@ angular.directive("watch", function(expression, element){    }; // parse    return function(){      this.$watch(watches); -  } +  };  });  //widget related diff --git a/test/CompilerSpec.js b/test/CompilerSpec.js new file mode 100644 index 00000000..35e0e605 --- /dev/null +++ b/test/CompilerSpec.js @@ -0,0 +1,186 @@ +function Template() { +  this.paths = []; +  this.children = []; +  this.inits = []; +} + +Template.prototype = { +  init: function(element, scope) { +    foreach(this.inits, function(fn) { +      scope.apply(fn, element); +    }); + +    var i, +        childNodes = element.childNodes, +        children = this.children, +        paths = this.paths, +        length = paths.length; +    for (i = 0; i < length; i++) { +      children[i].init(childNodes[paths[i]], scope); +    } +  }, + +  addInit:function(init) { +    if (init) { +      this.inits.push(init); +    } +  }, + + +  addChild: function(index, template) { +    this.paths.push(index); +    this.children.push(template); +  } +}; + +function Compiler(directives){ +  this.directives = directives; +} + +DIRECTIVE = /^ng-(.*)$/; + +/** + * return { + *   element: + *   init: function(element){...} + * } + * + * internal data structure: { + *  paths: [4, 5, 6], + *  directive: name, + *  init: function(expression, element){} + * } + * + * template : { + *   inits: [fn(), fn()} + *   paths: [1, 5], + *   templates: [ + *     inits: [] + *     paths: [] + *     templates: + *   ] + * } + */ +Compiler.prototype = { +  compile: function(element) { +    var template = this.templetize(element); +    return function(){ +      var scope = new Scope(); +      return { +        scope: scope, +        element:element, +        init: bind(template, template.init, element, scope) +      }; +    }; +  }, + +  templetize: function(element){ +    var items, item, length, i, directive, init, template, +        childTemplate, recurse = true; + +    // Process attributes/directives +    for (i = 0, items = element.attributes, length = items.length; +         i < length; i++) { +      item = items[i]; +      var match = item.name.match(DIRECTIVE); +      if (match) { +        directive = this.directives[match[1]]; +        if (directive) { +          init = directive.call({}, item.value, element); +          template = template || new Template(); +          template.addInit(init); +          recurse = recurse && init; +        } +      } +    } + +    // Process children +    if (recurse) { +      for (i = 0, items = element.childNodes, length = items.length; +           i < length; i++) { +        if(childTemplate = this.templetize(items[i])) { +          template = template || new Template(); +          template.addChild(i, childTemplate); +        } +      } +    } +    return template; +  } +}; + +describe('compiler', function(){ +  function element(html) { +    return jQuery(html)[0]; +  } + +  var compiler, directives, compile, log; + +  beforeEach(function(){ +    log = ""; +    directives = { +      hello: function(expression, element){ +        log += "hello "; +        return function() { +          log += expression; +        }; +      }, + +      watch: function(expression, element){ +        return function() { +          this.$watch(expression, function(val){ +            log += ":" + val; +          }); +        }; +      } + +    }; +    compiler = new Compiler(directives); +    compile = function(html){ +      var e = element(html); +      var view = compiler.compile(e)(e); +      view.init(); +      return view.scope; +    }; +  }); + +  it('should recognize a directive', function(){ +    var e = element('<div ng-directive="expr" ignore="me"></div>'); +    directives.directive = function(expression, element){ +      log += "found"; +      expect(expression).toEqual("expr"); +      expect(element).toEqual(e); +      return function initFn() { +        log += ":init"; +      }; +    }; +    var template = compiler.compile(e); +    var init = template(e).init; +    expect(log).toEqual("found"); +    init(); +    expect(log).toEqual("found:init"); +  }); + +  it('should recurse to children', function(){ +    var scope = compile('<div><span ng-hello="misko"/></div>'); +    expect(log).toEqual("hello misko"); +  }); + +  it('should watch scope', function(){ +    var scope = compile('<span ng-watch="name"/>'); +    expect(log).toEqual(""); +    scope.updateView(); +    scope.set('name', 'misko'); +    scope.updateView(); +    scope.updateView(); +    scope.set('name', 'adam'); +    scope.updateView(); +    scope.updateView(); +    expect(log).toEqual(":misko:adam"); +  }); + +  it('should prevent recursion', function(){ +    directives.stop = function(){ return false; }; +    var scope = compile('<span ng-hello="misko" ng-stop="true"><span ng-hello="adam"/></span>'); +    expect(log).toEqual("hello misko"); +  }); +}); | 
