BorderContainer.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  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.BorderContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dijit.layout.BorderContainer"] = true;
  8. dojo.provide("dijit.layout.BorderContainer");
  9. dojo.require("dijit.layout._LayoutWidget");
  10. dojo.require("dojo.cookie");
  11. dojo.require("dijit._Templated");
  12. dojo.declare(
  13. "dijit.layout.BorderContainer",
  14. dijit.layout._LayoutWidget,
  15. {
  16. // summary:
  17. // Provides layout in up to 5 regions, a mandatory center with optional borders along its 4 sides.
  18. //
  19. // description:
  20. // A BorderContainer is a box with a specified size, such as style="width: 500px; height: 500px;",
  21. // that contains a child widget marked region="center" and optionally children widgets marked
  22. // region equal to "top", "bottom", "leading", "trailing", "left" or "right".
  23. // Children along the edges will be laid out according to width or height dimensions and may
  24. // include optional splitters (splitter="true") to make them resizable by the user. The remaining
  25. // space is designated for the center region.
  26. //
  27. // The outer size must be specified on the BorderContainer node. Width must be specified for the sides
  28. // and height for the top and bottom, respectively. No dimensions should be specified on the center;
  29. // it will fill the remaining space. Regions named "leading" and "trailing" may be used just like
  30. // "left" and "right" except that they will be reversed in right-to-left environments.
  31. //
  32. // For complex layouts, multiple children can be specified for a single region. In this case, the
  33. // layoutPriority flag on the children determines which child is closer to the edge (low layoutPriority)
  34. // and which child is closer to the center (high layoutPriority). layoutPriority can also be used
  35. // instead of the design attribute to conrol layout precedence of horizontal vs. vertical panes.
  36. // example:
  37. // | <div dojoType="dijit.layout.BorderContainer" design="sidebar" gutters="false"
  38. // | style="width: 400px; height: 300px;">
  39. // | <div dojoType="dijit.layout.ContentPane" region="top">header text</div>
  40. // | <div dojoType="dijit.layout.ContentPane" region="right" splitter="true" style="width: 200px;">table of contents</div>
  41. // | <div dojoType="dijit.layout.ContentPane" region="center">client area</div>
  42. // | </div>
  43. // design: String
  44. // Which design is used for the layout:
  45. // - "headline" (default) where the top and bottom extend
  46. // the full width of the container
  47. // - "sidebar" where the left and right sides extend from top to bottom.
  48. design: "headline",
  49. // gutters: [const] Boolean
  50. // Give each pane a border and margin.
  51. // Margin determined by domNode.paddingLeft.
  52. // When false, only resizable panes have a gutter (i.e. draggable splitter) for resizing.
  53. gutters: true,
  54. // liveSplitters: [const] Boolean
  55. // Specifies whether splitters resize as you drag (true) or only upon mouseup (false)
  56. liveSplitters: true,
  57. // persist: Boolean
  58. // Save splitter positions in a cookie.
  59. persist: false,
  60. baseClass: "dijitBorderContainer",
  61. // _splitterClass: String
  62. // Optional hook to override the default Splitter widget used by BorderContainer
  63. _splitterClass: "dijit.layout._Splitter",
  64. postMixInProperties: function(){
  65. // change class name to indicate that BorderContainer is being used purely for
  66. // layout (like LayoutContainer) rather than for pretty formatting.
  67. if(!this.gutters){
  68. this.baseClass += "NoGutter";
  69. }
  70. this.inherited(arguments);
  71. },
  72. startup: function(){
  73. if(this._started){ return; }
  74. dojo.forEach(this.getChildren(), this._setupChild, this);
  75. this.inherited(arguments);
  76. },
  77. _setupChild: function(/*dijit._Widget*/ child){
  78. // Override _LayoutWidget._setupChild().
  79. var region = child.region;
  80. if(region){
  81. this.inherited(arguments);
  82. dojo.addClass(child.domNode, this.baseClass+"Pane");
  83. var ltr = this.isLeftToRight();
  84. if(region == "leading"){ region = ltr ? "left" : "right"; }
  85. if(region == "trailing"){ region = ltr ? "right" : "left"; }
  86. // Create draggable splitter for resizing pane,
  87. // or alternately if splitter=false but BorderContainer.gutters=true then
  88. // insert dummy div just for spacing
  89. if(region != "center" && (child.splitter || this.gutters) && !child._splitterWidget){
  90. var _Splitter = dojo.getObject(child.splitter ? this._splitterClass : "dijit.layout._Gutter");
  91. var splitter = new _Splitter({
  92. id: child.id + "_splitter",
  93. container: this,
  94. child: child,
  95. region: region,
  96. live: this.liveSplitters
  97. });
  98. splitter.isSplitter = true;
  99. child._splitterWidget = splitter;
  100. dojo.place(splitter.domNode, child.domNode, "after");
  101. // Splitters aren't added as Contained children, so we need to call startup explicitly
  102. splitter.startup();
  103. }
  104. child.region = region; // TODO: technically wrong since it overwrites "trailing" with "left" etc.
  105. }
  106. },
  107. layout: function(){
  108. // Implement _LayoutWidget.layout() virtual method.
  109. this._layoutChildren();
  110. },
  111. addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){
  112. // Override _LayoutWidget.addChild().
  113. this.inherited(arguments);
  114. if(this._started){
  115. this.layout(); //OPT
  116. }
  117. },
  118. removeChild: function(/*dijit._Widget*/ child){
  119. // Override _LayoutWidget.removeChild().
  120. var region = child.region;
  121. var splitter = child._splitterWidget
  122. if(splitter){
  123. splitter.destroy();
  124. delete child._splitterWidget;
  125. }
  126. this.inherited(arguments);
  127. if(this._started){
  128. this._layoutChildren();
  129. }
  130. // Clean up whatever style changes we made to the child pane.
  131. // Unclear how height and width should be handled.
  132. dojo.removeClass(child.domNode, this.baseClass+"Pane");
  133. dojo.style(child.domNode, {
  134. top: "auto",
  135. bottom: "auto",
  136. left: "auto",
  137. right: "auto",
  138. position: "static"
  139. });
  140. dojo.style(child.domNode, region == "top" || region == "bottom" ? "width" : "height", "auto");
  141. },
  142. getChildren: function(){
  143. // Override _LayoutWidget.getChildren() to only return real children, not the splitters.
  144. return dojo.filter(this.inherited(arguments), function(widget){
  145. return !widget.isSplitter;
  146. });
  147. },
  148. // TODO: remove in 2.0
  149. getSplitter: function(/*String*/region){
  150. // summary:
  151. // Returns the widget responsible for rendering the splitter associated with region
  152. // tags:
  153. // deprecated
  154. return dojo.filter(this.getChildren(), function(child){
  155. return child.region == region;
  156. })[0]._splitterWidget;
  157. },
  158. resize: function(newSize, currentSize){
  159. // Overrides _LayoutWidget.resize().
  160. // resetting potential padding to 0px to provide support for 100% width/height + padding
  161. // TODO: this hack doesn't respect the box model and is a temporary fix
  162. if(!this.cs || !this.pe){
  163. var node = this.domNode;
  164. this.cs = dojo.getComputedStyle(node);
  165. this.pe = dojo._getPadExtents(node, this.cs);
  166. this.pe.r = dojo._toPixelValue(node, this.cs.paddingRight);
  167. this.pe.b = dojo._toPixelValue(node, this.cs.paddingBottom);
  168. dojo.style(node, "padding", "0px");
  169. }
  170. this.inherited(arguments);
  171. },
  172. _layoutChildren: function(/*String?*/ changedChildId, /*Number?*/ changedChildSize){
  173. // summary:
  174. // This is the main routine for setting size/position of each child.
  175. // description:
  176. // With no arguments, measures the height of top/bottom panes, the width
  177. // of left/right panes, and then sizes all panes accordingly.
  178. //
  179. // With changedRegion specified (as "left", "top", "bottom", or "right"),
  180. // it changes that region's width/height to changedRegionSize and
  181. // then resizes other regions that were affected.
  182. // changedChildId:
  183. // Id of the child which should be resized because splitter was dragged.
  184. // changedChildSize:
  185. // The new width/height (in pixels) to make specified child
  186. if(!this._borderBox || !this._borderBox.h){
  187. // We are currently hidden, or we haven't been sized by our parent yet.
  188. // Abort. Someone will resize us later.
  189. return;
  190. }
  191. // Generate list of wrappers of my children in the order that I want layoutChildren()
  192. // to process them (i.e. from the outside to the inside)
  193. var wrappers = dojo.map(this.getChildren(), function(child, idx){
  194. return {
  195. pane: child,
  196. weight: [
  197. child.region == "center" ? Infinity : 0,
  198. child.layoutPriority,
  199. (this.design == "sidebar" ? 1 : -1) * (/top|bottom/.test(child.region) ? 1 : -1),
  200. idx
  201. ]
  202. };
  203. }, this);
  204. wrappers.sort(function(a, b){
  205. var aw = a.weight, bw = b.weight;
  206. for(var i=0; i<aw.length; i++){
  207. if(aw[i] != bw[i]){
  208. return aw[i] - bw[i];
  209. }
  210. }
  211. return 0;
  212. });
  213. // Make new list, combining the externally specified children with splitters and gutters
  214. var childrenAndSplitters = [];
  215. dojo.forEach(wrappers, function(wrapper){
  216. var pane = wrapper.pane;
  217. childrenAndSplitters.push(pane);
  218. if(pane._splitterWidget){
  219. childrenAndSplitters.push(pane._splitterWidget);
  220. }
  221. });
  222. // Compute the box in which to lay out my children
  223. var dim = {
  224. l: this.pe.l,
  225. t: this.pe.t,
  226. w: this._borderBox.w - this.pe.w,
  227. h: this._borderBox.h - this.pe.h
  228. };
  229. // Layout the children, possibly changing size due to a splitter drag
  230. dijit.layout.layoutChildren(this.domNode, dim, childrenAndSplitters,
  231. changedChildId, changedChildSize);
  232. },
  233. destroyRecursive: function(){
  234. // Destroy splitters first, while getChildren() still works
  235. dojo.forEach(this.getChildren(), function(child){
  236. var splitter = child._splitterWidget;
  237. if(splitter){
  238. splitter.destroy();
  239. }
  240. delete child._splitterWidget;
  241. });
  242. // Then destroy the real children, and myself
  243. this.inherited(arguments);
  244. }
  245. });
  246. // This argument can be specified for the children of a BorderContainer.
  247. // Since any widget can be specified as a LayoutContainer child, mix it
  248. // into the base widget class. (This is a hack, but it's effective.)
  249. dojo.extend(dijit._Widget, {
  250. // region: [const] String
  251. // Parameter for children of `dijit.layout.BorderContainer`.
  252. // Values: "top", "bottom", "leading", "trailing", "left", "right", "center".
  253. // See the `dijit.layout.BorderContainer` description for details.
  254. region: '',
  255. // layoutPriority: [const] Number
  256. // Parameter for children of `dijit.layout.BorderContainer`.
  257. // Children with a higher layoutPriority will be placed closer to the BorderContainer center,
  258. // between children with a lower layoutPriority.
  259. layoutPriority: 0,
  260. // splitter: [const] Boolean
  261. // Parameter for child of `dijit.layout.BorderContainer` where region != "center".
  262. // If true, enables user to resize the widget by putting a draggable splitter between
  263. // this widget and the region=center widget.
  264. splitter: false,
  265. // minSize: [const] Number
  266. // Parameter for children of `dijit.layout.BorderContainer`.
  267. // Specifies a minimum size (in pixels) for this widget when resized by a splitter.
  268. minSize: 0,
  269. // maxSize: [const] Number
  270. // Parameter for children of `dijit.layout.BorderContainer`.
  271. // Specifies a maximum size (in pixels) for this widget when resized by a splitter.
  272. maxSize: Infinity
  273. });
  274. dojo.declare("dijit.layout._Splitter", [ dijit._Widget, dijit._Templated ],
  275. {
  276. // summary:
  277. // A draggable spacer between two items in a `dijit.layout.BorderContainer`.
  278. // description:
  279. // This is instantiated by `dijit.layout.BorderContainer`. Users should not
  280. // create it directly.
  281. // tags:
  282. // private
  283. /*=====
  284. // container: [const] dijit.layout.BorderContainer
  285. // Pointer to the parent BorderContainer
  286. container: null,
  287. // child: [const] dijit.layout._LayoutWidget
  288. // Pointer to the pane associated with this splitter
  289. child: null,
  290. // region: [const] String
  291. // Region of pane associated with this splitter.
  292. // "top", "bottom", "left", "right".
  293. region: null,
  294. =====*/
  295. // live: [const] Boolean
  296. // If true, the child's size changes and the child widget is redrawn as you drag the splitter;
  297. // otherwise, the size doesn't change until you drop the splitter (by mouse-up)
  298. live: true,
  299. templateString: '<div class="dijitSplitter" dojoAttachEvent="onkeypress:_onKeyPress,onmousedown:_startDrag,onmouseenter:_onMouse,onmouseleave:_onMouse" tabIndex="0" role="separator"><div class="dijitSplitterThumb"></div></div>',
  300. postMixInProperties: function(){
  301. this.inherited(arguments);
  302. this.horizontal = /top|bottom/.test(this.region);
  303. this._factor = /top|left/.test(this.region) ? 1 : -1;
  304. this._cookieName = this.container.id + "_" + this.region;
  305. },
  306. buildRendering: function(){
  307. this.inherited(arguments);
  308. dojo.addClass(this.domNode, "dijitSplitter" + (this.horizontal ? "H" : "V"));
  309. if(this.container.persist){
  310. // restore old size
  311. var persistSize = dojo.cookie(this._cookieName);
  312. if(persistSize){
  313. this.child.domNode.style[this.horizontal ? "height" : "width"] = persistSize;
  314. }
  315. }
  316. },
  317. _computeMaxSize: function(){
  318. // summary:
  319. // Return the maximum size that my corresponding pane can be set to
  320. var dim = this.horizontal ? 'h' : 'w',
  321. childSize = dojo.marginBox(this.child.domNode)[dim],
  322. center = dojo.filter(this.container.getChildren(), function(child){ return child.region == "center";})[0],
  323. spaceAvailable = dojo.marginBox(center.domNode)[dim]; // can expand until center is crushed to 0
  324. return Math.min(this.child.maxSize, childSize + spaceAvailable);
  325. },
  326. _startDrag: function(e){
  327. if(!this.cover){
  328. this.cover = dojo.doc.createElement('div');
  329. dojo.addClass(this.cover, "dijitSplitterCover");
  330. dojo.place(this.cover, this.child.domNode, "after");
  331. }
  332. dojo.addClass(this.cover, "dijitSplitterCoverActive");
  333. // Safeguard in case the stop event was missed. Shouldn't be necessary if we always get the mouse up.
  334. if(this.fake){ dojo.destroy(this.fake); }
  335. if(!(this._resize = this.live)){ //TODO: disable live for IE6?
  336. // create fake splitter to display at old position while we drag
  337. (this.fake = this.domNode.cloneNode(true)).removeAttribute("id");
  338. dojo.addClass(this.domNode, "dijitSplitterShadow");
  339. dojo.place(this.fake, this.domNode, "after");
  340. }
  341. dojo.addClass(this.domNode, "dijitSplitterActive dijitSplitter" + (this.horizontal ? "H" : "V") + "Active");
  342. if(this.fake){
  343. dojo.removeClass(this.fake, "dijitSplitterHover dijitSplitter" + (this.horizontal ? "H" : "V") + "Hover");
  344. }
  345. //Performance: load data info local vars for onmousevent function closure
  346. var factor = this._factor,
  347. isHorizontal = this.horizontal,
  348. axis = isHorizontal ? "pageY" : "pageX",
  349. pageStart = e[axis],
  350. splitterStyle = this.domNode.style,
  351. dim = isHorizontal ? 'h' : 'w',
  352. childStart = dojo.marginBox(this.child.domNode)[dim],
  353. max = this._computeMaxSize(),
  354. min = this.child.minSize || 20,
  355. region = this.region,
  356. splitterAttr = region == "top" || region == "bottom" ? "top" : "left", // style attribute of splitter to adjust
  357. splitterStart = parseInt(splitterStyle[splitterAttr], 10),
  358. resize = this._resize,
  359. layoutFunc = dojo.hitch(this.container, "_layoutChildren", this.child.id),
  360. de = dojo.doc;
  361. this._handlers = (this._handlers || []).concat([
  362. dojo.connect(de, "onmousemove", this._drag = function(e, forceResize){
  363. var delta = e[axis] - pageStart,
  364. childSize = factor * delta + childStart,
  365. boundChildSize = Math.max(Math.min(childSize, max), min);
  366. if(resize || forceResize){
  367. layoutFunc(boundChildSize);
  368. }
  369. // TODO: setting style directly (usually) sets content box size, need to set margin box size
  370. splitterStyle[splitterAttr] = delta + splitterStart + factor*(boundChildSize - childSize) + "px";
  371. }),
  372. dojo.connect(de, "ondragstart", dojo.stopEvent),
  373. dojo.connect(dojo.body(), "onselectstart", dojo.stopEvent),
  374. dojo.connect(de, "onmouseup", this, "_stopDrag")
  375. ]);
  376. dojo.stopEvent(e);
  377. },
  378. _onMouse: function(e){
  379. var o = (e.type == "mouseover" || e.type == "mouseenter");
  380. dojo.toggleClass(this.domNode, "dijitSplitterHover", o);
  381. dojo.toggleClass(this.domNode, "dijitSplitter" + (this.horizontal ? "H" : "V") + "Hover", o);
  382. },
  383. _stopDrag: function(e){
  384. try{
  385. if(this.cover){
  386. dojo.removeClass(this.cover, "dijitSplitterCoverActive");
  387. }
  388. if(this.fake){ dojo.destroy(this.fake); }
  389. dojo.removeClass(this.domNode, "dijitSplitterActive dijitSplitter"
  390. + (this.horizontal ? "H" : "V") + "Active dijitSplitterShadow");
  391. this._drag(e); //TODO: redundant with onmousemove?
  392. this._drag(e, true);
  393. }finally{
  394. this._cleanupHandlers();
  395. delete this._drag;
  396. }
  397. if(this.container.persist){
  398. dojo.cookie(this._cookieName, this.child.domNode.style[this.horizontal ? "height" : "width"], {expires:365});
  399. }
  400. },
  401. _cleanupHandlers: function(){
  402. dojo.forEach(this._handlers, dojo.disconnect);
  403. delete this._handlers;
  404. },
  405. _onKeyPress: function(/*Event*/ e){
  406. // should we apply typematic to this?
  407. this._resize = true;
  408. var horizontal = this.horizontal;
  409. var tick = 1;
  410. var dk = dojo.keys;
  411. switch(e.charOrCode){
  412. case horizontal ? dk.UP_ARROW : dk.LEFT_ARROW:
  413. tick *= -1;
  414. // break;
  415. case horizontal ? dk.DOWN_ARROW : dk.RIGHT_ARROW:
  416. break;
  417. default:
  418. // this.inherited(arguments);
  419. return;
  420. }
  421. var childSize = dojo._getMarginSize(this.child.domNode)[ horizontal ? 'h' : 'w' ] + this._factor * tick;
  422. this.container._layoutChildren(this.child.id, Math.max(Math.min(childSize, this._computeMaxSize()), this.child.minSize));
  423. dojo.stopEvent(e);
  424. },
  425. destroy: function(){
  426. this._cleanupHandlers();
  427. delete this.child;
  428. delete this.container;
  429. delete this.cover;
  430. delete this.fake;
  431. this.inherited(arguments);
  432. }
  433. });
  434. dojo.declare("dijit.layout._Gutter", [dijit._Widget, dijit._Templated],
  435. {
  436. // summary:
  437. // Just a spacer div to separate side pane from center pane.
  438. // Basically a trick to lookup the gutter/splitter width from the theme.
  439. // description:
  440. // Instantiated by `dijit.layout.BorderContainer`. Users should not
  441. // create directly.
  442. // tags:
  443. // private
  444. templateString: '<div class="dijitGutter" role="presentation"></div>',
  445. postMixInProperties: function(){
  446. this.inherited(arguments);
  447. this.horizontal = /top|bottom/.test(this.region);
  448. },
  449. buildRendering: function(){
  450. this.inherited(arguments);
  451. dojo.addClass(this.domNode, "dijitGutter" + (this.horizontal ? "H" : "V"));
  452. }
  453. });
  454. }