UNPKG

dijit

Version:

Dijit provides a complete collection of user interface controls based on Dojo, giving you the power to create web applications that are highly optimized for usability, performance, internationalization, accessibility, but above all deliver an incredible u

666 lines (577 loc) 23.4 kB
define([ "dojo/_base/kernel", // kernel.deprecated "dojo/_base/lang", // lang.mixin lang.delegate lang.hitch lang.isFunction lang.isObject "../_Widget", "../_Container", "./_ContentPaneResizeMixin", "dojo/string", // string.substitute "dojo/html", // html._ContentSetter "dojo/_base/array", // array.forEach "dojo/_base/declare", // declare "dojo/_base/Deferred", // Deferred "dojo/dom", // dom.byId "dojo/dom-attr", // domAttr.attr "dojo/dom-construct", // empty() "dojo/_base/xhr", // xhr.get "dojo/i18n", // i18n.getLocalization "dojo/when", "dojo/i18n!../nls/loading" ], function(kernel, lang, _Widget, _Container, _ContentPaneResizeMixin, string, html, array, declare, Deferred, dom, domAttr, domConstruct, xhr, i18n, when){ // module: // dijit/layout/ContentPane return declare("dijit.layout.ContentPane", [_Widget, _Container, _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/_base/xhr.get|dojo._base/xhr.post // 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.then() 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, markupFactory: function(params, node, ctor){ var self = new ctor(params, node); // If a parse has started but is waiting for modules to load, then return a Promise for when the parser // finishes. Don't return a promise though for the case when content hasn't started loading because the // ContentPane is hidden and it has an href (ex: hidden pane of a TabContainer). In that case we consider // that initialization has already finished. return !self.href && self._contentSetter && self._contentSetter.parseDeferred && !self._contentSetter.parseDeferred.isFulfilled() ? self._contentSetter.parseDeferred.then(function(){ return self; }) : self; }, 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)){ srcNodeRef = dom.byId(srcNodeRef); var df = srcNodeRef.ownerDocument.createDocumentFragment(); 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.removeAttribute("title"); }, 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); } }, _startChildren: function(){ // summary: // Called when content is loaded. Calls startup on each child widget. Similar to ContentPane.startup() // itself, but avoids marking the ContentPane itself as "restarted" (see #15581). // This starts all the widgets array.forEach(this.getChildren(), function(obj){ if(!obj._started && !obj._destroyed && lang.isFunction(obj.startup)){ obj.startup(); obj._started = true; } }); // 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.then(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.then(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; }, destroy: function(){ 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.then(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)), returnedHtml; hand.then( function(html){ returnedHtml = html; try{ self._isDownloaded = true; return self._setContent(html, false); }catch(err){ self._onError('Content', err); // onContentError } }, function(err){ if(!hand.canceled){ // show error message in the pane self._onError('Download', err); // onDownloadError } delete self._xhrDfd; return err; } ).then(function(){ self.onDownloadEnd(); delete self._xhrDfd; return returnedHtml; }); // 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.resolve(data); }catch(e){ console.error('Error ' + (this.widgetId || this.id) + ' 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){ // All widgets will hit this branch widget.destroyRecursive(preserveDom); }else if(widget.destroy){ // Things like dojo/dnd/Source have destroy(), not destroyRecursive() widget.destroy(preserveDom); } widget._destroyed = true; }); 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._destroyed){ if(widget.destroyRecursive){ // All widgets will hit this branch widget.destroyRecursive(preserveDom); }else if(widget.destroy){ // Things like dojo/dnd/Source have destroy(), not destroyRecursive() widget.destroy(preserveDom); } widget._destroyed = true; } }); delete setter.parseResults; } // And then clear away all the DOM nodes if(!preserveDom){ domConstruct.empty(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 // returns: // Returns a Deferred promise that is resolved when the content is parsed. cont = this.preprocessContent(cont); // 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 || {}); var p = setter.set((lang.isObject(cont) && cont.domNode) ? cont.domNode : cont, setterParams); // dojox/layout/html/_base::_ContentSetter.set() returns a Promise that indicates when everything is completed. // dojo/html::_ContentSetter.set() currently returns the DOMNode, but that will be changed for 2.0. // So, if set() returns a promise then use it, otherwise fallback to waiting on setter.parseDeferred var self = this; return when(p && p.then ? p : setter.parseDeferred, function(){ // setter params must be pulled afresh from the ContentPane each time delete self._contentSetterParams; if(!isFakeContent){ if(self._started){ // Startup each top level child widget (and they will start their children, recursively) self._startChildren(); // 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 self._scheduleLayout(); } self._onLoadHandler(cont); } }); }, preprocessContent: function(/*String|DocumentFragment*/ content){ // summary: // Hook, called after content has loaded, before being processed. // description: // A subclass should preprocess the content and return the preprocessed content. // See https://bugs.dojotoolkit.org/ticket/9622 // returns: // Returns preprocessed content, either a String or DocumentFragment return content; }, _onError: function(type, err, consoleText){ this.onLoadDeferred.reject(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 } }); });