ContentPane.js 19 KB

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