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: ${foo}) // 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; });