blob: b972add36928c5c95bd852aa4d3d2fb132e5feb4 [file] [log] [blame]
/*
Copyright (c) 2004-2005, 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.string");
dojo.require("dojo.widget.Widget");
dojo.require("dojo.dom");
dojo.require("dojo.xml.Parse");
dojo.require("dojo.uri.*");
dojo.widget._cssFiles = {};
dojo.widget.defaultStrings = {
dojoRoot: dojo.hostenv.getBaseScriptUri(),
baseScriptUri: dojo.hostenv.getBaseScriptUri()
};
// static method to build from a template w/ or w/o a real widget in place
dojo.widget.buildFromTemplate = function(obj, templatePath, templateCssPath, templateString) {
var tpath = templatePath || obj.templatePath;
var cpath = templateCssPath || obj.templateCssPath;
if (!cpath && obj.templateCSSPath) {
obj.templateCssPath = cpath = obj.templateCSSPath;
obj.templateCSSPath = null;
dj_deprecated("templateCSSPath is deprecated, use templateCssPath");
}
// DEPRECATED: use Uri objects, not strings
if (tpath && !(tpath instanceof dojo.uri.Uri)) {
tpath = dojo.uri.dojoUri(tpath);
dj_deprecated("templatePath should be of type dojo.uri.Uri");
}
if (cpath && !(cpath instanceof dojo.uri.Uri)) {
cpath = dojo.uri.dojoUri(cpath);
dj_deprecated("templateCssPath should be of type dojo.uri.Uri");
}
var tmplts = dojo.widget.DomWidget.templates;
if(!obj["widgetType"]) { // don't have a real template here
do {
var dummyName = "__dummyTemplate__" + dojo.widget.buildFromTemplate.dummyCount++;
} while(tmplts[dummyName]);
obj.widgetType = dummyName;
}
if((cpath)&&(!dojo.widget._cssFiles[cpath])){
dojo.html.insertCssFile(cpath);
obj.templateCssPath = null;
dojo.widget._cssFiles[cpath] = true;
}
var ts = tmplts[obj.widgetType];
if(!ts){
tmplts[obj.widgetType] = {};
ts = tmplts[obj.widgetType];
}
if(!obj.templateString){
obj.templateString = templateString || ts["string"];
}
if(!obj.templateNode){
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){
var matches = tstring.match(/<body[^>]*>\s*([\s\S]+)\s*<\/body>/im);
if(matches){
tstring = matches[1];
}
}else{
tstring = "";
}
obj.templateString = tstring;
ts.string = tstring;
}
if(!ts["string"]) {
ts.string = obj.templateString;
}
}
dojo.widget.buildFromTemplate.dummyCount = 0;
dojo.widget.attachProperties = ["dojoAttachPoint", "id"];
dojo.widget.eventAttachProperty = "dojoAttachEvent";
dojo.widget.onBuildProperty = "dojoOnBuild";
dojo.widget.attachTemplateNodes = function(rootNode, targetObj, events){
// 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;
if(!rootNode){
rootNode = targetObj.domNode;
}
if(rootNode.nodeType != elementNodeType){
return;
}
// alert(events.length);
var nodes = 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?
var attachPoint = [];
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<this.attachProperties.length; z++){
if((targetObj[attachPoint[z]])&&(dojo.lang.isArray(targetObj[attachPoint[z]]))){
targetObj[attachPoint[z]].push(baseNode);
}else{
targetObj[attachPoint[z]]=baseNode;
}
}
break;
}
}
// 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;
}
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 = dojo.string.trim(evts[y]);
var tevt = dojo.string.trim(evts[y]);
if(evts[y].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;
}
var tf = function(){
var ntf = new String(thisFunc);
return function(evt){
if(_this[ntf]){
_this[ntf](dojo.event.browser.fixEvent(evt));
}
};
}();
dojo.event.browser.addListener(baseNode, tevt, tf, false, true);
}
}
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 = dojo.string.trim(evtVal);
var tf = function(){
var ntf = new String(thisFunc);
return function(evt){
if(_this[ntf]){
_this[ntf](dojo.event.browser.fixEvent(evt));
}
}
}();
dojo.event.browser.addListener(baseNode, domEvt, tf, false, true);
}
}
var onBuild = baseNode.getAttribute(this.onBuildProperty);
if(onBuild){
eval("var node = baseNode; var widget = targetObj; "+onBuild);
}
// strip IDs to prevent dupes
baseNode.id = "";
}
}
dojo.widget.getDojoEventsFromStr = function(str){
// 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].legth < 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;
}
dojo.widget.buildAndAttachTemplate = function(obj, templatePath, templateCssPath, templateString, targetObj) {
this.buildFromTemplate(obj, templatePath, templateCssPath, templateString);
var node = dojo.dom.createNodesFromText(obj.templateString, true)[0];
this.attachTemplateNodes(node, targetObj||obj, dojo.widget.getDojoEventsFromStr(templateString));
return node;
}
dojo.widget.DomWidget = function(){
dojo.widget.Widget.call(this);
if((arguments.length>0)&&(typeof arguments[0] == "object")){
this.create(arguments[0]);
}
}
dojo.inherits(dojo.widget.DomWidget, dojo.widget.Widget);
dojo.lang.extend(dojo.widget.DomWidget, {
templateNode: null,
templateString: null,
preventClobber: false,
domNode: null, // this is our visible representation of the widget!
containerNode: null, // holds child elements
// Process the given child widget, inserting it's dom node as a child of our dom node
// FIXME: should we support addition at an index in the children arr and
// order the display accordingly? Right now we always append.
addChild: function(widget, overrideContainerNode, pos, ref, insertIndex){
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{
this.addWidgetAsDirectChild(widget, overrideContainerNode, pos, ref, insertIndex);
this.registerChild(widget, insertIndex);
}
return widget;
},
addWidgetAsDirectChild: function(widget, overrideContainerNode, pos, ref, insertIndex){
if((!this.containerNode)&&(!overrideContainerNode)){
this.containerNode = this.domNode;
}
var cn = (overrideContainerNode) ? overrideContainerNode : this.containerNode;
if(!pos){ pos = "after"; }
if(!ref){ 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);
}
}
}
},
// Record that given widget descends from me
registerChild: function(widget, insertionIndex){
// 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++){
if (this.children[i].dojoInsertionIndex < insertionIndex){
idx = i;
}
}
this.children.splice(idx+1, 0, widget);
widget.parent = this;
widget.addedTo(this);
// 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];
},
// FIXME: we really need to normalize how we do things WRT "destroy" vs. "remove"
removeChild: function(widget){
for(var x=0; x<this.children.length; x++){
if(this.children[x] === widget){
this.children.splice(x, 1);
break;
}
}
return widget;
},
getFragNodeRef: function(frag){
if( !frag["dojo:"+this.widgetType.toLowerCase()] ){
dojo.raise("Error: no frag for widget type " + this.widgetType +
", id " + this.widgetId + " (maybe a widget has set it's type incorrectly)");
}
return (frag ? frag["dojo:"+this.widgetType.toLowerCase()]["nodeRef"] : null);
},
// Replace source domNode with generated dom structure, and register
// widget with parent.
postInitialize: function(args, frag, parentComp){
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)){
var oldNode = sourceNodeRef.parentNode.replaceChild(this.domNode, sourceNodeRef);
}
}
// 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;
}
// Expand my children widgets
if(this.isContainer){
//alert("recurse from " + this.widgetId);
// build any sub-components with us as the parent
var fragParser = dojo.widget.getParser();
fragParser.createComponents(frag, this);
}
},
startResize: function(coords){
dj_unimplemented("dojo.widget.DomWidget.startResize");
},
updateResize: function(coords){
dj_unimplemented("dojo.widget.DomWidget.updateResize");
},
endResize: function(coords){
dj_unimplemented("dojo.widget.DomWidget.endResize");
},
// method over-ride
buildRendering: function(args, frag){
// DOM widgets construct themselves from a template
var ts = dojo.widget.DomWidget.templates[this.widgetType];
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(args, frag){
// var start = new Date();
// copy template properties if they're already set in the templates object
var ts = dojo.widget.DomWidget.templates[this.widgetType];
if(ts){
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);
// 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.") ? this[key.substring(5)] : hash[key];
var value;
if((kval)||(dojo.lang.isString(kval))){
value = (dojo.lang.isFunction(kval)) ? kval.call(this, key, this.templateString) : kval;
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];
ts.node = this.templateNode;
}
}
if((!this.templateNode)&&(!matches)){
dojo.debug("weren't able to 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(this.domNode, this);
// 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){
if(!targetObj){ targetObj = this; }
return dojo.widget.attachTemplateNodes(baseNode, targetObj,
dojo.widget.getDojoEventsFromStr(this.templateString));
},
fillInTemplate: function(){
// dj_unimplemented("dojo.widget.DomWidget.fillInTemplate");
},
// method over-ride
destroyRendering: function(){
try{
var tempNode = this.domNode.parentNode.removeChild(this.domNode);
delete tempNode;
}catch(e){ /* squelch! */ }
},
// FIXME: method over-ride
cleanUp: function(){},
getContainerHeight: function(){
// FIXME: the generic DOM widget shouldn't be using HTML utils!
return dojo.html.getInnerHeight(this.domNode.parentNode);
},
getContainerWidth: function(){
// FIXME: the generic DOM widget shouldn't be using HTML utils!
return dojo.html.getInnerWidth(this.domNode.parentNode);
},
createNodesFromText: function(){
dj_unimplemented("dojo.widget.DomWidget.createNodesFromText");
}
});
dojo.widget.DomWidget.templates = {};