]> git.wh0rd.org - tt-rss.git/blame - lib/dijit/layout/BorderContainer.js
upgrade Dojo to 1.6.1
[tt-rss.git] / lib / dijit / layout / BorderContainer.js
CommitLineData
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
8if(!dojo._hasResource["dijit.layout.BorderContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
9dojo._hasResource["dijit.layout.BorderContainer"] = true;
2f01fe57
AD
10dojo.provide("dijit.layout.BorderContainer");
11dojo.require("dijit.layout._LayoutWidget");
12dojo.require("dojo.cookie");
81bea17a
AD
13dojo.require("dijit._Templated");
14
15
16dojo.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.)
288dojo.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
318dojo.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
503dojo.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}