ContentPane.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611
  1. define("dijit/layout/ContentPane", [
  2. "dojo/_base/kernel", // kernel.deprecated
  3. "dojo/_base/lang", // lang.mixin lang.delegate lang.hitch lang.isFunction lang.isObject
  4. "../_Widget",
  5. "./_ContentPaneResizeMixin",
  6. "dojo/string", // string.substitute
  7. "dojo/html", // html._ContentSetter html._emptyNode
  8. "dojo/i18n!../nls/loading",
  9. "dojo/_base/array", // array.forEach
  10. "dojo/_base/declare", // declare
  11. "dojo/_base/Deferred", // Deferred
  12. "dojo/dom", // dom.byId
  13. "dojo/dom-attr", // domAttr.attr
  14. "dojo/_base/window", // win.body win.doc.createDocumentFragment
  15. "dojo/_base/xhr", // xhr.get
  16. "dojo/i18n" // i18n.getLocalization
  17. ], function(kernel, lang, _Widget, _ContentPaneResizeMixin, string, html, nlsLoading,
  18. array, declare, Deferred, dom, domAttr, win, xhr, i18n){
  19. /*=====
  20. var _Widget = dijit._Widget;
  21. var _ContentPaneResizeMixin = dijit.layout._ContentPaneResizeMixin;
  22. =====*/
  23. // module:
  24. // dijit/layout/ContentPane
  25. // summary:
  26. // A widget containing an HTML fragment, specified inline
  27. // or by uri. Fragment may include widgets.
  28. return declare("dijit.layout.ContentPane", [_Widget, _ContentPaneResizeMixin], {
  29. // summary:
  30. // A widget containing an HTML fragment, specified inline
  31. // or by uri. Fragment may include widgets.
  32. //
  33. // description:
  34. // This widget embeds a document fragment in the page, specified
  35. // either by uri, javascript generated markup or DOM reference.
  36. // Any widgets within this content are instantiated and managed,
  37. // but laid out according to the HTML structure. Unlike IFRAME,
  38. // ContentPane embeds a document fragment as would be found
  39. // inside the BODY tag of a full HTML document. It should not
  40. // contain the HTML, HEAD, or BODY tags.
  41. // For more advanced functionality with scripts and
  42. // stylesheets, see dojox.layout.ContentPane. This widget may be
  43. // used stand alone or as a base class for other widgets.
  44. // ContentPane is useful as a child of other layout containers
  45. // such as BorderContainer or TabContainer, but note that those
  46. // widgets can contain any widget as a child.
  47. //
  48. // example:
  49. // Some quick samples:
  50. // To change the innerHTML: cp.set('content', '<b>new content</b>')
  51. //
  52. // Or you can send it a NodeList: cp.set('content', dojo.query('div [class=selected]', userSelection))
  53. //
  54. // To do an ajax update: cp.set('href', url)
  55. // href: String
  56. // The href of the content that displays now.
  57. // Set this at construction if you want to load data externally when the
  58. // pane is shown. (Set preload=true to load it immediately.)
  59. // Changing href after creation doesn't have any effect; Use set('href', ...);
  60. href: "",
  61. // content: String || DomNode || NodeList || dijit._Widget
  62. // The innerHTML of the ContentPane.
  63. // Note that the initialization parameter / argument to set("content", ...)
  64. // can be a String, DomNode, Nodelist, or _Widget.
  65. content: "",
  66. // extractContent: Boolean
  67. // Extract visible content from inside of <body> .... </body>.
  68. // I.e., strip <html> and <head> (and it's contents) from the href
  69. extractContent: false,
  70. // parseOnLoad: Boolean
  71. // Parse content and create the widgets, if any.
  72. parseOnLoad: true,
  73. // parserScope: String
  74. // Flag passed to parser. Root for attribute names to search for. If scopeName is dojo,
  75. // will search for data-dojo-type (or dojoType). For backwards compatibility
  76. // reasons defaults to dojo._scopeName (which is "dojo" except when
  77. // multi-version support is used, when it will be something like dojo16, dojo20, etc.)
  78. parserScope: kernel._scopeName,
  79. // preventCache: Boolean
  80. // Prevent caching of data from href's by appending a timestamp to the href.
  81. preventCache: false,
  82. // preload: Boolean
  83. // Force load of data on initialization even if pane is hidden.
  84. preload: false,
  85. // refreshOnShow: Boolean
  86. // Refresh (re-download) content when pane goes from hidden to shown
  87. refreshOnShow: false,
  88. // loadingMessage: String
  89. // Message that shows while downloading
  90. loadingMessage: "<span class='dijitContentPaneLoading'><span class='dijitInline dijitIconLoading'></span>${loadingState}</span>",
  91. // errorMessage: String
  92. // Message that shows if an error occurs
  93. errorMessage: "<span class='dijitContentPaneError'><span class='dijitInline dijitIconError'></span>${errorState}</span>",
  94. // isLoaded: [readonly] Boolean
  95. // True if the ContentPane has data in it, either specified
  96. // during initialization (via href or inline content), or set
  97. // via set('content', ...) / set('href', ...)
  98. //
  99. // False if it doesn't have any content, or if ContentPane is
  100. // still in the process of downloading href.
  101. isLoaded: false,
  102. baseClass: "dijitContentPane",
  103. /*======
  104. // ioMethod: dojo.xhrGet|dojo.xhrPost
  105. // Function that should grab the content specified via href.
  106. ioMethod: dojo.xhrGet,
  107. ======*/
  108. // ioArgs: Object
  109. // Parameters to pass to xhrGet() request, for example:
  110. // | <div data-dojo-type="dijit.layout.ContentPane" data-dojo-props="href: './bar', ioArgs: {timeout: 500}">
  111. ioArgs: {},
  112. // onLoadDeferred: [readonly] dojo.Deferred
  113. // This is the `dojo.Deferred` returned by set('href', ...) and refresh().
  114. // Calling onLoadDeferred.addCallback() or addErrback() registers your
  115. // callback to be called only once, when the prior set('href', ...) call or
  116. // the initial href parameter to the constructor finishes loading.
  117. //
  118. // This is different than an onLoad() handler which gets called any time any href
  119. // or content is loaded.
  120. onLoadDeferred: null,
  121. // Cancel _WidgetBase's _setTitleAttr because we don't want the title attribute (used to specify
  122. // tab labels) to be copied to ContentPane.domNode... otherwise a tooltip shows up over the
  123. // entire pane.
  124. _setTitleAttr: null,
  125. // Flag to parser that I'll parse my contents, so it shouldn't.
  126. stopParser: true,
  127. // template: [private] Boolean
  128. // Flag from the parser that this ContentPane is inside a template
  129. // so the contents are pre-parsed.
  130. // (TODO: this declaration can be commented out in 2.0)
  131. template: false,
  132. create: function(params, srcNodeRef){
  133. // Convert a srcNodeRef argument into a content parameter, so that the original contents are
  134. // processed in the same way as contents set via set("content", ...), calling the parser etc.
  135. // Avoid modifying original params object since that breaks NodeList instantiation, see #11906.
  136. if((!params || !params.template) && srcNodeRef && !("href" in params) && !("content" in params)){
  137. var df = win.doc.createDocumentFragment();
  138. srcNodeRef = dom.byId(srcNodeRef);
  139. while(srcNodeRef.firstChild){
  140. df.appendChild(srcNodeRef.firstChild);
  141. }
  142. params = lang.delegate(params, {content: df});
  143. }
  144. this.inherited(arguments, [params, srcNodeRef]);
  145. },
  146. postMixInProperties: function(){
  147. this.inherited(arguments);
  148. var messages = i18n.getLocalization("dijit", "loading", this.lang);
  149. this.loadingMessage = string.substitute(this.loadingMessage, messages);
  150. this.errorMessage = string.substitute(this.errorMessage, messages);
  151. },
  152. buildRendering: function(){
  153. this.inherited(arguments);
  154. // Since we have no template we need to set this.containerNode ourselves, to make getChildren() work.
  155. // For subclasses of ContentPane that do have a template, does nothing.
  156. if(!this.containerNode){
  157. this.containerNode = this.domNode;
  158. }
  159. // remove the title attribute so it doesn't show up when hovering
  160. // over a node (TODO: remove in 2.0, no longer needed after #11490)
  161. this.domNode.removeAttribute("title");
  162. if(!domAttr.get(this.domNode,"role")){
  163. this.domNode.setAttribute("role", "group");
  164. }
  165. },
  166. startup: function(){
  167. // summary:
  168. // Call startup() on all children including non _Widget ones like dojo.dnd.Source objects
  169. // This starts all the widgets
  170. this.inherited(arguments);
  171. // And this catches stuff like dojo.dnd.Source
  172. if(this._contentSetter){
  173. array.forEach(this._contentSetter.parseResults, function(obj){
  174. if(!obj._started && !obj._destroyed && lang.isFunction(obj.startup)){
  175. obj.startup();
  176. obj._started = true;
  177. }
  178. }, this);
  179. }
  180. },
  181. setHref: function(/*String|Uri*/ href){
  182. // summary:
  183. // Deprecated. Use set('href', ...) instead.
  184. kernel.deprecated("dijit.layout.ContentPane.setHref() is deprecated. Use set('href', ...) instead.", "", "2.0");
  185. return this.set("href", href);
  186. },
  187. _setHrefAttr: function(/*String|Uri*/ href){
  188. // summary:
  189. // Hook so set("href", ...) works.
  190. // description:
  191. // Reset the (external defined) content of this pane and replace with new url
  192. // Note: It delays the download until widget is shown if preload is false.
  193. // href:
  194. // url to the page you want to get, must be within the same domain as your mainpage
  195. // Cancel any in-flight requests (a set('href', ...) will cancel any in-flight set('href', ...))
  196. this.cancel();
  197. this.onLoadDeferred = new Deferred(lang.hitch(this, "cancel"));
  198. this.onLoadDeferred.addCallback(lang.hitch(this, "onLoad"));
  199. this._set("href", href);
  200. // _setHrefAttr() is called during creation and by the user, after creation.
  201. // Assuming preload == false, only in the second case do we actually load the URL;
  202. // otherwise it's done in startup(), and only if this widget is shown.
  203. if(this.preload || (this._created && this._isShown())){
  204. this._load();
  205. }else{
  206. // Set flag to indicate that href needs to be loaded the next time the
  207. // ContentPane is made visible
  208. this._hrefChanged = true;
  209. }
  210. return this.onLoadDeferred; // Deferred
  211. },
  212. setContent: function(/*String|DomNode|Nodelist*/data){
  213. // summary:
  214. // Deprecated. Use set('content', ...) instead.
  215. kernel.deprecated("dijit.layout.ContentPane.setContent() is deprecated. Use set('content', ...) instead.", "", "2.0");
  216. this.set("content", data);
  217. },
  218. _setContentAttr: function(/*String|DomNode|Nodelist*/data){
  219. // summary:
  220. // Hook to make set("content", ...) work.
  221. // Replaces old content with data content, include style classes from old content
  222. // data:
  223. // the new Content may be String, DomNode or NodeList
  224. //
  225. // if data is a NodeList (or an array of nodes) nodes are copied
  226. // so you can import nodes from another document implicitly
  227. // clear href so we can't run refresh and clear content
  228. // refresh should only work if we downloaded the content
  229. this._set("href", "");
  230. // Cancel any in-flight requests (a set('content', ...) will cancel any in-flight set('href', ...))
  231. this.cancel();
  232. // Even though user is just setting content directly, still need to define an onLoadDeferred
  233. // because the _onLoadHandler() handler is still getting called from setContent()
  234. this.onLoadDeferred = new Deferred(lang.hitch(this, "cancel"));
  235. if(this._created){
  236. // For back-compat reasons, call onLoad() for set('content', ...)
  237. // calls but not for content specified in srcNodeRef (ie: <div data-dojo-type=ContentPane>...</div>)
  238. // or as initialization parameter (ie: new ContentPane({content: ...})
  239. this.onLoadDeferred.addCallback(lang.hitch(this, "onLoad"));
  240. }
  241. this._setContent(data || "");
  242. this._isDownloaded = false; // mark that content is from a set('content') not a set('href')
  243. return this.onLoadDeferred; // Deferred
  244. },
  245. _getContentAttr: function(){
  246. // summary:
  247. // Hook to make get("content") work
  248. return this.containerNode.innerHTML;
  249. },
  250. cancel: function(){
  251. // summary:
  252. // Cancels an in-flight download of content
  253. if(this._xhrDfd && (this._xhrDfd.fired == -1)){
  254. this._xhrDfd.cancel();
  255. }
  256. delete this._xhrDfd; // garbage collect
  257. this.onLoadDeferred = null;
  258. },
  259. uninitialize: function(){
  260. if(this._beingDestroyed){
  261. this.cancel();
  262. }
  263. this.inherited(arguments);
  264. },
  265. destroyRecursive: function(/*Boolean*/ preserveDom){
  266. // summary:
  267. // Destroy the ContentPane and its contents
  268. // if we have multiple controllers destroying us, bail after the first
  269. if(this._beingDestroyed){
  270. return;
  271. }
  272. this.inherited(arguments);
  273. },
  274. _onShow: function(){
  275. // summary:
  276. // Called when the ContentPane is made visible
  277. // description:
  278. // For a plain ContentPane, this is called on initialization, from startup().
  279. // If the ContentPane is a hidden pane of a TabContainer etc., then it's
  280. // called whenever the pane is made visible.
  281. //
  282. // Does necessary processing, including href download and layout/resize of
  283. // child widget(s)
  284. this.inherited(arguments);
  285. if(this.href){
  286. if(!this._xhrDfd && // if there's an href that isn't already being loaded
  287. (!this.isLoaded || this._hrefChanged || this.refreshOnShow)
  288. ){
  289. return this.refresh(); // If child has an href, promise that fires when the load is complete
  290. }
  291. }
  292. },
  293. refresh: function(){
  294. // summary:
  295. // [Re]download contents of href and display
  296. // description:
  297. // 1. cancels any currently in-flight requests
  298. // 2. posts "loading..." message
  299. // 3. sends XHR to download new data
  300. // Cancel possible prior in-flight request
  301. this.cancel();
  302. this.onLoadDeferred = new Deferred(lang.hitch(this, "cancel"));
  303. this.onLoadDeferred.addCallback(lang.hitch(this, "onLoad"));
  304. this._load();
  305. return this.onLoadDeferred; // If child has an href, promise that fires when refresh is complete
  306. },
  307. _load: function(){
  308. // summary:
  309. // Load/reload the href specified in this.href
  310. // display loading message
  311. this._setContent(this.onDownloadStart(), true);
  312. var self = this;
  313. var getArgs = {
  314. preventCache: (this.preventCache || this.refreshOnShow),
  315. url: this.href,
  316. handleAs: "text"
  317. };
  318. if(lang.isObject(this.ioArgs)){
  319. lang.mixin(getArgs, this.ioArgs);
  320. }
  321. var hand = (this._xhrDfd = (this.ioMethod || xhr.get)(getArgs));
  322. hand.addCallback(function(html){
  323. try{
  324. self._isDownloaded = true;
  325. self._setContent(html, false);
  326. self.onDownloadEnd();
  327. }catch(err){
  328. self._onError('Content', err); // onContentError
  329. }
  330. delete self._xhrDfd;
  331. return html;
  332. });
  333. hand.addErrback(function(err){
  334. if(!hand.canceled){
  335. // show error message in the pane
  336. self._onError('Download', err); // onDownloadError
  337. }
  338. delete self._xhrDfd;
  339. return err;
  340. });
  341. // Remove flag saying that a load is needed
  342. delete this._hrefChanged;
  343. },
  344. _onLoadHandler: function(data){
  345. // summary:
  346. // This is called whenever new content is being loaded
  347. this._set("isLoaded", true);
  348. try{
  349. this.onLoadDeferred.callback(data);
  350. }catch(e){
  351. console.error('Error '+this.widgetId+' running custom onLoad code: ' + e.message);
  352. }
  353. },
  354. _onUnloadHandler: function(){
  355. // summary:
  356. // This is called whenever the content is being unloaded
  357. this._set("isLoaded", false);
  358. try{
  359. this.onUnload();
  360. }catch(e){
  361. console.error('Error '+this.widgetId+' running custom onUnload code: ' + e.message);
  362. }
  363. },
  364. destroyDescendants: function(/*Boolean*/ preserveDom){
  365. // summary:
  366. // Destroy all the widgets inside the ContentPane and empty containerNode
  367. // Make sure we call onUnload (but only when the ContentPane has real content)
  368. if(this.isLoaded){
  369. this._onUnloadHandler();
  370. }
  371. // Even if this.isLoaded == false there might still be a "Loading..." message
  372. // to erase, so continue...
  373. // For historical reasons we need to delete all widgets under this.containerNode,
  374. // even ones that the user has created manually.
  375. var setter = this._contentSetter;
  376. array.forEach(this.getChildren(), function(widget){
  377. if(widget.destroyRecursive){
  378. widget.destroyRecursive(preserveDom);
  379. }
  380. });
  381. if(setter){
  382. // Most of the widgets in setter.parseResults have already been destroyed, but
  383. // things like Menu that have been moved to <body> haven't yet
  384. array.forEach(setter.parseResults, function(widget){
  385. if(widget.destroyRecursive && widget.domNode && widget.domNode.parentNode == win.body()){
  386. widget.destroyRecursive(preserveDom);
  387. }
  388. });
  389. delete setter.parseResults;
  390. }
  391. // And then clear away all the DOM nodes
  392. if(!preserveDom){
  393. html._emptyNode(this.containerNode);
  394. }
  395. // Delete any state information we have about current contents
  396. delete this._singleChild;
  397. },
  398. _setContent: function(/*String|DocumentFragment*/ cont, /*Boolean*/ isFakeContent){
  399. // summary:
  400. // Insert the content into the container node
  401. // first get rid of child widgets
  402. this.destroyDescendants();
  403. // html.set will take care of the rest of the details
  404. // we provide an override for the error handling to ensure the widget gets the errors
  405. // configure the setter instance with only the relevant widget instance properties
  406. // NOTE: unless we hook into attr, or provide property setters for each property,
  407. // we need to re-configure the ContentSetter with each use
  408. var setter = this._contentSetter;
  409. if(! (setter && setter instanceof html._ContentSetter)){
  410. setter = this._contentSetter = new html._ContentSetter({
  411. node: this.containerNode,
  412. _onError: lang.hitch(this, this._onError),
  413. onContentError: lang.hitch(this, function(e){
  414. // fires if a domfault occurs when we are appending this.errorMessage
  415. // like for instance if domNode is a UL and we try append a DIV
  416. var errMess = this.onContentError(e);
  417. try{
  418. this.containerNode.innerHTML = errMess;
  419. }catch(e){
  420. console.error('Fatal '+this.id+' could not change content due to '+e.message, e);
  421. }
  422. })/*,
  423. _onError */
  424. });
  425. }
  426. var setterParams = lang.mixin({
  427. cleanContent: this.cleanContent,
  428. extractContent: this.extractContent,
  429. parseContent: !cont.domNode && this.parseOnLoad,
  430. parserScope: this.parserScope,
  431. startup: false,
  432. dir: this.dir,
  433. lang: this.lang,
  434. textDir: this.textDir
  435. }, this._contentSetterParams || {});
  436. setter.set( (lang.isObject(cont) && cont.domNode) ? cont.domNode : cont, setterParams );
  437. // setter params must be pulled afresh from the ContentPane each time
  438. delete this._contentSetterParams;
  439. if(this.doLayout){
  440. this._checkIfSingleChild();
  441. }
  442. if(!isFakeContent){
  443. if(this._started){
  444. // Startup each top level child widget (and they will start their children, recursively)
  445. delete this._started;
  446. this.startup();
  447. // Call resize() on each of my child layout widgets,
  448. // or resize() on my single child layout widget...
  449. // either now (if I'm currently visible) or when I become visible
  450. this._scheduleLayout();
  451. }
  452. this._onLoadHandler(cont);
  453. }
  454. },
  455. _onError: function(type, err, consoleText){
  456. this.onLoadDeferred.errback(err);
  457. // shows user the string that is returned by on[type]Error
  458. // override on[type]Error and return your own string to customize
  459. var errText = this['on' + type + 'Error'].call(this, err);
  460. if(consoleText){
  461. console.error(consoleText, err);
  462. }else if(errText){// a empty string won't change current content
  463. this._setContent(errText, true);
  464. }
  465. },
  466. // EVENT's, should be overide-able
  467. onLoad: function(/*===== data =====*/){
  468. // summary:
  469. // Event hook, is called after everything is loaded and widgetified
  470. // tags:
  471. // callback
  472. },
  473. onUnload: function(){
  474. // summary:
  475. // Event hook, is called before old content is cleared
  476. // tags:
  477. // callback
  478. },
  479. onDownloadStart: function(){
  480. // summary:
  481. // Called before download starts.
  482. // description:
  483. // The string returned by this function will be the html
  484. // that tells the user we are loading something.
  485. // Override with your own function if you want to change text.
  486. // tags:
  487. // extension
  488. return this.loadingMessage;
  489. },
  490. onContentError: function(/*Error*/ /*===== error =====*/){
  491. // summary:
  492. // Called on DOM faults, require faults etc. in content.
  493. //
  494. // In order to display an error message in the pane, return
  495. // the error message from this method, as an HTML string.
  496. //
  497. // By default (if this method is not overriden), it returns
  498. // nothing, so the error message is just printed to the console.
  499. // tags:
  500. // extension
  501. },
  502. onDownloadError: function(/*Error*/ /*===== error =====*/){
  503. // summary:
  504. // Called when download error occurs.
  505. //
  506. // In order to display an error message in the pane, return
  507. // the error message from this method, as an HTML string.
  508. //
  509. // Default behavior (if this method is not overriden) is to display
  510. // the error message inside the pane.
  511. // tags:
  512. // extension
  513. return this.errorMessage;
  514. },
  515. onDownloadEnd: function(){
  516. // summary:
  517. // Called when download is finished.
  518. // tags:
  519. // callback
  520. }
  521. });
  522. });