StackContainer.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. define("dijit/layout/StackContainer", [
  2. "dojo/_base/array", // array.forEach array.indexOf array.some
  3. "dojo/cookie", // cookie
  4. "dojo/_base/declare", // declare
  5. "dojo/dom-class", // domClass.add domClass.replace
  6. "dojo/_base/kernel", // kernel.isAsync
  7. "dojo/_base/lang", // lang.extend
  8. "dojo/ready",
  9. "dojo/topic", // publish
  10. "../registry", // registry.byId
  11. "../_WidgetBase",
  12. "./_LayoutWidget",
  13. "dojo/i18n!../nls/common"
  14. ], function(array, cookie, declare, domClass, kernel, lang, ready, topic,
  15. registry, _WidgetBase, _LayoutWidget){
  16. /*=====
  17. var _WidgetBase = dijit._WidgetBase;
  18. var _LayoutWidget = dijit.layout._LayoutWidget;
  19. var StackController = dijit.layout.StackController;
  20. =====*/
  21. // module:
  22. // dijit/layout/StackContainer
  23. // summary:
  24. // A container that has multiple children, but shows only one child at a time.
  25. // Back compat w/1.6, remove for 2.0
  26. if(!kernel.isAsync){
  27. ready(0, function(){
  28. var requires = ["dijit/layout/StackController"];
  29. require(requires); // use indirection so modules not rolled into a build
  30. });
  31. }
  32. // These arguments can be specified for the children of a StackContainer.
  33. // Since any widget can be specified as a StackContainer child, mix them
  34. // into the base widget class. (This is a hack, but it's effective.)
  35. lang.extend(_WidgetBase, {
  36. // selected: Boolean
  37. // Parameter for children of `dijit.layout.StackContainer` or subclasses.
  38. // Specifies that this widget should be the initially displayed pane.
  39. // Note: to change the selected child use `dijit.layout.StackContainer.selectChild`
  40. selected: false,
  41. // closable: Boolean
  42. // Parameter for children of `dijit.layout.StackContainer` or subclasses.
  43. // True if user can close (destroy) this child, such as (for example) clicking the X on the tab.
  44. closable: false,
  45. // iconClass: String
  46. // Parameter for children of `dijit.layout.StackContainer` or subclasses.
  47. // CSS Class specifying icon to use in label associated with this pane.
  48. iconClass: "dijitNoIcon",
  49. // showTitle: Boolean
  50. // Parameter for children of `dijit.layout.StackContainer` or subclasses.
  51. // When true, display title of this widget as tab label etc., rather than just using
  52. // icon specified in iconClass
  53. showTitle: true
  54. });
  55. return declare("dijit.layout.StackContainer", _LayoutWidget, {
  56. // summary:
  57. // A container that has multiple children, but shows only
  58. // one child at a time
  59. //
  60. // description:
  61. // A container for widgets (ContentPanes, for example) That displays
  62. // only one Widget at a time.
  63. //
  64. // Publishes topics [widgetId]-addChild, [widgetId]-removeChild, and [widgetId]-selectChild
  65. //
  66. // Can be base class for container, Wizard, Show, etc.
  67. // doLayout: Boolean
  68. // If true, change the size of my currently displayed child to match my size
  69. doLayout: true,
  70. // persist: Boolean
  71. // Remembers the selected child across sessions
  72. persist: false,
  73. baseClass: "dijitStackContainer",
  74. /*=====
  75. // selectedChildWidget: [readonly] dijit._Widget
  76. // References the currently selected child widget, if any.
  77. // Adjust selected child with selectChild() method.
  78. selectedChildWidget: null,
  79. =====*/
  80. buildRendering: function(){
  81. this.inherited(arguments);
  82. domClass.add(this.domNode, "dijitLayoutContainer");
  83. this.containerNode.setAttribute("role", "tabpanel");
  84. },
  85. postCreate: function(){
  86. this.inherited(arguments);
  87. this.connect(this.domNode, "onkeypress", this._onKeyPress);
  88. },
  89. startup: function(){
  90. if(this._started){ return; }
  91. var children = this.getChildren();
  92. // Setup each page panel to be initially hidden
  93. array.forEach(children, this._setupChild, this);
  94. // Figure out which child to initially display, defaulting to first one
  95. if(this.persist){
  96. this.selectedChildWidget = registry.byId(cookie(this.id + "_selectedChild"));
  97. }else{
  98. array.some(children, function(child){
  99. if(child.selected){
  100. this.selectedChildWidget = child;
  101. }
  102. return child.selected;
  103. }, this);
  104. }
  105. var selected = this.selectedChildWidget;
  106. if(!selected && children[0]){
  107. selected = this.selectedChildWidget = children[0];
  108. selected.selected = true;
  109. }
  110. // Publish information about myself so any StackControllers can initialize.
  111. // This needs to happen before this.inherited(arguments) so that for
  112. // TabContainer, this._contentBox doesn't include the space for the tab labels.
  113. topic.publish(this.id+"-startup", {children: children, selected: selected});
  114. // Startup each child widget, and do initial layout like setting this._contentBox,
  115. // then calls this.resize() which does the initial sizing on the selected child.
  116. this.inherited(arguments);
  117. },
  118. resize: function(){
  119. // Resize is called when we are first made visible (it's called from startup()
  120. // if we are initially visible). If this is the first time we've been made
  121. // visible then show our first child.
  122. if(!this._hasBeenShown){
  123. this._hasBeenShown = true;
  124. var selected = this.selectedChildWidget;
  125. if(selected){
  126. this._showChild(selected);
  127. }
  128. }
  129. this.inherited(arguments);
  130. },
  131. _setupChild: function(/*dijit._Widget*/ child){
  132. // Overrides _LayoutWidget._setupChild()
  133. this.inherited(arguments);
  134. domClass.replace(child.domNode, "dijitHidden", "dijitVisible");
  135. // remove the title attribute so it doesn't show up when i hover
  136. // over a node
  137. child.domNode.title = "";
  138. },
  139. addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){
  140. // Overrides _Container.addChild() to do layout and publish events
  141. this.inherited(arguments);
  142. if(this._started){
  143. topic.publish(this.id+"-addChild", child, insertIndex); // publish
  144. // in case the tab titles have overflowed from one line to two lines
  145. // (or, if this if first child, from zero lines to one line)
  146. // TODO: w/ScrollingTabController this is no longer necessary, although
  147. // ScrollTabController.resize() does need to get called to show/hide
  148. // the navigation buttons as appropriate, but that's handled in ScrollingTabController.onAddChild().
  149. // If this is updated to not layout [except for initial child added / last child removed], update
  150. // "childless startup" test in StackContainer.html to check for no resize event after second addChild()
  151. this.layout();
  152. // if this is the first child, then select it
  153. if(!this.selectedChildWidget){
  154. this.selectChild(child);
  155. }
  156. }
  157. },
  158. removeChild: function(/*dijit._Widget*/ page){
  159. // Overrides _Container.removeChild() to do layout and publish events
  160. this.inherited(arguments);
  161. if(this._started){
  162. // this will notify any tablists to remove a button; do this first because it may affect sizing
  163. topic.publish(this.id + "-removeChild", page); // publish
  164. }
  165. // If all our children are being destroyed than don't run the code below (to select another page),
  166. // because we are deleting every page one by one
  167. if(this._descendantsBeingDestroyed){ return; }
  168. // Select new page to display, also updating TabController to show the respective tab.
  169. // Do this before layout call because it can affect the height of the TabController.
  170. if(this.selectedChildWidget === page){
  171. this.selectedChildWidget = undefined;
  172. if(this._started){
  173. var children = this.getChildren();
  174. if(children.length){
  175. this.selectChild(children[0]);
  176. }
  177. }
  178. }
  179. if(this._started){
  180. // In case the tab titles now take up one line instead of two lines
  181. // (note though that ScrollingTabController never overflows to multiple lines),
  182. // or the height has changed slightly because of addition/removal of tab which close icon
  183. this.layout();
  184. }
  185. },
  186. selectChild: function(/*dijit._Widget|String*/ page, /*Boolean*/ animate){
  187. // summary:
  188. // Show the given widget (which must be one of my children)
  189. // page:
  190. // Reference to child widget or id of child widget
  191. page = registry.byId(page);
  192. if(this.selectedChildWidget != page){
  193. // Deselect old page and select new one
  194. var d = this._transition(page, this.selectedChildWidget, animate);
  195. this._set("selectedChildWidget", page);
  196. topic.publish(this.id+"-selectChild", page); // publish
  197. if(this.persist){
  198. cookie(this.id + "_selectedChild", this.selectedChildWidget.id);
  199. }
  200. }
  201. return d; // If child has an href, promise that fires when the child's href finishes loading
  202. },
  203. _transition: function(newWidget, oldWidget /*===== , animate =====*/){
  204. // summary:
  205. // Hide the old widget and display the new widget.
  206. // Subclasses should override this.
  207. // newWidget: dijit._Widget
  208. // The newly selected widget.
  209. // oldWidget: dijit._Widget
  210. // The previously selected widget.
  211. // animate: Boolean
  212. // Used by AccordionContainer to turn on/off slide effect.
  213. // tags:
  214. // protected extension
  215. if(oldWidget){
  216. this._hideChild(oldWidget);
  217. }
  218. var d = this._showChild(newWidget);
  219. // Size the new widget, in case this is the first time it's being shown,
  220. // or I have been resized since the last time it was shown.
  221. // Note that page must be visible for resizing to work.
  222. if(newWidget.resize){
  223. if(this.doLayout){
  224. newWidget.resize(this._containerContentBox || this._contentBox);
  225. }else{
  226. // the child should pick it's own size but we still need to call resize()
  227. // (with no arguments) to let the widget lay itself out
  228. newWidget.resize();
  229. }
  230. }
  231. return d; // If child has an href, promise that fires when the child's href finishes loading
  232. },
  233. _adjacent: function(/*Boolean*/ forward){
  234. // summary:
  235. // Gets the next/previous child widget in this container from the current selection.
  236. var children = this.getChildren();
  237. var index = array.indexOf(children, this.selectedChildWidget);
  238. index += forward ? 1 : children.length - 1;
  239. return children[ index % children.length ]; // dijit._Widget
  240. },
  241. forward: function(){
  242. // summary:
  243. // Advance to next page.
  244. return this.selectChild(this._adjacent(true), true);
  245. },
  246. back: function(){
  247. // summary:
  248. // Go back to previous page.
  249. return this.selectChild(this._adjacent(false), true);
  250. },
  251. _onKeyPress: function(e){
  252. topic.publish(this.id+"-containerKeyPress", { e: e, page: this}); // publish
  253. },
  254. layout: function(){
  255. // Implement _LayoutWidget.layout() virtual method.
  256. var child = this.selectedChildWidget;
  257. if(child && child.resize){
  258. if(this.doLayout){
  259. child.resize(this._containerContentBox || this._contentBox);
  260. }else{
  261. child.resize();
  262. }
  263. }
  264. },
  265. _showChild: function(/*dijit._Widget*/ page){
  266. // summary:
  267. // Show the specified child by changing it's CSS, and call _onShow()/onShow() so
  268. // it can do any updates it needs regarding loading href's etc.
  269. // returns:
  270. // Promise that fires when page has finished showing, or true if there's no href
  271. var children = this.getChildren();
  272. page.isFirstChild = (page == children[0]);
  273. page.isLastChild = (page == children[children.length-1]);
  274. page._set("selected", true);
  275. domClass.replace(page.domNode, "dijitVisible", "dijitHidden");
  276. return (page._onShow && page._onShow()) || true;
  277. },
  278. _hideChild: function(/*dijit._Widget*/ page){
  279. // summary:
  280. // Hide the specified child by changing it's CSS, and call _onHide() so
  281. // it's notified.
  282. page._set("selected", false);
  283. domClass.replace(page.domNode, "dijitHidden", "dijitVisible");
  284. page.onHide && page.onHide();
  285. },
  286. closeChild: function(/*dijit._Widget*/ page){
  287. // summary:
  288. // Callback when user clicks the [X] to remove a page.
  289. // If onClose() returns true then remove and destroy the child.
  290. // tags:
  291. // private
  292. var remove = page.onClose(this, page);
  293. if(remove){
  294. this.removeChild(page);
  295. // makes sure we can clean up executeScripts in ContentPane onUnLoad
  296. page.destroyRecursive();
  297. }
  298. },
  299. destroyDescendants: function(/*Boolean*/ preserveDom){
  300. this._descendantsBeingDestroyed = true;
  301. this.selectedChildWidget = undefined;
  302. array.forEach(this.getChildren(), function(child){
  303. if(!preserveDom){
  304. this.removeChild(child);
  305. }
  306. child.destroyRecursive(preserveDom);
  307. }, this);
  308. this._descendantsBeingDestroyed = false;
  309. }
  310. });
  311. });