StackController.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  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.StackController"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dijit.layout.StackController"] = true;
  8. dojo.provide("dijit.layout.StackController");
  9. dojo.require("dijit._Widget");
  10. dojo.require("dijit._Templated");
  11. dojo.require("dijit._Container");
  12. dojo.require("dijit.form.ToggleButton");
  13. dojo.requireLocalization("dijit", "common", 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.StackController",
  16. [dijit._Widget, dijit._Templated, dijit._Container],
  17. {
  18. // summary:
  19. // Set of buttons to select a page in a page list.
  20. // description:
  21. // Monitors the specified StackContainer, and whenever a page is
  22. // added, deleted, or selected, updates itself accordingly.
  23. templateString: "<span role='tablist' dojoAttachEvent='onkeypress' class='dijitStackController'></span>",
  24. // containerId: [const] String
  25. // The id of the page container that I point to
  26. containerId: "",
  27. // buttonWidget: [const] String
  28. // The name of the button widget to create to correspond to each page
  29. buttonWidget: "dijit.layout._StackButton",
  30. constructor: function(){
  31. this.pane2button = {}; // mapping from pane id to buttons
  32. this.pane2connects = {}; // mapping from pane id to this.connect() handles
  33. this.pane2watches = {}; // mapping from pane id to watch() handles
  34. },
  35. buildRendering: function(){
  36. this.inherited(arguments);
  37. dijit.setWaiRole(this.domNode, "tablist"); // TODO: unneeded? it's in template above.
  38. },
  39. postCreate: function(){
  40. this.inherited(arguments);
  41. // Listen to notifications from StackContainer
  42. this.subscribe(this.containerId+"-startup", "onStartup");
  43. this.subscribe(this.containerId+"-addChild", "onAddChild");
  44. this.subscribe(this.containerId+"-removeChild", "onRemoveChild");
  45. this.subscribe(this.containerId+"-selectChild", "onSelectChild");
  46. this.subscribe(this.containerId+"-containerKeyPress", "onContainerKeyPress");
  47. },
  48. onStartup: function(/*Object*/ info){
  49. // summary:
  50. // Called after StackContainer has finished initializing
  51. // tags:
  52. // private
  53. dojo.forEach(info.children, this.onAddChild, this);
  54. if(info.selected){
  55. // Show button corresponding to selected pane (unless selected
  56. // is null because there are no panes)
  57. this.onSelectChild(info.selected);
  58. }
  59. },
  60. destroy: function(){
  61. for(var pane in this.pane2button){
  62. this.onRemoveChild(dijit.byId(pane));
  63. }
  64. this.inherited(arguments);
  65. },
  66. onAddChild: function(/*dijit._Widget*/ page, /*Integer?*/ insertIndex){
  67. // summary:
  68. // Called whenever a page is added to the container.
  69. // Create button corresponding to the page.
  70. // tags:
  71. // private
  72. // create an instance of the button widget
  73. var cls = dojo.getObject(this.buttonWidget);
  74. var button = new cls({
  75. id: this.id + "_" + page.id,
  76. label: page.title,
  77. dir: page.dir,
  78. lang: page.lang,
  79. showLabel: page.showTitle,
  80. iconClass: page.iconClass,
  81. closeButton: page.closable,
  82. title: page.tooltip
  83. });
  84. dijit.setWaiState(button.focusNode,"selected", "false");
  85. // map from page attribute to corresponding tab button attribute
  86. var pageAttrList = ["title", "showTitle", "iconClass", "closable", "tooltip"],
  87. buttonAttrList = ["label", "showLabel", "iconClass", "closeButton", "title"];
  88. // watch() so events like page title changes are reflected in tab button
  89. this.pane2watches[page.id] = dojo.map(pageAttrList, function(pageAttr, idx){
  90. return page.watch(pageAttr, function(name, oldVal, newVal){
  91. button.set(buttonAttrList[idx], newVal);
  92. });
  93. });
  94. // connections so that clicking a tab button selects the corresponding page
  95. this.pane2connects[page.id] = [
  96. this.connect(button, 'onClick', dojo.hitch(this,"onButtonClick", page)),
  97. this.connect(button, 'onClickCloseButton', dojo.hitch(this,"onCloseButtonClick", page))
  98. ];
  99. this.addChild(button, insertIndex);
  100. this.pane2button[page.id] = button;
  101. page.controlButton = button; // this value might be overwritten if two tabs point to same container
  102. if(!this._currentChild){ // put the first child into the tab order
  103. button.focusNode.setAttribute("tabIndex", "0");
  104. dijit.setWaiState(button.focusNode, "selected", "true");
  105. this._currentChild = page;
  106. }
  107. // make sure all tabs have the same length
  108. if(!this.isLeftToRight() && dojo.isIE && this._rectifyRtlTabList){
  109. this._rectifyRtlTabList();
  110. }
  111. },
  112. onRemoveChild: function(/*dijit._Widget*/ page){
  113. // summary:
  114. // Called whenever a page is removed from the container.
  115. // Remove the button corresponding to the page.
  116. // tags:
  117. // private
  118. if(this._currentChild === page){ this._currentChild = null; }
  119. // disconnect/unwatch connections/watches related to page being removed
  120. dojo.forEach(this.pane2connects[page.id], dojo.hitch(this, "disconnect"));
  121. delete this.pane2connects[page.id];
  122. dojo.forEach(this.pane2watches[page.id], function(w){ w.unwatch(); });
  123. delete this.pane2watches[page.id];
  124. var button = this.pane2button[page.id];
  125. if(button){
  126. this.removeChild(button);
  127. delete this.pane2button[page.id];
  128. button.destroy();
  129. }
  130. delete page.controlButton;
  131. },
  132. onSelectChild: function(/*dijit._Widget*/ page){
  133. // summary:
  134. // Called when a page has been selected in the StackContainer, either by me or by another StackController
  135. // tags:
  136. // private
  137. if(!page){ return; }
  138. if(this._currentChild){
  139. var oldButton=this.pane2button[this._currentChild.id];
  140. oldButton.set('checked', false);
  141. dijit.setWaiState(oldButton.focusNode, "selected", "false");
  142. oldButton.focusNode.setAttribute("tabIndex", "-1");
  143. }
  144. var newButton=this.pane2button[page.id];
  145. newButton.set('checked', true);
  146. dijit.setWaiState(newButton.focusNode, "selected", "true");
  147. this._currentChild = page;
  148. newButton.focusNode.setAttribute("tabIndex", "0");
  149. var container = dijit.byId(this.containerId);
  150. dijit.setWaiState(container.containerNode, "labelledby", newButton.id);
  151. },
  152. onButtonClick: function(/*dijit._Widget*/ page){
  153. // summary:
  154. // Called whenever one of my child buttons is pressed in an attempt to select a page
  155. // tags:
  156. // private
  157. var container = dijit.byId(this.containerId);
  158. container.selectChild(page);
  159. },
  160. onCloseButtonClick: function(/*dijit._Widget*/ page){
  161. // summary:
  162. // Called whenever one of my child buttons [X] is pressed in an attempt to close a page
  163. // tags:
  164. // private
  165. var container = dijit.byId(this.containerId);
  166. container.closeChild(page);
  167. if(this._currentChild){
  168. var b = this.pane2button[this._currentChild.id];
  169. if(b){
  170. dijit.focus(b.focusNode || b.domNode);
  171. }
  172. }
  173. },
  174. // TODO: this is a bit redundant with forward, back api in StackContainer
  175. adjacent: function(/*Boolean*/ forward){
  176. // summary:
  177. // Helper for onkeypress to find next/previous button
  178. // tags:
  179. // private
  180. if(!this.isLeftToRight() && (!this.tabPosition || /top|bottom/.test(this.tabPosition))){ forward = !forward; }
  181. // find currently focused button in children array
  182. var children = this.getChildren();
  183. var current = dojo.indexOf(children, this.pane2button[this._currentChild.id]);
  184. // pick next button to focus on
  185. var offset = forward ? 1 : children.length - 1;
  186. return children[ (current + offset) % children.length ]; // dijit._Widget
  187. },
  188. onkeypress: function(/*Event*/ e){
  189. // summary:
  190. // Handle keystrokes on the page list, for advancing to next/previous button
  191. // and closing the current page if the page is closable.
  192. // tags:
  193. // private
  194. if(this.disabled || e.altKey ){ return; }
  195. var forward = null;
  196. if(e.ctrlKey || !e._djpage){
  197. var k = dojo.keys;
  198. switch(e.charOrCode){
  199. case k.LEFT_ARROW:
  200. case k.UP_ARROW:
  201. if(!e._djpage){ forward = false; }
  202. break;
  203. case k.PAGE_UP:
  204. if(e.ctrlKey){ forward = false; }
  205. break;
  206. case k.RIGHT_ARROW:
  207. case k.DOWN_ARROW:
  208. if(!e._djpage){ forward = true; }
  209. break;
  210. case k.PAGE_DOWN:
  211. if(e.ctrlKey){ forward = true; }
  212. break;
  213. case k.HOME:
  214. case k.END:
  215. var children = this.getChildren();
  216. if(children && children.length){
  217. children[e.charOrCode == k.HOME ? 0 : children.length-1].onClick();
  218. }
  219. dojo.stopEvent(e);
  220. break;
  221. case k.DELETE:
  222. if(this._currentChild.closable){
  223. this.onCloseButtonClick(this._currentChild);
  224. }
  225. dojo.stopEvent(e);
  226. break;
  227. default:
  228. if(e.ctrlKey){
  229. if(e.charOrCode === k.TAB){
  230. this.adjacent(!e.shiftKey).onClick();
  231. dojo.stopEvent(e);
  232. }else if(e.charOrCode == "w"){
  233. if(this._currentChild.closable){
  234. this.onCloseButtonClick(this._currentChild);
  235. }
  236. dojo.stopEvent(e); // avoid browser tab closing.
  237. }
  238. }
  239. }
  240. // handle next/previous page navigation (left/right arrow, etc.)
  241. if(forward !== null){
  242. this.adjacent(forward).onClick();
  243. dojo.stopEvent(e);
  244. }
  245. }
  246. },
  247. onContainerKeyPress: function(/*Object*/ info){
  248. // summary:
  249. // Called when there was a keypress on the container
  250. // tags:
  251. // private
  252. info.e._djpage = info.page;
  253. this.onkeypress(info.e);
  254. }
  255. });
  256. dojo.declare("dijit.layout._StackButton",
  257. dijit.form.ToggleButton,
  258. {
  259. // summary:
  260. // Internal widget used by StackContainer.
  261. // description:
  262. // The button-like or tab-like object you click to select or delete a page
  263. // tags:
  264. // private
  265. // Override _FormWidget.tabIndex.
  266. // StackContainer buttons are not in the tab order by default.
  267. // Probably we should be calling this.startupKeyNavChildren() instead.
  268. tabIndex: "-1",
  269. buildRendering: function(/*Event*/ evt){
  270. this.inherited(arguments);
  271. dijit.setWaiRole((this.focusNode || this.domNode), "tab");
  272. },
  273. onClick: function(/*Event*/ evt){
  274. // summary:
  275. // This is for TabContainer where the tabs are <span> rather than button,
  276. // so need to set focus explicitly (on some browsers)
  277. // Note that you shouldn't override this method, but you can connect to it.
  278. dijit.focus(this.focusNode);
  279. // ... now let StackController catch the event and tell me what to do
  280. },
  281. onClickCloseButton: function(/*Event*/ evt){
  282. // summary:
  283. // StackContainer connects to this function; if your widget contains a close button
  284. // then clicking it should call this function.
  285. // Note that you shouldn't override this method, but you can connect to it.
  286. evt.stopPropagation();
  287. }
  288. });
  289. }