SplitContainer.js 16 KB

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