613 lines
20 KiB
JavaScript
613 lines
20 KiB
JavaScript
//>>built
|
|
define("dijit/layout/ContentPane", [
|
|
"dojo/_base/kernel", // kernel.deprecated
|
|
"dojo/_base/lang", // lang.mixin lang.delegate lang.hitch lang.isFunction lang.isObject
|
|
"../_Widget",
|
|
"./_ContentPaneResizeMixin",
|
|
"dojo/string", // string.substitute
|
|
"dojo/html", // html._ContentSetter html._emptyNode
|
|
"dojo/i18n!../nls/loading",
|
|
"dojo/_base/array", // array.forEach
|
|
"dojo/_base/declare", // declare
|
|
"dojo/_base/Deferred", // Deferred
|
|
"dojo/dom", // dom.byId
|
|
"dojo/dom-attr", // domAttr.attr
|
|
"dojo/_base/window", // win.body win.doc.createDocumentFragment
|
|
"dojo/_base/xhr", // xhr.get
|
|
"dojo/i18n" // i18n.getLocalization
|
|
], function(kernel, lang, _Widget, _ContentPaneResizeMixin, string, html, nlsLoading,
|
|
array, declare, Deferred, dom, domAttr, win, xhr, i18n){
|
|
|
|
/*=====
|
|
var _Widget = dijit._Widget;
|
|
var _ContentPaneResizeMixin = dijit.layout._ContentPaneResizeMixin;
|
|
=====*/
|
|
|
|
// module:
|
|
// dijit/layout/ContentPane
|
|
// summary:
|
|
// A widget containing an HTML fragment, specified inline
|
|
// or by uri. Fragment may include widgets.
|
|
|
|
|
|
return declare("dijit.layout.ContentPane", [_Widget, _ContentPaneResizeMixin], {
|
|
// summary:
|
|
// A widget containing an HTML fragment, specified inline
|
|
// or by uri. Fragment may include widgets.
|
|
//
|
|
// description:
|
|
// This widget embeds a document fragment in the page, specified
|
|
// either by uri, javascript generated markup or DOM reference.
|
|
// Any widgets within this content are instantiated and managed,
|
|
// but laid out according to the HTML structure. Unlike IFRAME,
|
|
// ContentPane embeds a document fragment as would be found
|
|
// inside the BODY tag of a full HTML document. It should not
|
|
// contain the HTML, HEAD, or BODY tags.
|
|
// For more advanced functionality with scripts and
|
|
// stylesheets, see dojox.layout.ContentPane. This widget may be
|
|
// used stand alone or as a base class for other widgets.
|
|
// ContentPane is useful as a child of other layout containers
|
|
// such as BorderContainer or TabContainer, but note that those
|
|
// widgets can contain any widget as a child.
|
|
//
|
|
// example:
|
|
// Some quick samples:
|
|
// To change the innerHTML: cp.set('content', '<b>new content</b>')
|
|
//
|
|
// Or you can send it a NodeList: cp.set('content', dojo.query('div [class=selected]', userSelection))
|
|
//
|
|
// To do an ajax update: cp.set('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 set('href', ...);
|
|
href: "",
|
|
|
|
// content: String || DomNode || NodeList || dijit._Widget
|
|
// The innerHTML of the ContentPane.
|
|
// Note that the initialization parameter / argument to set("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,
|
|
|
|
// parserScope: String
|
|
// Flag passed to parser. Root for attribute names to search for. If scopeName is dojo,
|
|
// will search for data-dojo-type (or dojoType). For backwards compatibility
|
|
// reasons defaults to dojo._scopeName (which is "dojo" except when
|
|
// multi-version support is used, when it will be something like dojo16, dojo20, etc.)
|
|
parserScope: kernel._scopeName,
|
|
|
|
// 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'><span class='dijitInline dijitIconLoading'></span>${loadingState}</span>",
|
|
|
|
// errorMessage: String
|
|
// Message that shows if an error occurs
|
|
errorMessage: "<span class='dijitContentPaneError'><span class='dijitInline dijitIconError'></span>${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 set('content', ...) / set('href', ...)
|
|
//
|
|
// False if it doesn't have any content, or if ContentPane is
|
|
// still in the process of downloading href.
|
|
isLoaded: false,
|
|
|
|
baseClass: "dijitContentPane",
|
|
|
|
/*======
|
|
// ioMethod: dojo.xhrGet|dojo.xhrPost
|
|
// Function that should grab the content specified via href.
|
|
ioMethod: dojo.xhrGet,
|
|
======*/
|
|
|
|
// ioArgs: Object
|
|
// Parameters to pass to xhrGet() request, for example:
|
|
// | <div data-dojo-type="dijit.layout.ContentPane" data-dojo-props="href: './bar', ioArgs: {timeout: 500}">
|
|
ioArgs: {},
|
|
|
|
// onLoadDeferred: [readonly] dojo.Deferred
|
|
// This is the `dojo.Deferred` returned by set('href', ...) and refresh().
|
|
// Calling onLoadDeferred.addCallback() or addErrback() registers your
|
|
// callback to be called only once, when the prior set('href', ...) call or
|
|
// the initial href parameter to the constructor finishes loading.
|
|
//
|
|
// This is different than an onLoad() handler which gets called any time any href
|
|
// or content is loaded.
|
|
onLoadDeferred: null,
|
|
|
|
// Cancel _WidgetBase's _setTitleAttr because we don't want the title attribute (used to specify
|
|
// tab labels) to be copied to ContentPane.domNode... otherwise a tooltip shows up over the
|
|
// entire pane.
|
|
_setTitleAttr: null,
|
|
|
|
// Flag to parser that I'll parse my contents, so it shouldn't.
|
|
stopParser: true,
|
|
|
|
// template: [private] Boolean
|
|
// Flag from the parser that this ContentPane is inside a template
|
|
// so the contents are pre-parsed.
|
|
// (TODO: this declaration can be commented out in 2.0)
|
|
template: false,
|
|
|
|
create: function(params, srcNodeRef){
|
|
// Convert a srcNodeRef argument into a content parameter, so that the original contents are
|
|
// processed in the same way as contents set via set("content", ...), calling the parser etc.
|
|
// Avoid modifying original params object since that breaks NodeList instantiation, see #11906.
|
|
if((!params || !params.template) && srcNodeRef && !("href" in params) && !("content" in params)){
|
|
var df = win.doc.createDocumentFragment();
|
|
srcNodeRef = dom.byId(srcNodeRef);
|
|
while(srcNodeRef.firstChild){
|
|
df.appendChild(srcNodeRef.firstChild);
|
|
}
|
|
params = lang.delegate(params, {content: df});
|
|
}
|
|
this.inherited(arguments, [params, srcNodeRef]);
|
|
},
|
|
|
|
postMixInProperties: function(){
|
|
this.inherited(arguments);
|
|
var messages = i18n.getLocalization("dijit", "loading", this.lang);
|
|
this.loadingMessage = string.substitute(this.loadingMessage, messages);
|
|
this.errorMessage = string.substitute(this.errorMessage, messages);
|
|
},
|
|
|
|
buildRendering: function(){
|
|
this.inherited(arguments);
|
|
|
|
// Since we have no template we need to set this.containerNode ourselves, to make getChildren() work.
|
|
// For subclasses of ContentPane that do have a template, does nothing.
|
|
if(!this.containerNode){
|
|
this.containerNode = this.domNode;
|
|
}
|
|
|
|
// remove the title attribute so it doesn't show up when hovering
|
|
// over a node (TODO: remove in 2.0, no longer needed after #11490)
|
|
this.domNode.title = "";
|
|
|
|
if(!domAttr.get(this.domNode,"role")){
|
|
this.domNode.setAttribute("role", "group");
|
|
}
|
|
},
|
|
|
|
startup: function(){
|
|
// summary:
|
|
// Call startup() on all children including non _Widget ones like dojo.dnd.Source objects
|
|
|
|
// This starts all the widgets
|
|
this.inherited(arguments);
|
|
|
|
// And this catches stuff like dojo.dnd.Source
|
|
if(this._contentSetter){
|
|
array.forEach(this._contentSetter.parseResults, function(obj){
|
|
if(!obj._started && !obj._destroyed && lang.isFunction(obj.startup)){
|
|
obj.startup();
|
|
obj._started = true;
|
|
}
|
|
}, this);
|
|
}
|
|
},
|
|
|
|
setHref: function(/*String|Uri*/ href){
|
|
// summary:
|
|
// Deprecated. Use set('href', ...) instead.
|
|
kernel.deprecated("dijit.layout.ContentPane.setHref() is deprecated. Use set('href', ...) instead.", "", "2.0");
|
|
return this.set("href", href);
|
|
},
|
|
_setHrefAttr: function(/*String|Uri*/ href){
|
|
// summary:
|
|
// Hook so set("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 (a set('href', ...) will cancel any in-flight set('href', ...))
|
|
this.cancel();
|
|
|
|
this.onLoadDeferred = new Deferred(lang.hitch(this, "cancel"));
|
|
this.onLoadDeferred.addCallback(lang.hitch(this, "onLoad"));
|
|
|
|
this._set("href", href);
|
|
|
|
// _setHrefAttr() is called during creation and by the user, after creation.
|
|
// Assuming preload == false, only in the second case do we actually load the URL;
|
|
// otherwise it's done in startup(), and only if this widget is shown.
|
|
if(this.preload || (this._created && this._isShown())){
|
|
this._load();
|
|
}else{
|
|
// Set flag to indicate that href needs to be loaded the next time the
|
|
// ContentPane is made visible
|
|
this._hrefChanged = true;
|
|
}
|
|
|
|
return this.onLoadDeferred; // Deferred
|
|
},
|
|
|
|
setContent: function(/*String|DomNode|Nodelist*/data){
|
|
// summary:
|
|
// Deprecated. Use set('content', ...) instead.
|
|
kernel.deprecated("dijit.layout.ContentPane.setContent() is deprecated. Use set('content', ...) instead.", "", "2.0");
|
|
this.set("content", data);
|
|
},
|
|
_setContentAttr: function(/*String|DomNode|Nodelist*/data){
|
|
// summary:
|
|
// Hook to make set("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._set("href", "");
|
|
|
|
// Cancel any in-flight requests (a set('content', ...) will cancel any in-flight set('href', ...))
|
|
this.cancel();
|
|
|
|
// Even though user is just setting content directly, still need to define an onLoadDeferred
|
|
// because the _onLoadHandler() handler is still getting called from setContent()
|
|
this.onLoadDeferred = new Deferred(lang.hitch(this, "cancel"));
|
|
if(this._created){
|
|
// For back-compat reasons, call onLoad() for set('content', ...)
|
|
// calls but not for content specified in srcNodeRef (ie: <div data-dojo-type=ContentPane>...</div>)
|
|
// or as initialization parameter (ie: new ContentPane({content: ...})
|
|
this.onLoadDeferred.addCallback(lang.hitch(this, "onLoad"));
|
|
}
|
|
|
|
this._setContent(data || "");
|
|
|
|
this._isDownloaded = false; // mark that content is from a set('content') not a set('href')
|
|
|
|
return this.onLoadDeferred; // Deferred
|
|
},
|
|
_getContentAttr: function(){
|
|
// summary:
|
|
// Hook to make get("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
|
|
|
|
this.onLoadDeferred = null;
|
|
},
|
|
|
|
uninitialize: function(){
|
|
if(this._beingDestroyed){
|
|
this.cancel();
|
|
}
|
|
this.inherited(arguments);
|
|
},
|
|
|
|
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.inherited(arguments);
|
|
},
|
|
|
|
_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 whenever the pane is made visible.
|
|
//
|
|
// Does necessary processing, including href download and layout/resize of
|
|
// child widget(s)
|
|
|
|
this.inherited(arguments);
|
|
|
|
if(this.href){
|
|
if(!this._xhrDfd && // if there's an href that isn't already being loaded
|
|
(!this.isLoaded || this._hrefChanged || this.refreshOnShow)
|
|
){
|
|
return this.refresh(); // If child has an href, promise that fires when the load is complete
|
|
}
|
|
}
|
|
},
|
|
|
|
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 in-flight request
|
|
this.cancel();
|
|
|
|
this.onLoadDeferred = new Deferred(lang.hitch(this, "cancel"));
|
|
this.onLoadDeferred.addCallback(lang.hitch(this, "onLoad"));
|
|
this._load();
|
|
return this.onLoadDeferred; // If child has an href, promise that fires when refresh is complete
|
|
},
|
|
|
|
_load: function(){
|
|
// summary:
|
|
// Load/reload the href specified in this.href
|
|
|
|
// display loading message
|
|
this._setContent(this.onDownloadStart(), true);
|
|
|
|
var self = this;
|
|
var getArgs = {
|
|
preventCache: (this.preventCache || this.refreshOnShow),
|
|
url: this.href,
|
|
handleAs: "text"
|
|
};
|
|
if(lang.isObject(this.ioArgs)){
|
|
lang.mixin(getArgs, this.ioArgs);
|
|
}
|
|
|
|
var hand = (this._xhrDfd = (this.ioMethod || xhr.get)(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;
|
|
});
|
|
|
|
// Remove flag saying that a load is needed
|
|
delete this._hrefChanged;
|
|
},
|
|
|
|
_onLoadHandler: function(data){
|
|
// summary:
|
|
// This is called whenever new content is being loaded
|
|
this._set("isLoaded", true);
|
|
try{
|
|
this.onLoadDeferred.callback(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._set("isLoaded", false);
|
|
try{
|
|
this.onUnload();
|
|
}catch(e){
|
|
console.error('Error '+this.widgetId+' running custom onUnload code: ' + e.message);
|
|
}
|
|
},
|
|
|
|
destroyDescendants: function(/*Boolean*/ preserveDom){
|
|
// 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;
|
|
array.forEach(this.getChildren(), function(widget){
|
|
if(widget.destroyRecursive){
|
|
widget.destroyRecursive(preserveDom);
|
|
}
|
|
});
|
|
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
|
|
array.forEach(setter.parseResults, function(widget){
|
|
if(widget.destroyRecursive && widget.domNode && widget.domNode.parentNode == win.body()){
|
|
widget.destroyRecursive(preserveDom);
|
|
}
|
|
});
|
|
delete setter.parseResults;
|
|
}
|
|
|
|
// And then clear away all the DOM nodes
|
|
if(!preserveDom){
|
|
html._emptyNode(this.containerNode);
|
|
}
|
|
|
|
// Delete any state information we have about current contents
|
|
delete this._singleChild;
|
|
},
|
|
|
|
_setContent: function(/*String|DocumentFragment*/ cont, /*Boolean*/ isFakeContent){
|
|
// summary:
|
|
// Insert the content into the container node
|
|
|
|
// first get rid of child widgets
|
|
this.destroyDescendants();
|
|
|
|
// html.set will take care of the rest of the details
|
|
// we provide an override 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 html._ContentSetter)){
|
|
setter = this._contentSetter = new html._ContentSetter({
|
|
node: this.containerNode,
|
|
_onError: lang.hitch(this, this._onError),
|
|
onContentError: lang.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 = lang.mixin({
|
|
cleanContent: this.cleanContent,
|
|
extractContent: this.extractContent,
|
|
parseContent: !cont.domNode && this.parseOnLoad,
|
|
parserScope: this.parserScope,
|
|
startup: false,
|
|
dir: this.dir,
|
|
lang: this.lang,
|
|
textDir: this.textDir
|
|
}, this._contentSetterParams || {});
|
|
|
|
setter.set( (lang.isObject(cont) && cont.domNode) ? cont.domNode : cont, setterParams );
|
|
|
|
// setter params must be pulled afresh from the ContentPane each time
|
|
delete this._contentSetterParams;
|
|
|
|
if(this.doLayout){
|
|
this._checkIfSingleChild();
|
|
}
|
|
|
|
if(!isFakeContent){
|
|
if(this._started){
|
|
// Startup each top level child widget (and they will start their children, recursively)
|
|
delete this._started;
|
|
this.startup();
|
|
|
|
// 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){
|
|
this.onLoadDeferred.errback(err);
|
|
|
|
// shows user the string that is returned by on[type]Error
|
|
// override 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);
|
|
}
|
|
},
|
|
|
|
// 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
|
|
}
|
|
});
|
|
|
|
});
|