initial revision of new plugable compiler

This commit is contained in:
Misko Hevery 2010-03-18 12:20:06 -07:00
parent f1b50b92ac
commit 7634a3ed52
6 changed files with 224 additions and 27 deletions

View file

@ -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)));
};
}

View file

@ -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;
}
};
};

View file

@ -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){

View file

@ -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));
}
};

View file

@ -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

186
test/CompilerSpec.js Normal file
View file

@ -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");
});
});