ToggleSplitter.js 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. define("dojox/layout/ToggleSplitter", ["dojo", "dijit", "dijit/layout/BorderContainer"], function(dojo, dijit) {
  2. dojo.experimental("dojox.layout.ToggleSplitter");
  3. dojo.declare("dojox.layout.ToggleSplitter", dijit.layout._Splitter, {
  4. // summary:
  5. // A draggable and clickable spacer between two items in a dijit.layout.BorderContainer`.
  6. // description:
  7. // This is instantiated by `dijit.layout.BorderContainer. Users should not
  8. // create it directly.
  9. // tags:
  10. // private
  11. /*=====
  12. // container: [const] dijit.layout.BorderContainer
  13. // Pointer to the parent BorderContainer
  14. container: null,
  15. // child: [const] dijit.layout._LayoutWidget
  16. // Pointer to the pane associated with this splitter
  17. child: null,
  18. // region: [const] String
  19. // Region of pane associated with this splitter.
  20. // "top", "bottom", "left", "right".
  21. region: null,
  22. =====*/
  23. // state: String
  24. // the initial and current state of the splitter (and its attached pane)
  25. // It has three values: full, collapsed (optional), closed
  26. state: "full",
  27. // _closedSize: String
  28. // the css height/width value to apply by default when the attached pane is closed
  29. _closedSize: "0",
  30. baseClass: "dojoxToggleSplitter",
  31. templateString: '<div class="dijitSplitter dojoxToggleSplitter" dojoAttachEvent="onkeypress:_onKeyPress,onmousedown:_startDrag,onmouseenter:_onMouse,onmouseleave:_onMouse">' +
  32. '<div dojoAttachPoint="toggleNode" class="dijitSplitterThumb dojoxToggleSplitterIcon" tabIndex="0" role="separator" ' +
  33. 'dojoAttachEvent="onmousedown:_onToggleNodeMouseDown,onclick:_toggle,onmouseenter:_onToggleNodeMouseMove,onmouseleave:_onToggleNodeMouseMove,onfocus:_onToggleNodeMouseMove,onblur:_onToggleNodeMouseMove">' +
  34. '<span class="dojoxToggleSplitterA11y" dojoAttachPoint="a11yText"></span></div>' +
  35. '</div>',
  36. postCreate: function(){
  37. this.inherited(arguments);
  38. // add a region css hook so that it can figure out the region correctly
  39. var region = this.region;
  40. dojo.addClass(this.domNode, this.baseClass + region.charAt(0).toUpperCase() + region.substring(1));
  41. },
  42. startup: function(){
  43. this.inherited(arguments);
  44. // we have to wait until startup to be sure the child exists in the dom
  45. // and has non-zero size (if its supposed to be showing)
  46. var parentPane = this.child,
  47. paneNode = this.child.domNode,
  48. intPaneSize = dojo.style(paneNode, (this.horizontal ? "height" : "width"));
  49. this.domNode.setAttribute("aria-controls", paneNode.id);
  50. // creation of splitters is an opaque process in BorderContainer,
  51. // so if we want to get init params, we have to retrieve them from the attached BC child
  52. // NOTE: for this to work we have to extend the prototype of dijit._Widget (some more)
  53. dojo.forEach(["toggleSplitterState", "toggleSplitterFullSize", "toggleSplitterCollapsedSize"], function(name){
  54. var pname = name.substring("toggleSplitter".length);
  55. pname = pname.charAt(0).toLowerCase() + pname.substring(1);
  56. if(name in this.child){
  57. this[pname] = this.child[name];
  58. }
  59. }, this);
  60. if(!this.fullSize){
  61. // Store the current size as the fullSize if none was provided
  62. // dojo.style always returns a integer (pixel) value for height/width
  63. // use an arbitrary default if a pane was initialized closed and no fullSize provided
  64. // If collapsedSize is not specified, collapsed state does not exist.
  65. this.fullSize = this.state == "full" ? intPaneSize + "px" : "75px";
  66. }
  67. this._openStyleProps = this._getStyleProps(paneNode, "full");
  68. // update state
  69. this._started = true;
  70. this.set("state", this.state);
  71. return this;
  72. },
  73. _onKeyPress: function(evt){
  74. if(this.state == "full"){
  75. this.inherited(arguments);
  76. }
  77. if(evt.charCode == dojo.keys.SPACE || evt.keyCode == dojo.keys.ENTER){
  78. this._toggle(evt);
  79. }
  80. },
  81. _onToggleNodeMouseDown: function(evt){
  82. dojo.stopEvent(evt);
  83. this.toggleNode.focus();
  84. },
  85. _startDrag: function(e){
  86. if(this.state == "full"){
  87. this.inherited(arguments);
  88. }
  89. },
  90. _stopDrag: function(e){
  91. this.inherited(arguments);
  92. this.toggleNode.blur();
  93. },
  94. _toggle: function(evt){
  95. var state;
  96. switch(this.state){
  97. case "full":
  98. state = this.collapsedSize ? "collapsed" : "closed";
  99. break;
  100. case "collapsed":
  101. state = "closed";
  102. break;
  103. default:
  104. state = "full";
  105. }
  106. this.set("state", state);
  107. },
  108. _onToggleNodeMouseMove: function(evt){
  109. var baseClass = this.baseClass,
  110. toggleNode = this.toggleNode,
  111. on = this.state == "full" || this.state == "collapsed",
  112. leave = evt.type == "mouseout" || evt.type == "blur";
  113. dojo.toggleClass(toggleNode, baseClass + "IconOpen", leave && on);
  114. dojo.toggleClass(toggleNode, baseClass + "IconOpenHover", !leave && on);
  115. dojo.toggleClass(toggleNode, baseClass + "IconClosed", leave && !on);
  116. dojo.toggleClass(toggleNode, baseClass + "IconClosedHover", !leave && !on);
  117. },
  118. _handleOnChange: function(preState){
  119. // summary
  120. // Effect the state change with the new value of this.state
  121. var paneNode = this.child.domNode,
  122. openProps, paneStyle,
  123. dim = this.horizontal ? "height" : "width";
  124. if(this.state == "full"){
  125. // change to full open state
  126. var styleProps = dojo.mixin({
  127. display: "block",
  128. overflow: "auto",
  129. visibility: "visible"
  130. }, this._openStyleProps);
  131. styleProps[dim] = (this._openStyleProps && this._openStyleProps[dim]) ? this._openStyleProps[dim] : this.fullSize;
  132. dojo.style(this.domNode, "cursor", "");
  133. dojo.style(paneNode, styleProps);
  134. }else if(this.state == "collapsed"){
  135. paneStyle = dojo.getComputedStyle(paneNode);
  136. openProps = this._getStyleProps(paneNode, "full", paneStyle);
  137. this._openStyleProps = openProps;
  138. dojo.style(this.domNode, "cursor", "auto");
  139. dojo.style(paneNode, dim, this.collapsedSize);
  140. }else{
  141. // change to closed state
  142. if(!this.collapsedSize){
  143. paneStyle = dojo.getComputedStyle(paneNode);
  144. openProps = this._getStyleProps(paneNode, "full", paneStyle);
  145. this._openStyleProps = openProps;
  146. }
  147. var closedProps = this._getStyleProps(paneNode, "closed", paneStyle);
  148. dojo.style(this.domNode, "cursor", "auto");
  149. dojo.style(paneNode, closedProps);
  150. }
  151. this._setStateClass();
  152. if(this.container._started){
  153. this.container._layoutChildren(this.region);
  154. }
  155. },
  156. _getStyleProps: function(paneNode, state, paneStyle){
  157. // summary:
  158. // Create an object with the style property name: values
  159. // that will need to be applied to the child pane render the given state
  160. if(!paneStyle){
  161. paneStyle = dojo.getComputedStyle(paneNode);
  162. }
  163. var styleProps = {},
  164. dim = this.horizontal ? "height" : "width";
  165. styleProps["overflow"] = (state != "closed") ? paneStyle["overflow"] : "hidden";
  166. styleProps["visibility"] = (state != "closed") ? paneStyle["visibility"] : "hidden";
  167. // Use the inline width/height style value, in preference to the computedStyle
  168. // for the open width/height
  169. styleProps[dim] = (state != "closed") ? paneNode.style[dim] || paneStyle[dim] : this._closedSize;
  170. // We include the padding, border, margin width values for restoring on state full open
  171. var edgeNames = ["Top", "Right", "Bottom", "Left"];
  172. dojo.forEach(["padding", "margin", "border"], function(pname){
  173. for(var i = 0; i < edgeNames.length; i++){
  174. var fullName = pname + edgeNames[i];
  175. if(pname == "border"){
  176. fullName += "Width";
  177. }
  178. if(undefined !== paneStyle[fullName]){
  179. styleProps[fullName] = (state != "closed") ? paneStyle[fullName] : 0;
  180. }
  181. }
  182. });
  183. return styleProps;
  184. },
  185. _setStateClass: function(){
  186. // Summary:
  187. // Apply the appropriate classes for the current open state
  188. var arrow = "&#9652", region = this.region.toLowerCase(),
  189. baseClass = this.baseClass,
  190. toggleNode = this.toggleNode,
  191. on = this.state == "full" || this.state == "collapsed",
  192. focused = this.focused;
  193. dojo.toggleClass(toggleNode, baseClass + "IconOpen", on && !focused);
  194. dojo.toggleClass(toggleNode, baseClass + "IconClosed", !on && !focused);
  195. dojo.toggleClass(toggleNode, baseClass + "IconOpenHover", on && focused);
  196. dojo.toggleClass(toggleNode, baseClass + "IconClosedHover", !on && focused);
  197. // For a11y
  198. if(region == "top" && on || region == "bottom" && !on){
  199. arrow = "&#9650";
  200. }else if(region == "top" && !on || region == "bottom" && on){
  201. arrow = "&#9660";
  202. }else if(region == "right" && on || region == "left" && !on){
  203. arrow = "&#9654";
  204. }else if(region == "right" && !on || region == "left" && on){
  205. arrow = "&#9664";
  206. }
  207. this.a11yText.innerHTML = arrow;
  208. },
  209. _setStateAttr: function(/*Strring*/ state){
  210. // summary:
  211. // setter for the state property
  212. if(!this._started) {
  213. return;
  214. }
  215. var preState = this.state;
  216. this.state = state;
  217. this._handleOnChange(preState);
  218. var evtName;
  219. switch(state){
  220. case "full":
  221. this.domNode.setAttribute("aria-expanded", true);
  222. evtName = "onOpen";
  223. break;
  224. case "collapsed":
  225. this.domNode.setAttribute("aria-expanded", true);
  226. evtName = "onCollapsed";
  227. break;
  228. default:
  229. this.domNode.setAttribute("aria-expanded", false);
  230. evtName = "onClosed";
  231. }
  232. this[evtName](this.child);
  233. },
  234. onOpen: function(pane){ /*Stub*/ },
  235. onCollapsed: function(pane){ /*Stub*/ },
  236. onClosed: function(pane){ /*Stub*/ }
  237. });
  238. // As BC places no constraints on what kind of widgets can be children
  239. // we have to extend the base class to ensure the properties we need can be set (both in markup and programatically)
  240. dojo.extend(dijit._Widget, {
  241. // toggleSplitterOpen: Boolean
  242. toggleSplitterState: "full",
  243. // toggleSplitterClosedThreshold: String
  244. // A css size value (e.g. "100px")
  245. toggleSplitterFullSize: "",
  246. toggleSplitterCollapsedSize: ""
  247. });
  248. });