mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-03-16 23:30:23 +00:00
got few directives working
This commit is contained in:
parent
f6664ed7f6
commit
84552f7f8a
10 changed files with 391 additions and 232 deletions
|
|
@ -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){
|
||||
|
|
|
|||
149
src/Compiler.js
149
src/Compiler.js
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
107
src/Parser.js
107
src/Parser.js
|
|
@ -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(":");
|
||||
|
|
|
|||
52
src/Scope.js
52
src/Scope.js
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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){
|
||||
|
|
|
|||
29
src/directivesAngularCom.js
Normal file
29
src/directivesAngularCom.js
Normal 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]);
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
2
test.sh
2
test.sh
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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
71
test/directivesSpec.js
Normal 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(){});
|
||||
});
|
||||
Loading…
Reference in a new issue