can
Version:
MIT-licensed, client-side, JavaScript framework that makes building rich web applications easy.
145 lines (137 loc) • 4.05 kB
JavaScript
steal("can/util","can/view/target","./utils.js","./mustache_core.js",function( can, target, utils, mustacheCore ) {
var decodeHTML = typeof document !== "undefined" && (function(){
var el = document.createElement('div');
return function(html){
if(html.indexOf("&") === -1) {
return html.replace(/\r\n/g,"\n");
}
el.innerHTML = html;
return el.childNodes.length === 0 ? "" : el.childNodes.item(0).nodeValue;
};
})();
// ## HTMLSectionBuilder
//
// Contains a stack of HTMLSections.
// An HTMLSection is created everytime a subsection is found. For example:
//
// {{#if items}} {{#items}} X
//
// At the point X was being processed, their would be 2 HTMLSections in the
// stack. One for the content of `{{#if items}}` and the other for the
// content of `{{#items}}`
var HTMLSectionBuilder = function(){
this.stack = [new HTMLSection()];
};
can.extend(HTMLSectionBuilder.prototype,utils.mixins);
can.extend(HTMLSectionBuilder.prototype,{
startSubSection: function(process){
var newSection = new HTMLSection(process);
this.stack.push(newSection);
return newSection;
},
// Ends the current section and returns a renderer.
// But only returns a renderer if there is a template.
endSubSectionAndReturnRenderer: function(){
if(this.last().isEmpty()) {
this.stack.pop();
return null;
} else {
var htmlSection = this.endSection();
return can.proxy(htmlSection.compiled.hydrate, htmlSection.compiled);
}
},
startSection: function( process ) {
var newSection = new HTMLSection(process);
this.last().add(newSection.targetCallback);
// adding a section within a section ...
// the stack has section ...
this.stack.push(newSection);
},
endSection: function(){
this.last().compile();
return this.stack.pop();
},
inverse: function(){
this.last().inverse();
},
compile: function(){
var compiled = this.stack.pop().compile();
return function(scope, options, nodeList){
if ( !(scope instanceof can.view.Scope) ) {
scope = can.view.Scope.refsScope().add(scope || {});
}
if ( !(options instanceof mustacheCore.Options) ) {
options = new mustacheCore.Options(options || {});
}
return compiled.hydrate(scope, options, nodeList);
};
},
push: function(chars){
this.last().push(chars);
},
pop: function(){
return this.last().pop();
}
});
var HTMLSection = function(process){
this.data = "targetData";
this.targetData = [];
// A record of what targetData element we are within.
this.targetStack = [];
var self = this;
this.targetCallback = function(scope, options, sectionNode){
process.call(this,
scope,
options,
sectionNode,
can.proxy(self.compiled.hydrate, self.compiled),
self.inverseCompiled && can.proxy(self.inverseCompiled.hydrate, self.inverseCompiled) ) ;
};
};
can.extend(HTMLSection.prototype,{
inverse: function(){
this.inverseData = [];
this.data = "inverseData";
},
// Adds a DOM node.
push: function(data){
this.add(data);
this.targetStack.push(data);
},
pop: function(){
return this.targetStack.pop();
},
add: function(data){
if(typeof data === "string"){
data = decodeHTML(data);
}
if(this.targetStack.length) {
can.last(this.targetStack).children.push(data);
} else {
this[this.data].push(data);
}
},
compile: function(){
this.compiled = target(this.targetData, can.document || can.global.document);
if(this.inverseData) {
this.inverseCompiled = target(this.inverseData, can.document || can.global.document);
delete this.inverseData;
}
this.targetStack = this.targetData = null;
return this.compiled;
},
children: function(){
if(this.targetStack.length) {
return can.last(this.targetStack).children;
} else {
return this[this.data];
}
},
// Returns if a section is empty
isEmpty: function(){
return !this.targetData.length;
}
});
HTMLSectionBuilder.HTMLSection = HTMLSection;
return HTMLSectionBuilder;
});