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