aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMisko Hevery2010-03-18 14:43:49 -0700
committerMisko Hevery2010-03-18 14:43:49 -0700
commitdf607da0d1b9726bce6584238fe3ad7e9b65a966 (patch)
tree472a81182226bde4c3541592f940983c9486eea2
parent7634a3ed5227f8bc2a2ba83752d0e2c78adb6051 (diff)
downloadangular.js-df607da0d1b9726bce6584238fe3ad7e9b65a966.tar.bz2
support for templates
-rw-r--r--lib/jstestdriver/JsTestDriver.jarbin3090800 -> 3092033 bytes
-rw-r--r--src/Scope.js15
-rw-r--r--src/directives.js8
-rw-r--r--test/CompilerSpec.js95
4 files changed, 82 insertions, 36 deletions
diff --git a/lib/jstestdriver/JsTestDriver.jar b/lib/jstestdriver/JsTestDriver.jar
index 1a37d230..2c7a5154 100644
--- a/lib/jstestdriver/JsTestDriver.jar
+++ b/lib/jstestdriver/JsTestDriver.jar
Binary files differ
diff --git a/src/Scope.js b/src/Scope.js
index 442477b4..3633f960 100644
--- a/src/Scope.js
+++ b/src/Scope.js
@@ -1,5 +1,6 @@
function Scope(initialState, name) {
this.widgets = [];
+ this.evals = [];
this.watchListeners = {};
this.name = name;
initialState = initialState || {};
@@ -11,6 +12,7 @@ function Scope(initialState, name) {
this.state['$root'] = this.state;
}
this.set('$watch', bind(this, this.addWatchListener));
+ this.set('$eval', bind(this, this.addEval));
};
Scope.expressionCache = {};
@@ -48,17 +50,23 @@ Scope.prototype = {
updateView: function() {
var self = this;
this.fireWatchers();
- _.each(this.widgets, function(widget){
+ foreach(this.widgets, function(widget){
self.evalWidget(widget, "", {}, function(){
this.updateView(self);
});
});
+ foreach(this.evals, bind(this, this.apply));
},
addWidget: function(controller) {
if (controller) this.widgets.push(controller);
},
+ addEval: function(fn, listener) {
+ // todo: this should take a function/string and a listener
+ this.evals.push(fn);
+ },
+
isProperty: function(exp) {
for ( var i = 0; i < exp.length; i++) {
var ch = exp.charAt(i);
@@ -190,8 +198,7 @@ Scope.prototype = {
},
fireWatchers: function() {
- var self = this;
- var fired = false;
+ var self = this, fired = false;
foreach(this.watchListeners, function(watcher) {
var value = self.eval(watcher.expression);
if (value !== watcher.lastValue) {
@@ -206,6 +213,6 @@ Scope.prototype = {
},
apply: function(fn) {
- fn.apply(this.state, slice(arguments, 0, arguments.length));
+ fn.apply(this.state, slice.call(arguments, 1, arguments.length));
}
};
diff --git a/src/directives.js b/src/directives.js
index 7c5cc257..26cbfe2c 100644
--- a/src/directives.js
+++ b/src/directives.js
@@ -58,12 +58,12 @@ 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.templetize(element);
+ var template = this.compile(element);
var lhs = "item";
var rhs = "items";
- var children = [];
return function(){
- this.$watch(rhs, function(items){
+ var children = [];
+ this.$eval(rhs, function(items){
foreach(children, function(child){
child.element.remove();
});
@@ -102,7 +102,7 @@ angular.directive("action", function(expression, element){
//ng-eval
angular.directive("eval", function(expression, element){
return function(){
- this.$onUpdate( expression);
+ this.$eval(expression);
};
});
//ng-watch
diff --git a/test/CompilerSpec.js b/test/CompilerSpec.js
index 35e0e605..4ffdf7f5 100644
--- a/test/CompilerSpec.js
+++ b/test/CompilerSpec.js
@@ -1,3 +1,9 @@
+/**
+ * Template provides directions an how to bind to a given element.
+ * It contains a list of init functions which need to be called to
+ * bind to a new instance of elements. It also provides a list
+ * of child paths which contain child templates
+ */
function Template() {
this.paths = [];
this.children = [];
@@ -26,6 +32,11 @@ Template.prototype = {
}
},
+ setExclusiveInit: function(init) {
+ this.inits = [init];
+ this.addInit = noop;
+ },
+
addChild: function(index, template) {
this.paths.push(index);
@@ -33,39 +44,22 @@ Template.prototype = {
}
};
+///////////////////////////////////
+// Compiler
+//////////////////////////////////
+
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 template = this.templetize(element) || new Template();
+ return function(element){
var scope = new Scope();
+ scope.element = element;
return {
scope: scope,
element:element,
@@ -79,17 +73,24 @@ Compiler.prototype = {
childTemplate, recurse = true;
// Process attributes/directives
- for (i = 0, items = element.attributes, length = items.length;
+ 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);
+ init = directive.call(this, item.value, element);
template = template || new Template();
- template.addInit(init);
+ if (directive.exclusive) {
+ template.setExclusiveInit(init);
+ i = length; // quit iterations
+ } else {
+ template.addInit(init);
+ }
recurse = recurse && init;
+ } else {
+ error("Directive '" + match[0] + "' is not recognized.");
}
}
}
@@ -136,7 +137,7 @@ describe('compiler', function(){
};
compiler = new Compiler(directives);
compile = function(html){
- var e = element(html);
+ var e = element("<div>" + html + "</div>");
var view = compiler.compile(e)(e);
view.init();
return view.scope;
@@ -183,4 +184,42 @@ describe('compiler', function(){
var scope = compile('<span ng-hello="misko" ng-stop="true"><span ng-hello="adam"/></span>');
expect(log).toEqual("hello misko");
});
+
+ it('should allow creation of templates', function(){
+ directives.duplicate = function(expr, element){
+ var template,
+ marker = document.createComment("marker"),
+ parentNode = element.parentNode;
+ parentNode.insertBefore(marker, element);
+ parentNode.removeChild(element);
+ element.removeAttribute("ng-duplicate");
+ template = this.compile(element);
+ return function(marker) {
+ var parentNode = marker.parentNode;
+ this.$eval(function() {
+ parentNode.insertBefore(
+ template(element.cloneNode(true)).element,
+ marker.nextSibling);
+ });
+ };
+ };
+ var scope = compile('before<span ng-duplicate="expr">x</span>after');
+ expect($(scope.element).html()).toEqual('before<!--marker-->after');
+ scope.updateView();
+ expect($(scope.element).html()).toEqual('before<!--marker--><span>x</span>after');
+ scope.updateView();
+ expect($(scope.element).html()).toEqual('before<!--marker--><span>x</span><span>x</span>after');
+ });
+
+ it('should allow for exculsive tags which suppress others', function(){
+ directives.exclusive = function(){
+ return function() {
+ log += ('exclusive');
+ };
+ };
+ directives.exclusive.exclusive = true;
+
+ compile('<span ng-hello="misko", ng-exclusive/>');
+ expect(log).toEqual('exclusive');
+ });
});