Subversion Repositories Applications.papyrus

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

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