aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMisko Hevery2010-03-18 12:20:06 -0700
committerMisko Hevery2010-03-18 12:20:06 -0700
commit7634a3ed5227f8bc2a2ba83752d0e2c78adb6051 (patch)
treeef94843f25eae83d0492b18149a084868829693c
parentf1b50b92ac69f5c58984f5e88015507552d29df2 (diff)
downloadangular.js-7634a3ed5227f8bc2a2ba83752d0e2c78adb6051.tar.bz2
initial revision of new plugable compiler
-rw-r--r--src/Angular.js10
-rw-r--r--src/DataStore.js32
-rw-r--r--src/Resource.js4
-rw-r--r--src/Scope.js5
-rw-r--r--src/directives.js14
-rw-r--r--test/CompilerSpec.js186
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");
+ });
+});