diff options
| -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"); + }); +}); |
