/* Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ if(!dojo._hasResource["dijit._Templated"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. dojo._hasResource["dijit._Templated"] = true; dojo.provide("dijit._Templated"); dojo.require("dijit._Widget"); dojo.require("dojo.string"); dojo.require("dojo.parser"); dojo.require("dojo.cache"); dojo.declare("dijit._Templated", null, { // summary: // Mixin for widgets that are instantiated from a template // templateString: [protected] String // A string that represents the widget template. Pre-empts the // templatePath. In builds that have their strings "interned", the // templatePath is converted to an inline templateString, thereby // preventing a synchronous network call. // // 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 dojo.cache() instead. templatePath: null, // widgetsInTemplate: [protected] Boolean // Should we parse the template to find widgets that might be // declared in markup inside it? False by default. widgetsInTemplate: false, // skipNodeCache: [protected] Boolean // If using a cached widget template node 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, // _earlyTemplatedStartup: Boolean // A fallback to preserve the 1.0 - 1.3 behavior of children in // templates having their startup called before the parent widget // fires postCreate. Defaults to 'false', causing child widgets to // have their .startup() called immediately before a parent widget // .startup(), but always after the parent .postCreate(). Set to // 'true' to re-enable to previous, arguably broken, behavior. _earlyTemplatedStartup: false, /*===== // _attachPoints: [private] String[] // List of widget attribute names associated with dojoAttachPoint=... in the // template, ex: ["containerNode", "labelNode"] _attachPoints: [], =====*/ /*===== // _attachEvents: [private] Handle[] // List of connections associated with dojoAttachEvent=... in the // template _attachEvents: [], =====*/ constructor: function(){ this._attachPoints = []; this._attachEvents = []; }, _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 dojo.string.substitute(tmpl, this, function(value, key){ if(key.charAt(0) == '!'){ value = dojo.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 : // Safer substitution, see heading "Attribute values" in // http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.3.2 value.toString().replace(/"/g,"""); //TODO: add &? use encodeXML method? }, this); }, buildRendering: function(){ // summary: // Construct the UI for this widget from a template, setting this.domNode. // tags: // protected // 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 = dijit._Templated.getCachedTemplate(this.templatePath, this.templateString, this._skipNodeCache); var node; if(dojo.isString(cached)){ node = dojo._toDom(this._stringRepl(cached)); 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 _Widget.buildRendering() to get base classes assigned // TODO: change the baseClass assignment to attributeMap this.inherited(arguments); // recurse through the node, looking for, and attaching to, our // attachment points and events, which should be defined on the template node. this._attachTemplateNodes(node); if(this.widgetsInTemplate){ // Store widgets that we need to start at a later point in time var cw = (this._startupWidgets = dojo.parser.parse(node, { noStart: !this._earlyTemplatedStartup, template: true, inherited: {dir: this.dir, lang: this.lang}, propsThis: this, // so data-dojo-props of widgets in the template can reference "this" to refer to me scope: "dojo" // even in multi-version mode templates use dojoType/data-dojo-type })); this._supportingWidgets = dijit.findWidgets(node); this._attachTemplateNodes(cw, function(n,p){ return n[p]; }); } this._fillContent(this.srcNodeRef); }, _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); } } }, _attachTemplateNodes: function(rootNode, getAttrFunc){ // summary: // Iterate through the template and attach functions and nodes accordingly. // Alternately, if rootNode is an array of widgets, then will process dojoAttachPoint // etc. for those widgets. // description: // Map widget properties and functions to the handlers specified in // the dom node and it's descendants. This function iterates over all // nodes and looks for these properties: // * dojoAttachPoint // * dojoAttachEvent // * waiRole // * waiState // rootNode: DomNode|Array[Widgets] // the node to search for properties. All children will be searched. // getAttrFunc: Function? // a function which will be used to obtain property for a given // DomNode/Widget // tags: // private getAttrFunc = getAttrFunc || function(n,p){ return n.getAttribute(p); }; var nodes = dojo.isArray(rootNode) ? rootNode : (rootNode.all || rootNode.getElementsByTagName("*")); var x = dojo.isArray(rootNode) ? 0 : -1; for(; x<nodes.length; x++){ var baseNode = (x == -1) ? rootNode : nodes[x]; if(this.widgetsInTemplate && (getAttrFunc(baseNode, "dojoType") || getAttrFunc(baseNode, "data-dojo-type"))){ continue; } // Process dojoAttachPoint var attachPoint = getAttrFunc(baseNode, "dojoAttachPoint") || getAttrFunc(baseNode, "data-dojo-attach-point"); if(attachPoint){ var point, points = attachPoint.split(/\s*,\s*/); while((point = points.shift())){ if(dojo.isArray(this[point])){ this[point].push(baseNode); }else{ this[point]=baseNode; } this._attachPoints.push(point); } } // Process dojoAttachEvent var attachEvent = getAttrFunc(baseNode, "dojoAttachEvent") || getAttrFunc(baseNode, "data-dojo-attach-event");; if(attachEvent){ // NOTE: we want to support attributes that have the form // "domEvent: nativeEvent; ..." var event, events = attachEvent.split(/\s*,\s*/); var trim = dojo.trim; while((event = events.shift())){ if(event){ var thisFunc = null; if(event.indexOf(":") != -1){ // oh, if only JS had tuple assignment var funcNameArr = event.split(":"); event = trim(funcNameArr[0]); thisFunc = trim(funcNameArr[1]); }else{ event = trim(event); } if(!thisFunc){ thisFunc = event; } this._attachEvents.push(this.connect(baseNode, event, thisFunc)); } } } // waiRole, waiState // TODO: remove this in 2.0, templates are now using role=... and aria-XXX=... attributes directicly var role = getAttrFunc(baseNode, "waiRole"); if(role){ dijit.setWaiRole(baseNode, role); } var values = getAttrFunc(baseNode, "waiState"); if(values){ dojo.forEach(values.split(/\s*,\s*/), function(stateValue){ if(stateValue.indexOf('-') != -1){ var pair = stateValue.split('-'); dijit.setWaiState(baseNode, pair[0], pair[1]); } }); } } }, startup: function(){ dojo.forEach(this._startupWidgets, function(w){ if(w && !w._started && w.startup){ w.startup(); } }); this.inherited(arguments); }, destroyRendering: function(){ // Delete all attach points to prevent IE6 memory leaks. dojo.forEach(this._attachPoints, function(point){ delete this[point]; }, this); this._attachPoints = []; // And same for event handlers dojo.forEach(this._attachEvents, this.disconnect, this); this._attachEvents = []; this.inherited(arguments); } } ); // key is either templatePath or templateString; object is either string or DOM tree dijit._Templated._templateCache = {}; dijit._Templated.getCachedTemplate = function(templatePath, templateString, alwaysUseString){ // summary: // Static method to get a template based on the templatePath or // templateString key // templatePath: String||dojo.uri.Uri // The URL to get the template from. // templateString: String? // a string to use in lieu of fetching the template from a URL. Takes precedence // over templatePath // 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 = dijit._Templated._templateCache; var key = templateString || templatePath; 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 == dojo.doc){ // string or node of the same document return cached; } }catch(e){ /* squelch */ } // IE can throw an exception if cached.ownerDocument was reloaded dojo.destroy(cached); } // If necessary, load template string from template path if(!templateString){ templateString = dojo.cache(templatePath, {sanitize: true}); } templateString = dojo.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 = dojo._toDom(templateString); if(node.nodeType != 1){ throw new Error("Invalid template: " + templateString); } return (tmplts[key] = node); //Node } }; if(dojo.isIE){ dojo.addOnWindowUnload(function(){ var cache = dijit._Templated._templateCache; for(var key in cache){ var value = cache[key]; if(typeof value == "object"){ // value is either a string or a DOM node template dojo.destroy(value); } delete cache[key]; } }); } // These arguments can be specified for widgets which are used in templates. // Since any widget can be specified as sub widgets in template, mix it // into the base widget class. (This is a hack, but it's effective.) dojo.extend(dijit._Widget,{ dojoAttachEvent: "", dojoAttachPoint: "", waiRole: "", waiState:"" }); }