got few directives working

This commit is contained in:
Misko Hevery 2010-03-22 13:58:04 -07:00
parent f6664ed7f6
commit 84552f7f8a
10 changed files with 391 additions and 232 deletions

View file

@ -18,23 +18,32 @@ if (typeof Node == 'undefined') {
}
function noop() {}
function identity($) {return $;}
if (!window['console']) window['console']={'log':noop, 'error':noop};
function extension(angular, name) {
var extPoint;
return angular[name] || (extPoint = angular[name] = function (name, fn, prop){
if (isDefined(fn)) {
extPoint[name] = extend(fn, prop || {});
}
return extPoint[name];
});
}
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'] = {}),
angularDirective = extension(angular, 'directive'),
angularMarkup = extension(angular, 'markup'),
angularWidget = extension(angular, 'widget'),
angularValidator = extension(angular, 'validator'),
angularFilter = extension(angular, 'filter'),
angularFormatter = extension(angular, 'formatter'),
angularCallbacks = extension(angular, 'callbacks'),
angularAlert = angular['alert'] || (angular['alert'] = function(){
log(arguments); window.alert.apply(window, arguments);
});
@ -45,7 +54,15 @@ var isVisible = isVisible || function (element) {
};
function isDefined(value){
return typeof value !== 'undefined';
return typeof value != 'undefined';
}
function isObject(value){
return typeof value == 'object';
}
function isFunction(value){
return typeof value == 'function';
}
function log(a, b, c){

View file

@ -13,7 +13,7 @@ function Template() {
Template.prototype = {
init: function(element, scope) {
foreach(this.inits, function(fn) {
scope.apply(fn, nodeLite(element));
scope.apply(fn, jqLite(element));
});
var i,
@ -35,8 +35,10 @@ Template.prototype = {
addChild: function(index, template) {
this.paths.push(index);
this.children.push(template);
if (template) {
this.paths.push(index);
this.children.push(template);
}
},
empty: function() {
@ -45,31 +47,37 @@ Template.prototype = {
};
///////////////////////////////////
//NodeLite
//JQLite
//////////////////////////////////
function NodeLite(element) {
function JQLite(element) {
this.element = element;
}
function nodeLite(element) {
return element instanceof NodeLite ? element : new NodeLite(element);
function jqLite(element) {
if (typeof element == 'string') {
var div = document.createElement('div');
div.innerHTML = element;
element = div.childNodes[0];
}
return element instanceof JQLite ? element : new JQLite(element);
}
NodeLite.prototype = {
JQLite.prototype = {
eachTextNode: function(fn){
var i, chldNodes = this.element.childNodes || [], size = chldNodes.length, chld;
for (i = 0; i < size; i++) {
if((chld = new NodeLite(chldNodes[i])).isText()) {
if((chld = new JQLite(chldNodes[i])).isText()) {
fn(chld, i);
}
}
},
eachNode: function(fn){
var i, chldNodes = this.element.childNodes || [], size = chldNodes.length, chld;
for (i = 0; i < size; i++) {
if(!(chld = new NodeLite(chldNodes[i])).isText()) {
if(!(chld = new JQLite(chldNodes[i])).isText()) {
fn(chld, i);
}
}
@ -84,34 +92,51 @@ NodeLite.prototype = {
},
replaceWith: function(replaceNode) {
this.element.parentNode.replaceChild(nodeLite(replaceNode).element, this.element);
this.element.parentNode.replaceChild(jqLite(replaceNode).element, this.element);
},
removeAttribute: function(name) {
remove: function() {
this.element.parentNode.removeChild(this.element);
},
removeAttr: function(name) {
this.element.removeAttribute(name);
},
after: function(element) {
this.element.parentNode.insertBefore(nodeLite(element).element, this.element.nextSibling);
this.element.parentNode.insertBefore(jqLite(element).element, this.element.nextSibling);
},
attr: function(name, value){
if (isDefined(value)) {
this.element.setAttribute(name, value);
var e = this.element;
if (isObject(name)) {
foreach(name, function(value, name){
e.setAttribute(name, value);
});
} else if (isDefined(value)) {
e.setAttribute(name, value);
} else {
return this.element.getAttribute(name);
return e.getAttribute(name);
}
},
text: function(value) {
if (isDefined(value)) {
this.element.nodeValue = value;
this.element.textContent = value;
}
return this.element.nodeValue;
return this.element.textContent;
},
html: function(value) {
if (isDefined(value)) {
this.element.innerHTML = value;
}
return this.element.innerHTML;
},
parent: function() { return jqLite(this.element.parentNode);},
isText: function() { return this.element.nodeType == Node.TEXT_NODE; },
clone: function() { return nodeLite(this.element.cloneNode(true)); }
clone: function() { return jqLite(this.element.cloneNode(true)); }
};
///////////////////////////////////
@ -124,14 +149,14 @@ function Compiler(markup, directives, widgets){
this.widgets = widgets;
}
DIRECTIVE = /^ng-(.*)$/;
Compiler.prototype = {
compile: function(element) {
var template = this.templetize(nodeLite(element)) || new Template();
return function(element){
var scope = new Scope();
compile: function(rawElement) {
rawElement = jqLite(rawElement);
var template = this.templatize(rawElement) || new Template();
return function(element, parentScope){
var scope = new Scope(parentScope);
scope.element = element;
// todo return should be a scope with everything already set on it as element
return {
scope: scope,
element:element,
@ -140,57 +165,57 @@ Compiler.prototype = {
};
},
templetize: function(element){
templatize: function(element){
var self = this,
elementName = element.element.nodeName,
widgets = self.widgets,
widget = widgets[elementName],
markup = self.markup,
markupSize = markup.length,
directives = self.directives,
widgets = self.widgets,
recurse = true,
descend = true,
exclusive = false,
directiveQueue = [],
template = new Template();
template = new Template(),
selfApi = {
compile: bind(self, self.compile),
reference:function(name) {return jqLite(document.createComment(name));},
descend: function(value){ if(isDefined(value)) descend = value; return descend;}
};
// process markup for text nodes only
element.eachTextNode(function(textNode){
for (var i = 0, text = textNode.text(); i < markupSize; i++) {
markup[i].call(self, text, textNode, element);
}
});
if (widget) {
template.addInit(widget.call(selfApi, element));
} else {
// process markup for text nodes only
element.eachTextNode(function(textNode){
for (var i = 0, text = textNode.text(); i < markupSize; i++) {
markup[i].call(selfApi, text, textNode, element);
}
});
// Process attributes/directives
element.eachAttribute(function(name, value){
var match = name.match(DIRECTIVE),
directive;
if (!exclusive && match) {
directive = directives[match[1]];
if (directive) {
// Process attributes/directives
element.eachAttribute(function(name, value){
var directive = directives[name];
if (!exclusive && directive) {
if (directive.exclusive) {
exclusive = true;
directiveQueue = [];
}
directiveQueue.push(bind(self, directive, value, element));
} else {
error("Directive '" + match[0] + "' is not recognized.");
}
}
});
// Execute directives
foreach(directiveQueue, function(directive){
var init = directive();
template.addInit(init);
recurse = recurse && init;
});
// Process non text child nodes
if (recurse) {
element.eachNode(function(child, i){
var childTemplate = self.templetize(child);
if(childTemplate) {
template.addChild(i, childTemplate);
directiveQueue.push(bind(selfApi, directive, value, element));
}
});
// Execute directives
foreach(directiveQueue, function(directive){
template.addInit(directive());
});
// Process non text child nodes
if (descend) {
element.eachNode(function(child, i){
template.addChild(i, self.templatize(child));
});
}
}
return template.empty() ? null : template;
}

View file

@ -40,7 +40,7 @@ Lexer.prototype = {
return false;
}
},
parse: function() {
var tokens = this.tokens;
var OPERATORS = Lexer.OPERATORS;
@ -103,22 +103,22 @@ Lexer.prototype = {
}
return tokens;
},
isNumber: function(ch) {
return '0' <= ch && ch <= '9';
},
isWhitespace: function(ch) {
return ch == ' ' || ch == '\r' || ch == '\t' ||
ch == '\n' || ch == '\v';
},
isIdent: function(ch) {
return 'a' <= ch && ch <= 'z' ||
'A' <= ch && ch <= 'Z' ||
'_' == ch || ch == '$';
},
readNumber: function() {
var number = "";
var start = this.index;
@ -135,7 +135,7 @@ Lexer.prototype = {
this.tokens.push({index:start, text:number,
fn:function(){return number;}});
},
readIdent: function() {
var ident = "";
var start = this.index;
@ -157,15 +157,17 @@ Lexer.prototype = {
}
this.tokens.push({index:start, text:ident, fn:fn});
},
readString: function(quote) {
var start = this.index;
var dateParseLength = this.dateParseLength;
this.index++;
var string = "";
var rawString = quote;
var escape = false;
while (this.index < this.text.length) {
var ch = this.text.charAt(this.index);
rawString += ch;
if (escape) {
if (ch == 'u') {
var hex = this.text.substring(this.index + 1, this.index + 5);
@ -184,7 +186,7 @@ Lexer.prototype = {
escape = true;
} else if (ch == quote) {
this.index++;
this.tokens.push({index:start, text:string,
this.tokens.push({index:start, text:rawString, string:string,
fn:function(){
return (string.length == dateParseLength) ?
angular['String']['toDate'](string) : string;
@ -199,7 +201,7 @@ Lexer.prototype = {
this.text.substring(start) + "] starting at column '" +
(start+1) + "' in expression '" + this.text + "'.";
},
readRegexp: function(quote) {
var start = this.index;
this.index++;
@ -249,18 +251,18 @@ Parser.ZERO = function(){
Parser.prototype = {
error: function(msg, token) {
throw "Token '" + token.text +
"' is " + msg + " at column='" +
(token.index + 1) + "' of expression '" +
throw "Token '" + token.text +
"' is " + msg + " at column='" +
(token.index + 1) + "' of expression '" +
this.text + "' starting at '" + this.text.substring(token.index) + "'.";
},
peekToken: function() {
if (this.tokens.length === 0)
if (this.tokens.length === 0)
throw "Unexpected end of expression: " + this.text;
return this.tokens[0];
},
peek: function(e1, e2, e3, e4) {
var tokens = this.tokens;
if (tokens.length > 0) {
@ -273,7 +275,7 @@ Parser.prototype = {
}
return false;
},
expect: function(e1, e2, e3, e4){
var token = this.peek(e1, e2, e3, e4);
if (token) {
@ -283,7 +285,7 @@ Parser.prototype = {
}
return false;
},
consume: function(e1){
if (!this.expect(e1)) {
var token = this.peek();
@ -293,30 +295,30 @@ Parser.prototype = {
this.text.substring(token.index) + "'.";
}
},
_unary: function(fn, right) {
return function(self) {
return fn(self, right(self));
};
},
_binary: function(left, fn, right) {
return function(self) {
return fn(self, left(self), right(self));
};
},
hasTokens: function () {
return this.tokens.length > 0;
},
assertAllConsumed: function(){
if (this.tokens.length !== 0) {
throw "Did not understand '" + this.text.substring(this.tokens[0].index) +
"' while evaluating '" + this.text + "'.";
}
},
statements: function(){
var statements = [];
while(true) {
@ -335,7 +337,7 @@ Parser.prototype = {
}
}
},
filterChain: function(){
var left = this.expression();
var token;
@ -347,15 +349,15 @@ Parser.prototype = {
}
}
},
filter: function(){
return this._pipeFunction(angularFilter);
},
validator: function(){
return this._pipeFunction(angularValidator);
},
_pipeFunction: function(fnScope){
var fn = this.functionIdent(fnScope);
var argsFn = [];
@ -373,7 +375,7 @@ Parser.prototype = {
var _this = this;
foreach(self, function(v, k) {
if (k.charAt(0) == '$') {
_this[k] = v;
_this[k] = v;
}
});
};
@ -386,11 +388,11 @@ Parser.prototype = {
}
}
},
expression: function(){
return this.throwStmt();
},
throwStmt: function(){
if (this.expect('throw')) {
var throwExp = this.assignment();
@ -401,7 +403,7 @@ Parser.prototype = {
return this.assignment();
}
},
assignment: function(){
var left = this.logicalOR();
var token;
@ -417,7 +419,7 @@ Parser.prototype = {
return left;
}
},
logicalOR: function(){
var left = this.logicalAND();
var token;
@ -429,7 +431,7 @@ Parser.prototype = {
}
}
},
logicalAND: function(){
var left = this.equality();
var token;
@ -438,7 +440,7 @@ Parser.prototype = {
}
return left;
},
equality: function(){
var left = this.relational();
var token;
@ -447,7 +449,7 @@ Parser.prototype = {
}
return left;
},
relational: function(){
var left = this.additive();
var token;
@ -456,7 +458,7 @@ Parser.prototype = {
}
return left;
},
additive: function(){
var left = this.multiplicative();
var token;
@ -465,7 +467,7 @@ Parser.prototype = {
}
return left;
},
multiplicative: function(){
var left = this.unary();
var token;
@ -474,7 +476,7 @@ Parser.prototype = {
}
return left;
},
unary: function(){
var token;
if (this.expect('+')) {
@ -487,7 +489,7 @@ Parser.prototype = {
return this.primary();
}
},
functionIdent: function(fnScope) {
var token = this.expect();
var element = token.text.split('.');
@ -504,7 +506,7 @@ Parser.prototype = {
}
return instance;
},
primary: function() {
var primary;
if (this.expect('(')) {
@ -540,7 +542,7 @@ Parser.prototype = {
}
return primary;
},
closure: function(hasArgs) {
var args = [];
if (hasArgs) {
@ -566,7 +568,7 @@ Parser.prototype = {
};
};
},
fieldAccess: function(object) {
var field = this.expect().text;
var fn = function (self){
@ -575,7 +577,7 @@ Parser.prototype = {
fn.isAssignable = field;
return fn;
},
objectIndex: function(obj) {
var indexFn = this.expression();
this.consume(']');
@ -592,7 +594,7 @@ Parser.prototype = {
};
}
},
functionCall: function(fn) {
var argsFn = [];
if (this.peekToken().text != ')') {
@ -614,7 +616,7 @@ Parser.prototype = {
}
};
},
// This is used with json array declaration
arrayDeclaration: function () {
var elementFns = [];
@ -632,12 +634,13 @@ Parser.prototype = {
return array;
};
},
object: function () {
var keyValues = [];
if (this.peekToken().text != '}') {
do {
var key = this.expect().text;
var token = this.expect(),
key = token.string || token.text;
this.consume(":");
var value = this.expression();
keyValues.push({key:key, value:value});
@ -654,7 +657,7 @@ Parser.prototype = {
return object;
};
},
entityDeclaration: function () {
var decl = [];
while(this.hasTokens()) {
@ -671,7 +674,7 @@ Parser.prototype = {
return code;
};
},
entityDecl: function () {
var entity = this.expect().text;
var instance;
@ -690,16 +693,16 @@ Parser.prototype = {
var document = Entity();
document['$$anchor'] = instance;
self.scope.set(instance, document);
return "$anchor." + instance + ":{" +
return "$anchor." + instance + ":{" +
instance + "=" + entity + ".load($anchor." + instance + ");" +
instance + ".$$anchor=" + angular['String']['quote'](instance) + ";" +
instance + ".$$anchor=" + angular['String']['quote'](instance) + ";" +
"};";
} else {
return "";
}
};
},
watch: function () {
var decl = [];
while(this.hasTokens()) {
@ -716,7 +719,7 @@ Parser.prototype = {
}
};
},
watchDecl: function () {
var anchorName = this.expect().text;
this.consume(":");

View file

@ -1,18 +1,23 @@
function Scope(initialState, name) {
this.widgets = [];
this.evals = [];
this.watchListeners = {};
this.name = name;
var self = this;
self.widgets = [];
self.evals = [];
self.watchListeners = {};
self.name = name;
initialState = initialState || {};
var State = function(){};
State.prototype = initialState;
this.state = new State();
this.state['$parent'] = initialState;
self.state = new State();
extend(self.state, {
'$parent': initialState,
'$watch': bind(self, self.addWatchListener),
'$eval': bind(self, self.eval),
// change name to onEval?
'$addEval': bind(self, self.addEval)
});
if (name == "ROOT") {
this.state['$root'] = this.state;
self.state['$root'] = self.state;
}
this.set('$watch', bind(this, this.addWatchListener));
this.set('$eval', bind(this, this.addEval));
};
Scope.expressionCache = {};
@ -47,6 +52,7 @@ Scope.getter = function(instance, path) {
};
Scope.prototype = {
// TODO: rename to update? or eval?
updateView: function() {
var self = this;
this.fireWatchers();
@ -64,7 +70,13 @@ Scope.prototype = {
addEval: function(fn, listener) {
// todo: this should take a function/string and a listener
this.evals.push(fn);
// todo: this is a hack, which will need to be cleaned up.
var self = this,
listenFn = listener || noop,
expr = bind(self, self.compile(fn), {scope: self, self: self.state});
this.evals.push(function(){
self.apply(listenFn, expr());
});
},
isProperty: function(exp) {
@ -103,15 +115,21 @@ Scope.prototype = {
this.eval(expressionText + "=" + toJson(value));
},
compile: function(exp) {
if (isFunction(exp)) return exp;
var expFn = Scope.expressionCache[exp];
if (!expFn) {
var parser = new Parser(exp);
expFn = parser.statements();
parser.assertAllConsumed();
Scope.expressionCache[exp] = expFn;
}
return expFn;
},
eval: function(expressionText, context) {
// log('Scope.eval', expressionText);
var expression = Scope.expressionCache[expressionText];
if (!expression) {
var parser = new Parser(expressionText);
expression = parser.statements();
parser.assertAllConsumed();
Scope.expressionCache[expressionText] = expression;
}
var expression = this.compile(expressionText);
context = context || {};
context.scope = this;
context.self = this.state;

View file

@ -1,86 +1,82 @@
angular.directive("auth", function(expression, element){
angularDirective("ng-init", function(expression){
return function(){
if(expression == "eager") {
this.$users.fetchCurrent();
}
this.$eval(expression);
};
});
//expression = "book=Book:{year=2000}"
angular.directive("entity", function(expression, element){
//parse expression, ignore element
var entityName; // "Book";
var instanceName; // "book";
var defaults; // {year: 2000};
parse(expression);
angularDirective("ng-eval", function(expression){
return function(){
this[entityName] = this.$datastore.entity(entityName, defaults);
this[instanceName] = this[entityName]();
this.$watch("$anchor."+instanceName, function(newAnchor){
this[instanceName] = this[entityName].get(this.$anchor[instanceName]);
});
this.$addEval(expression);
};
});
angular.directive("init", function(expression, element){
return function(){
this.$eval(expresssion);
};
});
//translation of {{ }} to ng-bind is external to this
angular.directive("bind", function(expression, element){
return function() {
angular.directive("ng-bind", function(expression){
return function(element) {
this.$watch(expression, function(value){
element.innerText = value;
element.text(value);
});
};
});
// translation of {{ }} to ng-bind-attr is external to this
// <a href="http://example.com?id={{book.$id}}" alt="{{book.$name}}">link</a>
// 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){
return function(expression, element){
var jElement = jQuery(element);
this.$watch(expression, _(jElement.attr).bind(jElement));
angular.directive("ng-bind-attr", function(expression){
return function(element){
this.$watch(expression, bind(element, element.attr));
};
});
angular.directive("repeat", function(expression, element){
var anchor = document.createComment(expression);
jQuery(element).replace(anchor);
var template = this.compile(element);
var lhs = "item";
var rhs = "items";
angular.directive("ng-non-bindable", function(){
this.descend(false);
});
angular.directive("ng-repeat", function(expression, element){
var reference = this.reference("ng-repeat: " + expression),
r = element.removeAttr('ng-repeat'),
template = this.compile(element),
path = expression.split(' in '),
lhs = path[0],
rhs = path[1];
var parent = element.parent();
element.replaceWith(reference);
return function(){
var children = [];
this.$eval(rhs, function(items){
foreach(children, function(child){
child.element.remove();
});
foreach(items, function(item){
var child = template(item); // create scope
element.addChild(child.element, anchor);
children.push(child);
var children = [],
currentScope = this;
this.$addEval(rhs, function(items){
var index = 0, childCount = children.length, childScope, lastElement = reference;
foreach(items, function(value, key){
if (index < childCount) {
// reuse existing child
childScope = children[index];
} else {
// grow children
childScope = template(element.clone(), currentScope);
childScope.init();
childScope.scope.set('$index', index);
childScope.element.attr('ng-index', index);
lastElement.after(childScope.element);
children.push(childScope);
}
childScope.scope.set(lhs, value);
childScope.scope.updateView();
lastElement = childScope.element;
index ++;
});
// shrink children
while(children.length > index) {
children.pop().element.remove();
}
});
};
});
}, {exclusive: true});
/////////////////////////////////////////
/////////////////////////////////////////
/////////////////////////////////////////
/////////////////////////////////////////
/////////////////////////////////////////
//ng-non-bindable
angular.directive("non-bindable", function(expression, element){
return false;
});
//Styling
//
@ -99,12 +95,6 @@ angular.directive("action", function(expression, element){
};
});
//ng-eval
angular.directive("eval", function(expression, element){
return function(){
this.$eval(expression);
};
});
//ng-watch
// <div ng-watch="$anchor.book: book=Book.get();"/>
angular.directive("watch", function(expression, element){

View file

@ -0,0 +1,29 @@
angular.directive("auth", function(expression, element){
return function(){
if(expression == "eager") {
this.$users.fetchCurrent();
}
};
});
//expression = "book=Book:{year=2000}"
angular.directive("entity", function(expression, element){
//parse expression, ignore element
var entityName; // "Book";
var instanceName; // "book";
var defaults; // {year: 2000};
parse(expression);
return function(){
this[entityName] = this.$datastore.entity(entityName, defaults);
this[instanceName] = this[entityName]();
this.$watch("$anchor."+instanceName, function(newAnchor){
this[instanceName] = this[entityName].get(this.$anchor[instanceName]);
});
};
});

View file

@ -1,2 +1,2 @@
java -jar lib/jstestdriver/JsTestDriver.jar --tests all | grep -v lib/jasmine
java -jar lib/jstestdriver/JsTestDriver.jar --tests all | grep -v lib/jasmine

View file

@ -36,7 +36,7 @@ describe('compiler', function(){
});
it('should recognize a directive', function(){
var e = element('<div ng-directive="expr" ignore="me"></div>');
var e = element('<div directive="expr" ignore="me"></div>');
directives.directive = function(expression, element){
log += "found";
expect(expression).toEqual("expr");
@ -53,12 +53,12 @@ describe('compiler', function(){
});
it('should recurse to children', function(){
var scope = compile('<div><span ng-hello="misko"/></div>');
var scope = compile('<div><span hello="misko"/></div>');
expect(log).toEqual("hello misko");
});
it('should watch scope', function(){
var scope = compile('<span ng-watch="name"/>');
var scope = compile('<span watch="name"/>');
expect(log).toEqual("");
scope.updateView();
scope.set('name', 'misko');
@ -70,24 +70,24 @@ describe('compiler', function(){
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>');
it('should prevent descend', function(){
directives.stop = function(){ this.descend(false); };
var scope = compile('<span hello="misko" stop="true"><span hello="adam"/></span>');
expect(log).toEqual("hello misko");
});
it('should allow creation of templates', function(){
directives.duplicate = function(expr, element){
element.replaceWith(document.createComment("marker"));
element.removeAttribute("ng-duplicate");
element.removeAttr("duplicate");
var template = this.compile(element);
return function(marker) {
this.$eval(function() {
this.$addEval(function() {
marker.after(template(element.clone()).element);
});
};
};
var scope = compile('before<span ng-duplicate="expr">x</span>after');
var scope = compile('before<span 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');
@ -103,7 +103,7 @@ describe('compiler', function(){
};
directives.exclusive.exclusive = true;
compile('<span ng-hello="misko", ng-exclusive/>');
compile('<span hello="misko", exclusive/>');
expect(log).toEqual('exclusive');
});
@ -111,24 +111,25 @@ describe('compiler', function(){
markup.push(function(text, textNode, parentNode) {
if (text == 'middle') {
expect(textNode.text()).toEqual(text);
parentNode.attr('ng-hello', text);
parentNode.attr('hello', text);
textNode.text('replaced');
}
});
var scope = compile('before<span>middle</span>after');
expect(scope.element.innerHTML).toEqual('before<span ng-hello="middle">replaced</span>after');
expect(scope.element.innerHTML).toEqual('before<span hello="middle">replaced</span>after');
expect(log).toEqual("hello middle");
});
xit('should replace widgets', function(){
widgets.button = function(element) {
element.parentNode.replaceChild(button, element);
it('should replace widgets', function(){
widgets['NG:BUTTON'] = function(element) {
element.replaceWith('<div>button</div>', element);
return function(element) {
log += 'init';
};
};
var scope = compile('<ng:button>push me</ng:button>');
expect(scope.element.innerHTML).toEqual('before<span ng-hello="middle">replaced</span>after');
expect(scope.element.innerHTML).toEqual('<div>button</div>');
expect(log).toEqual('init');
});
});

View file

@ -41,7 +41,7 @@ LexerTest.prototype.testTokenizeAString = function(){
i++;
assertEquals(tokens[i].index, 15);
assertEquals(tokens[i].text, "a'c");
assertEquals(tokens[i].string, "a'c");
i++;
assertEquals(tokens[i].index, 21);
@ -49,7 +49,7 @@ LexerTest.prototype.testTokenizeAString = function(){
i++;
assertEquals(tokens[i].index, 22);
assertEquals(tokens[i].text, 'd"e');
assertEquals(tokens[i].string, 'd"e');
};
@ -68,10 +68,10 @@ LexerTest.prototype.testQuotedString = function(){
var tokens = lexer.parse();
assertEquals(1, tokens[1].index);
assertEquals("'", tokens[1].text);
assertEquals("'", tokens[1].string);
assertEquals(7, tokens[3].index);
assertEquals('"', tokens[3].text);
assertEquals('"', tokens[3].string);
};
@ -80,14 +80,14 @@ LexerTest.prototype.testQuotedStringEscape = function(){
var lexer = new Lexer(str);
var tokens = lexer.parse();
assertEquals('"\n\f\r\t\v\u00A0', tokens[0].text);
assertEquals('"\n\f\r\t\v\u00A0', tokens[0].string);
};
LexerTest.prototype.testTokenizeUnicode = function(){
var lexer = new Lexer('"\\u00A0"');
var tokens = lexer.parse();
assertEquals(1, tokens.length);
assertEquals('\u00a0', tokens[0].text);
assertEquals('\u00a0', tokens[0].string);
};
LexerTest.prototype.testTokenizeRegExpWithOptions = function(){
@ -408,7 +408,7 @@ ParserTest.prototype.testItShouldParseOnChangeIntoHashSet = function () {
ParserTest.prototype.testItShouldParseOnChangeBlockIntoHashSet = function () {
var scope = new Scope({count:0});
var listeners = {a:[], b:[]};
scope.watch("a:{count=count+1;count=count+20;};b:count=count+300",
scope.watch("a:{count=count+1;count=count+20;};b:count=count+300",
function(n, fn){listeners[n].push(fn);});
assertEquals(1, scope.watchListeners.a.listeners.length);
@ -477,3 +477,8 @@ ParserTest.prototype.testNegationBug = function () {
assertEquals(12/6/2, scope.eval("12/6/2"));
};
ParserTest.prototype.testBugStringConfusesParser = function() {
var scope = new Scope();
assertEquals('!', scope.eval('suffix = "!"'));
};

71
test/directivesSpec.js Normal file
View file

@ -0,0 +1,71 @@
describe("directives", function(){
var compile, element;
beforeEach(function() {
var compiler = new Compiler(angularMarkup, angularDirective, angularWidget);
compile = function(html) {
element = jqLite(html);
var view = compiler.compile(element.element)(element.element);
view.init();
return view.scope;
};
});
it("should ng-init", function() {
var scope = compile('<div ng-init="a=123"></div>');
expect(scope.get('a')).toEqual(123);
});
it("should ng-eval", function() {
var scope = compile('<div ng-init="a=0" ng-eval="a = a + 1"></div>');
expect(scope.get('a')).toEqual(0);
scope.updateView();
expect(scope.get('a')).toEqual(1);
scope.updateView();
expect(scope.get('a')).toEqual(2);
});
it('should ng-bind', function() {
var scope = compile('<div ng-bind="a"></div>');
expect(element.text()).toEqual('');
scope.set('a', 'misko');
scope.updateView();
expect(element.text()).toEqual('misko');
});
it('should ng-bind-attr', function(){
var scope = compile('<img ng-bind-attr="{src:\'mysrc\', alt:\'myalt\'}"/>');
expect(element.attr('src')).toEqual(null);
expect(element.attr('alt')).toEqual(null);
scope.updateView();
expect(element.attr('src')).toEqual('mysrc');
expect(element.attr('alt')).toEqual('myalt');
});
it('should ng-non-bindable', function(){
var scope = compile('<div ng-non-bindable><span ng-bind="name"></span></div>');
scope.set('name', 'misko');
scope.updateView();
expect(element.text()).toEqual('');
});
it('should ng-repeat over array', function(){
var scope = compile('<ul><li ng-repeat="item in items" ng-init="suffix = \';\'" ng-bind="item + suffix"></li></ul>');
scope.set('items', ['misko', 'shyam']);
scope.updateView();
expect(element.text()).toEqual('misko;shyam;');
scope.set('items', ['adam', 'kai', 'brad']);
scope.updateView();
expect(element.text()).toEqual('adam;kai;brad;');
scope.set('items', ['brad']);
scope.updateView();
expect(element.text()).toEqual('brad;');
});
it('should ng-repeat over object', function(){});
it('should error on wrong parsing of ng-repeat', function(){});
});