You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

206 lines
7.0 KiB
JavaScript

define([
"dojo/cache", // dojo.cache
"dojo/_base/declare", // declare
"dojo/dom-construct", // domConstruct.destroy, domConstruct.toDom
"dojo/_base/lang", // lang.getObject
"dojo/on",
"dojo/sniff", // has("ie")
"dojo/string", // string.substitute string.trim
"./_AttachMixin"
], function(cache, declare, domConstruct, lang, on, has, string, _AttachMixin){
// module:
// dijit/_TemplatedMixin
var _TemplatedMixin = declare("dijit._TemplatedMixin", _AttachMixin, {
// summary:
// Mixin for widgets that are instantiated from a template
// templateString: [protected] String
// A string that represents the widget template.
// Use in conjunction with dojo.cache() to load from a file.
templateString: null,
// templatePath: [protected deprecated] String
// Path to template (HTML file) for this widget relative to dojo.baseUrl.
// Deprecated: use templateString with require([... "dojo/text!..."], ...) instead
templatePath: null,
// skipNodeCache: [protected] Boolean
// If using a cached widget template nodes poses issues for a
// particular widget class, it can set this property to ensure
// that its template is always re-built from a string
_skipNodeCache: false,
/*=====
// _rendered: Boolean
// Not normally use, but this flag can be set by the app if the server has already rendered the template,
// i.e. already inlining the template for the widget into the main page. Reduces _TemplatedMixin to
// just function like _AttachMixin.
_rendered: false,
=====*/
// Set _AttachMixin.searchContainerNode to true for back-compat for widgets that have data-dojo-attach-point's
// and events inside this.containerNode. Remove for 2.0.
searchContainerNode: true,
_stringRepl: function(tmpl){
// summary:
// Does substitution of ${foo} type properties in template string
// tags:
// private
var className = this.declaredClass, _this = this;
// Cache contains a string because we need to do property replacement
// do the property replacement
return string.substitute(tmpl, this, function(value, key){
if(key.charAt(0) == '!'){ value = lang.getObject(key.substr(1), false, _this); }
if(typeof value == "undefined"){ throw new Error(className+" template:"+key); } // a debugging aide
if(value == null){ return ""; }
// Substitution keys beginning with ! will skip the transform step,
// in case a user wishes to insert unescaped markup, e.g. ${!foo}
return key.charAt(0) == "!" ? value : this._escapeValue("" + value);
}, this);
},
_escapeValue: function(/*String*/ val){
// summary:
// Escape a value to be inserted into the template, either into an attribute value
// (ex: foo="${bar}") or as inner text of an element (ex: <span>${foo}</span>)
// Safer substitution, see heading "Attribute values" in
// http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.3.2
// and also https://www.owasp.org/index.php/XSS_%28Cross_Site_Scripting%29_Prevention_Cheat_Sheet#RULE_.231_-_HTML_Escape_Before_Inserting_Untrusted_Data_into_HTML_Element_Content
return val.replace(/["'<>&]/g, function(val){
return {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
"\"": "&quot;",
"'": "&#x27;"
}[val];
});
},
buildRendering: function(){
// summary:
// Construct the UI for this widget from a template, setting this.domNode.
// tags:
// protected
if(!this._rendered){
if(!this.templateString){
this.templateString = cache(this.templatePath, {sanitize: true});
}
// Lookup cached version of template, and download to cache if it
// isn't there already. Returns either a DomNode or a string, depending on
// whether or not the template contains ${foo} replacement parameters.
var cached = _TemplatedMixin.getCachedTemplate(this.templateString, this._skipNodeCache, this.ownerDocument);
var node;
if(lang.isString(cached)){
node = domConstruct.toDom(this._stringRepl(cached), this.ownerDocument);
if(node.nodeType != 1){
// Flag common problems such as templates with multiple top level nodes (nodeType == 11)
throw new Error("Invalid template: " + cached);
}
}else{
// if it's a node, all we have to do is clone it
node = cached.cloneNode(true);
}
this.domNode = node;
}
// Call down to _WidgetBase.buildRendering() to get base classes assigned
// TODO: change the baseClass assignment to _setBaseClassAttr
this.inherited(arguments);
if(!this._rendered){
this._fillContent(this.srcNodeRef);
}
this._rendered = true;
},
_fillContent: function(/*DomNode*/ source){
// summary:
// Relocate source contents to templated container node.
// this.containerNode must be able to receive children, or exceptions will be thrown.
// tags:
// protected
var dest = this.containerNode;
if(source && dest){
while(source.hasChildNodes()){
dest.appendChild(source.firstChild);
}
}
}
});
// key is templateString; object is either string or DOM tree
_TemplatedMixin._templateCache = {};
_TemplatedMixin.getCachedTemplate = function(templateString, alwaysUseString, doc){
// summary:
// Static method to get a template based on the templatePath or
// templateString key
// templateString: String
// The template
// alwaysUseString: Boolean
// Don't cache the DOM tree for this template, even if it doesn't have any variables
// doc: Document?
// The target document. Defaults to document global if unspecified.
// returns: Mixed
// Either string (if there are ${} variables that need to be replaced) or just
// a DOM tree (if the node can be cloned directly)
// is it already cached?
var tmplts = _TemplatedMixin._templateCache;
var key = templateString;
var cached = tmplts[key];
if(cached){
try{
// if the cached value is an innerHTML string (no ownerDocument) or a DOM tree created within the
// current document, then use the current cached value
if(!cached.ownerDocument || cached.ownerDocument == (doc || document)){
// string or node of the same document
return cached;
}
}catch(e){ /* squelch */ } // IE can throw an exception if cached.ownerDocument was reloaded
domConstruct.destroy(cached);
}
templateString = string.trim(templateString);
if(alwaysUseString || templateString.match(/\$\{([^\}]+)\}/g)){
// there are variables in the template so all we can do is cache the string
return (tmplts[key] = templateString); //String
}else{
// there are no variables in the template so we can cache the DOM tree
var node = domConstruct.toDom(templateString, doc);
if(node.nodeType != 1){
throw new Error("Invalid template: " + templateString);
}
return (tmplts[key] = node); //Node
}
};
if(has("ie")){
on(window, "unload", function(){
var cache = _TemplatedMixin._templateCache;
for(var key in cache){
var value = cache[key];
if(typeof value == "object"){ // value is either a string or a DOM node template
domConstruct.destroy(value);
}
delete cache[key];
}
});
}
return _TemplatedMixin;
});