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