3d57b8b397067e4afeafcb8e7176f915f9aa8777.svn-base 16 KB

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