- Author:
- David Nickerson <nickerso@users.sourceforge.net>
- Date:
- 2009-07-16 02:00:03+12:00
- Desc:
- the starting point for the HH tutorial example
- Permanent Source URI:
- https://models.fieldml.org/workspace/a1/rawfile/f6a8f90307388eb4b040ee3566b84d88b59247f7/dojo-presentation/js/dojo/dijit/layout/ContentPane.js
dojo.provide("dijit.layout.ContentPane");
dojo.require("dijit._Widget");
dojo.require("dijit.layout._LayoutWidget");
dojo.require("dojo.parser");
dojo.require("dojo.string");
dojo.requireLocalization("dijit", "loading");
dojo.declare(
"dijit.layout.ContentPane",
dijit._Widget,
{
// summary:
// A widget that acts as a Container for other widgets, and includes a ajax interface
// description:
// A widget that can be used as a standalone widget
// or as a baseclass for other widgets
// Handles replacement of document fragment using either external uri or javascript
// generated markup or DOM content, instantiating widgets within that content.
// Don't confuse it with an iframe, it only needs/wants document fragments.
// It's useful as a child of LayoutContainer, SplitContainer, or TabContainer.
// But note that those classes can contain any widget as a child.
// example:
// Some quick samples:
// To change the innerHTML use .attr('content', '<b>new content</b>')
//
// Or you can send it a NodeList, .attr('content', dojo.query('div [class=selected]', userSelection))
// please note that the nodes in NodeList will copied, not moved
//
// To do a ajax update use .attr('href', url)
// href: String
// The href of the content that displays now.
// Set this at construction if you want to load data externally when the
// pane is shown. (Set preload=true to load it immediately.)
// Changing href after creation doesn't have any effect; use attr('href', ...);
href: "",
/*=====
// content: String
// The innerHTML of the ContentPane.
// Note that the initialization parameter / argument to attr("content", ...)
// can be a String, DomNode, Nodelist, or widget.
content: "",
=====*/
// extractContent: Boolean
// Extract visible content from inside of <body> .... </body>
extractContent: false,
// parseOnLoad: Boolean
// parse content and create the widgets, if any
parseOnLoad: true,
// preventCache: Boolean
// Cache content retreived externally
preventCache: false,
// preload: Boolean
// Force load of data even if pane is hidden.
preload: false,
// refreshOnShow: Boolean
// Refresh (re-download) content when pane goes from hidden to shown
refreshOnShow: false,
// loadingMessage: String
// Message that shows while downloading
loadingMessage: "<span class='dijitContentPaneLoading'>${loadingState}</span>",
// errorMessage: String
// Message that shows if an error occurs
errorMessage: "<span class='dijitContentPaneError'>${errorState}</span>",
// isLoaded: Boolean
// Tells loading status see onLoad|onUnload for event hooks
isLoaded: false,
baseClass: "dijitContentPane",
// doLayout: String/Boolean
// false - don't adjust size of children
// true - looks for the first sizable child widget (ie, having resize() method) and sets it's size to
// however big the ContentPane is (TODO: implement)
// auto - if there is a single sizable child widget (ie, having resize() method), set it's size to
// however big the ContentPane is
doLayout: "auto",
postMixInProperties: function(){
this.inherited(arguments);
var messages = dojo.i18n.getLocalization("dijit", "loading", this.lang);
this.loadingMessage = dojo.string.substitute(this.loadingMessage, messages);
this.errorMessage = dojo.string.substitute(this.errorMessage, messages);
},
postCreate: function(){
// remove the title attribute so it doesn't show up when i hover
// over a node
this.domNode.title = "";
if(!this.containerNode){
// make getDescendants() work
this.containerNode = this.domNode;
}
if(this.preload){
this._loadCheck();
}
if(this.content){
this.attr("content", this.content);
}
if (!dijit.hasWaiRole(this.domNode)){
dijit.setWaiRole(this.domNode, "group");
}
dojo.addClass(this.domNode, this.baseClass);
},
startup: function(){
if(this._started){ return; }
if(this.doLayout != "false" && this.doLayout !== false){
this._checkIfSingleChild();
if(this._singleChild){
this._singleChild.startup();
}
}
this._loadCheck();
this.inherited(arguments);
},
_checkIfSingleChild: function(){
// summary:
// Test if we have exactly one widget as a child, and if so assume that we are a container for that widget,
// and should propogate startup() and resize() calls to it.
// TODO: if there are two child widgets (a data store and a TabContainer, for example),
// should still find the TabContainer
var childNodes = dojo.query(">", this.containerNode),
childWidgets = childNodes.filter("[widgetId]");
if(childNodes.length == 1 && childWidgets.length == 1){
this.isContainer = true;
this._singleChild = dijit.byNode(childWidgets[0]);
}else{
delete this.isContainer;
delete this._singleChild;
}
},
refresh: function(){
// summary:
// Force a refresh (re-download) of content, be sure to turn off cache
// we return result of _prepareLoad here to avoid code dup. in dojox.layout.ContentPane
return this._prepareLoad(true);
},
setHref: function(/*String|Uri*/ href){
dojo.deprecated("dijit.layout.ContentPane.setHref() is deprecated. Use attr('href', ...) instead.", "", "2.0");
return this.attr("href", data);
},
_attrSetHref: function(/*String|Uri*/ href){
// summary:
// Hook so attr("href", ...) works.
// description:
// Reset the (external defined) content of this pane and replace with new url
// Note: It delays the download until widget is shown if preload is false.
// href:
// url to the page you want to get, must be within the same domain as your mainpage
this.href = href;
// we return result of _prepareLoad here to avoid code dup. in dojox.layout.ContentPane
return this._prepareLoad();
},
setContent: function(/*String|DomNode|Nodelist*/data){
dojo.deprecated("dijit.layout.ContentPane.setContent() is deprecated. Use attr('content', ...) instead.", "", "2.0");
this.attr("content", data);
},
_attrSetContent: function(/*String|DomNode|Nodelist*/data){
// summary:
// Hook to make attr("content", ...) work.
// Replaces old content with data content, include style classes from old content
// data:
// the new Content may be String, DomNode or NodeList
//
// if data is a NodeList (or an array of nodes) nodes are copied
// so you can import nodes from another document implicitly
// clear href so we cant run refresh and clear content
// refresh should only work if we downloaded the content
if(!this._isDownloaded){
this.href = "";
this._onUnloadHandler();
}
this._setContent(data || "");
this._isDownloaded = false; // must be set after _setContent(..), pathadjust in dojox.layout.ContentPane
if(this.parseOnLoad){
this._createSubWidgets();
}
if(this.doLayout != "false" && this.doLayout !== false){
this._checkIfSingleChild();
if(this._singleChild && this._singleChild.resize){
this._singleChild.startup();
var cb = this._contentBox || dojo.contentBox(this.containerNode);
this._singleChild.resize({w: cb.w, h: cb.h});
}
}
this._onLoadHandler();
},
_attrGetContent: function(){
// summary: hook to make attr("content") work
return this.containerNode.innerHTML;
},
cancel: function(){
// summary:
// Cancels a inflight download of content
if(this._xhrDfd && (this._xhrDfd.fired == -1)){
this._xhrDfd.cancel();
}
delete this._xhrDfd; // garbage collect
},
destroy: function(){
// if we have multiple controllers destroying us, bail after the first
if(this._beingDestroyed){
return;
}
// make sure we call onUnload
this._onUnloadHandler();
this._beingDestroyed = true;
this.inherited(arguments);
},
resize: function(size){
dojo.marginBox(this.domNode, size);
// Compute content box size in case we [later] need to size child
// If either height or width wasn't specified by the user, then query node for it.
// But note that setting the margin box and then immediately querying dimensions may return
// inaccurate results, so try not to depend on it.
var node = this.containerNode,
mb = dojo.mixin(dojo.marginBox(node), size||{});
var cb = this._contentBox = dijit.layout.marginBox2contentBox(node, mb);
// If we have a single widget child then size it to fit snugly within my borders
if(this._singleChild && this._singleChild.resize){
// note: if widget has padding this._contentBox will have l and t set,
// but don't pass them to resize() or it will doubly-offset the child
this._singleChild.resize({w: cb.w, h: cb.h});
}
},
_prepareLoad: function(forceLoad){
// sets up for a xhrLoad, load is deferred until widget onShow
// cancels a inflight download
this.cancel();
this.isLoaded = false;
this._loadCheck(forceLoad);
},
_isShown: function(){
// summary: returns true if the content is currently shown
if("open" in this){
return this.open; // for TitlePane, etc.
}else{
var node = this.domNode;
return (node.style.display != 'none') && (node.style.visibility != 'hidden');
}
},
_loadCheck: function(/*Boolean*/ forceLoad){
// call this when you change onShow (onSelected) status when selected in parent container
// it's used as a trigger for href download when this.domNode.display != 'none'
// sequence:
// if no href -> bail
// forceLoad -> always load
// this.preload -> load when download not in progress, domNode display doesn't matter
// this.refreshOnShow -> load when download in progress bails, domNode display !='none' AND
// this.open !== false (undefined is ok), isLoaded doesn't matter
// else -> load when download not in progress, if this.open !== false (undefined is ok) AND
// domNode display != 'none', isLoaded must be false
var displayState = this._isShown();
if(this.href &&
(forceLoad ||
(this.preload && !this._xhrDfd) ||
(this.refreshOnShow && displayState && !this._xhrDfd) ||
(!this.isLoaded && displayState && !this._xhrDfd)
)
){
this._downloadExternalContent();
}
},
_downloadExternalContent: function(){
this._onUnloadHandler();
// display loading message
this._setContent(
this.onDownloadStart.call(this)
);
var self = this;
var getArgs = {
preventCache: (this.preventCache || this.refreshOnShow),
url: this.href,
handleAs: "text"
};
if(dojo.isObject(this.ioArgs)){
dojo.mixin(getArgs, this.ioArgs);
}
var hand = this._xhrDfd = (this.ioMethod || dojo.xhrGet)(getArgs);
hand.addCallback(function(html){
try{
self.onDownloadEnd.call(self);
self._isDownloaded = true;
self.attr.call(self, 'content', html); // onload event is called from here
}catch(err){
self._onError.call(self, 'Content', err); // onContentError
}
delete self._xhrDfd;
return html;
});
hand.addErrback(function(err){
if(!hand.cancelled){
// show error message in the pane
self._onError.call(self, 'Download', err); // onDownloadError
}
delete self._xhrDfd;
return err;
});
},
_onLoadHandler: function(){
this.isLoaded = true;
try{
this.onLoad.call(this);
}catch(e){
console.error('Error '+this.widgetId+' running custom onLoad code');
}
},
_onUnloadHandler: function(){
this.isLoaded = false;
this.cancel();
try{
this.onUnload.call(this);
}catch(e){
console.error('Error '+this.widgetId+' running custom onUnload code');
}
},
_setContent: function(cont){
// first get rid of child widgets
this.destroyDescendants();
try{
// ... and then get rid of child dom nodes
var node = this.containerNode;
while(node.firstChild){
dojo._destroyElement(node.firstChild);
}
if(typeof cont == "string"){
// dijit.ContentPane does only minimal fixes,
// No pathAdjustments, script retrieval, style clean etc
// some of these should be available in the dojox.layout.ContentPane
if(this.extractContent){
var match = cont.match(/<body[^>]*>\s*([\s\S]+)\s*<\/body>/im);
if(match){ cont = match[1]; }
}
node.innerHTML = cont;
}else if(cont.domNode){
// single widget child
node.appendChild(cont.domNode);
}else{
// domNode or NodeList
if(cont.nodeType){ // domNode (htmlNode 1 or textNode 3)
node.appendChild(cont);
}else{// nodelist or array such as dojo.Nodelist
dojo.forEach(cont, function(n){
node.appendChild(n.cloneNode(true));
});
}
}
}catch(e){
// check if a domfault occurs when we are appending this.errorMessage
// like for instance if domNode is a UL and we try append a DIV
var errMess = this.onContentError(e);
try{
node.innerHTML = errMess;
}catch(e){
console.error('Fatal '+this.id+' could not change content due to '+e.message, e);
}
}
},
_onError: function(type, err, consoleText){
// shows user the string that is returned by on[type]Error
// overide on[type]Error and return your own string to customize
var errText = this['on' + type + 'Error'].call(this, err);
if(consoleText){
console.error(consoleText, err);
}else if(errText){// a empty string won't change current content
this._setContent.call(this, errText);
}
},
_createSubWidgets: function(){
// summary: scan my contents and create subwidgets
try{
dojo.parser.parse(this.containerNode, true);
}catch(e){
this._onError('Content', e, "Couldn't create widgets in "+this.id
+(this.href ? " from "+this.href : ""));
}
},
// EVENT's, should be overide-able
onLoad: function(e){
// summary:
// Event hook, is called after everything is loaded and widgetified
},
onUnload: function(e){
// summary:
// Event hook, is called before old content is cleared
},
onDownloadStart: function(){
// summary:
// called before download starts
// the string returned by this function will be the html
// that tells the user we are loading something
// override with your own function if you want to change text
return this.loadingMessage;
},
onContentError: function(/*Error*/ error){
// summary:
// called on DOM faults, require fault etc in content
// default is to display errormessage inside pane
},
onDownloadError: function(/*Error*/ error){
// summary:
// Called when download error occurs, default is to display
// errormessage inside pane. Overide function to change that.
// The string returned by this function will be the html
// that tells the user a error happend
return this.errorMessage;
},
onDownloadEnd: function(){
// summary:
// called when download is finished
}
});