blob: 28288a9e683b044af131cfd4ba8f7b5e01e09861 [file] [log] [blame]
/*
Copyright (c) 2004-2006, The Dojo Foundation
All Rights Reserved.
Licensed under the Academic Free License version 2.1 or above OR the
modified BSD license. For more information on Dojo licensing, see:
http://dojotoolkit.org/community/licensing.shtml
*/
dojo.provide("dojo.widget.DomWidget");
dojo.require("dojo.event.*");
dojo.require("dojo.widget.Widget");
dojo.require("dojo.dom");
dojo.require("dojo.html.style");
dojo.require("dojo.xml.Parse");
dojo.require("dojo.uri.*");
dojo.require("dojo.lang.func");
dojo.require("dojo.lang.extras");
dojo.widget._cssFiles = {};
dojo.widget._cssStrings = {};
dojo.widget._templateCache = {};
dojo.widget.defaultStrings = {
// summary: a mapping of strings that are used in template variable replacement
dojoRoot: dojo.hostenv.getBaseScriptUri(),
baseScriptUri: dojo.hostenv.getBaseScriptUri()
};
dojo.widget.fillFromTemplateCache = function(obj, templatePath, templateString, avoidCache){
// summary:
// static method to build from a template w/ or w/o a real widget in
// place
// obj: DomWidget
// an instance of dojo.widget.DomWidget to initialize the template for
// templatePath: String
// the URL to get the template from. dojo.uri.Uri is often passed as well.
// templateString: String?
// a string to use in lieu of fetching the template from a URL
// avoidCache: Boolean?
// should the template system not use whatever is in the cache and
// always use the passed templatePath or templateString?
// dojo.debug("avoidCache:", avoidCache);
var tpath = templatePath || obj.templatePath;
var tmplts = dojo.widget._templateCache;
if(!tpath && !obj["widgetType"]) { // don't have a real template here
do {
var dummyName = "__dummyTemplate__" + dojo.widget._templateCache.dummyCount++;
} while(tmplts[dummyName]);
obj.widgetType = dummyName;
}
var wt = tpath?tpath.toString():obj.widgetType;
var ts = tmplts[wt];
if(!ts){
tmplts[wt] = {"string": null, "node": null};
if(avoidCache){
ts = {};
}else{
ts = tmplts[wt];
}
}
if((!obj.templateString)&&(!avoidCache)){
obj.templateString = templateString || ts["string"];
}
if((!obj.templateNode)&&(!avoidCache)){
obj.templateNode = ts["node"];
}
if((!obj.templateNode)&&(!obj.templateString)&&(tpath)){
// fetch a text fragment and assign it to templateString
// NOTE: we rely on blocking IO here!
var tstring = dojo.hostenv.getText(tpath);
if(tstring){
// strip <?xml ...?> declarations so that external SVG and XML
// documents can be added to a document without worry
tstring = tstring.replace(/^\s*<\?xml(\s)+version=[\'\"](\d)*.(\d)*[\'\"](\s)*\?>/im, "");
var matches = tstring.match(/<body[^>]*>\s*([\s\S]+)\s*<\/body>/im);
if(matches){
tstring = matches[1];
}
}else{
tstring = "";
}
obj.templateString = tstring;
if(!avoidCache){
tmplts[wt]["string"] = tstring;
}
}
if((!ts["string"])&&(!avoidCache)){
ts.string = obj.templateString;
}
}
dojo.widget._templateCache.dummyCount = 0;
// Array: list of properties to search for node-to-property mappings
dojo.widget.attachProperties = ["dojoAttachPoint", "id"];
// String: name of the property to use for mapping DOM events to widget functions
dojo.widget.eventAttachProperty = "dojoAttachEvent";
// String: property name of code to evaluate when the widget is constructed
dojo.widget.onBuildProperty = "dojoOnBuild";
// Array: possible accessibility values to set on widget elements - role or state
dojo.widget.waiNames = ["waiRole", "waiState"];
dojo.widget.wai = {
// summary: Contains functions to set accessibility roles and states
// onto widget elements
waiRole: {
// name: String:
// information for mapping accessibility role
name: "waiRole",
// namespace: String:
// URI of the namespace for the set of roles
"namespace": "http://www.w3.org/TR/xhtml2",
// alias: String:
// The alias to assign the namespace
alias: "x2",
// prefix: String:
// The prefix to assign to the role value
prefix: "wairole:"
},
waiState: {
// name: String:
// information for mapping accessibility state
name: "waiState",
// namespace: String:
// URI of the namespace for the set of states
"namespace": "http://www.w3.org/2005/07/aaa",
// alias: String:
// The alias to assign the namespace
alias: "aaa",
// prefix: String:
// empty string - state value does not require prefix
prefix: ""
},
setAttr: function(/*DomNode*/node, /*String*/ ns, /*String*/ attr, /*String|Boolean*/value){
// summary: Use appropriate API to set the role or state attribute onto the element.
// description: In IE use the generic setAttribute() api. Append a namespace
// alias to the attribute name and appropriate prefix to the value.
// Otherwise, use the setAttribueNS api to set the namespaced attribute. Also
// add the appropriate prefix to the attribute value.
if(dojo.render.html.ie){
node.setAttribute(this[ns].alias+":"+ attr, this[ns].prefix+value);
}else{
node.setAttributeNS(this[ns]["namespace"], attr, this[ns].prefix+value);
}
},
getAttr: function(/*DomNode*/ node, /*String*/ ns, /*String|Boolena*/ attr){
// Summary: Use the appropriate API to retrieve the role or state value
// Description: In IE use the generic getAttribute() api. An alias value
// was added to the attribute name to simulate a namespace when the attribute
// was set. Otherwise use the getAttributeNS() api to retrieve the state value
if(dojo.render.html.ie){
return node.getAttribute(this[ns].alias+":"+attr);
}else{
return node.getAttributeNS(this[ns]["namespace"], attr);
}
},
removeAttr: function(/*DomNode*/ node, /*String*/ ns, /*String|Boolena*/ attr){
// summary: Use the appropriate API to remove the role or state value
// description: In IE use the generic removeAttribute() api. An alias value
// was added to the attribute name to simulate a namespace when the attribute
// was set. Otherwise use the removeAttributeNS() api to remove the state value
var success = true; //only IE returns a value
if(dojo.render.html.ie){
success = node.removeAttribute(this[ns].alias+":"+attr);
}else{
node.removeAttributeNS(this[ns]["namespace"], attr);
}
return success;
}
};
dojo.widget.attachTemplateNodes = function(rootNode, /*Widget*/ targetObj, events ){
// summary:
// 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
// * any "dojoOn*" proprties passed in the events array
// rootNode: DomNode
// the node to search for properties. All children will be searched.
// events: Array
// a list of properties generated from getDojoEventsFromStr.
// FIXME: this method is still taking WAAAY too long. We need ways of optimizing:
// a.) what we are looking for on each node
// b.) the nodes that are subject to interrogation (use xpath instead?)
// c.) how expensive event assignment is (less eval(), more connect())
// var start = new Date();
var elementNodeType = dojo.dom.ELEMENT_NODE;
function trim(str){
return str.replace(/^\s+|\s+$/g, "");
}
if(!rootNode){
rootNode = targetObj.domNode;
}
if(rootNode.nodeType != elementNodeType){
return;
}
// alert(events.length);
var nodes = rootNode.all || rootNode.getElementsByTagName("*");
var _this = targetObj;
for(var x=-1; x<nodes.length; x++){
var baseNode = (x == -1) ? rootNode : nodes[x];
// FIXME: is this going to have capitalization problems? Could use getAttribute(name, 0); to get attributes case-insensitve
var attachPoint = [];
if(!targetObj.widgetsInTemplate || !baseNode.getAttribute('dojoType')){
for(var y=0; y<this.attachProperties.length; y++){
var tmpAttachPoint = baseNode.getAttribute(this.attachProperties[y]);
if(tmpAttachPoint){
attachPoint = tmpAttachPoint.split(";");
for(var z=0; z<attachPoint.length; z++){
if(dojo.lang.isArray(targetObj[attachPoint[z]])){
targetObj[attachPoint[z]].push(baseNode);
}else{
targetObj[attachPoint[z]]=baseNode;
}
}
break;
}
}
var attachEvent = baseNode.getAttribute(this.eventAttachProperty);
if(attachEvent){
// NOTE: we want to support attributes that have the form
// "domEvent: nativeEvent; ..."
var evts = attachEvent.split(";");
for(var y=0; y<evts.length; y++){
if((!evts[y])||(!evts[y].length)){ continue; }
var thisFunc = null;
var tevt = trim(evts[y]);
if(evts[y].indexOf(":") >= 0){
// oh, if only JS had tuple assignment
var funcNameArr = tevt.split(":");
tevt = trim(funcNameArr[0]);
thisFunc = trim(funcNameArr[1]);
}
if(!thisFunc){
thisFunc = tevt;
}
var tf = function(){
var ntf = new String(thisFunc);
return function(evt){
if(_this[ntf]){
_this[ntf](dojo.event.browser.fixEvent(evt, this));
}
};
}();
dojo.event.browser.addListener(baseNode, tevt, tf, false, true);
// dojo.event.browser.addListener(baseNode, tevt, dojo.lang.hitch(_this, thisFunc));
}
}
for(var y=0; y<events.length; y++){
//alert(events[x]);
var evtVal = baseNode.getAttribute(events[y]);
if((evtVal)&&(evtVal.length)){
var thisFunc = null;
var domEvt = events[y].substr(4); // clober the "dojo" prefix
thisFunc = trim(evtVal);
var funcs = [thisFunc];
if(thisFunc.indexOf(";")>=0){
funcs = dojo.lang.map(thisFunc.split(";"), trim);
}
for(var z=0; z<funcs.length; z++){
if(!funcs[z].length){ continue; }
var tf = function(){
var ntf = new String(funcs[z]);
return function(evt){
if(_this[ntf]){
_this[ntf](dojo.event.browser.fixEvent(evt, this));
}
}
}();
dojo.event.browser.addListener(baseNode, domEvt, tf, false, true);
// dojo.event.browser.addListener(baseNode, domEvt, dojo.lang.hitch(_this, funcs[z]));
}
}
}
}
// continue;
// FIXME: we need to put this into some kind of lookup structure
// instead of direct assignment
var tmpltPoint = baseNode.getAttribute(this.templateProperty);
if(tmpltPoint){
targetObj[tmpltPoint]=baseNode;
}
dojo.lang.forEach(dojo.widget.waiNames, function(name){
var wai = dojo.widget.wai[name];
var val = baseNode.getAttribute(wai.name);
if(val){
if(val.indexOf('-') == -1){
dojo.widget.wai.setAttr(baseNode, wai.name, "role", val);
}else{
// this is a state-value pair
var statePair = val.split('-');
dojo.widget.wai.setAttr(baseNode, wai.name, statePair[0], statePair[1]);
}
}
}, this);
var onBuild = baseNode.getAttribute(this.onBuildProperty);
if(onBuild){
eval("var node = baseNode; var widget = targetObj; "+onBuild);
}
}
}
dojo.widget.getDojoEventsFromStr = function(str){
// summary:
// generates a list of properties with names that match the form
// dojoOn*
// str: String
// the template string to search
// var lstr = str.toLowerCase();
var re = /(dojoOn([a-z]+)(\s?))=/gi;
var evts = str ? str.match(re)||[] : [];
var ret = [];
var lem = {};
for(var x=0; x<evts.length; x++){
if(evts[x].length < 1){ continue; }
var cm = evts[x].replace(/\s/, "");
cm = (cm.slice(0, cm.length-1));
if(!lem[cm]){
lem[cm] = true;
ret.push(cm);
}
}
return ret; // Array
}
dojo.declare("dojo.widget.DomWidget",
dojo.widget.Widget,
function(){
// summary:
// dojo.widget.DomWidget is the superclass that provides behavior for all
// DOM-based renderers, including HtmlWidget and SvgWidget. DomWidget
// implements the templating system that most widget authors use to define
// the UI for their widgets.
if((arguments.length>0)&&(typeof arguments[0] == "object")){
this.create(arguments[0]);
}
},
{
// templateNode: DomNode
// a node that represents the widget template. Pre-empts both templateString and templatePath.
templateNode: null,
// templateString 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.
templateString: null,
// templateCssString String:
// a string that represents the CSS for the widgettemplate.
// Pre-empts the templateCssPath. In builds that have their
// strings "interned", the templateCssPath is converted to an
// inline templateCssString, thereby preventing a synchronous
// network call.
templateCssString: null,
// preventClobber Boolean:
// should the widget not replace the node from which it was
// constructed? Widgets that apply behaviors to pre-existing parts
// of a page can be implemented easily by setting this to "true".
// In these cases, the domNode property will point to the node
// which the widget was created from.
preventClobber: false,
// domNode DomNode:
// this is our visible representation of the widget! Other DOM
// Nodes may by assigned to other properties, usually through the
// template system's dojoAttachPonit syntax, but the domNode
// property is the canonical "top level" node in widget UI.
domNode: null,
// containerNode DomNode:
// holds child elements. "containerNode" is generally set via a
// dojoAttachPoint assignment and it designates where widgets that
// are defined as "children" of the parent will be placed
// visually.
containerNode: null,
// widgetsInTemplate Boolean:
// should we parse the template to find widgets that might be
// declared in markup inside it? false by default.
widgetsInTemplate: false,
addChild: function(/*Widget*/ widget, overrideContainerNode, pos, ref, insertIndex){
// summary:
// Process the given child widget, inserting it's dom node as
// a child of our dom node
// overrideContainerNode: DomNode?
// a non-default container node for the widget
// pos: String?
// can be one of "before", "after", "first", or "last". This
// has the same meaning as in dojo.dom.insertAtPosition()
// ref: DomNode?
// a node to place the widget relative to
// insertIndex: int?
// DOM index, same meaning as in dojo.dom.insertAtIndex()
// returns: the widget that was inserted
// FIXME: should we support addition at an index in the children arr and
// order the display accordingly? Right now we always append.
if(!this.isContainer){ // we aren't allowed to contain other widgets, it seems
dojo.debug("dojo.widget.DomWidget.addChild() attempted on non-container widget");
return null;
}else{
if(insertIndex == undefined){
insertIndex = this.children.length;
}
this.addWidgetAsDirectChild(widget, overrideContainerNode, pos, ref, insertIndex);
this.registerChild(widget, insertIndex);
}
return widget; // Widget
},
addWidgetAsDirectChild: function(/*Widget*/ widget, overrideContainerNode, pos, ref, insertIndex){
// summary:
// Process the given child widget, inserting it's dom node as
// a child of our dom node
// overrideContainerNode: DomNode
// a non-default container node for the widget
// pos: String?
// can be one of "before", "after", "first", or "last". This
// has the same meaning as in dojo.dom.insertAtPosition()
// ref: DomNode?
// a node to place the widget relative to
// insertIndex: int?
// DOM index, same meaning as in dojo.dom.insertAtIndex()
if((!this.containerNode)&&(!overrideContainerNode)){
this.containerNode = this.domNode;
}
var cn = (overrideContainerNode) ? overrideContainerNode : this.containerNode;
if(!pos){ pos = "after"; }
if(!ref){
if(!cn){ cn = dojo.body(); }
ref = cn.lastChild;
}
if(!insertIndex) { insertIndex = 0; }
widget.domNode.setAttribute("dojoinsertionindex", insertIndex);
// insert the child widget domNode directly underneath my domNode, in the
// specified position (by default, append to end)
if(!ref){
cn.appendChild(widget.domNode);
}else{
// FIXME: was this meant to be the (ugly hack) way to support insert @ index?
//dojo.dom[pos](widget.domNode, ref, insertIndex);
// CAL: this appears to be the intended way to insert a node at a given position...
if (pos == 'insertAtIndex'){
// dojo.debug("idx:", insertIndex, "isLast:", ref === cn.lastChild);
dojo.dom.insertAtIndex(widget.domNode, ref.parentNode, insertIndex);
}else{
// dojo.debug("pos:", pos, "isLast:", ref === cn.lastChild);
if((pos == "after")&&(ref === cn.lastChild)){
cn.appendChild(widget.domNode);
}else{
dojo.dom.insertAtPosition(widget.domNode, cn, pos);
}
}
}
},
registerChild: function(widget, insertionIndex){
// summary: record that given widget descends from me
// widget: Widget
// the widget that is now a child
// insertionIndex: int
// where in the children[] array to place it
// we need to insert the child at the right point in the parent's
// 'children' array, based on the insertionIndex
widget.dojoInsertionIndex = insertionIndex;
var idx = -1;
for(var i=0; i<this.children.length; i++){
//This appears to fix an out of order issue in the case of mixed
//markup and programmatically added children. Previously, if a child
//existed from markup, and another child was addChild()d without specifying
//any additional parameters, it would end up first in the list, when in fact
//it should be after. I can't see cases where this would break things, but
//I could see no other obvious solution. -dustin
if (this.children[i].dojoInsertionIndex <= insertionIndex){
idx = i;
}
}
this.children.splice(idx+1, 0, widget);
widget.parent = this;
widget.addedTo(this, idx+1);
// If this widget was created programatically, then it was erroneously added
// to dojo.widget.manager.topWidgets. Fix that here.
delete dojo.widget.manager.topWidgets[widget.widgetId];
},
removeChild: function(/*Widget*/ widget){
// summary: detach child domNode from parent domNode
dojo.dom.removeNode(widget.domNode);
// remove child widget from parent widget
return dojo.widget.DomWidget.superclass.removeChild.call(this, widget); // Widget
},
getFragNodeRef: function(frag){
// summary:
// returns the source node, if any, that the widget was
// declared from
// frag: Object
// an opaque data structure generated by the first-pass parser
if(!frag){return null;} // null
if(!frag[this.getNamespacedType()]){
dojo.raise("Error: no frag for widget type " + this.getNamespacedType()
+ ", id " + this.widgetId
+ " (maybe a widget has set it's type incorrectly)");
}
return frag[this.getNamespacedType()]["nodeRef"]; // DomNode
},
postInitialize: function(/*Object*/ args, /*Object*/ frag, /*Widget*/ parentComp){
// summary:
// Replace the source domNode with the generated dom
// structure, and register the widget with its parent.
// This is an implementation of the stub function defined in
// dojo.widget.Widget.
//dojo.profile.start(this.widgetType + " postInitialize");
var sourceNodeRef = this.getFragNodeRef(frag);
// Stick my generated dom into the output tree
//alert(this.widgetId + ": replacing " + sourceNodeRef + " with " + this.domNode.innerHTML);
if (parentComp && (parentComp.snarfChildDomOutput || !sourceNodeRef)){
// Add my generated dom as a direct child of my parent widget
// This is important for generated widgets, and also cases where I am generating an
// <li> node that can't be inserted back into the original DOM tree
parentComp.addWidgetAsDirectChild(this, "", "insertAtIndex", "", args["dojoinsertionindex"], sourceNodeRef);
} else if (sourceNodeRef){
// Do in-place replacement of the my source node with my generated dom
if(this.domNode && (this.domNode !== sourceNodeRef)){
this._sourceNodeRef = dojo.dom.replaceNode(sourceNodeRef, this.domNode);
}
}
// Register myself with my parent, or with the widget manager if
// I have no parent
// TODO: the code below erroneously adds all programatically generated widgets
// to topWidgets (since we don't know who the parent is until after creation finishes)
if ( parentComp ) {
parentComp.registerChild(this, args.dojoinsertionindex);
} else {
dojo.widget.manager.topWidgets[this.widgetId]=this;
}
if(this.widgetsInTemplate){
var parser = new dojo.xml.Parse();
var subContainerNode;
//TODO: use xpath here?
var subnodes = this.domNode.getElementsByTagName("*");
for(var i=0;i<subnodes.length;i++){
if(subnodes[i].getAttribute('dojoAttachPoint') == 'subContainerWidget'){
subContainerNode = subnodes[i];
// break;
}
if(subnodes[i].getAttribute('dojoType')){
subnodes[i].setAttribute('isSubWidget', true);
}
}
if (this.isContainer && !this.containerNode){
//no containerNode is available, which means a widget is used as a container. find it here and move
//all dom nodes defined in the main html page as children of this.domNode into the actual container
//widget's node (at this point, the subwidgets defined in the template file is not parsed yet)
if(subContainerNode){
var src = this.getFragNodeRef(frag);
if (src){
dojo.dom.moveChildren(src, subContainerNode);
//do not need to follow children nodes in the main html page, as they
//will be dealt with in the subContainerWidget
frag['dojoDontFollow'] = true;
}
}else{
dojo.debug("No subContainerWidget node can be found in template file for widget "+this);
}
}
var templatefrag = parser.parseElement(this.domNode, null, true);
// createSubComponents not createComponents because frag has already been created
dojo.widget.getParser().createSubComponents(templatefrag, this);
//find all the sub widgets defined in the template file of this widget
var subwidgets = [];
var stack = [this];
var w;
while((w = stack.pop())){
for(var i = 0; i < w.children.length; i++){
var cwidget = w.children[i];
if(cwidget._processedSubWidgets || !cwidget.extraArgs['issubwidget']){ continue; }
subwidgets.push(cwidget);
if(cwidget.isContainer){
stack.push(cwidget);
}
}
}
//connect event to this widget/attach dom node
for(var i = 0; i < subwidgets.length; i++){
var widget = subwidgets[i];
if(widget._processedSubWidgets){
dojo.debug("This should not happen: widget._processedSubWidgets is already true!");
return;
}
widget._processedSubWidgets = true;
if(widget.extraArgs['dojoattachevent']){
var evts = widget.extraArgs['dojoattachevent'].split(";");
for(var j=0; j<evts.length; j++){
var thisFunc = null;
var tevt = dojo.string.trim(evts[j]);
if(tevt.indexOf(":") >= 0){
// oh, if only JS had tuple assignment
var funcNameArr = tevt.split(":");
tevt = dojo.string.trim(funcNameArr[0]);
thisFunc = dojo.string.trim(funcNameArr[1]);
}
if(!thisFunc){
thisFunc = tevt;
}
if(dojo.lang.isFunction(widget[tevt])){
dojo.event.kwConnect({
srcObj: widget,
srcFunc: tevt,
targetObj: this,
targetFunc: thisFunc
});
}else{
alert(tevt+" is not a function in widget "+widget);
}
}
}
if(widget.extraArgs['dojoattachpoint']){
//don't attach widget.domNode here, as we do not know which
//dom node we should connect to (in checkbox widget case,
//it is inputNode). So we make the widget itself available
this[widget.extraArgs['dojoattachpoint']] = widget;
}
}
}
//dojo.profile.end(this.widgetType + " postInitialize");
// Expand my children widgets
/* dojoDontFollow is important for a very special case
* basically if you have a widget that you instantiate from script
* and that widget is a container, and it contains a reference to a parent
* instance, the parser will start recursively parsing until the browser
* complains. So the solution is to set an initialization property of
* dojoDontFollow: true and then it won't recurse where it shouldn't
*/
if(this.isContainer && !frag["dojoDontFollow"]){
//alert("recurse from " + this.widgetId);
// build any sub-components with us as the parent
dojo.widget.getParser().createSubComponents(frag, this);
}
},
// method over-ride
buildRendering: function(/*Object*/ args, /*Object*/ frag){
// summary:
// Construct the UI for this widget, generally from a
// template. This can be over-ridden for custom UI creation to
// to side-step the template system. This is an
// implementation of the stub function defined in
// dojo.widget.Widget.
// DOM widgets construct themselves from a template
var ts = dojo.widget._templateCache[this.widgetType];
// Handle style for this widget here, as even if templatePath
// is not set, style specified by templateCssString or templateCssPath
// should be applied. templateCssString has higher priority
// than templateCssPath
if(args["templatecsspath"]){
args["templateCssPath"] = args["templatecsspath"];
}
var cpath = args["templateCssPath"] || this.templateCssPath;
if(cpath && !dojo.widget._cssFiles[cpath.toString()]){
if((!this.templateCssString)&&(cpath)){
this.templateCssString = dojo.hostenv.getText(cpath);
this.templateCssPath = null;
}
dojo.widget._cssFiles[cpath.toString()] = true;
}
if((this["templateCssString"])&&(!dojo.widget._cssStrings[this.templateCssString])){
dojo.html.insertCssText(this.templateCssString, null, cpath);
dojo.widget._cssStrings[this.templateCssString] = true;
}
if(
(!this.preventClobber)&&(
(this.templatePath)||
(this.templateNode)||
(
(this["templateString"])&&(this.templateString.length)
)||
(
(typeof ts != "undefined")&&( (ts["string"])||(ts["node"]) )
)
)
){
// if it looks like we can build the thing from a template, do it!
this.buildFromTemplate(args, frag);
}else{
// otherwise, assign the DOM node that was the source of the widget
// parsing to be the root node
this.domNode = this.getFragNodeRef(frag);
}
this.fillInTemplate(args, frag); // this is where individual widgets
// will handle population of data
// from properties, remote data
// sets, etc.
},
buildFromTemplate: function(/*Object*/ args, /*Object*/ frag){
// summary:
// Called by buildRendering, creates the actual UI in a DomWidget.
// var start = new Date();
// copy template properties if they're already set in the templates object
// dojo.debug("buildFromTemplate:", this);
var avoidCache = false;
if(args["templatepath"]){
// avoidCache = true;
args["templatePath"] = args["templatepath"];
}
dojo.widget.fillFromTemplateCache( this,
args["templatePath"],
null,
avoidCache);
var ts = dojo.widget._templateCache[this.templatePath?this.templatePath.toString():this.widgetType];
if((ts)&&(!avoidCache)){
if(!this.templateString.length){
this.templateString = ts["string"];
}
if(!this.templateNode){
this.templateNode = ts["node"];
}
}
var matches = false;
var node = null;
// var tstr = new String(this.templateString);
var tstr = this.templateString;
// attempt to clone a template node, if there is one
if((!this.templateNode)&&(this.templateString)){
matches = this.templateString.match(/\$\{([^\}]+)\}/g);
if(matches) {
// if we do property replacement, don't create a templateNode
// to clone from.
var hash = this.strings || {};
// FIXME: should this hash of default replacements be cached in
// templateString?
for(var key in dojo.widget.defaultStrings) {
if(dojo.lang.isUndefined(hash[key])) {
hash[key] = dojo.widget.defaultStrings[key];
}
}
// FIXME: this is a lot of string munging. Can we make it faster?
for(var i = 0; i < matches.length; i++) {
var key = matches[i];
key = key.substring(2, key.length-1);
var kval = (key.substring(0, 5) == "this.") ? dojo.lang.getObjPathValue(key.substring(5), this) : hash[key];
var value;
if((kval)||(dojo.lang.isString(kval))){
value = new String((dojo.lang.isFunction(kval)) ? kval.call(this, key, this.templateString) : kval);
// Safer substitution, see heading "Attribute values" in
// http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.3.2
while (value.indexOf("\"") > -1) {
value=value.replace("\"","&quot;");
}
tstr = tstr.replace(matches[i], value);
}
}
}else{
// otherwise, we are required to instantiate a copy of the template
// string if one is provided.
// FIXME: need to be able to distinguish here what should be done
// or provide a generic interface across all DOM implementations
// FIMXE: this breaks if the template has whitespace as its first
// characters
// node = this.createNodesFromText(this.templateString, true);
// this.templateNode = node[0].cloneNode(true); // we're optimistic here
this.templateNode = this.createNodesFromText(this.templateString, true)[0];
if(!avoidCache){
ts.node = this.templateNode;
}
}
}
if((!this.templateNode)&&(!matches)){
dojo.debug("DomWidget.buildFromTemplate: could not create template");
return false;
}else if(!matches){
node = this.templateNode.cloneNode(true);
if(!node){ return false; }
}else{
node = this.createNodesFromText(tstr, true)[0];
}
// recurse through the node, looking for, and attaching to, our
// attachment points which should be defined on the template node.
this.domNode = node;
// dojo.profile.start("attachTemplateNodes");
this.attachTemplateNodes();
// dojo.profile.end("attachTemplateNodes");
// relocate source contents to templated container node
// this.containerNode must be able to receive children, or exceptions will be thrown
if (this.isContainer && this.containerNode){
var src = this.getFragNodeRef(frag);
if (src){
dojo.dom.moveChildren(src, this.containerNode);
}
}
},
attachTemplateNodes: function(baseNode, targetObj){
// summary:
// hooks up event handlers and property/node linkages. Calls
// dojo.widget.attachTemplateNodes to do all the hard work.
// baseNode: DomNode
// defaults to "this.domNode"
// targetObj: Widget
// defaults to "this"
if(!baseNode){ baseNode = this.domNode; }
if(!targetObj){ targetObj = this; }
return dojo.widget.attachTemplateNodes(baseNode, targetObj,
dojo.widget.getDojoEventsFromStr(this.templateString));
},
fillInTemplate: function(){
// summary:
// stub function! sub-classes may use as a default UI
// initializer function. The UI rendering will be available by
// the time this is called from buildRendering. If
// buildRendering is over-ridden, this function may not be
// fired!
// dojo.unimplemented("dojo.widget.DomWidget.fillInTemplate");
},
// method over-ride
destroyRendering: function(){
// summary: UI destructor. Destroy the dom nodes associated w/this widget.
try{
dojo.dom.destroyNode(this.domNode);
delete this.domNode;
}catch(e){ /* squelch! */ }
if(this._sourceNodeRef){
try{
dojo.dom.destroyNode(this._sourceNodeRef);
}catch(e){ /* squelch! */ }
}
},
createNodesFromText: function(){
// summary
// Attempts to create a set of nodes based on the structure of the passed text.
// Implemented in HtmlWidget and SvgWidget.
dojo.unimplemented("dojo.widget.DomWidget.createNodesFromText");
}
}
);