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