You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
205 lines
7.0 KiB
205 lines
7.0 KiB
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 { |
|
"&": "&", |
|
"<": "<", |
|
">": ">", |
|
"\"": """, |
|
"'": "'" |
|
}[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; |
|
});
|
|
|