diff options
| -rw-r--r-- | src/Compiler.js | 188 | ||||
| -rw-r--r-- | test/CompilerSpec.js | 14 |
2 files changed, 194 insertions, 8 deletions
diff --git a/src/Compiler.js b/src/Compiler.js new file mode 100644 index 00000000..f4d901fb --- /dev/null +++ b/src/Compiler.js @@ -0,0 +1,188 @@ +/** + * Template provides directions an how to bind to a given element. + * It contains a list of init functions which need to be called to + * bind to a new instance of elements. It also provides a list + * of child paths which contain child templates + */ +function Template() { + this.paths = []; + this.children = []; + this.inits = []; +} + +Template.prototype = { + init: function(element, scope) { + foreach(this.inits, function(fn) { + scope.apply(fn, nodeLite(element)); + }); + + var i, + childNodes = element.childNodes, + children = this.children, + paths = this.paths, + length = paths.length; + for (i = 0; i < length; i++) { + children[i].init(childNodes[paths[i]], scope); + } + }, + + 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); + } +}; + +/////////////////////////////////// +//NodeLite +////////////////////////////////// + +function NodeLite(element) { + this.element = element; +} + +function nodeLite(element) { + return element instanceof NodeLite ? element : new NodeLite(element); +} + +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); + } + } + }, + + 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); + } + }, + + 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 Compiler(markup, directives, widgets){ + this.markup = markup; + 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(); + scope.element = element; + return { + scope: scope, + element:element, + init: bind(template, template.init, element, scope) + }; + }; + }, + + templetize: function(element){ + var self = this, + markup = self.markup, + markupSize = markup.length, + directives = self.directives, + widgets = self.widgets, + recurse = 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); + } + }); + + // 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(); + if (directive.exclusive) { + template.setExclusiveInit(init); + exclusive = true; + } else { + template.addInit(init); + } + recurse = recurse && init; + } else { + error("Directive '" + match[0] + "' is not recognized."); + } + } + }); + + // 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); + } + }); + } + return template; + } +}; diff --git a/test/CompilerSpec.js b/test/CompilerSpec.js index 2c156cc4..7bf48d18 100644 --- a/test/CompilerSpec.js +++ b/test/CompilerSpec.js @@ -78,17 +78,14 @@ describe('compiler', function(){ it('should allow creation of templates', function(){ directives.duplicate = function(expr, element){ - var template, - marker = document.createComment("marker"); - element.replaceWith(marker); + element.replaceWith(document.createComment("marker")); element.removeAttribute("ng-duplicate"); - template = this.compile(element); + var template = this.compile(element); return function(marker) { - var parentNode = marker.parentNode; this.$eval(function() { - parentNode.insertBefore( - template(element.clone()).element, - marker.nextSibling); + dump("A"); + marker.after(template(element.clone()).element); + dump("B"); }); }; }; @@ -135,4 +132,5 @@ describe('compiler', function(){ var scope = compile('<ng:button>push me</ng:button>'); expect(scope.element.innerHTML).toEqual('before<span ng-hello="middle">replaced</span>after'); }); + }); |
