8398c9048d
code was modified slightly, so the code differs from the original downloadable 1.9.5 version
650 lines
20 KiB
JavaScript
650 lines
20 KiB
JavaScript
dojo.provide("dijit.layout.ContentPane");
|
|
|
|
dojo.require("dijit._Widget");
|
|
dojo.require("dijit._Contained");
|
|
dojo.require("dijit.layout._LayoutWidget"); // for dijit.layout.marginBox2contentBox()
|
|
|
|
dojo.require("dojo.parser");
|
|
dojo.require("dojo.string");
|
|
dojo.require("dojo.html");
|
|
dojo.requireLocalization("dijit", "loading");
|
|
|
|
dojo.declare(
|
|
"dijit.layout.ContentPane", dijit._Widget,
|
|
{
|
|
// summary:
|
|
// A widget that acts as a container for mixed HTML and widgets, and includes an 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 || DomNode || NodeList || dijit._Widget
|
|
// 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>.
|
|
// I.e., strip <html> and <head> (and it's contents) from the href
|
|
extractContent: false,
|
|
|
|
// parseOnLoad: Boolean
|
|
// Parse content and create the widgets, if any.
|
|
parseOnLoad: true,
|
|
|
|
// preventCache: Boolean
|
|
// Prevent caching of data from href's by appending a timestamp to the href.
|
|
preventCache: false,
|
|
|
|
// preload: Boolean
|
|
// Force load of data on initialization 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: [readonly] Boolean
|
|
// True if the ContentPane has data in it, either specified
|
|
// during initialization (via href or inline content), or set
|
|
// via attr('content', ...) / attr('href', ...)
|
|
//
|
|
// False if it doesn't have any content, or if ContentPane is
|
|
// still in the process of downloading href.
|
|
isLoaded: false,
|
|
|
|
baseClass: "dijitContentPane",
|
|
|
|
// doLayout: Boolean
|
|
// - false - don't adjust size of children
|
|
// - true - if there is a single visible child widget, set it's size to
|
|
// however big the ContentPane is
|
|
doLayout: true,
|
|
|
|
// ioArgs: Object
|
|
// Parameters to pass to xhrGet() request, for example:
|
|
// | <div dojoType="dijit.layout.ContentPane" href="./bar" ioArgs="{timeout: 500}">
|
|
ioArgs: {},
|
|
|
|
// isContainer: [protected] Boolean
|
|
// Just a flag indicating that this widget will call resize() on
|
|
// its children. _LayoutWidget based widgets check for
|
|
//
|
|
// | if(!this.getParent || !this.getParent()){
|
|
//
|
|
// and if getParent() returns false because !parent.isContainer,
|
|
// then they resize themselves on initialization.
|
|
isContainer: true,
|
|
|
|
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);
|
|
|
|
// Detect if we were initialized with data
|
|
if(!this.href && this.srcNodeRef && this.srcNodeRef.innerHTML){
|
|
this.isLoaded = true;
|
|
}
|
|
},
|
|
|
|
buildRendering: function(){
|
|
// Overrides Widget.buildRendering().
|
|
// Since we have no template we need to set this.containerNode ourselves.
|
|
// For subclasses of ContentPane do have a template, does nothing.
|
|
this.inherited(arguments);
|
|
if(!this.containerNode){
|
|
// make getDescendants() work
|
|
this.containerNode = this.domNode;
|
|
}
|
|
},
|
|
|
|
postCreate: function(){
|
|
// remove the title attribute so it doesn't show up when hovering
|
|
// over a node
|
|
this.domNode.title = "";
|
|
|
|
if (!dojo.attr(this.domNode,"role")){
|
|
dijit.setWaiRole(this.domNode, "group");
|
|
}
|
|
|
|
dojo.addClass(this.domNode, this.baseClass);
|
|
},
|
|
|
|
startup: function(){
|
|
// summary:
|
|
// See `dijit.layout._LayoutWidget.startup` for description.
|
|
// Although ContentPane doesn't extend _LayoutWidget, it does implement
|
|
// the same API.
|
|
if(this._started){ return; }
|
|
|
|
if(this.isLoaded){
|
|
dojo.forEach(this.getChildren(), function(child){
|
|
child.startup();
|
|
});
|
|
|
|
// If we have static content in the content pane (specified during
|
|
// initialization) then we need to do layout now... unless we are
|
|
// a child of a TabContainer etc. in which case wait until the TabContainer
|
|
// calls resize() on us.
|
|
if(this.doLayout){
|
|
this._checkIfSingleChild();
|
|
}
|
|
if(!this._singleChild || !dijit._Contained.prototype.getParent.call(this)){
|
|
this._scheduleLayout();
|
|
}
|
|
}
|
|
|
|
// If we have an href then check if we should load it now
|
|
this._loadCheck();
|
|
|
|
this.inherited(arguments);
|
|
},
|
|
|
|
_checkIfSingleChild: function(){
|
|
// summary:
|
|
// Test if we have exactly one visible 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.
|
|
// Skips over things like data stores since they aren't visible.
|
|
|
|
var childNodes = dojo.query(">", this.containerNode),
|
|
childWidgetNodes = childNodes.filter(function(node){
|
|
return dojo.hasAttr(node, "dojoType") || dojo.hasAttr(node, "widgetId");
|
|
}),
|
|
candidateWidgets = dojo.filter(childWidgetNodes.map(dijit.byNode), function(widget){
|
|
return widget && widget.domNode && widget.resize;
|
|
});
|
|
|
|
if(
|
|
// all child nodes are widgets
|
|
childNodes.length == childWidgetNodes.length &&
|
|
|
|
// all but one are invisible (like dojo.data)
|
|
candidateWidgets.length == 1
|
|
){
|
|
this._singleChild = candidateWidgets[0];
|
|
}else{
|
|
delete this._singleChild;
|
|
}
|
|
},
|
|
|
|
setHref: function(/*String|Uri*/ href){
|
|
// summary:
|
|
// Deprecated. Use attr('href', ...) instead.
|
|
dojo.deprecated("dijit.layout.ContentPane.setHref() is deprecated. Use attr('href', ...) instead.", "", "2.0");
|
|
return this.attr("href", href);
|
|
},
|
|
_setHrefAttr: 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
|
|
|
|
// Cancel any in-flight requests (an attr('href') will cancel any in-flight attr('href', ...))
|
|
this.cancel();
|
|
|
|
this.href = href;
|
|
|
|
// _setHrefAttr() is called during creation and by the user, after creation.
|
|
// only in the second case do we actually load the URL; otherwise it's done in startup()
|
|
if(this._created && (this.preload || this._isShown())){
|
|
// we return result of refresh() here to avoid code dup. in dojox.layout.ContentPane
|
|
return this.refresh();
|
|
}else{
|
|
// Set flag to indicate that href needs to be loaded the next time the
|
|
// ContentPane is made visible
|
|
this._hrefChanged = true;
|
|
}
|
|
},
|
|
|
|
setContent: function(/*String|DomNode|Nodelist*/data){
|
|
// summary:
|
|
// Deprecated. Use attr('content', ...) instead.
|
|
dojo.deprecated("dijit.layout.ContentPane.setContent() is deprecated. Use attr('content', ...) instead.", "", "2.0");
|
|
this.attr("content", data);
|
|
},
|
|
_setContentAttr: 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 can't run refresh and clear content
|
|
// refresh should only work if we downloaded the content
|
|
this.href = "";
|
|
|
|
// Cancel any in-flight requests (an attr('content') will cancel any in-flight attr('href', ...))
|
|
this.cancel();
|
|
|
|
this._setContent(data || "");
|
|
|
|
this._isDownloaded = false; // mark that content is from a attr('content') not an attr('href')
|
|
},
|
|
_getContentAttr: function(){
|
|
// summary:
|
|
// Hook to make attr("content") work
|
|
return this.containerNode.innerHTML;
|
|
},
|
|
|
|
cancel: function(){
|
|
// summary:
|
|
// Cancels an in-flight download of content
|
|
if(this._xhrDfd && (this._xhrDfd.fired == -1)){
|
|
this._xhrDfd.cancel();
|
|
}
|
|
delete this._xhrDfd; // garbage collect
|
|
},
|
|
|
|
uninitialize: function(){
|
|
if(this._beingDestroyed){
|
|
this.cancel();
|
|
}
|
|
},
|
|
|
|
destroyRecursive: function(/*Boolean*/ preserveDom){
|
|
// summary:
|
|
// Destroy the ContentPane and its contents
|
|
|
|
// if we have multiple controllers destroying us, bail after the first
|
|
if(this._beingDestroyed){
|
|
return;
|
|
}
|
|
this._beingDestroyed = true;
|
|
this.inherited(arguments);
|
|
},
|
|
|
|
resize: function(size){
|
|
// summary:
|
|
// See `dijit.layout._LayoutWidget.resize` for description.
|
|
// Although ContentPane doesn't extend _LayoutWidget, it does implement
|
|
// the same API.
|
|
|
|
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});
|
|
}
|
|
},
|
|
|
|
_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') && !dojo.hasClass(node, "dijitHidden");
|
|
}
|
|
},
|
|
|
|
_onShow: function(){
|
|
// summary:
|
|
// Called when the ContentPane is made visible
|
|
// description:
|
|
// For a plain ContentPane, this is called on initialization, from startup().
|
|
// If the ContentPane is a hidden pane of a TabContainer etc., then it's
|
|
// called whever the pane is made visible.
|
|
//
|
|
// Does processing necessary, including href download and layout/resize of
|
|
// child widget(s)
|
|
|
|
if(this._needLayout){
|
|
// If a layout has been scheduled for when we become visible, do it now
|
|
this._layoutChildren();
|
|
}
|
|
|
|
// Do lazy-load of URL
|
|
this._loadCheck();
|
|
|
|
// call onShow, if we have one
|
|
if(this.onShow){
|
|
this.onShow();
|
|
}
|
|
},
|
|
|
|
_loadCheck: function(){
|
|
// summary:
|
|
// Call this to load href contents if necessary.
|
|
// description:
|
|
// Call when !ContentPane has been made visible [from prior hidden state],
|
|
// or href has been changed, or on startup, etc.
|
|
|
|
if(
|
|
(this.href && !this._xhrDfd) && // if there's an href that isn't already being loaded
|
|
(!this.isLoaded || this._hrefChanged || this.refreshOnShow) && // and we need a [re]load
|
|
(this.preload || this._isShown()) // and now is the time to [re]load
|
|
){
|
|
delete this._hrefChanged;
|
|
this.refresh();
|
|
}
|
|
},
|
|
|
|
refresh: function(){
|
|
// summary:
|
|
// [Re]download contents of href and display
|
|
// description:
|
|
// 1. cancels any currently in-flight requests
|
|
// 2. posts "loading..." message
|
|
// 3. sends XHR to download new data
|
|
|
|
// cancel possible prior inflight request
|
|
this.cancel();
|
|
|
|
// display loading message
|
|
this._setContent(this.onDownloadStart(), true);
|
|
|
|
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._isDownloaded = true;
|
|
self._setContent(html, false);
|
|
self.onDownloadEnd();
|
|
}catch(err){
|
|
self._onError('Content', err); // onContentError
|
|
}
|
|
delete self._xhrDfd;
|
|
return html;
|
|
});
|
|
|
|
hand.addErrback(function(err){
|
|
if(!hand.canceled){
|
|
// show error message in the pane
|
|
self._onError('Download', err); // onDownloadError
|
|
}
|
|
delete self._xhrDfd;
|
|
return err;
|
|
});
|
|
},
|
|
|
|
_onLoadHandler: function(data){
|
|
// summary:
|
|
// This is called whenever new content is being loaded
|
|
this.isLoaded = true;
|
|
try{
|
|
this.onLoad(data);
|
|
}catch(e){
|
|
console.error('Error '+this.widgetId+' running custom onLoad code: ' + e.message);
|
|
}
|
|
},
|
|
|
|
_onUnloadHandler: function(){
|
|
// summary:
|
|
// This is called whenever the content is being unloaded
|
|
this.isLoaded = false;
|
|
try{
|
|
this.onUnload();
|
|
}catch(e){
|
|
console.error('Error '+this.widgetId+' running custom onUnload code: ' + e.message);
|
|
}
|
|
},
|
|
|
|
destroyDescendants: function(){
|
|
// summary:
|
|
// Destroy all the widgets inside the ContentPane and empty containerNode
|
|
|
|
// Make sure we call onUnload (but only when the ContentPane has real content)
|
|
if(this.isLoaded){
|
|
this._onUnloadHandler();
|
|
}
|
|
|
|
// Even if this.isLoaded == false there might still be a "Loading..." message
|
|
// to erase, so continue...
|
|
|
|
// For historical reasons we need to delete all widgets under this.containerNode,
|
|
// even ones that the user has created manually.
|
|
var setter = this._contentSetter;
|
|
dojo.forEach(this.getChildren(), function(widget){
|
|
if(widget.destroyRecursive){
|
|
widget.destroyRecursive();
|
|
}
|
|
});
|
|
if(setter){
|
|
// Most of the widgets in setter.parseResults have already been destroyed, but
|
|
// things like Menu that have been moved to <body> haven't yet
|
|
dojo.forEach(setter.parseResults, function(widget){
|
|
if(widget.destroyRecursive && widget.domNode && widget.domNode.parentNode == dojo.body()){
|
|
widget.destroyRecursive();
|
|
}
|
|
});
|
|
delete setter.parseResults;
|
|
}
|
|
|
|
// And then clear away all the DOM nodes
|
|
dojo.html._emptyNode(this.containerNode);
|
|
},
|
|
|
|
_setContent: function(cont, isFakeContent){
|
|
// summary:
|
|
// Insert the content into the container node
|
|
|
|
// first get rid of child widgets
|
|
this.destroyDescendants();
|
|
|
|
// Delete any state information we have about current contents
|
|
delete this._singleChild;
|
|
|
|
// dojo.html.set will take care of the rest of the details
|
|
// we provide an overide for the error handling to ensure the widget gets the errors
|
|
// configure the setter instance with only the relevant widget instance properties
|
|
// NOTE: unless we hook into attr, or provide property setters for each property,
|
|
// we need to re-configure the ContentSetter with each use
|
|
var setter = this._contentSetter;
|
|
if(! (setter && setter instanceof dojo.html._ContentSetter)) {
|
|
setter = this._contentSetter = new dojo.html._ContentSetter({
|
|
node: this.containerNode,
|
|
_onError: dojo.hitch(this, this._onError),
|
|
onContentError: dojo.hitch(this, function(e){
|
|
// fires 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{
|
|
this.containerNode.innerHTML = errMess;
|
|
}catch(e){
|
|
console.error('Fatal '+this.id+' could not change content due to '+e.message, e);
|
|
}
|
|
})/*,
|
|
_onError */
|
|
});
|
|
};
|
|
|
|
var setterParams = dojo.mixin({
|
|
cleanContent: this.cleanContent,
|
|
extractContent: this.extractContent,
|
|
parseContent: this.parseOnLoad
|
|
}, this._contentSetterParams || {});
|
|
|
|
dojo.mixin(setter, setterParams);
|
|
|
|
setter.set( (dojo.isObject(cont) && cont.domNode) ? cont.domNode : cont );
|
|
|
|
// setter params must be pulled afresh from the ContentPane each time
|
|
delete this._contentSetterParams;
|
|
|
|
if(!isFakeContent){
|
|
dojo.forEach(this.getChildren(), function(child){
|
|
child.startup();
|
|
});
|
|
|
|
if(this.doLayout){
|
|
this._checkIfSingleChild();
|
|
}
|
|
|
|
// Call resize() on each of my child layout widgets,
|
|
// or resize() on my single child layout widget...
|
|
// either now (if I'm currently visible)
|
|
// or when I become visible
|
|
this._scheduleLayout();
|
|
|
|
this._onLoadHandler(cont);
|
|
}
|
|
},
|
|
|
|
_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(errText, true);
|
|
}
|
|
},
|
|
|
|
_scheduleLayout: function(){
|
|
// summary:
|
|
// Call resize() on each of my child layout widgets, either now
|
|
// (if I'm currently visible) or when I become visible
|
|
if(this._isShown()){
|
|
this._layoutChildren();
|
|
}else{
|
|
this._needLayout = true;
|
|
}
|
|
},
|
|
|
|
_layoutChildren: function(){
|
|
// summary:
|
|
// Since I am a Container widget, each of my children expects me to
|
|
// call resize() or layout() on them.
|
|
// description:
|
|
// Should be called on initialization and also whenever we get new content
|
|
// (from an href, or from attr('content', ...))... but deferred until
|
|
// the ContentPane is visible
|
|
|
|
if(this._singleChild && this._singleChild.resize){
|
|
var cb = this._contentBox || dojo.contentBox(this.containerNode);
|
|
this._singleChild.resize({w: cb.w, h: cb.h});
|
|
}else{
|
|
// All my child widgets are independently sized (rather than matching my size),
|
|
// but I still need to call resize() on each child to make it layout.
|
|
dojo.forEach(this.getChildren(), function(widget){
|
|
if(widget.resize){
|
|
widget.resize();
|
|
}
|
|
});
|
|
}
|
|
delete this._needLayout;
|
|
},
|
|
|
|
// EVENT's, should be overide-able
|
|
onLoad: function(data){
|
|
// summary:
|
|
// Event hook, is called after everything is loaded and widgetified
|
|
// tags:
|
|
// callback
|
|
},
|
|
|
|
onUnload: function(){
|
|
// summary:
|
|
// Event hook, is called before old content is cleared
|
|
// tags:
|
|
// callback
|
|
},
|
|
|
|
onDownloadStart: function(){
|
|
// summary:
|
|
// Called before download starts.
|
|
// description:
|
|
// 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.
|
|
// tags:
|
|
// extension
|
|
return this.loadingMessage;
|
|
},
|
|
|
|
onContentError: function(/*Error*/ error){
|
|
// summary:
|
|
// Called on DOM faults, require faults etc. in content.
|
|
//
|
|
// In order to display an error message in the pane, return
|
|
// the error message from this method, as an HTML string.
|
|
//
|
|
// By default (if this method is not overriden), it returns
|
|
// nothing, so the error message is just printed to the console.
|
|
// tags:
|
|
// extension
|
|
},
|
|
|
|
onDownloadError: function(/*Error*/ error){
|
|
// summary:
|
|
// Called when download error occurs.
|
|
//
|
|
// In order to display an error message in the pane, return
|
|
// the error message from this method, as an HTML string.
|
|
//
|
|
// Default behavior (if this method is not overriden) is to display
|
|
// the error message inside the pane.
|
|
// tags:
|
|
// extension
|
|
return this.errorMessage;
|
|
},
|
|
|
|
onDownloadEnd: function(){
|
|
// summary:
|
|
// Called when download is finished.
|
|
// tags:
|
|
// callback
|
|
}
|
|
});
|