/*
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 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(/
]*>\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= 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=0){
funcs = dojo.lang.map(thisFunc.split(";"), trim);
}
for(var z=0; z0)&&(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 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= 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("\"",""");
}
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");
}
}
);