aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Angular.js119
-rw-r--r--src/Compiler.js208
-rw-r--r--src/Filters.js52
-rw-r--r--src/Scope.js65
-rw-r--r--src/directives.js234
-rw-r--r--src/directivesAngularCom.js29
-rw-r--r--src/jqLite.js185
-rw-r--r--src/markup.js64
-rw-r--r--src/scenario/bootstrap.js6
-rw-r--r--src/widgets2.js101
10 files changed, 787 insertions, 276 deletions
diff --git a/src/Angular.js b/src/Angular.js
index 39a6e91d..95f7325a 100644
--- a/src/Angular.js
+++ b/src/Angular.js
@@ -1,6 +1,7 @@
if (typeof document.getAttribute == 'undefined')
document.getAttribute = function() {};
if (typeof Node == 'undefined') {
+ //TODO: can we get rid of this?
Node = {
ELEMENT_NODE : 1,
ATTRIBUTE_NODE : 2,
@@ -18,24 +19,43 @@ if (typeof Node == 'undefined') {
}
function noop() {}
+function identity($) {return $;}
if (!window['console']) window['console']={'log':noop, 'error':noop};
+function extensionMap(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];
+ });
+}
+
+function extensionList(angular, name) {
+ var extPoint, length = 0;
+ return angular[name] || (extPoint = angular[name] = function (fn, prop){
+ if (isDefined(fn)) {
+ extPoint[length] = extend(fn, prop || {});
+ length++;
+ }
+ return extPoint;
+ });
+}
+
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'] = {}),
- angularAlert = angular['alert'] || (angular['alert'] = function(){
+ jQuery = window['jQuery'] || window['$'], // weirdness to make IE happy
+ slice = Array.prototype.slice,
+ angular = window['angular'] || (window['angular'] = {}),
+ angularTextMarkup = extensionList(angular, 'textMarkup'),
+ angularAttrMarkup = extensionList(angular, 'attrMarkup'),
+ angularDirective = extensionMap(angular, 'directive'),
+ angularWidget = extensionMap(angular, 'widget'),
+ angularValidator = extensionMap(angular, 'validator'),
+ angularFilter = extensionMap(angular, 'filter'),
+ angularFormatter = extensionMap(angular, 'formatter'),
+ angularCallbacks = extensionMap(angular, 'callbacks'),
+ angularAlert = angular['alert'] || (angular['alert'] = function(){
log(arguments); window.alert.apply(window, arguments);
});
angular['copy'] = copy;
@@ -44,6 +64,35 @@ var isVisible = isVisible || function (element) {
return jQuery(element).is(":visible");
};
+function foreach(obj, iterator, context) {
+ var key;
+ if (obj) {
+ if (obj.forEach) {
+ obj.forEach(iterator, context);
+ } else if (obj instanceof Array) {
+ for (key = 0; key < obj.length; key++)
+ iterator.call(context, obj[key], key);
+ } else {
+ for (key in obj)
+ iterator.call(context, obj[key], key);
+ }
+ }
+ return obj;
+}
+
+function extend(dst, obj) {
+ foreach(obj, function(value, key){
+ dst[key] = value;
+ });
+ return dst;
+}
+
+function isDefined(value){ return typeof value != 'undefined'; }
+function isObject(value){ return typeof value == 'object';}
+function isString(value){ return typeof value == 'string';}
+function isArray(value) { return value instanceof Array; }
+function isFunction(value){ return typeof value == 'function';}
+
function log(a, b, c){
var console = window['console'];
switch(arguments.length) {
@@ -97,14 +146,15 @@ function isNode(inp) {
}
function isLeafNode (node) {
- switch (node.nodeName) {
- case "OPTION":
- case "PRE":
- case "TITLE":
- return true;
- default:
- return false;
+ if (node) {
+ switch (node.nodeName) {
+ case "OPTION":
+ case "PRE":
+ case "TITLE":
+ return true;
+ }
}
+ return false;
}
function copy(source, destination){
@@ -160,15 +210,32 @@ 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, curryArgs.concat(slice.call(arguments, 0, arguments.length)));
};
}
+function bindTry(_this, _function) {
+ var args = arguments,
+ last = args.length - 1,
+ curryArgs = slice.call(args, 2, last),
+ exceptionHandler = args[last];
+ return function() {
+ try {
+ return _function.apply(_this, curryArgs.concat(slice.call(arguments, 0, arguments.length)));
+ } catch (e) {
+ if (e = exceptionHandler(e)) throw e;
+ }
+ };
+}
+
+function errorHandlerFor(element) {
+ return function(error){
+ element.attr('ng-error', angular.toJson(error));
+ element.addClass('ng-exception');
+ };
+}
+
function outerHTML(node) {
var temp = document.createElement('div');
temp.appendChild(node);
diff --git a/src/Compiler.js b/src/Compiler.js
index f4d901fb..ba598a43 100644
--- a/src/Compiler.js
+++ b/src/Compiler.js
@@ -12,12 +12,13 @@ function Template() {
Template.prototype = {
init: function(element, scope) {
+ element = jqLite(element);
foreach(this.inits, function(fn) {
- scope.apply(fn, nodeLite(element));
+ scope.apply(fn, element);
});
var i,
- childNodes = element.childNodes,
+ childNodes = element[0].childNodes,
children = this.children,
paths = this.paths,
length = paths.length;
@@ -26,106 +27,74 @@ Template.prototype = {
}
},
+
addInit:function(init) {
if (init) {
this.inits.push(init);
}
},
- setExclusiveInit: function(init) {
- this.inits = [init];
- this.addInit = noop;
- },
-
addChild: function(index, template) {
- this.paths.push(index);
- this.children.push(template);
+ if (template) {
+ this.paths.push(index);
+ this.children.push(template);
+ }
+ },
+
+ empty: function() {
+ return this.inits.length == 0 && this.paths.length == 0;
}
};
///////////////////////////////////
-//NodeLite
+//Compiler
//////////////////////////////////
-
-function NodeLite(element) {
- this.element = element;
-}
-
-function nodeLite(element) {
- return element instanceof NodeLite ? element : new NodeLite(element);
+function isTextNode(node) {
+ return node.nodeType == Node.TEXT_NODE;
}
-NodeLite.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()) {
- fn(chld, i);
- }
+function eachTextNode(element, fn){
+ var i, chldNodes = element[0].childNodes || [], size = chldNodes.length, chld;
+ for (i = 0; i < size; i++) {
+ if(isTextNode(chld = chldNodes[i])) {
+ fn(jqLite(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()) {
- fn(chld, i);
- }
- }
- },
+ }
+}
- eachAttribute: function(fn){
- var i, attrs = this.element.attributes || [], size = attrs.length, chld, attr;
- for (i = 0; i < size; i++) {
- var attr = attrs[i];
- fn(attr.name, attr.value);
+function eachNode(element, fn){
+ var i, chldNodes = element[0].childNodes || [], size = chldNodes.length, chld;
+ for (i = 0; i < size; i++) {
+ if(!isTextNode(chld = chldNodes[i])) {
+ fn(jqLite(chld), i);
}
- },
-
- replaceWith: function(replaceNode) {
- this.element.parentNode.replaceChild(nodeLite(replaceNode).element, this.element);
- },
-
- removeAttribute: function(name) {
- this.element.removeAttribute(name);
- },
-
- after: function(element) {
- this.element.parentNode.insertBefore(element, this.element.nextSibling);
- },
-
- attr: function(name, value){
- if (typeof value == 'undefined') {
- return this.element.getAttribute(name);
- } else {
- this.element.setAttribute(name);
- }
- },
-
- isText: function() { return this.element.nodeType == Node.TEXT_NODE; },
- text: function() { return this.element.nodeValue; },
- clone: function() { return nodeLite(this.element.cloneNode(true)); }
-};
+ }
+}
-///////////////////////////////////
-//Compiler
-//////////////////////////////////
+function eachAttribute(element, fn){
+ var i, attrs = element[0].attributes || [], size = attrs.length, chld, attr;
+ for (i = 0; i < size; i++) {
+ var attr = attrs[i];
+ fn(attr.name, attr.value);
+ }
+}
-function Compiler(markup, directives, widgets){
- this.markup = markup;
+function Compiler(textMarkup, attrMarkup, directives, widgets){
+ this.textMarkup = textMarkup;
+ this.attrMarkup = attrMarkup;
this.directives = directives;
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,
@@ -134,55 +103,64 @@ Compiler.prototype = {
};
},
- templetize: function(element){
+ templatize: function(element){
var self = this,
- markup = self.markup,
- markupSize = markup.length,
- directives = self.directives,
+ elementName = element[0].nodeName,
widgets = self.widgets,
- recurse = true,
+ widget = widgets[elementName],
+ directives = self.directives,
+ descend = true,
exclusive = false,
- template;
-
- // 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);
- }
- });
+ directiveQueue = [],
+ template = new Template(),
+ selfApi = {
+ compile: bind(self, self.compile),
+ comment:function(text) {return jqLite(document.createComment(text));},
+ element:function(type) {return jqLite(document.createElement(type));},
+ text:function(text) {return jqLite(document.createTextNode(text));},
+ descend: function(value){ if(isDefined(value)) descend = value; return descend;}
+ };
+
+ if (widget) {
+ template.addInit(widget.call(selfApi, element));
+ } else {
+ // process markup for text nodes only
+ eachTextNode(element, function(textNode){
+ var text = textNode.text();
+ foreach(self.textMarkup, function(markup){
+ markup.call(selfApi, text, textNode, element);
+ });
+ });
- // Process attributes/directives
- element.eachAttribute(function(name, value){
- var match = name.match(DIRECTIVE),
- directive, init;
- if (!exclusive && match) {
- directive = directives[match[1]];
- if (directive) {
- init = directive.call(self, value, element);
- template = template || new Template();
+ // Process attributes/directives
+ eachAttribute(element, function(name, value){
+ foreach(self.attrMarkup, function(markup){
+ markup.call(selfApi, value, name, element);
+ });
+ });
+ eachAttribute(element, function(name, value){
+ var directive = directives[name];
+ if (!exclusive && directive) {
if (directive.exclusive) {
- template.setExclusiveInit(init);
exclusive = true;
- } else {
- template.addInit(init);
+ directiveQueue = [];
}
- recurse = recurse && init;
- } else {
- error("Directive '" + match[0] + "' is not recognized.");
+ directiveQueue.push(bindTry(selfApi, directive, value, element, errorHandlerFor(element)));
}
- }
- });
+ });
- // Process non text child nodes
- if (recurse) {
- element.eachNode(function(child, i){
- var childTemplate = self.templetize(child);
- if(childTemplate) {
- template = template || new Template();
- template.addChild(i, childTemplate);
- }
+ // Execute directives
+ foreach(directiveQueue, function(directive){
+ template.addInit(directive());
});
+
+ // Process non text child nodes
+ if (descend) {
+ eachNode(element, function(child, i){
+ template.addChild(i, self.templatize(child));
+ });
+ }
}
- return template;
+ return template.empty() ? null : template;
}
};
diff --git a/src/Filters.js b/src/Filters.js
index 60d53fb9..dac8d31d 100644
--- a/src/Filters.js
+++ b/src/Filters.js
@@ -27,7 +27,7 @@ foreach({
jQuery(this.$element).toggleClass('ng-format-negative', amount < 0);
return '$' + angularFilter['number'].apply(this, [amount, 2]);
},
-
+
'number': function(amount, fractionSize){
if (isNaN(amount) || !isFinite(amount)) {
return '';
@@ -55,15 +55,15 @@ foreach({
}
return text;
},
-
+
'date': function(amount) {
},
-
+
'json': function(object) {
jQuery(this.$element).addClass("ng-monospace");
return toJson(object, true);
},
-
+
'trackPackage': (function(){
var MATCHERS = [
{ name: "UPS",
@@ -89,7 +89,7 @@ foreach({
var returnValue;
foreach(MATCHERS, function(carrier){
foreach(carrier.regexp, function(regexp){
- if (regexp.test(tNo)) {
+ if (!returnValue && regexp.test(tNo)) {
var text = carrier.name + ": " + trackingNo;
var url = carrier.url + trackingNo;
returnValue = new angularFilter.Meta({
@@ -97,19 +97,17 @@ foreach({
url:url,
html: '<a href="' + escapeAttr(url) + '">' + text + '</a>',
trackingNo:trackingNo});
- _.breakLoop();
}
});
- if (returnValue) _.breakLoop();
});
- if (returnValue)
+ if (returnValue)
return returnValue;
else if (trackingNo)
return noMatch || new angularFilter.Meta({text:trackingNo + " is not recognized"});
else
return null;
};})(),
-
+
'link': function(obj, title) {
var text = title || angularFilter.Meta.get(obj);
var url = angularFilter.Meta.get(obj, "url") || angularFilter.Meta.get(obj);
@@ -122,13 +120,13 @@ foreach({
}
return obj;
},
-
-
+
+
'bytes': (function(){
var SUFFIX = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
return function(size) {
if(size === null) return "";
-
+
var suffix = 0;
while (size > 1000) {
size = size / 1024;
@@ -142,7 +140,7 @@ foreach({
return txt + " " + SUFFIX[suffix];
};
})(),
-
+
'image': function(obj, width, height) {
if (obj && obj.url) {
var style = "";
@@ -155,36 +153,36 @@ foreach({
}
return null;
},
-
+
'lowercase': function (obj) {
var text = angularFilter.Meta.get(obj);
return text ? ("" + text).toLowerCase() : text;
},
-
+
'uppercase': function (obj) {
var text = angularFilter.Meta.get(obj);
return text ? ("" + text).toUpperCase() : text;
},
-
+
'linecount': function (obj) {
var text = angularFilter.Meta.get(obj);
if (text==='' || !text) return 1;
return text.split(/\n|\f/).length;
},
-
+
'if': function (result, expression) {
return expression ? result : undefined;
},
-
+
'unless': function (result, expression) {
return expression ? undefined : result;
},
-
+
'googleChartApi': extend(
function(type, data, width, height) {
data = data || {};
var chart = {
- 'cht':type,
+ 'cht':type,
'chco':angularFilterGoogleChartApi['collect'](data, 'color'),
'chtt':angularFilterGoogleChartApi['title'](data),
'chdl':angularFilterGoogleChartApi['collect'](data, 'label'),
@@ -210,7 +208,7 @@ foreach({
var values = seriesValues.join('|');
return values === "" ? null : "t:" + values;
},
-
+
'title': function(data){
var titles = [];
var title = data['title'] || [];
@@ -219,7 +217,7 @@ foreach({
});
return titles.join('|');
},
-
+
'collect': function(data, key){
var outterValues = [];
var count = 0;
@@ -234,7 +232,7 @@ foreach({
});
return count?outterValues.join(','):null;
},
-
+
'encode': function(params, width, height) {
width = width || 200;
height = height || width;
@@ -253,8 +251,8 @@ foreach({
}
}
),
-
-
+
+
'qrcode': function(value, width, height) {
return angularFilterGoogleChartApi['encode']({
'cht':'qr', 'chl':encodeURIComponent(value)}, width, height);
@@ -291,11 +289,11 @@ foreach({
return angularFilterGoogleChartApi('s', data, width, height);
}
},
-
+
'html': function(html){
return new angularFilter.Meta({html:html});
},
-
+
'linky': function(text){
if (!text) return text;
function regExpEscape(text) {
diff --git a/src/Scope.js b/src/Scope.js
index 3633f960..daafabb0 100644
--- a/src/Scope.js
+++ b/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 = self.compile(fn);
+ this.evals.push(function(){
+ self.apply(listenFn, expr());
+ });
},
isProperty: function(exp) {
@@ -103,19 +115,26 @@ Scope.prototype = {
this.eval(expressionText + "=" + toJson(value));
},
- eval: function(expressionText, context) {
-// log('Scope.eval', expressionText);
- var expression = Scope.expressionCache[expressionText];
- if (!expression) {
- var parser = new Parser(expressionText);
- expression = parser.statements();
+ compile: function(exp) {
+ if (isFunction(exp)) return bind(this.state, exp);
+ var expFn = Scope.expressionCache[exp], self = this;
+ if (!expFn) {
+ var parser = new Parser(exp);
+ expFn = parser.statements();
parser.assertAllConsumed();
- Scope.expressionCache[expressionText] = expression;
+ Scope.expressionCache[exp] = expFn;
}
- context = context || {};
- context.scope = this;
- context.self = this.state;
- return expression(context);
+ return function(context){
+ context = context || {};
+ context.self = self.state;
+ context.scope = self;
+ return expFn.call(self, context);
+ };
+ },
+
+ eval: function(expressionText, context) {
+// log('Scope.eval', expressionText);
+ return this.compile(expressionText)(context);
},
//TODO: Refactor. This function needs to be an execution closure for widgets
@@ -189,6 +208,10 @@ Scope.prototype = {
},
addWatchListener: function(watchExpression, listener) {
+ // TODO: clean me up!
+ if (!isFunction(listener)) {
+ listener = this.compile(listener);
+ }
var watcher = this.watchListeners[watchExpression];
if (!watcher) {
watcher = {listeners:[], expression:watchExpression};
diff --git a/src/directives.js b/src/directives.js
index 26cbfe2c..747da3f5 100644
--- a/src/directives.js
+++ b/src/directives.js
@@ -1,121 +1,187 @@
-
-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);
+angularDirective("ng-bind", function(expression){
+ return function(element) {
+ this.$watch(expression, function(value){
+ element.text(value);
+ });
};
});
-
-//translation of {{ }} to ng-bind is external to this
-angular.directive("bind", function(expression, element){
- return function() {
- this.$watch(expression, function(value){
- element.innerText = value;
+var bindTemplateCache = {};
+function compileBindTemplate(template){
+ var fn = bindTemplateCache[template];
+ if (!fn) {
+ var bindings = [];
+ foreach(parseBindings(template), function(text){
+ var exp = binding(text);
+ bindings.push(exp ? function(){
+ return this.$eval(exp);
+ } : function(){
+ return text;
+ });
+ });
+ bindTemplateCache[template] = fn = function(){
+ var parts = [], self = this;
+ foreach(bindings, function(fn){
+ parts.push(fn.call(self));
+ });
+ return parts.join('');
+ };
+ }
+ return fn;
+};
+angularDirective("ng-bind-template", function(expression){
+ var templateFn = compileBindTemplate(expression);
+ return function(element) {
+ var lastValue;
+ this.$addEval(function() {
+ var value = templateFn.call(this);
+ if (value != lastValue) {
+ element.text(value);
+ lastValue = 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));
+angularDirective("ng-bind-attr", function(expression){
+ return function(element){
+ this.$addEval(function(){
+ foreach(this.$eval(expression), function(value, key){
+ element.attr(key, compileBindTemplate(value).call(this));
+ }, this);
+ });
};
});
-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";
+angularDirective("ng-non-bindable", function(){
+ this.descend(false);
+});
+
+angularDirective("ng-repeat", function(expression, element){
+ var reference = this.comment("ng-repeat: " + expression),
+ r = element.removeAttr('ng-repeat'),
+ template = this.compile(element),
+ match = expression.match(/^\s*(.+)\s+in\s+(.*)\s*$/),
+ lhs, rhs, valueIdent, keyIdent;
+ if (! match) {
+ throw "Expected ng-repeat in form of 'item in collection' but got '" +
+ expression + "'.";
+ }
+ lhs = match[1];
+ rhs = match[2];
+ match = lhs.match(/^([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\)$/);
+ if (!match) {
+ throw "'item' in 'item in collection' should be identifier or (key, value) but got '" +
+ keyValue + "'.";
+ }
+ valueIdent = match[3] || match[1];
+ keyIdent = match[2];
+
+ 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(valueIdent, value);
+ if (keyIdent) childScope.scope.set(keyIdent, key);
+ childScope.scope.updateView();
+ lastElement = childScope.element;
+ index ++;
});
+ // shrink children
+ while(children.length > index) {
+ children.pop().element.remove();
+ }
});
};
-});
+}, {exclusive: true});
+angularDirective("ng-action", function(expression, element){
+ return function(){
+ var self = this;
+ element.click(function(){
+ self.$eval(expression);
+ });
+ };
+});
-//ng-non-bindable
-angular.directive("non-bindable", function(expression, element){
- return false;
+angularDirective("ng-watch", function(expression, element){
+ var match = expression.match(/^([^.]*):(.*)$/);
+ if (!match) {
+ throw "Expecting watch expression 'ident_to_watch: watch_statement' got '"
+ + expression + "'";
+ }
+ return function(){
+ this.$watch(match[1], match[2]);
+ };
});
-//Styling
-//
-//ng-class
-//ng-class-odd, ng-class-even
-//ng-style
-//ng-show, ng-hide
+function ngClass(selector) {
+ return function(expression, element){
+ var existing = element[0].className + ' ';
+ return function(element){
+ this.$addEval(expression, function(value){
+ if (selector(this.$index)) {
+ if (isArray(value)) value = value.join(' ');
+ element[0].className = (existing + value).replace(/\s\s+/g, ' ');
+ }
+ });
+ };
+ };
+}
+angularDirective("ng-class", ngClass(function(){return true;}));
+angularDirective("ng-class-odd", ngClass(function(i){return i % 2 == 1;}));
+angularDirective("ng-class-even", ngClass(function(i){return i % 2 == 0;}));
-angular.directive("action", function(expression, element){
- return function(){
- var self = this;
- jQuery(element).click(function(){
- self.$eval(expression);
+angularDirective("ng-show", function(expression, element){
+ return function(element){
+ this.$addEval(expression, function(value){
+ element.css('display', toBoolean(value) ? '' : 'none');
});
};
});
-//ng-eval
-angular.directive("eval", function(expression, element){
- return function(){
- this.$eval(expression);
+angularDirective("ng-hide", function(expression, element){
+ return function(element){
+ this.$addEval(expression, function(value){
+ element.css('display', toBoolean(value) ? 'none' : '');
+ });
};
});
-//ng-watch
-// <div ng-watch="$anchor.book: book=Book.get();"/>
-angular.directive("watch", function(expression, element){
- var watches = {
- 'lhs':'rhs'
- }; // parse
- return function(){
- this.$watch(watches);
+
+angularDirective("ng-style", function(expression, element){
+ return function(element){
+ this.$addEval(expression, function(value){
+ element.css(value);
+ });
};
});
-//widget related
-//ng-validate, ng-required, ng-formatter
-//ng-error
diff --git a/src/directivesAngularCom.js b/src/directivesAngularCom.js
new file mode 100644
index 00000000..84032bdd
--- /dev/null
+++ b/src/directivesAngularCom.js
@@ -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]);
+ });
+ };
+});
+
+
diff --git a/src/jqLite.js b/src/jqLite.js
new file mode 100644
index 00000000..035a7a1b
--- /dev/null
+++ b/src/jqLite.js
@@ -0,0 +1,185 @@
+
+///////////////////////////////////
+//JQLite
+//////////////////////////////////
+
+var jqCache = {};
+var jqName = 'ng-' + new Date().getTime();
+var jqId = 1;
+function jqNextId() { return jqId++; }
+
+var addEventListener = window.document.attachEvent ?
+ function(element, type, fn) {
+ element.attachEvent('on' + type, fn);
+ } : function(element, type, fn) {
+ element.addEventListener(type, fn, false);
+ };
+
+var removeEventListener = window.document.detachEvent ?
+ function(element, type, fn) {
+ element.detachEvent('on' + type, fn);
+ } : function(element, type, fn) {
+ element.removeEventListener(type, fn, false);
+ };
+
+function jqClearData(element) {
+ var cacheId = element[jqName],
+ cache = jqCache[cacheId];
+ if (cache) {
+ foreach(cache.bind || {}, function(fn, type){
+ removeEventListener(element, type, fn);
+ });
+ delete jqCache[cacheId];
+ delete element[jqName];
+ }
+};
+
+function JQLite(element) {
+ this[0] = 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);
+}
+
+JQLite.prototype = {
+ data: function(key, value) {
+ var element = this[0],
+ cacheId = element[jqName],
+ cache = jqCache[cacheId || -1];
+ if (isDefined(value)) {
+ if (!cache) {
+ element[jqName] = cacheId = jqNextId();
+ cache = jqCache[cacheId] = {};
+ }
+ cache[key] = value;
+ } else {
+ return cache ? cache[key] : null;
+ }
+ },
+
+ removeData: function(){
+ jqClearData(this[0]);
+ },
+
+ dealoc: function(){
+ (function dealoc(element){
+ jqClearData(element);
+ for ( var i = 0, children = element.childNodes; i < children.length; i++) {
+ dealoc(children[0]);
+ }
+ })(this[0]);
+ },
+
+ bind: function(type, fn){
+ var element = this[0],
+ bind = this.data('bind'),
+ eventHandler;
+ if (!bind) this.data('bind', bind = {});
+ eventHandler = bind[type];
+ if (!eventHandler) {
+ bind[type] = eventHandler = function() {
+ var self = this;
+ foreach(eventHandler.fns, function(fn){
+ fn.apply(self, arguments);
+ });
+ };
+ eventHandler.fns = [];
+ addEventListener(element, type, eventHandler);
+ }
+ eventHandler.fns.push(fn);
+ },
+
+ trigger: function(type) {
+ var cache = this.data('bind');
+ if (cache) {
+ (cache[type] || noop)();
+ }
+ },
+
+ click: function(fn) {
+ if (fn)
+ this.bind('click', fn);
+ else
+ this.trigger('click');
+ },
+
+ replaceWith: function(replaceNode) {
+ this[0].parentNode.replaceChild(jqLite(replaceNode)[0], this[0]);
+ },
+
+ remove: function() {
+ this.dealoc();
+ this[0].parentNode.removeChild(this[0]);
+ },
+
+ removeAttr: function(name) {
+ this[0].removeAttribute(name);
+ },
+
+ after: function(element) {
+ this[0].parentNode.insertBefore(jqLite(element)[0], this[0].nextSibling);
+ },
+
+ hasClass: function(selector) {
+ var className = " " + selector + " ";
+ if ( (" " + this[0].className + " ").replace(/[\n\t]/g, " ").indexOf( className ) > -1 ) {
+ return true;
+ }
+ return false;
+ },
+
+ addClass: function( selector ) {
+ if (!this.hasClass(selector)) {
+ this[0].className += ' ' + selector;
+ }
+ },
+
+ css: function(name, value) {
+ var style = this[0].style;
+ if (isString(name)) {
+ if (isDefined(value)) {
+ style[name] = value;
+ } else {
+ return style[name];
+ }
+ } else {
+ extend(style, name);
+ }
+ },
+
+ attr: function(name, value){
+ var e = this[0];
+ if (isObject(name)) {
+ foreach(name, function(value, name){
+ e.setAttribute(name, value);
+ });
+ } else if (isDefined(value)) {
+ e.setAttribute(name, value);
+ } else {
+ return e.getAttribute(name);
+ }
+ },
+
+ text: function(value) {
+ if (isDefined(value)) {
+ this[0].textContent = value;
+ }
+ return this[0].textContent;
+ },
+
+ html: function(value) {
+ if (isDefined(value)) {
+ this[0].innerHTML = value;
+ }
+ return this[0].innerHTML;
+ },
+
+ parent: function() { return jqLite(this[0].parentNode);},
+ clone: function() { return jqLite(this[0].cloneNode(true)); }
+};
diff --git a/src/markup.js b/src/markup.js
new file mode 100644
index 00000000..add7ce03
--- /dev/null
+++ b/src/markup.js
@@ -0,0 +1,64 @@
+function parseBindings(string) {
+ var results = [];
+ var lastIndex = 0;
+ var index;
+ while((index = string.indexOf('{{', lastIndex)) > -1) {
+ if (lastIndex < index)
+ results.push(string.substr(lastIndex, index - lastIndex));
+ lastIndex = index;
+
+ index = string.indexOf('}}', index);
+ index = index < 0 ? string.length : index + 2;
+
+ results.push(string.substr(lastIndex, index - lastIndex));
+ lastIndex = index;
+ }
+ if (lastIndex != string.length)
+ results.push(string.substr(lastIndex, string.length - lastIndex));
+ return results.length === 0 ? [ string ] : results;
+};
+
+function binding(string) {
+ var binding = string.replace(/\n/gm, ' ').match(/^\{\{(.*)\}\}$/);
+ return binding ? binding[1] : null;
+};
+
+function hasBindings(bindings) {
+ return bindings.length > 1 || Binder.binding(bindings[0]) !== null;
+};
+
+angularTextMarkup(function(text, textNode, parentElement) {
+ var bindings = parseBindings(text),
+ self = this;
+ if (isLeafNode(parentElement[0])) {
+ parentElement.attr('ng-bind-template', text);
+ } else {
+ var cursor = textNode, newElement;
+ foreach(parseBindings(text), function(text){
+ var exp = binding(text);
+ if (exp) {
+ newElement = self.element('span');
+ newElement.attr('ng-bind', exp);
+ } else {
+ newElement = self.text(text);
+ }
+ cursor.after(newElement);
+ cursor = newElement;
+ });
+ }
+ textNode.remove();
+});
+
+var NG_BIND_ATTR = 'ng-bind-attr';
+angularAttrMarkup(function(value, name, element){
+ if (name.substr(0, 3) != 'ng-') {
+ var bindings = parseBindings(value),
+ bindAttr;
+ if (hasBindings(bindings)) {
+ element.removeAttr(name);
+ bindAttr = fromJson(element.attr(NG_BIND_ATTR) || "{}");
+ bindAttr[name] = value;
+ element.attr(NG_BIND_ATTR, toJson(bindAttr));
+ }
+ }
+});
diff --git a/src/scenario/bootstrap.js b/src/scenario/bootstrap.js
index 1d40b9d0..b49530df 100644
--- a/src/scenario/bootstrap.js
+++ b/src/scenario/bootstrap.js
@@ -8,7 +8,7 @@
var parts = src.match(filename);
return parts[1];
}
- }
+ }
})();
function addScript(path) {
document.write('<script type="text/javascript" src="' + prefix + path + '"></script>');
@@ -17,7 +17,7 @@
document.write('<link rel="stylesheet" type="text/css" href="' + prefix + path + '"/>');
};
window.onload = function(){
- if (!_.stepper) {
+ if (!_.stepper) {
_.stepper = function(collection, iterator, done){
var keys = _.keys(collection);
function next() {
@@ -38,7 +38,7 @@
};
addCSS("../../css/angular-scenario.css");
addScript("../../lib/underscore/underscore.js");
- addScript("../../lib/jquery/jquery-1.3.2.js");
+ addScript("../../lib/jquery/jquery-1.4.2.js");
addScript("../angular-bootstrap.js");
addScript("_namespace.js");
addScript("Steps.js");
diff --git a/src/widgets2.js b/src/widgets2.js
new file mode 100644
index 00000000..b0f467d4
--- /dev/null
+++ b/src/widgets2.js
@@ -0,0 +1,101 @@
+/////////////////////////////////////////
+/////////////////////////////////////////
+/////////////////////////////////////////
+/////////////////////////////////////////
+/////////////////////////////////////////
+
+
+
+
+
+//widget related
+//ng-validate, ng-required, ng-formatter
+//ng-error
+
+//ng-scope ng-controller????
+
+// <input type="text" name="bla" ng-action=""> -> <ng:textinput name="" ng-action=""/>
+angular.widget("inputtext", function(element) {
+ var expression = element.attr('name');
+ var formatter = this.formatter(element.attr('formatter'));
+ var validator = this.validator(element.attr('validator'));
+
+ function validate(value) {
+ var error = validator(element);
+ if (error) {
+ element.addClass("ng-error");
+ scope.markInvalid(this); //move out of scope
+ } else {
+ scope.clearInvalid(this);
+ }
+ }
+
+
+ element.keyup(this.withScope(function(){
+ this.$evalSet(expression, formatter.parse(element.val()));
+ validate(element.val());
+ }));
+
+ return {watch: expression, apply: function(newValue){
+ element.val(formatter.format(newValue));
+ validate(element.val());
+ }};
+
+});
+
+angular.widget("inputfile", function(element) {
+
+});
+
+angular.widget("inputradio", function(element) {
+
+});
+
+
+// <ng:colorpicker name="chosenColor" >
+angular.widget("colorpicker", function(element) {
+ var name = element.attr('datasource');
+ var formatter = this.formatter(element.attr('ng-formatter'));
+
+ element.colorPicker(this.withScope(function(selectedColor){
+ this.$evalSet(name, formatter.parse(selectedColor));
+ }));
+
+ return function(){
+ this.$watch(expression, function(cmyk){
+ element.setColor(formatter.format(cmyk));
+ });
+ };
+});
+
+angular.widget("template", function(element) {
+ var srcExpression = element.attr('src');
+ var self = this;
+ return {watch:srcExpression, apply:function(src){
+ $.load(src, function(html){
+ self.destroy(element);
+ element.html(html);
+ self.compile(element);
+ });
+ }};
+});
+
+
+/**
+ *
+ * {
+ * withScope: //safely executes, with a try/catch. applies scope
+ * compile:
+ * widget:
+ * directive:
+ * validator:
+ * formatter:
+ *
+ *
+ * config:
+ * loadCSS:
+ * loadScript:
+ * loadTemplate:
+ * }
+ *
+ **/