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