SplitContainer.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589
  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.SplitContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dijit.layout.SplitContainer"] = true;
  8. dojo.provide("dijit.layout.SplitContainer");
  9. dojo.require("dojo.cookie");
  10. dojo.require("dijit.layout._LayoutWidget");
  11. //
  12. // FIXME: make it prettier
  13. // FIXME: active dragging upwards doesn't always shift other bars (direction calculation is wrong in this case)
  14. //
  15. dojo.declare("dijit.layout.SplitContainer",
  16. dijit.layout._LayoutWidget,
  17. {
  18. // summary:
  19. // Deprecated. Use `dijit.layout.BorderContainer` instead.
  20. // description:
  21. // A Container widget with sizing handles in-between each child.
  22. // Contains multiple children widgets, all of which are displayed side by side
  23. // (either horizontally or vertically); there's a bar between each of the children,
  24. // and you can adjust the relative size of each child by dragging the bars.
  25. //
  26. // You must specify a size (width and height) for the SplitContainer.
  27. // tags:
  28. // deprecated
  29. constructor: function(){
  30. dojo.deprecated("dijit.layout.SplitContainer is deprecated", "use BorderContainer with splitter instead", 2.0);
  31. },
  32. // activeSizing: Boolean
  33. // If true, the children's size changes as you drag the bar;
  34. // otherwise, the sizes don't change until you drop the bar (by mouse-up)
  35. activeSizing: false,
  36. // sizerWidth: Integer
  37. // Size in pixels of the bar between each child
  38. sizerWidth: 7, // FIXME: this should be a CSS attribute (at 7 because css wants it to be 7 until we fix to css)
  39. // orientation: String
  40. // either 'horizontal' or vertical; indicates whether the children are
  41. // arranged side-by-side or up/down.
  42. orientation: 'horizontal',
  43. // persist: Boolean
  44. // Save splitter positions in a cookie
  45. persist: true,
  46. baseClass: "dijitSplitContainer",
  47. postMixInProperties: function(){
  48. this.inherited("postMixInProperties",arguments);
  49. this.isHorizontal = (this.orientation == 'horizontal');
  50. },
  51. postCreate: function(){
  52. this.inherited(arguments);
  53. this.sizers = [];
  54. // overflow has to be explicitly hidden for splitContainers using gekko (trac #1435)
  55. // to keep other combined css classes from inadvertantly making the overflow visible
  56. if(dojo.isMozilla){
  57. this.domNode.style.overflow = '-moz-scrollbars-none'; // hidden doesn't work
  58. }
  59. // create the fake dragger
  60. if(typeof this.sizerWidth == "object"){
  61. try{ //FIXME: do this without a try/catch
  62. this.sizerWidth = parseInt(this.sizerWidth.toString());
  63. }catch(e){ this.sizerWidth = 7; }
  64. }
  65. var sizer = dojo.doc.createElement('div');
  66. this.virtualSizer = sizer;
  67. sizer.style.position = 'relative';
  68. // #1681: work around the dreaded 'quirky percentages in IE' layout bug
  69. // If the splitcontainer's dimensions are specified in percentages, it
  70. // will be resized when the virtualsizer is displayed in _showSizingLine
  71. // (typically expanding its bounds unnecessarily). This happens because
  72. // we use position: relative for .dijitSplitContainer.
  73. // The workaround: instead of changing the display style attribute,
  74. // switch to changing the zIndex (bring to front/move to back)
  75. sizer.style.zIndex = 10;
  76. sizer.className = this.isHorizontal ? 'dijitSplitContainerVirtualSizerH' : 'dijitSplitContainerVirtualSizerV';
  77. this.domNode.appendChild(sizer);
  78. dojo.setSelectable(sizer, false);
  79. },
  80. destroy: function(){
  81. delete this.virtualSizer;
  82. dojo.forEach(this._ownconnects, dojo.disconnect);
  83. this.inherited(arguments);
  84. },
  85. startup: function(){
  86. if(this._started){ return; }
  87. dojo.forEach(this.getChildren(), function(child, i, children){
  88. // attach the children and create the draggers
  89. this._setupChild(child);
  90. if(i < children.length-1){
  91. this._addSizer();
  92. }
  93. }, this);
  94. if(this.persist){
  95. this._restoreState();
  96. }
  97. this.inherited(arguments);
  98. },
  99. _setupChild: function(/*dijit._Widget*/ child){
  100. this.inherited(arguments);
  101. child.domNode.style.position = "absolute";
  102. dojo.addClass(child.domNode, "dijitSplitPane");
  103. },
  104. _onSizerMouseDown: function(e){
  105. if(e.target.id){
  106. for(var i=0;i<this.sizers.length;i++){
  107. if(this.sizers[i].id == e.target.id){
  108. break;
  109. }
  110. }
  111. if(i<this.sizers.length){
  112. this.beginSizing(e,i);
  113. }
  114. }
  115. },
  116. _addSizer: function(index){
  117. index = index === undefined ? this.sizers.length : index;
  118. // TODO: use a template for this!!!
  119. var sizer = dojo.doc.createElement('div');
  120. sizer.id=dijit.getUniqueId('dijit_layout_SplitterContainer_Splitter');
  121. this.sizers.splice(index,0,sizer);
  122. this.domNode.appendChild(sizer);
  123. sizer.className = this.isHorizontal ? 'dijitSplitContainerSizerH' : 'dijitSplitContainerSizerV';
  124. // add the thumb div
  125. var thumb = dojo.doc.createElement('div');
  126. thumb.className = 'thumb';
  127. sizer.appendChild(thumb);
  128. // FIXME: are you serious? why aren't we using mover start/stop combo?
  129. this.connect(sizer, "onmousedown", '_onSizerMouseDown');
  130. dojo.setSelectable(sizer, false);
  131. },
  132. removeChild: function(widget){
  133. // summary:
  134. // Remove sizer, but only if widget is really our child and
  135. // we have at least one sizer to throw away
  136. if(this.sizers.length){
  137. var i=dojo.indexOf(this.getChildren(), widget)
  138. if(i != -1){
  139. if(i == this.sizers.length){
  140. i--;
  141. }
  142. dojo.destroy(this.sizers[i]);
  143. this.sizers.splice(i,1);
  144. }
  145. }
  146. // Remove widget and repaint
  147. this.inherited(arguments);
  148. if(this._started){
  149. this.layout();
  150. }
  151. },
  152. addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){
  153. // summary:
  154. // Add a child widget to the container
  155. // child:
  156. // a widget to add
  157. // insertIndex:
  158. // postion in the "stack" to add the child widget
  159. this.inherited(arguments);
  160. if(this._started){
  161. // Do the stuff that startup() does for each widget
  162. var children = this.getChildren();
  163. if(children.length > 1){
  164. this._addSizer(insertIndex);
  165. }
  166. // and then reposition (ie, shrink) every pane to make room for the new guy
  167. this.layout();
  168. }
  169. },
  170. layout: function(){
  171. // summary:
  172. // Do layout of panels
  173. // base class defines this._contentBox on initial creation and also
  174. // on resize
  175. this.paneWidth = this._contentBox.w;
  176. this.paneHeight = this._contentBox.h;
  177. var children = this.getChildren();
  178. if(!children.length){ return; }
  179. //
  180. // calculate space
  181. //
  182. var space = this.isHorizontal ? this.paneWidth : this.paneHeight;
  183. if(children.length > 1){
  184. space -= this.sizerWidth * (children.length - 1);
  185. }
  186. //
  187. // calculate total of SizeShare values
  188. //
  189. var outOf = 0;
  190. dojo.forEach(children, function(child){
  191. outOf += child.sizeShare;
  192. });
  193. //
  194. // work out actual pixels per sizeshare unit
  195. //
  196. var pixPerUnit = space / outOf;
  197. //
  198. // set the SizeActual member of each pane
  199. //
  200. var totalSize = 0;
  201. dojo.forEach(children.slice(0, children.length - 1), function(child){
  202. var size = Math.round(pixPerUnit * child.sizeShare);
  203. child.sizeActual = size;
  204. totalSize += size;
  205. });
  206. children[children.length-1].sizeActual = space - totalSize;
  207. //
  208. // make sure the sizes are ok
  209. //
  210. this._checkSizes();
  211. //
  212. // now loop, positioning each pane and letting children resize themselves
  213. //
  214. var pos = 0;
  215. var size = children[0].sizeActual;
  216. this._movePanel(children[0], pos, size);
  217. children[0].position = pos;
  218. pos += size;
  219. // if we don't have any sizers, our layout method hasn't been called yet
  220. // so bail until we are called..TODO: REVISIT: need to change the startup
  221. // algorithm to guaranteed the ordering of calls to layout method
  222. if(!this.sizers){
  223. return;
  224. }
  225. dojo.some(children.slice(1), function(child, i){
  226. // error-checking
  227. if(!this.sizers[i]){
  228. return true;
  229. }
  230. // first we position the sizing handle before this pane
  231. this._moveSlider(this.sizers[i], pos, this.sizerWidth);
  232. this.sizers[i].position = pos;
  233. pos += this.sizerWidth;
  234. size = child.sizeActual;
  235. this._movePanel(child, pos, size);
  236. child.position = pos;
  237. pos += size;
  238. }, this);
  239. },
  240. _movePanel: function(panel, pos, size){
  241. if(this.isHorizontal){
  242. panel.domNode.style.left = pos + 'px'; // TODO: resize() takes l and t parameters too, don't need to set manually
  243. panel.domNode.style.top = 0;
  244. var box = {w: size, h: this.paneHeight};
  245. if(panel.resize){
  246. panel.resize(box);
  247. }else{
  248. dojo.marginBox(panel.domNode, box);
  249. }
  250. }else{
  251. panel.domNode.style.left = 0; // TODO: resize() takes l and t parameters too, don't need to set manually
  252. panel.domNode.style.top = pos + 'px';
  253. var box = {w: this.paneWidth, h: size};
  254. if(panel.resize){
  255. panel.resize(box);
  256. }else{
  257. dojo.marginBox(panel.domNode, box);
  258. }
  259. }
  260. },
  261. _moveSlider: function(slider, pos, size){
  262. if(this.isHorizontal){
  263. slider.style.left = pos + 'px';
  264. slider.style.top = 0;
  265. dojo.marginBox(slider, { w: size, h: this.paneHeight });
  266. }else{
  267. slider.style.left = 0;
  268. slider.style.top = pos + 'px';
  269. dojo.marginBox(slider, { w: this.paneWidth, h: size });
  270. }
  271. },
  272. _growPane: function(growth, pane){
  273. if(growth > 0){
  274. if(pane.sizeActual > pane.sizeMin){
  275. if((pane.sizeActual - pane.sizeMin) > growth){
  276. // stick all the growth in this pane
  277. pane.sizeActual = pane.sizeActual - growth;
  278. growth = 0;
  279. }else{
  280. // put as much growth in here as we can
  281. growth -= pane.sizeActual - pane.sizeMin;
  282. pane.sizeActual = pane.sizeMin;
  283. }
  284. }
  285. }
  286. return growth;
  287. },
  288. _checkSizes: function(){
  289. var totalMinSize = 0;
  290. var totalSize = 0;
  291. var children = this.getChildren();
  292. dojo.forEach(children, function(child){
  293. totalSize += child.sizeActual;
  294. totalMinSize += child.sizeMin;
  295. });
  296. // only make adjustments if we have enough space for all the minimums
  297. if(totalMinSize <= totalSize){
  298. var growth = 0;
  299. dojo.forEach(children, function(child){
  300. if(child.sizeActual < child.sizeMin){
  301. growth += child.sizeMin - child.sizeActual;
  302. child.sizeActual = child.sizeMin;
  303. }
  304. });
  305. if(growth > 0){
  306. var list = this.isDraggingLeft ? children.reverse() : children;
  307. dojo.forEach(list, function(child){
  308. growth = this._growPane(growth, child);
  309. }, this);
  310. }
  311. }else{
  312. dojo.forEach(children, function(child){
  313. child.sizeActual = Math.round(totalSize * (child.sizeMin / totalMinSize));
  314. });
  315. }
  316. },
  317. beginSizing: function(e, i){
  318. var children = this.getChildren();
  319. this.paneBefore = children[i];
  320. this.paneAfter = children[i+1];
  321. this.isSizing = true;
  322. this.sizingSplitter = this.sizers[i];
  323. if(!this.cover){
  324. this.cover = dojo.create('div', {
  325. style: {
  326. position:'absolute',
  327. zIndex:5,
  328. top: 0,
  329. left: 0,
  330. width: "100%",
  331. height: "100%"
  332. }
  333. }, this.domNode);
  334. }else{
  335. this.cover.style.zIndex = 5;
  336. }
  337. this.sizingSplitter.style.zIndex = 6;
  338. // TODO: REVISIT - we want MARGIN_BOX and core hasn't exposed that yet (but can't we use it anyway if we pay attention? we do elsewhere.)
  339. this.originPos = dojo.position(children[0].domNode, true);
  340. if(this.isHorizontal){
  341. var client = e.layerX || e.offsetX || 0;
  342. var screen = e.pageX;
  343. this.originPos = this.originPos.x;
  344. }else{
  345. var client = e.layerY || e.offsetY || 0;
  346. var screen = e.pageY;
  347. this.originPos = this.originPos.y;
  348. }
  349. this.startPoint = this.lastPoint = screen;
  350. this.screenToClientOffset = screen - client;
  351. this.dragOffset = this.lastPoint - this.paneBefore.sizeActual - this.originPos - this.paneBefore.position;
  352. if(!this.activeSizing){
  353. this._showSizingLine();
  354. }
  355. //
  356. // attach mouse events
  357. //
  358. this._ownconnects = [];
  359. this._ownconnects.push(dojo.connect(dojo.doc.documentElement, "onmousemove", this, "changeSizing"));
  360. this._ownconnects.push(dojo.connect(dojo.doc.documentElement, "onmouseup", this, "endSizing"));
  361. dojo.stopEvent(e);
  362. },
  363. changeSizing: function(e){
  364. if(!this.isSizing){ return; }
  365. this.lastPoint = this.isHorizontal ? e.pageX : e.pageY;
  366. this.movePoint();
  367. if(this.activeSizing){
  368. this._updateSize();
  369. }else{
  370. this._moveSizingLine();
  371. }
  372. dojo.stopEvent(e);
  373. },
  374. endSizing: function(e){
  375. if(!this.isSizing){ return; }
  376. if(this.cover){
  377. this.cover.style.zIndex = -1;
  378. }
  379. if(!this.activeSizing){
  380. this._hideSizingLine();
  381. }
  382. this._updateSize();
  383. this.isSizing = false;
  384. if(this.persist){
  385. this._saveState(this);
  386. }
  387. dojo.forEach(this._ownconnects, dojo.disconnect);
  388. },
  389. movePoint: function(){
  390. // make sure lastPoint is a legal point to drag to
  391. var p = this.lastPoint - this.screenToClientOffset;
  392. var a = p - this.dragOffset;
  393. a = this.legaliseSplitPoint(a);
  394. p = a + this.dragOffset;
  395. this.lastPoint = p + this.screenToClientOffset;
  396. },
  397. legaliseSplitPoint: function(a){
  398. a += this.sizingSplitter.position;
  399. this.isDraggingLeft = !!(a > 0);
  400. if(!this.activeSizing){
  401. var min = this.paneBefore.position + this.paneBefore.sizeMin;
  402. if(a < min){
  403. a = min;
  404. }
  405. var max = this.paneAfter.position + (this.paneAfter.sizeActual - (this.sizerWidth + this.paneAfter.sizeMin));
  406. if(a > max){
  407. a = max;
  408. }
  409. }
  410. a -= this.sizingSplitter.position;
  411. this._checkSizes();
  412. return a;
  413. },
  414. _updateSize: function(){
  415. //FIXME: sometimes this.lastPoint is NaN
  416. var pos = this.lastPoint - this.dragOffset - this.originPos;
  417. var start_region = this.paneBefore.position;
  418. var end_region = this.paneAfter.position + this.paneAfter.sizeActual;
  419. this.paneBefore.sizeActual = pos - start_region;
  420. this.paneAfter.position = pos + this.sizerWidth;
  421. this.paneAfter.sizeActual = end_region - this.paneAfter.position;
  422. dojo.forEach(this.getChildren(), function(child){
  423. child.sizeShare = child.sizeActual;
  424. });
  425. if(this._started){
  426. this.layout();
  427. }
  428. },
  429. _showSizingLine: function(){
  430. this._moveSizingLine();
  431. dojo.marginBox(this.virtualSizer,
  432. this.isHorizontal ? { w: this.sizerWidth, h: this.paneHeight } : { w: this.paneWidth, h: this.sizerWidth });
  433. this.virtualSizer.style.display = 'block';
  434. },
  435. _hideSizingLine: function(){
  436. this.virtualSizer.style.display = 'none';
  437. },
  438. _moveSizingLine: function(){
  439. var pos = (this.lastPoint - this.startPoint) + this.sizingSplitter.position;
  440. dojo.style(this.virtualSizer,(this.isHorizontal ? "left" : "top"),pos+"px");
  441. // this.virtualSizer.style[ this.isHorizontal ? "left" : "top" ] = pos + 'px'; // FIXME: remove this line if the previous is better
  442. },
  443. _getCookieName: function(i){
  444. return this.id + "_" + i;
  445. },
  446. _restoreState: function(){
  447. dojo.forEach(this.getChildren(), function(child, i){
  448. var cookieName = this._getCookieName(i);
  449. var cookieValue = dojo.cookie(cookieName);
  450. if(cookieValue){
  451. var pos = parseInt(cookieValue);
  452. if(typeof pos == "number"){
  453. child.sizeShare = pos;
  454. }
  455. }
  456. }, this);
  457. },
  458. _saveState: function(){
  459. if(!this.persist){
  460. return;
  461. }
  462. dojo.forEach(this.getChildren(), function(child, i){
  463. dojo.cookie(this._getCookieName(i), child.sizeShare, {expires:365});
  464. }, this);
  465. }
  466. });
  467. // These arguments can be specified for the children of a SplitContainer.
  468. // Since any widget can be specified as a SplitContainer child, mix them
  469. // into the base widget class. (This is a hack, but it's effective.)
  470. dojo.extend(dijit._Widget, {
  471. // sizeMin: [deprecated] Integer
  472. // Deprecated. Parameter for children of `dijit.layout.SplitContainer`.
  473. // Minimum size (width or height) of a child of a SplitContainer.
  474. // The value is relative to other children's sizeShare properties.
  475. sizeMin: 10,
  476. // sizeShare: [deprecated] Integer
  477. // Deprecated. Parameter for children of `dijit.layout.SplitContainer`.
  478. // Size (width or height) of a child of a SplitContainer.
  479. // The value is relative to other children's sizeShare properties.
  480. // For example, if there are two children and each has sizeShare=10, then
  481. // each takes up 50% of the available space.
  482. sizeShare: 10
  483. });
  484. }