source: Dev/trunk/src/client/dijit/layout/SplitContainer.js @ 529

Last change on this file since 529 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

File size: 16.7 KB
Line 
1define([
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
30var 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                //              position in the "stack" to add the child widget
205
206                // SplitContainer puts all the child widgets first, and all the splitters at the end.
207                // (This is not ideal for accessibility but not going to fix because the widget is deprecated.)
208                // So, just need to maintain that order so that _Container.addChild() puts the widgets where expected.
209                if(typeof insertIndex == "undefined" || insertIndex == "last"){
210                        insertIndex = this.getChildren().length;
211                }
212
213                this.inherited(arguments, [child, insertIndex]);
214
215                if(this._started){
216                        // Do the stuff that startup() does for each widget
217                        var children = this.getChildren();
218                        if(children.length > 1){
219                                this._addSizer(insertIndex);
220                        }
221
222                        // and then reposition (ie, shrink) every pane to make room for the new guy
223                        this.layout();
224                }
225        },
226
227        layout: function(){
228                // summary:
229                //              Do layout of panels
230
231                // base class defines this._contentBox on initial creation and also
232                // on resize
233                this.paneWidth = this._contentBox.w;
234                this.paneHeight = this._contentBox.h;
235
236                var children = this.getChildren();
237                if(!children.length){ return; }
238
239                //
240                // calculate space
241                //
242
243                var space = this.isHorizontal ? this.paneWidth : this.paneHeight;
244                if(children.length > 1){
245                        space -= this.sizerWidth * (children.length - 1);
246                }
247
248                //
249                // calculate total of SizeShare values
250                //
251                var outOf = 0;
252                array.forEach(children, function(child){
253                        outOf += child.sizeShare;
254                });
255
256                //
257                // work out actual pixels per sizeshare unit
258                //
259                var pixPerUnit = space / outOf;
260
261                //
262                // set the SizeActual member of each pane
263                //
264                var totalSize = 0;
265                array.forEach(children.slice(0, children.length - 1), function(child){
266                        var size = Math.round(pixPerUnit * child.sizeShare);
267                        child.sizeActual = size;
268                        totalSize += size;
269                });
270
271                children[children.length-1].sizeActual = space - totalSize;
272
273                //
274                // make sure the sizes are ok
275                //
276                this._checkSizes();
277
278                //
279                // now loop, positioning each pane and letting children resize themselves
280                //
281
282                var pos = 0;
283                var size = children[0].sizeActual;
284                this._movePanel(children[0], pos, size);
285                children[0].position = pos;
286                pos += size;
287
288                // if we don't have any sizers, our layout method hasn't been called yet
289                // so bail until we are called..TODO: REVISIT: need to change the startup
290                // algorithm to guaranteed the ordering of calls to layout method
291                if(!this.sizers){
292                        return;
293                }
294
295                array.some(children.slice(1), function(child, i){
296                        // error-checking
297                        if(!this.sizers[i]){
298                                return true;
299                        }
300                        // first we position the sizing handle before this pane
301                        this._moveSlider(this.sizers[i], pos, this.sizerWidth);
302                        this.sizers[i].position = pos;
303                        pos += this.sizerWidth;
304
305                        size = child.sizeActual;
306                        this._movePanel(child, pos, size);
307                        child.position = pos;
308                        pos += size;
309                }, this);
310        },
311
312        _movePanel: function(panel, pos, size){
313                var box;
314                if(this.isHorizontal){
315                        panel.domNode.style.left = pos + 'px';  // TODO: resize() takes l and t parameters too, don't need to set manually
316                        panel.domNode.style.top = 0;
317                        box = {w: size, h: this.paneHeight};
318                        if(panel.resize){
319                                panel.resize(box);
320                        }else{
321                                domGeometry.setMarginBox(panel.domNode, box);
322                        }
323                }else{
324                        panel.domNode.style.left = 0;   // TODO: resize() takes l and t parameters too, don't need to set manually
325                        panel.domNode.style.top = pos + 'px';
326                        box = {w: this.paneWidth, h: size};
327                        if(panel.resize){
328                                panel.resize(box);
329                        }else{
330                                domGeometry.setMarginBox(panel.domNode, box);
331                        }
332                }
333        },
334
335        _moveSlider: function(slider, pos, size){
336                if(this.isHorizontal){
337                        slider.style.left = pos + 'px';
338                        slider.style.top = 0;
339                        domGeometry.setMarginBox(slider, { w: size, h: this.paneHeight });
340                }else{
341                        slider.style.left = 0;
342                        slider.style.top = pos + 'px';
343                        domGeometry.setMarginBox(slider, { w: this.paneWidth, h: size });
344                }
345        },
346
347        _growPane: function(growth, pane){
348                if(growth > 0){
349                        if(pane.sizeActual > pane.sizeMin){
350                                if((pane.sizeActual - pane.sizeMin) > growth){
351
352                                        // stick all the growth in this pane
353                                        pane.sizeActual = pane.sizeActual - growth;
354                                        growth = 0;
355                                }else{
356                                        // put as much growth in here as we can
357                                        growth -= pane.sizeActual - pane.sizeMin;
358                                        pane.sizeActual = pane.sizeMin;
359                                }
360                        }
361                }
362                return growth;
363        },
364
365        _checkSizes: function(){
366
367                var totalMinSize = 0;
368                var totalSize = 0;
369                var children = this.getChildren();
370
371                array.forEach(children, function(child){
372                        totalSize += child.sizeActual;
373                        totalMinSize += child.sizeMin;
374                });
375
376                // only make adjustments if we have enough space for all the minimums
377
378                if(totalMinSize <= totalSize){
379
380                        var growth = 0;
381
382                        array.forEach(children, function(child){
383                                if(child.sizeActual < child.sizeMin){
384                                        growth += child.sizeMin - child.sizeActual;
385                                        child.sizeActual = child.sizeMin;
386                                }
387                        });
388
389                        if(growth > 0){
390                                var list = this.isDraggingLeft ? children.reverse() : children;
391                                array.forEach(list, function(child){
392                                        growth = this._growPane(growth, child);
393                                }, this);
394                        }
395                }else{
396                        array.forEach(children, function(child){
397                                child.sizeActual = Math.round(totalSize * (child.sizeMin / totalMinSize));
398                        });
399                }
400        },
401
402        beginSizing: function(e, i){
403                // summary:
404                //              Begin dragging the splitter between child[i] and child[i+1]
405
406                var children = this.getChildren();
407
408                this.paneBefore = children[i];
409                this.paneAfter = children[i+1];
410
411                this.paneBefore.sizeBeforeDrag = this.paneBefore.sizeActual;
412                this.paneAfter.sizeBeforeDrag = this.paneAfter.sizeActual;
413                this.paneAfter.positionBeforeDrag = this.paneAfter.position;
414
415                this.isSizing = true;
416                this.sizingSplitter = this.sizers[i];
417                this.sizingSplitter.positionBeforeDrag = domStyle.get(this.sizingSplitter,(this.isHorizontal ? "left" : "top"));
418
419                if(!this.cover){
420                        this.cover = domConstruct.create('div', {
421                                style: {
422                                        position:'absolute',
423                                        zIndex:5,
424                                        top: 0,
425                                        left: 0,
426                                        width: "100%",
427                                        height: "100%"
428                                }
429                        }, this.domNode);
430                }else{
431                        this.cover.style.zIndex = 5;
432                }
433                this.sizingSplitter.style.zIndex = 6;
434
435                // startPoint is the e.pageX or e.pageY at start of drag
436                this.startPoint = this.lastPoint = (this.isHorizontal ? e.pageX : e.pageY);
437
438                // Calculate maximum to the left or right that splitter is allowed to be dragged
439                // minDelta is negative to indicate left/upward drag where end.pageX < start.pageX.
440                this.maxDelta = this.paneAfter.sizeActual - this.paneAfter.sizeMin;
441                this.minDelta = -1 * (this.paneBefore.sizeActual - this.paneBefore.sizeMin);
442
443                if(!this.activeSizing){
444                        this._showSizingLine();
445                }
446
447                // attach mouse events
448                this._ownconnects = [
449                        on(this.ownerDocument.documentElement, "mousemove", lang.hitch(this, "changeSizing")),
450                        on(this.ownerDocument.documentElement, "mouseup", lang.hitch(this, "endSizing"))
451                ];
452
453                event.stop(e);
454        },
455
456        changeSizing: function(e){
457                // summary:
458                //              Called on mousemove while dragging the splitter
459
460                if(!this.isSizing){ return; }
461
462                // lastPoint is the most recent e.pageX or e.pageY during the drag
463                this.lastPoint = this.isHorizontal ? e.pageX : e.pageY;
464                var delta = Math.max(Math.min(this.lastPoint - this.startPoint, this.maxDelta), this.minDelta);
465
466                if(this.activeSizing){
467                        this._updateSize(delta);
468                }else{
469                        this._moveSizingLine(delta);
470                }
471                event.stop(e);
472        },
473
474        endSizing: function(){
475                if(!this.isSizing){ return; }
476                if(this.cover){
477                        this.cover.style.zIndex = -1;
478                }
479                if(!this.activeSizing){
480                        this._hideSizingLine();
481                }
482
483                var delta = Math.max(Math.min(this.lastPoint - this.startPoint, this.maxDelta), this.minDelta);
484                this._updateSize(delta);
485
486                this.isSizing = false;
487
488                if(this.persist){
489                        this._saveState(this);
490                }
491
492                var h;
493                while(h = this._ownconnects.pop()){ h.remove(); }
494        },
495
496        _updateSize: function(/*Number*/ delta){
497                // summary:
498                //              Resets sizes of panes before and after splitter being dragged.
499                //              Called during a drag, for active sizing, or at the end of a drag otherwise.
500                // delta: Number
501                //              Change in slider position compared to start of drag.   But note that
502                //              this function may be called multiple times during drag.
503
504                this.paneBefore.sizeActual = this.paneBefore.sizeBeforeDrag + delta;
505                this.paneAfter.position = this.paneAfter.positionBeforeDrag + delta;
506                this.paneAfter.sizeActual = this.paneAfter.sizeBeforeDrag - delta;
507
508                array.forEach(this.getChildren(), function(child){
509                        child.sizeShare = child.sizeActual;
510                });
511
512                if(this._started){
513                        this.layout();
514                }
515        },
516
517        _showSizingLine: function(){
518                // summary:
519                //              Show virtual splitter, for non-active resizing
520
521                this._moveSizingLine(0);
522
523                domGeometry.setMarginBox(this.virtualSizer,
524                        this.isHorizontal ? { w: this.sizerWidth, h: this.paneHeight } : { w: this.paneWidth, h: this.sizerWidth });
525
526                this.virtualSizer.style.display = 'block';
527        },
528
529        _hideSizingLine: function(){
530                this.virtualSizer.style.display = 'none';
531        },
532
533        _moveSizingLine: function(/*Number*/ delta){
534                // summary:
535                //              Called for non-active resizing, to move the virtual splitter without adjusting the size of the panes
536                var pos = delta + this.sizingSplitter.positionBeforeDrag;
537                domStyle.set(this.virtualSizer,(this.isHorizontal ? "left" : "top"),pos+"px");
538        },
539
540        _getCookieName: function(i){
541                return this.id + "_" + i;
542        },
543
544        _restoreState: function(){
545                array.forEach(this.getChildren(), function(child, i){
546                        var cookieName = this._getCookieName(i);
547                        var cookieValue = cookie(cookieName);
548                        if(cookieValue){
549                                var pos = parseInt(cookieValue);
550                                if(typeof pos == "number"){
551                                        child.sizeShare = pos;
552                                }
553                        }
554                }, this);
555        },
556
557        _saveState: function(){
558                if(!this.persist){
559                        return;
560                }
561                array.forEach(this.getChildren(), function(child, i){
562                        cookie(this._getCookieName(i), child.sizeShare, {expires:365});
563                }, this);
564        }
565});
566
567SplitContainer.ChildWidgetProperties = {
568        // summary:
569        //              These properties can be specified for the children of a SplitContainer.
570
571        // sizeMin: [deprecated] Integer
572        //              Minimum size (width or height) of a child of a SplitContainer.
573        //              The value is relative to other children's sizeShare properties.
574        sizeMin: 10,
575
576        // sizeShare: [deprecated] Integer
577        //              Size (width or height) of a child of a SplitContainer.
578        //              The value is relative to other children's sizeShare properties.
579        //              For example, if there are two children and each has sizeShare=10, then
580        //              each takes up 50% of the available space.
581        sizeShare: 10
582};
583
584// Since any widget can be specified as a SplitContainer child, mix them
585// into the base widget class.  (This is a hack, but it's effective.)
586// This is for the benefit of the parser.   Remove for 2.0.  Also, hide from doc viewer.
587lang.extend(_WidgetBase, /*===== {} || =====*/ SplitContainer.ChildWidgetProperties);
588
589return SplitContainer;
590
591});
Note: See TracBrowser for help on using the repository browser.