]> git.wh0rd.org Git - 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 });