aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/Angular.js77
-rw-r--r--src/Compiler.js259
-rw-r--r--src/Filters.js52
-rw-r--r--src/Scope.js2
-rw-r--r--src/directives.js65
-rw-r--r--src/jqLite.js185
-rw-r--r--src/markup.js64
-rw-r--r--src/widgets2.js18
-rw-r--r--test/CompilerSpec.js9
-rw-r--r--test/directivesSpec.js12
-rw-r--r--test/markupSpec.js49
11 files changed, 503 insertions, 289 deletions
diff --git a/src/Angular.js b/src/Angular.js
index ce1038cc..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,
@@ -21,7 +22,7 @@ function noop() {}
function identity($) {return $;}
if (!window['console']) window['console']={'log':noop, 'error':noop};
-function extension(angular, name) {
+function extensionMap(angular, name) {
var extPoint;
return angular[name] || (extPoint = angular[name] = function (name, fn, prop){
if (isDefined(fn)) {
@@ -31,20 +32,30 @@ function extension(angular, 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,
- angular = window['angular'] || (window['angular'] = {}),
- 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(){
+ 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;
@@ -53,6 +64,29 @@ 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';}
@@ -112,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){
diff --git a/src/Compiler.js b/src/Compiler.js
index ca94c893..ba598a43 100644
--- a/src/Compiler.js
+++ b/src/Compiler.js
@@ -48,224 +48,41 @@ Template.prototype = {
};
///////////////////////////////////
-//JQLite
+//Compiler
//////////////////////////////////
-
-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 isTextNode(node) {
+ return node.nodeType == Node.TEXT_NODE;
}
-function jqLite(element) {
- if (typeof element == 'string') {
- var div = document.createElement('div');
- div.innerHTML = element;
- element = div.childNodes[0];
+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);
+ }
}
- 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;
+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);
}
- },
-
- 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');
- },
-
- eachTextNode: function(fn){
- var i, chldNodes = this[0].childNodes || [], size = chldNodes.length, chld;
- for (i = 0; i < size; i++) {
- if((chld = new JQLite(chldNodes[i])).isText()) {
- fn(chld, i);
- }
- }
- },
-
-
- eachNode: function(fn){
- var i, chldNodes = this[0].childNodes || [], size = chldNodes.length, chld;
- for (i = 0; i < size; i++) {
- if(!(chld = new JQLite(chldNodes[i])).isText()) {
- fn(chld, i);
- }
- }
- },
-
- eachAttribute: function(fn){
- var i, attrs = this[0].attributes || [], size = attrs.length, chld, attr;
- for (i = 0; i < size; i++) {
- var attr = attrs[i];
- fn(attr.name, attr.value);
- }
- },
-
- 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);},
- isText: function() { return this[0].nodeType == Node.TEXT_NODE; },
- clone: function() { return jqLite(this[0].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;
}
@@ -291,8 +108,6 @@ Compiler.prototype = {
elementName = element[0].nodeName,
widgets = self.widgets,
widget = widgets[elementName],
- markup = self.markup,
- markupSize = markup.length,
directives = self.directives,
descend = true,
exclusive = false,
@@ -300,7 +115,9 @@ Compiler.prototype = {
template = new Template(),
selfApi = {
compile: bind(self, self.compile),
- reference:function(name) {return jqLite(document.createComment(name));},
+ 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;}
};
@@ -308,14 +125,20 @@ Compiler.prototype = {
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);
- }
+ 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){
+ 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) {
@@ -333,7 +156,7 @@ Compiler.prototype = {
// Process non text child nodes
if (descend) {
- element.eachNode(function(child, i){
+ eachNode(element, function(child, i){
template.addChild(i, self.templatize(child));
});
}
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 a3e128b6..daafabb0 100644
--- a/src/Scope.js
+++ b/src/Scope.js
@@ -116,7 +116,7 @@ Scope.prototype = {
},
compile: function(exp) {
- if (isFunction(exp)) return exp;
+ if (isFunction(exp)) return bind(this.state, exp);
var expFn = Scope.expressionCache[exp], self = this;
if (!expFn) {
var parser = new Parser(exp);
diff --git a/src/directives.js b/src/directives.js
index bbf68669..747da3f5 100644
--- a/src/directives.js
+++ b/src/directives.js
@@ -18,9 +18,50 @@ angularDirective("ng-bind", function(expression){
};
});
+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;
+ }
+ });
+ };
+});
+
angularDirective("ng-bind-attr", function(expression){
return function(element){
- this.$watch(expression, bind(element, element.attr));
+ this.$addEval(function(){
+ foreach(this.$eval(expression), function(value, key){
+ element.attr(key, compileBindTemplate(value).call(this));
+ }, this);
+ });
};
});
@@ -29,7 +70,7 @@ angularDirective("ng-non-bindable", function(){
});
angularDirective("ng-repeat", function(expression, element){
- var reference = this.reference("ng-repeat: " + expression),
+ var reference = this.comment("ng-repeat: " + expression),
r = element.removeAttr('ng-repeat'),
template = this.compile(element),
match = expression.match(/^\s*(.+)\s+in\s+(.*)\s*$/),
@@ -135,18 +176,12 @@ angularDirective("ng-hide", function(expression, element){
});
};
});
-/////////////////////////////////////////
-/////////////////////////////////////////
-/////////////////////////////////////////
-/////////////////////////////////////////
-/////////////////////////////////////////
-
-
-
-
-//widget related
-//ng-validate, ng-required, ng-formatter
-//ng-error
+angularDirective("ng-style", function(expression, element){
+ return function(element){
+ this.$addEval(expression, function(value){
+ element.css(value);
+ });
+ };
+});
-//ng-scope ng-controller????
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/widgets2.js b/src/widgets2.js
index 0d7bbd49..b0f467d4 100644
--- a/src/widgets2.js
+++ b/src/widgets2.js
@@ -1,3 +1,19 @@
+/////////////////////////////////////////
+/////////////////////////////////////////
+/////////////////////////////////////////
+/////////////////////////////////////////
+/////////////////////////////////////////
+
+
+
+
+
+//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');
@@ -49,7 +65,7 @@ angular.widget("colorpicker", function(element) {
this.$watch(expression, function(cmyk){
element.setColor(formatter.format(cmyk));
});
- }
+ };
});
angular.widget("template", function(element) {
diff --git a/test/CompilerSpec.js b/test/CompilerSpec.js
index 57b597c4..a49a2551 100644
--- a/test/CompilerSpec.js
+++ b/test/CompilerSpec.js
@@ -3,7 +3,7 @@ describe('compiler', function(){
return jQuery(html)[0];
}
- var compiler, markup, directives, widgets, compile, log;
+ var compiler, textMarkup, directives, widgets, compile, log;
beforeEach(function(){
log = "";
@@ -24,9 +24,10 @@ describe('compiler', function(){
}
};
- markup = [];
+ textMarkup = [];
+ attrMarkup = [];
widgets = {};
- compiler = new Compiler(markup, directives, widgets);
+ compiler = new Compiler(textMarkup, attrMarkup, directives, widgets);
compile = function(html){
var e = element("<div>" + html + "</div>");
var view = compiler.compile(e)(e);
@@ -108,7 +109,7 @@ describe('compiler', function(){
});
it('should process markup before directives', function(){
- markup.push(function(text, textNode, parentNode) {
+ textMarkup.push(function(text, textNode, parentNode) {
if (text == 'middle') {
expect(textNode.text()).toEqual(text);
parentNode.attr('hello', text);
diff --git a/test/directivesSpec.js b/test/directivesSpec.js
index d125d326..18bedb64 100644
--- a/test/directivesSpec.js
+++ b/test/directivesSpec.js
@@ -3,7 +3,7 @@ describe("directives", function(){
var compile, element;
beforeEach(function() {
- var compiler = new Compiler(angularMarkup, angularDirective, angularWidget);
+ var compiler = new Compiler(angularTextMarkup, angularAttrMarkup, angularDirective, angularWidget);
compile = function(html) {
element = jqLite(html);
var view = compiler.compile(element)(element);
@@ -39,6 +39,14 @@ describe("directives", function(){
expect(element.text()).toEqual('misko');
});
+ it('should ng-bind-template', function() {
+ var scope = compile('<div ng-bind-template="Hello {{name}}!"></div>');
+ expect(element.text()).toEqual('');
+ scope.set('name', 'Misko');
+ scope.updateView();
+ expect(element.text()).toEqual('Hello Misko!');
+ });
+
it('should ng-bind-attr', function(){
var scope = compile('<img ng-bind-attr="{src:\'mysrc\', alt:\'myalt\'}"/>');
expect(element.attr('src')).toEqual(null);
@@ -81,7 +89,7 @@ describe("directives", function(){
it('should error on wrong parsing of ng-repeat', function(){
var scope = compile('<ul><li ng-repeat="i dont parse"></li></ul>');
var log = "";
- element.eachNode(function(li){
+ eachNode(element, function(li){
log += li.attr('ng-error') + ';';
log += li.hasClass('ng-exception') + ';';
});
diff --git a/test/markupSpec.js b/test/markupSpec.js
new file mode 100644
index 00000000..9e89af7b
--- /dev/null
+++ b/test/markupSpec.js
@@ -0,0 +1,49 @@
+describe("markups", function(){
+
+ var compile, element, scope;
+
+ beforeEach(function() {
+ scope = null;
+ element = null;
+ var compiler = new Compiler(angularTextMarkup, angularAttrMarkup, angularDirective, angularWidget);
+ compile = function(html) {
+ element = jqLite(html);
+ var view = compiler.compile(element)(element);
+ view.init();
+ scope = view.scope;
+ };
+ });
+
+ afterEach(function(){
+ if (element) {
+ element.remove();
+ }
+ expect(_(jqCache).size()).toEqual(0);
+ });
+
+ it('should translate {{}} in text', function(){
+ compile('<div>hello {{name}}!</div>');
+ expect(element.html()).toEqual('hello <span ng-bind="name"></span>!');
+ scope.set('name', 'Misko');
+ scope.updateView();
+ expect(element.html()).toEqual('hello <span ng-bind="name">Misko</span>!');
+ });
+
+ it('should translate {{}} in terminal nodes', function(){
+ compile('<select><option>Greet {{name}}!</option></select>');
+ expect(element.html()).toEqual('<option ng-bind-template="Greet {{name}}!"></option>');
+ scope.set('name', 'Misko');
+ scope.updateView();
+ expect(element.html()).toEqual('<option ng-bind-template="Greet {{name}}!">Greet Misko!</option>');
+ });
+
+ it('should translate {{}} in attributes', function(){
+ compile('<img src="http://server/{{path}}.png"/>');
+ expect(element.attr('src')).toEqual();
+ expect(element.attr('ng-bind-attr')).toEqual('{"src":"http://server/{{path}}.png"}');
+ scope.set('path', 'a/b');
+ scope.updateView();
+ expect(element.attr('src')).toEqual("http://server/a/b.png");
+ });
+
+});