source: Dev/branches/rest-dojo-ui/client/dijit/layout/SplitContainer.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

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