310 lines
11 KiB
JavaScript
310 lines
11 KiB
JavaScript
define("dijit/_TemplatedMixin", [
|
|
"dojo/_base/lang", // lang.getObject
|
|
"dojo/touch",
|
|
"./_WidgetBase",
|
|
"dojo/string", // string.substitute string.trim
|
|
"dojo/cache", // dojo.cache
|
|
"dojo/_base/array", // array.forEach
|
|
"dojo/_base/declare", // declare
|
|
"dojo/dom-construct", // domConstruct.destroy, domConstruct.toDom
|
|
"dojo/sniff", // has("ie")
|
|
"dojo/_base/unload" // unload.addOnWindowUnload
|
|
], function(lang, touch, _WidgetBase, string, cache, array, declare, domConstruct, has, unload) {
|
|
|
|
// module:
|
|
// dijit/_TemplatedMixin
|
|
|
|
var _TemplatedMixin = declare("dijit._TemplatedMixin", null, {
|
|
// 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,
|
|
|
|
// _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 data-dojo-attach-point=... in the
|
|
// template, ex: ["containerNode", "labelNode"]
|
|
_attachPoints: [],
|
|
|
|
// _attachEvents: [private] Handle[]
|
|
// List of connections associated with data-dojo-attach-event=... in the
|
|
// template
|
|
_attachEvents: [],
|
|
=====*/
|
|
|
|
constructor: function(/*===== params, srcNodeRef =====*/){
|
|
// summary:
|
|
// Create the widget.
|
|
// params: Object|null
|
|
// Hash of initialization parameters for widget, including scalar values (like title, duration etc.)
|
|
// and functions, typically callbacks like onClick.
|
|
// The hash can contain any of the widget's properties, excluding read-only properties.
|
|
// srcNodeRef: DOMNode|String?
|
|
// If a srcNodeRef (DOM node) is specified, replace srcNodeRef with my generated DOM tree.
|
|
|
|
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 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 :
|
|
// 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
|
|
|
|
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 _Widget.buildRendering() to get base classes assigned
|
|
// TODO: change the baseClass assignment to _setBaseClassAttr
|
|
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, function(n,p){ return n.getAttribute(p); });
|
|
|
|
this._beforeFillContent(); // hook for _WidgetsInTemplateMixin
|
|
|
|
this._fillContent(this.srcNodeRef);
|
|
},
|
|
|
|
_beforeFillContent: function(){
|
|
},
|
|
|
|
_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 data-dojo-attach-point
|
|
// 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/data-dojo-attach-point
|
|
// - dojoAttachEvent/data-dojo-attach-event
|
|
// rootNode: DomNode|Widget[]
|
|
// 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
|
|
|
|
var nodes = lang.isArray(rootNode) ? rootNode : (rootNode.all || rootNode.getElementsByTagName("*"));
|
|
var x = lang.isArray(rootNode) ? 0 : -1;
|
|
for(; x < 0 || nodes[x]; x++){ // don't access nodes.length on IE, see #14346
|
|
var baseNode = (x == -1) ? rootNode : nodes[x];
|
|
if(this.widgetsInTemplate && (getAttrFunc(baseNode, "dojoType") || getAttrFunc(baseNode, "data-dojo-type"))){
|
|
continue;
|
|
}
|
|
// Process data-dojo-attach-point
|
|
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(lang.isArray(this[point])){
|
|
this[point].push(baseNode);
|
|
}else{
|
|
this[point]=baseNode;
|
|
}
|
|
this._attachPoints.push(point);
|
|
}
|
|
}
|
|
|
|
// Process data-dojo-attach-event
|
|
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 = lang.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;
|
|
}
|
|
// Map "press", "move" and "release" to keys.touch, keys.move, keys.release
|
|
this._attachEvents.push(this.connect(baseNode, touch[event] || event, thisFunc));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
destroyRendering: function(){
|
|
// Delete all attach points to prevent IE6 memory leaks.
|
|
array.forEach(this._attachPoints, function(point){
|
|
delete this[point];
|
|
}, this);
|
|
this._attachPoints = [];
|
|
|
|
// And same for event handlers
|
|
array.forEach(this._attachEvents, this.disconnect, this);
|
|
this._attachEvents = [];
|
|
|
|
this.inherited(arguments);
|
|
}
|
|
});
|
|
|
|
// 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")){
|
|
unload.addOnWindowUnload(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];
|
|
}
|
|
});
|
|
}
|
|
|
|
// 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.).
|
|
// Remove for 2.0. Also, hide from API doc parser.
|
|
lang.extend(_WidgetBase, /*===== {} || =====*/ {
|
|
dojoAttachEvent: "",
|
|
dojoAttachPoint: ""
|
|
});
|
|
|
|
return _TemplatedMixin;
|
|
});
|