]> git.wh0rd.org - tt-rss.git/blob - lib/dijit/layout/SplitContainer.js.uncompressed.js
upgrade dojo to 1.8.3 (refs #570)
[tt-rss.git] / lib / dijit / layout / SplitContainer.js.uncompressed.js
1 define("dijit/layout/SplitContainer", [
2 "dojo/_base/array", // array.forEach array.indexOf array.some
3 "dojo/cookie", // cookie
4 "dojo/_base/declare", // declare
5 "dojo/dom", // dom.setSelectable
6 "dojo/dom-class", // domClass.add
7 "dojo/dom-construct", // domConstruct.create domConstruct.destroy
8 "dojo/dom-geometry", // domGeometry.marginBox domGeometry.position
9 "dojo/dom-style", // domStyle.style
10 "dojo/_base/event", // event.stop
11 "dojo/_base/kernel", // kernel.deprecated
12 "dojo/_base/lang", // lang.extend lang.hitch
13 "dojo/on",
14 "dojo/sniff", // has("mozilla")
15 "../registry", // registry.getUniqueId()
16 "../_WidgetBase",
17 "./_LayoutWidget"
18 ], function(array, cookie, declare, dom, domClass, domConstruct, domGeometry, domStyle,
19 event, kernel, lang, on, has, registry, _WidgetBase, _LayoutWidget){
20
21 // module:
22 // dijit/layout/SplitContainer
23
24 //
25 // FIXME: make it prettier
26 // FIXME: active dragging upwards doesn't always shift other bars (direction calculation is wrong in this case)
27 // FIXME: sizeWidth should be a CSS attribute (at 7 because css wants it to be 7 until we fix to css)
28 //
29
30 var SplitContainer = declare("dijit.layout.SplitContainer", _LayoutWidget, {
31 // summary:
32 // Deprecated. Use `dijit/layout/BorderContainer` instead.
33 // description:
34 // A Container widget with sizing handles in-between each child.
35 // Contains multiple children widgets, all of which are displayed side by side
36 // (either horizontally or vertically); there's a bar between each of the children,
37 // and you can adjust the relative size of each child by dragging the bars.
38 //
39 // You must specify a size (width and height) for the SplitContainer.
40 //
41 // See `SplitContainer.ChildWidgetProperties` for details on the properties that can be set on
42 // children of a `SplitContainer`.
43 // tags:
44 // deprecated
45
46 constructor: function(){
47 kernel.deprecated("dijit.layout.SplitContainer is deprecated", "use BorderContainer with splitter instead", 2.0);
48 },
49
50 // activeSizing: Boolean
51 // If true, the children's size changes as you drag the bar;
52 // otherwise, the sizes don't change until you drop the bar (by mouse-up)
53 activeSizing: false,
54
55 // sizerWidth: Integer
56 // Size in pixels of the bar between each child
57 sizerWidth: 7,
58
59 // orientation: String
60 // either 'horizontal' or vertical; indicates whether the children are
61 // arranged side-by-side or up/down.
62 orientation: 'horizontal',
63
64 // persist: Boolean
65 // Save splitter positions in a cookie
66 persist: true,
67
68 baseClass: "dijitSplitContainer",
69
70 postMixInProperties: function(){
71 this.inherited("postMixInProperties",arguments);
72 this.isHorizontal = (this.orientation == 'horizontal');
73 },
74
75 postCreate: function(){
76 this.inherited(arguments);
77 this.sizers = [];
78
79 // overflow has to be explicitly hidden for splitContainers using gekko (trac #1435)
80 // to keep other combined css classes from inadvertantly making the overflow visible
81 if(has("mozilla")){
82 this.domNode.style.overflow = '-moz-scrollbars-none'; // hidden doesn't work
83 }
84
85 // create the fake dragger
86 if(typeof this.sizerWidth == "object"){
87 try{ //FIXME: do this without a try/catch
88 this.sizerWidth = parseInt(this.sizerWidth.toString());
89 }catch(e){ this.sizerWidth = 7; }
90 }
91 var sizer = this.ownerDocument.createElement('div');
92 this.virtualSizer = sizer;
93 sizer.style.position = 'relative';
94
95 // #1681: work around the dreaded 'quirky percentages in IE' layout bug
96 // If the splitcontainer's dimensions are specified in percentages, it
97 // will be resized when the virtualsizer is displayed in _showSizingLine
98 // (typically expanding its bounds unnecessarily). This happens because
99 // we use position: relative for .dijitSplitContainer.
100 // The workaround: instead of changing the display style attribute,
101 // switch to changing the zIndex (bring to front/move to back)
102
103 sizer.style.zIndex = 10;
104 sizer.className = this.isHorizontal ? 'dijitSplitContainerVirtualSizerH' : 'dijitSplitContainerVirtualSizerV';
105 this.domNode.appendChild(sizer);
106 dom.setSelectable(sizer, false);
107 },
108
109 destroy: function(){
110 delete this.virtualSizer;
111 if(this._ownconnects){
112 var h;
113 while(h = this._ownconnects.pop()){ h.remove(); }
114 }
115 this.inherited(arguments);
116 },
117 startup: function(){
118 if(this._started){ return; }
119
120 array.forEach(this.getChildren(), function(child, i, children){
121 // attach the children and create the draggers
122 this._setupChild(child);
123
124 if(i < children.length-1){
125 this._addSizer();
126 }
127 }, this);
128
129 if(this.persist){
130 this._restoreState();
131 }
132
133 this.inherited(arguments);
134 },
135
136 _setupChild: function(/*dijit/_WidgetBase*/ child){
137 this.inherited(arguments);
138 child.domNode.style.position = "absolute";
139 domClass.add(child.domNode, "dijitSplitPane");
140 },
141
142 _onSizerMouseDown: function(e){
143 if(e.target.id){
144 for(var i=0;i<this.sizers.length;i++){
145 if(this.sizers[i].id == e.target.id){
146 break;
147 }
148 }
149 if(i<this.sizers.length){
150 this.beginSizing(e,i);
151 }
152 }
153 },
154 _addSizer: function(index){
155 index = index === undefined ? this.sizers.length : index;
156
157 // TODO: use a template for this!!!
158 var sizer = this.ownerDocument.createElement('div');
159 sizer.id=registry.getUniqueId('dijit_layout_SplitterContainer_Splitter');
160 this.sizers.splice(index,0,sizer);
161 this.domNode.appendChild(sizer);
162
163 sizer.className = this.isHorizontal ? 'dijitSplitContainerSizerH' : 'dijitSplitContainerSizerV';
164
165 // add the thumb div
166 var thumb = this.ownerDocument.createElement('div');
167 thumb.className = 'thumb';
168 sizer.appendChild(thumb);
169
170 // FIXME: are you serious? why aren't we using mover start/stop combo?
171 this.connect(sizer, "onmousedown", '_onSizerMouseDown');
172
173 dom.setSelectable(sizer, false);
174 },
175
176 removeChild: function(widget){
177 // summary:
178 // Remove sizer, but only if widget is really our child and
179 // we have at least one sizer to throw away
180 if(this.sizers.length){
181 var i = array.indexOf(this.getChildren(), widget);
182 if(i != -1){
183 if(i == this.sizers.length){
184 i--;
185 }
186 domConstruct.destroy(this.sizers[i]);
187 this.sizers.splice(i,1);
188 }
189 }
190
191 // Remove widget and repaint
192 this.inherited(arguments);
193 if(this._started){
194 this.layout();
195 }
196 },
197
198 addChild: function(/*dijit/_WidgetBase*/ child, /*Integer?*/ insertIndex){
199 // summary:
200 // Add a child widget to the container
201 // child:
202 // a widget to add
203 // insertIndex:
204 // postion in the "stack" to add the child widget
205
206 this.inherited(arguments);
207
208 if(this._started){
209 // Do the stuff that startup() does for each widget
210 var children = this.getChildren();
211 if(children.length > 1){
212 this._addSizer(insertIndex);
213 }
214
215 // and then reposition (ie, shrink) every pane to make room for the new guy
216 this.layout();
217 }
218 },
219
220 layout: function(){
221 // summary:
222 // Do layout of panels
223
224 // base class defines this._contentBox on initial creation and also
225 // on resize
226 this.paneWidth = this._contentBox.w;
227 this.paneHeight = this._contentBox.h;
228
229 var children = this.getChildren();
230 if(!children.length){ return; }
231
232 //
233 // calculate space
234 //
235
236 var space = this.isHorizontal ? this.paneWidth : this.paneHeight;
237 if(children.length > 1){
238 space -= this.sizerWidth * (children.length - 1);
239 }
240
241 //
242 // calculate total of SizeShare values
243 //
244 var outOf = 0;
245 array.forEach(children, function(child){
246 outOf += child.sizeShare;
247 });
248
249 //
250 // work out actual pixels per sizeshare unit
251 //
252 var pixPerUnit = space / outOf;
253
254 //
255 // set the SizeActual member of each pane
256 //
257 var totalSize = 0;
258 array.forEach(children.slice(0, children.length - 1), function(child){
259 var size = Math.round(pixPerUnit * child.sizeShare);
260 child.sizeActual = size;
261 totalSize += size;
262 });
263
264 children[children.length-1].sizeActual = space - totalSize;
265
266 //
267 // make sure the sizes are ok
268 //
269 this._checkSizes();
270
271 //
272 // now loop, positioning each pane and letting children resize themselves
273 //
274
275 var pos = 0;
276 var size = children[0].sizeActual;
277 this._movePanel(children[0], pos, size);
278 children[0].position = pos;
279 pos += size;
280
281 // if we don't have any sizers, our layout method hasn't been called yet
282 // so bail until we are called..TODO: REVISIT: need to change the startup
283 // algorithm to guaranteed the ordering of calls to layout method
284 if(!this.sizers){
285 return;
286 }
287
288 array.some(children.slice(1), function(child, i){
289 // error-checking
290 if(!this.sizers[i]){
291 return true;
292 }
293 // first we position the sizing handle before this pane
294 this._moveSlider(this.sizers[i], pos, this.sizerWidth);
295 this.sizers[i].position = pos;
296 pos += this.sizerWidth;
297
298 size = child.sizeActual;
299 this._movePanel(child, pos, size);
300 child.position = pos;
301 pos += size;
302 }, this);
303 },
304
305 _movePanel: function(panel, pos, size){
306 var box;
307 if(this.isHorizontal){
308 panel.domNode.style.left = pos + 'px'; // TODO: resize() takes l and t parameters too, don't need to set manually
309 panel.domNode.style.top = 0;
310 box = {w: size, h: this.paneHeight};
311 if(panel.resize){
312 panel.resize(box);
313 }else{
314 domGeometry.setMarginBox(panel.domNode, box);
315 }
316 }else{
317 panel.domNode.style.left = 0; // TODO: resize() takes l and t parameters too, don't need to set manually
318 panel.domNode.style.top = pos + 'px';
319 box = {w: this.paneWidth, h: size};
320 if(panel.resize){
321 panel.resize(box);
322 }else{
323 domGeometry.setMarginBox(panel.domNode, box);
324 }
325 }
326 },
327
328 _moveSlider: function(slider, pos, size){
329 if(this.isHorizontal){
330 slider.style.left = pos + 'px';
331 slider.style.top = 0;
332 domGeometry.setMarginBox(slider, { w: size, h: this.paneHeight });
333 }else{
334 slider.style.left = 0;
335 slider.style.top = pos + 'px';
336 domGeometry.setMarginBox(slider, { w: this.paneWidth, h: size });
337 }
338 },
339
340 _growPane: function(growth, pane){
341 if(growth > 0){
342 if(pane.sizeActual > pane.sizeMin){
343 if((pane.sizeActual - pane.sizeMin) > growth){
344
345 // stick all the growth in this pane
346 pane.sizeActual = pane.sizeActual - growth;
347 growth = 0;
348 }else{
349 // put as much growth in here as we can
350 growth -= pane.sizeActual - pane.sizeMin;
351 pane.sizeActual = pane.sizeMin;
352 }
353 }
354 }
355 return growth;
356 },
357
358 _checkSizes: function(){
359
360 var totalMinSize = 0;
361 var totalSize = 0;
362 var children = this.getChildren();
363
364 array.forEach(children, function(child){
365 totalSize += child.sizeActual;
366 totalMinSize += child.sizeMin;
367 });
368
369 // only make adjustments if we have enough space for all the minimums
370
371 if(totalMinSize <= totalSize){
372
373 var growth = 0;
374
375 array.forEach(children, function(child){
376 if(child.sizeActual < child.sizeMin){
377 growth += child.sizeMin - child.sizeActual;
378 child.sizeActual = child.sizeMin;
379 }
380 });
381
382 if(growth > 0){
383 var list = this.isDraggingLeft ? children.reverse() : children;
384 array.forEach(list, function(child){
385 growth = this._growPane(growth, child);
386 }, this);
387 }
388 }else{
389 array.forEach(children, function(child){
390 child.sizeActual = Math.round(totalSize * (child.sizeMin / totalMinSize));
391 });
392 }
393 },
394
395 beginSizing: function(e, i){
396 // summary:
397 // Begin dragging the splitter between child[i] and child[i+1]
398
399 var children = this.getChildren();
400
401 this.paneBefore = children[i];
402 this.paneAfter = children[i+1];
403
404 this.paneBefore.sizeBeforeDrag = this.paneBefore.sizeActual;
405 this.paneAfter.sizeBeforeDrag = this.paneAfter.sizeActual;
406 this.paneAfter.positionBeforeDrag = this.paneAfter.position;
407
408 this.isSizing = true;
409 this.sizingSplitter = this.sizers[i];
410 this.sizingSplitter.positionBeforeDrag = domStyle.get(this.sizingSplitter,(this.isHorizontal ? "left" : "top"));
411
412 if(!this.cover){
413 this.cover = domConstruct.create('div', {
414 style: {
415 position:'absolute',
416 zIndex:5,
417 top: 0,
418 left: 0,
419 width: "100%",
420 height: "100%"
421 }
422 }, this.domNode);
423 }else{
424 this.cover.style.zIndex = 5;
425 }
426 this.sizingSplitter.style.zIndex = 6;
427
428 // startPoint is the e.pageX or e.pageY at start of drag
429 this.startPoint = this.lastPoint = (this.isHorizontal ? e.pageX : e.pageY);
430
431 // Calculate maximum to the left or right that splitter is allowed to be dragged
432 // minDelta is negative to indicate left/upward drag where end.pageX < start.pageX.
433 this.maxDelta = this.paneAfter.sizeActual - this.paneAfter.sizeMin;
434 this.minDelta = -1 * (this.paneBefore.sizeActual - this.paneBefore.sizeMin);
435
436 if(!this.activeSizing){
437 this._showSizingLine();
438 }
439
440 // attach mouse events
441 this._ownconnects = [
442 on(this.ownerDocument.documentElement, "mousemove", lang.hitch(this, "changeSizing")),
443 on(this.ownerDocument.documentElement, "mouseup", lang.hitch(this, "endSizing"))
444 ];
445
446 event.stop(e);
447 },
448
449 changeSizing: function(e){
450 // summary:
451 // Called on mousemove while dragging the splitter
452
453 if(!this.isSizing){ return; }
454
455 // lastPoint is the most recent e.pageX or e.pageY during the drag
456 this.lastPoint = this.isHorizontal ? e.pageX : e.pageY;
457 var delta = Math.max(Math.min(this.lastPoint - this.startPoint, this.maxDelta), this.minDelta);
458
459 if(this.activeSizing){
460 this._updateSize(delta);
461 }else{
462 this._moveSizingLine(delta);
463 }
464 event.stop(e);
465 },
466
467 endSizing: function(){
468 if(!this.isSizing){ return; }
469 if(this.cover){
470 this.cover.style.zIndex = -1;
471 }
472 if(!this.activeSizing){
473 this._hideSizingLine();
474 }
475
476 var delta = Math.max(Math.min(this.lastPoint - this.startPoint, this.maxDelta), this.minDelta);
477 this._updateSize(delta);
478
479 this.isSizing = false;
480
481 if(this.persist){
482 this._saveState(this);
483 }
484
485 var h;
486 while(h = this._ownconnects.pop()){ h.remove(); }
487 },
488
489 _updateSize: function(/*Number*/ delta){
490 // summary:
491 // Resets sizes of panes before and after splitter being dragged.
492 // Called during a drag, for active sizing, or at the end of a drag otherwise.
493 // delta: Number
494 // Change in slider position compared to start of drag. But note that
495 // this function may be called multiple times during drag.
496
497 this.paneBefore.sizeActual = this.paneBefore.sizeBeforeDrag + delta;
498 this.paneAfter.position = this.paneAfter.positionBeforeDrag + delta;
499 this.paneAfter.sizeActual = this.paneAfter.sizeBeforeDrag - delta;
500
501 array.forEach(this.getChildren(), function(child){
502 child.sizeShare = child.sizeActual;
503 });
504
505 if(this._started){
506 this.layout();
507 }
508 },
509
510 _showSizingLine: function(){
511 // summary:
512 // Show virtual splitter, for non-active resizing
513
514 this._moveSizingLine(0);
515
516 domGeometry.setMarginBox(this.virtualSizer,
517 this.isHorizontal ? { w: this.sizerWidth, h: this.paneHeight } : { w: this.paneWidth, h: this.sizerWidth });
518
519 this.virtualSizer.style.display = 'block';
520 },
521
522 _hideSizingLine: function(){
523 this.virtualSizer.style.display = 'none';
524 },
525
526 _moveSizingLine: function(/*Number*/ delta){
527 // summary:
528 // Called for non-active resizing, to move the virtual splitter without adjusting the size of the panes
529 var pos = delta + this.sizingSplitter.positionBeforeDrag;
530 domStyle.set(this.virtualSizer,(this.isHorizontal ? "left" : "top"),pos+"px");
531 },
532
533 _getCookieName: function(i){
534 return this.id + "_" + i;
535 },
536
537 _restoreState: function(){
538 array.forEach(this.getChildren(), function(child, i){
539 var cookieName = this._getCookieName(i);
540 var cookieValue = cookie(cookieName);
541 if(cookieValue){
542 var pos = parseInt(cookieValue);
543 if(typeof pos == "number"){
544 child.sizeShare = pos;
545 }
546 }
547 }, this);
548 },
549
550 _saveState: function(){
551 if(!this.persist){
552 return;
553 }
554 array.forEach(this.getChildren(), function(child, i){
555 cookie(this._getCookieName(i), child.sizeShare, {expires:365});
556 }, this);
557 }
558 });
559
560 SplitContainer.ChildWidgetProperties = {
561 // summary:
562 // These properties can be specified for the children of a SplitContainer.
563
564 // sizeMin: [deprecated] Integer
565 // Minimum size (width or height) of a child of a SplitContainer.
566 // The value is relative to other children's sizeShare properties.
567 sizeMin: 10,
568
569 // sizeShare: [deprecated] Integer
570 // Size (width or height) of a child of a SplitContainer.
571 // The value is relative to other children's sizeShare properties.
572 // For example, if there are two children and each has sizeShare=10, then
573 // each takes up 50% of the available space.
574 sizeShare: 10
575 };
576
577 // Since any widget can be specified as a SplitContainer child, mix them
578 // into the base widget class. (This is a hack, but it's effective.)
579 // This is for the benefit of the parser. Remove for 2.0. Also, hide from doc viewer.
580 lang.extend(_WidgetBase, /*===== {} || =====*/ SplitContainer.ChildWidgetProperties);
581
582 return SplitContainer;
583
584 });