source: Dev/trunk/src/client/dijit/layout/BorderContainer.js @ 532

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

Added Dojo 1.9.3 release.

File size: 16.3 KB
Line 
1define([
2        "dojo/_base/array", // array.filter array.forEach array.map
3        "dojo/cookie", // cookie
4        "dojo/_base/declare", // declare
5        "dojo/dom-class", // domClass.add domClass.remove domClass.toggle
6        "dojo/dom-construct", // domConstruct.destroy domConstruct.place
7        "dojo/dom-geometry", // domGeometry.marginBox
8        "dojo/dom-style", // domStyle.style
9        "dojo/keys",
10        "dojo/_base/lang", // getObject() hitch() delegate()
11        "dojo/on",
12        "dojo/touch",
13        "../_WidgetBase",
14        "../_Widget",
15        "../_TemplatedMixin",
16        "./LayoutContainer",
17        "./utils"        // layoutUtils.layoutChildren
18], function(array, cookie, declare, domClass, domConstruct, domGeometry, domStyle, keys, lang, on, touch,
19                        _WidgetBase, _Widget, _TemplatedMixin, LayoutContainer, layoutUtils){
20
21        // module:
22        //              dijit/layout/BorderContainer
23
24        var _Splitter = declare("dijit.layout._Splitter", [_Widget, _TemplatedMixin ], {
25                // summary:
26                //              A draggable spacer between two items in a `dijit/layout/BorderContainer`.
27                // description:
28                //              This is instantiated by `dijit/layout/BorderContainer`.  Users should not
29                //              create it directly.
30                // tags:
31                //              private
32
33                /*=====
34                 // container: [const] dijit/layout/BorderContainer
35                 //             Pointer to the parent BorderContainer
36                 container: null,
37
38                 // child: [const] dijit/layout/_LayoutWidget
39                 //             Pointer to the pane associated with this splitter
40                 child: null,
41
42                 // region: [const] String
43                 //             Region of pane associated with this splitter.
44                 //             "top", "bottom", "left", "right".
45                 region: null,
46                 =====*/
47
48                // live: [const] Boolean
49                //              If true, the child's size changes and the child widget is redrawn as you drag the splitter;
50                //              otherwise, the size doesn't change until you drop the splitter (by mouse-up)
51                live: true,
52
53                templateString: '<div class="dijitSplitter" data-dojo-attach-event="onkeydown:_onKeyDown,press:_startDrag,onmouseenter:_onMouse,onmouseleave:_onMouse" tabIndex="0" role="separator"><div class="dijitSplitterThumb"></div></div>',
54
55                constructor: function(){
56                        this._handlers = [];
57                },
58
59                postMixInProperties: function(){
60                        this.inherited(arguments);
61
62                        this.horizontal = /top|bottom/.test(this.region);
63                        this._factor = /top|left/.test(this.region) ? 1 : -1;
64                        this._cookieName = this.container.id + "_" + this.region;
65                },
66
67                buildRendering: function(){
68                        this.inherited(arguments);
69
70                        domClass.add(this.domNode, "dijitSplitter" + (this.horizontal ? "H" : "V"));
71
72                        if(this.container.persist){
73                                // restore old size
74                                var persistSize = cookie(this._cookieName);
75                                if(persistSize){
76                                        this.child.domNode.style[this.horizontal ? "height" : "width"] = persistSize;
77                                }
78                        }
79                },
80
81                _computeMaxSize: function(){
82                        // summary:
83                        //              Return the maximum size that my corresponding pane can be set to
84
85                        var dim = this.horizontal ? 'h' : 'w',
86                                childSize = domGeometry.getMarginBox(this.child.domNode)[dim],
87                                center = array.filter(this.container.getChildren(), function(child){
88                                        return child.region == "center";
89                                })[0];
90
91                        // Can expand until center is crushed.  But always leave room for center's padding + border,
92                        //  otherwise on the next call domGeometry methods start to lie about size.
93                        var spaceAvailable = domGeometry.getContentBox(center.domNode)[dim] - 10;
94
95                        return Math.min(this.child.maxSize, childSize + spaceAvailable);
96                },
97
98                _startDrag: function(e){
99                        if(!this.cover){
100                                this.cover = domConstruct.place("<div class=dijitSplitterCover></div>", this.child.domNode, "after");
101                        }
102                        domClass.add(this.cover, "dijitSplitterCoverActive");
103
104                        // Safeguard in case the stop event was missed.  Shouldn't be necessary if we always get the mouse up.
105                        if(this.fake){
106                                domConstruct.destroy(this.fake);
107                        }
108                        if(!(this._resize = this.live)){ //TODO: disable live for IE6?
109                                // create fake splitter to display at old position while we drag
110                                (this.fake = this.domNode.cloneNode(true)).removeAttribute("id");
111                                domClass.add(this.domNode, "dijitSplitterShadow");
112                                domConstruct.place(this.fake, this.domNode, "after");
113                        }
114                        domClass.add(this.domNode, "dijitSplitterActive dijitSplitter" + (this.horizontal ? "H" : "V") + "Active");
115                        if(this.fake){
116                                domClass.remove(this.fake, "dijitSplitterHover dijitSplitter" + (this.horizontal ? "H" : "V") + "Hover");
117                        }
118
119                        //Performance: load data info local vars for onmousevent function closure
120                        var factor = this._factor,
121                                isHorizontal = this.horizontal,
122                                axis = isHorizontal ? "pageY" : "pageX",
123                                pageStart = e[axis],
124                                splitterStyle = this.domNode.style,
125                                dim = isHorizontal ? 'h' : 'w',
126                                childCS = domStyle.getComputedStyle(this.child.domNode),
127                                childStart = domGeometry.getMarginBox(this.child.domNode, childCS)[dim],
128                                max = this._computeMaxSize(),
129                                min = Math.max(this.child.minSize, domGeometry.getPadBorderExtents(this.child.domNode, childCS)[dim] + 10),
130                                region = this.region,
131                                splitterAttr = region == "top" || region == "bottom" ? "top" : "left", // style attribute of splitter to adjust
132                                splitterStart = parseInt(splitterStyle[splitterAttr], 10),
133                                resize = this._resize,
134                                layoutFunc = lang.hitch(this.container, "_layoutChildren", this.child.id),
135                                de = this.ownerDocument;
136
137                        this._handlers = this._handlers.concat([
138                                on(de, touch.move, this._drag = function(e, forceResize){
139                                        var delta = e[axis] - pageStart,
140                                                childSize = factor * delta + childStart,
141                                                boundChildSize = Math.max(Math.min(childSize, max), min);
142
143                                        if(resize || forceResize){
144                                                layoutFunc(boundChildSize);
145                                        }
146                                        // TODO: setting style directly (usually) sets content box size, need to set margin box size
147                                        splitterStyle[splitterAttr] = delta + splitterStart + factor * (boundChildSize - childSize) + "px";
148                                }),
149                                on(de, "dragstart", function(e){
150                                        e.stopPropagation();
151                                        e.preventDefault();
152                                }),
153                                on(this.ownerDocumentBody, "selectstart", function(e){
154                                        e.stopPropagation();
155                                        e.preventDefault();
156                                }),
157                                on(de, touch.release, lang.hitch(this, "_stopDrag"))
158                        ]);
159                        e.stopPropagation();
160                        e.preventDefault();
161                },
162
163                _onMouse: function(e){
164                        // summary:
165                        //              Handler for onmouseenter / onmouseleave events
166                        var o = (e.type == "mouseover" || e.type == "mouseenter");
167                        domClass.toggle(this.domNode, "dijitSplitterHover", o);
168                        domClass.toggle(this.domNode, "dijitSplitter" + (this.horizontal ? "H" : "V") + "Hover", o);
169                },
170
171                _stopDrag: function(e){
172                        try{
173                                if(this.cover){
174                                        domClass.remove(this.cover, "dijitSplitterCoverActive");
175                                }
176                                if(this.fake){
177                                        domConstruct.destroy(this.fake);
178                                }
179                                domClass.remove(this.domNode, "dijitSplitterActive dijitSplitter"
180                                        + (this.horizontal ? "H" : "V") + "Active dijitSplitterShadow");
181                                this._drag(e); //TODO: redundant with onmousemove?
182                                this._drag(e, true);
183                        }finally{
184                                this._cleanupHandlers();
185                                delete this._drag;
186                        }
187
188                        if(this.container.persist){
189                                cookie(this._cookieName, this.child.domNode.style[this.horizontal ? "height" : "width"], {expires: 365});
190                        }
191                },
192
193                _cleanupHandlers: function(){
194                        var h;
195                        while(h = this._handlers.pop()){
196                                h.remove();
197                        }
198                },
199
200                _onKeyDown: function(/*Event*/ e){
201                        // should we apply typematic to this?
202                        this._resize = true;
203                        var horizontal = this.horizontal;
204                        var tick = 1;
205                        switch(e.keyCode){
206                                case horizontal ? keys.UP_ARROW : keys.LEFT_ARROW:
207                                        tick *= -1;
208//                              break;
209                                case horizontal ? keys.DOWN_ARROW : keys.RIGHT_ARROW:
210                                        break;
211                                default:
212//                              this.inherited(arguments);
213                                        return;
214                        }
215                        var childSize = domGeometry.getMarginSize(this.child.domNode)[ horizontal ? 'h' : 'w' ] + this._factor * tick;
216                        this.container._layoutChildren(this.child.id, Math.max(Math.min(childSize, this._computeMaxSize()), this.child.minSize));
217                        e.stopPropagation();
218                        e.preventDefault();
219                },
220
221                destroy: function(){
222                        this._cleanupHandlers();
223                        delete this.child;
224                        delete this.container;
225                        delete this.cover;
226                        delete this.fake;
227                        this.inherited(arguments);
228                }
229        });
230
231        var _Gutter = declare("dijit.layout._Gutter", [_Widget, _TemplatedMixin], {
232                // summary:
233                //              Just a spacer div to separate side pane from center pane.
234                //              Basically a trick to lookup the gutter/splitter width from the theme.
235                // description:
236                //              Instantiated by `dijit/layout/BorderContainer`.  Users should not
237                //              create directly.
238                // tags:
239                //              private
240
241                templateString: '<div class="dijitGutter" role="presentation"></div>',
242
243                postMixInProperties: function(){
244                        this.inherited(arguments);
245                        this.horizontal = /top|bottom/.test(this.region);
246                },
247
248                buildRendering: function(){
249                        this.inherited(arguments);
250                        domClass.add(this.domNode, "dijitGutter" + (this.horizontal ? "H" : "V"));
251                }
252        });
253
254        var BorderContainer = declare("dijit.layout.BorderContainer", LayoutContainer, {
255                // summary:
256                //              A BorderContainer is a `dijit/LayoutContainer` that can have draggable splitters between the children,
257                //              in order to adjust their sizes.
258                //
259                //              In addition, it automatically adds some space between the children even
260                //              if they don't have a draggable splitter between them, and space between the edge of the BorderContainer
261                //              and the children that are adjacent to the edge.  Note that the intended style is that all the children
262                //              have borders, but (despite the name) the BorderContainer itself does not.
263                //
264                //              See `BorderContainer.ChildWidgetProperties` for details on the properties that can be set on
265                //              children of a `BorderContainer`.
266
267                // gutters: [const] Boolean
268                //              Give each pane a border and margin.
269                //              Margin determined by domNode.paddingLeft.
270                //              When false, only resizable panes have a gutter (i.e. draggable splitter) for resizing.
271                gutters: true,
272
273                // liveSplitters: [const] Boolean
274                //              Specifies whether splitters resize as you drag (true) or only upon mouseup (false)
275                liveSplitters: true,
276
277                // persist: Boolean
278                //              Save splitter positions in a cookie.
279                persist: false,
280
281                baseClass: "dijitBorderContainer",
282
283                // _splitterClass: Function||String
284                //              Optional hook to override the default Splitter widget used by BorderContainer
285                _splitterClass: _Splitter,
286
287                postMixInProperties: function(){
288                        // change class name to indicate that BorderContainer is being used purely for
289                        // layout (like LayoutContainer) rather than for pretty formatting.
290                        if(!this.gutters){
291                                this.baseClass += "NoGutter";
292                        }
293                        this.inherited(arguments);
294                },
295
296                _setupChild: function(/*dijit/_WidgetBase*/ child){
297                        // Override LayoutContainer._setupChild().
298
299                        this.inherited(arguments);
300
301                        var region = child.region, ltr = child.isLeftToRight();
302                        if(region == "leading"){
303                                region = ltr ? "left" : "right";
304                        }
305                        if(region == "trailing"){
306                                region = ltr ? "right" : "left";
307                        }
308
309                        if(region){
310                                // Create draggable splitter for resizing pane,
311                                // or alternately if splitter=false but BorderContainer.gutters=true then
312                                // insert dummy div just for spacing
313                                if(region != "center" && (child.splitter || this.gutters) && !child._splitterWidget){
314                                        var _Splitter = child.splitter ? this._splitterClass : _Gutter;
315                                        if(lang.isString(_Splitter)){
316                                                _Splitter = lang.getObject(_Splitter);  // for back-compat, remove in 2.0
317                                        }
318                                        var splitter = new _Splitter({
319                                                id: child.id + "_splitter",
320                                                container: this,
321                                                child: child,
322                                                region: region,
323                                                live: this.liveSplitters
324                                        });
325                                        splitter.isSplitter = true;
326                                        child._splitterWidget = splitter;
327
328                                        // Make the tab order match the visual layout by placing the splitter before or after the pane,
329                                        // depending on where the splitter is visually compared to the pane.
330                                        var before = region == "bottom" || region == (this.isLeftToRight() ? "right" : "left");
331                                        domConstruct.place(splitter.domNode, child.domNode, before ? "before" : "after");
332
333                                        // Splitters aren't added as Contained children, so we need to call startup explicitly
334                                        splitter.startup();
335                                }
336                        }
337                },
338
339                layout: function(){
340                        // Implement _LayoutWidget.layout() virtual method.
341                        this._layoutChildren();
342                },
343
344                removeChild: function(/*dijit/_WidgetBase*/ child){
345                        // Override _LayoutWidget.removeChild().
346
347                        var splitter = child._splitterWidget;
348                        if(splitter){
349                                splitter.destroy();
350                                delete child._splitterWidget;
351                        }
352
353                        this.inherited(arguments);
354                },
355
356                getChildren: function(){
357                        // Override _LayoutWidget.getChildren() to only return real children, not the splitters.
358                        return array.filter(this.inherited(arguments), function(widget){
359                                return !widget.isSplitter;
360                        });
361                },
362
363                // TODO: remove in 2.0
364                getSplitter: function(/*String*/region){
365                        // summary:
366                        //              Returns the widget responsible for rendering the splitter associated with region
367                        // tags:
368                        //              deprecated
369                        return array.filter(this.getChildren(), function(child){
370                                return child.region == region;
371                        })[0]._splitterWidget;
372                },
373
374                resize: function(newSize, currentSize){
375                        // Overrides _LayoutWidget.resize().
376
377                        // resetting potential padding to 0px to provide support for 100% width/height + padding
378                        // TODO: this hack doesn't respect the box model and is a temporary fix
379                        if(!this.cs || !this.pe){
380                                var node = this.domNode;
381                                this.cs = domStyle.getComputedStyle(node);
382                                this.pe = domGeometry.getPadExtents(node, this.cs);
383                                this.pe.r = domStyle.toPixelValue(node, this.cs.paddingRight);
384                                this.pe.b = domStyle.toPixelValue(node, this.cs.paddingBottom);
385
386                                domStyle.set(node, "padding", "0px");
387                        }
388
389                        this.inherited(arguments);
390                },
391
392                _layoutChildren: function(/*String?*/ changedChildId, /*Number?*/ changedChildSize){
393                        // summary:
394                        //              This is the main routine for setting size/position of each child.
395                        // description:
396                        //              With no arguments, measures the height of top/bottom panes, the width
397                        //              of left/right panes, and then sizes all panes accordingly.
398                        //
399                        //              With changedRegion specified (as "left", "top", "bottom", or "right"),
400                        //              it changes that region's width/height to changedRegionSize and
401                        //              then resizes other regions that were affected.
402                        // changedChildId:
403                        //              Id of the child which should be resized because splitter was dragged.
404                        // changedChildSize:
405                        //              The new width/height (in pixels) to make specified child
406
407                        if(!this._borderBox || !this._borderBox.h){
408                                // We are currently hidden, or we haven't been sized by our parent yet.
409                                // Abort.   Someone will resize us later.
410                                return;
411                        }
412
413                        // Combining the externally specified children with splitters and gutters
414                        var childrenAndSplitters = [];
415                        array.forEach(this._getOrderedChildren(), function(pane){
416                                childrenAndSplitters.push(pane);
417                                if(pane._splitterWidget){
418                                        childrenAndSplitters.push(pane._splitterWidget);
419                                }
420                        });
421
422                        // Compute the box in which to lay out my children
423                        var dim = {
424                                l: this.pe.l,
425                                t: this.pe.t,
426                                w: this._borderBox.w - this.pe.w,
427                                h: this._borderBox.h - this.pe.h
428                        };
429
430                        // Layout the children, possibly changing size due to a splitter drag
431                        layoutUtils.layoutChildren(this.domNode, dim, childrenAndSplitters,
432                                changedChildId, changedChildSize);
433                },
434
435                destroyRecursive: function(){
436                        // Destroy splitters first, while getChildren() still works
437                        array.forEach(this.getChildren(), function(child){
438                                var splitter = child._splitterWidget;
439                                if(splitter){
440                                        splitter.destroy();
441                                }
442                                delete child._splitterWidget;
443                        });
444
445                        // Then destroy the real children, and myself
446                        this.inherited(arguments);
447                }
448        });
449
450        BorderContainer.ChildWidgetProperties = {
451                // summary:
452                //              These properties can be specified for the children of a BorderContainer.
453
454                // splitter: [const] Boolean
455                //              Parameter for children where region != "center".
456                //              If true, enables user to resize the widget by putting a draggable splitter between
457                //              this widget and the region=center widget.
458                splitter: false,
459
460                // minSize: [const] Number
461                //              Specifies a minimum size (in pixels) for this widget when resized by a splitter.
462                minSize: 0,
463
464                // maxSize: [const] Number
465                //              Specifies a maximum size (in pixels) for this widget when resized by a splitter.
466                maxSize: Infinity
467        };
468        lang.mixin(BorderContainer.ChildWidgetProperties, LayoutContainer.ChildWidgetProperties);
469
470        // Since any widget can be specified as a BorderContainer child, mix it
471        // into the base widget class.  (This is a hack, but it's effective.)
472        // This is for the benefit of the parser.   Remove for 2.0.  Also, hide from doc viewer.
473        lang.extend(_WidgetBase, /*===== {} || =====*/ BorderContainer.ChildWidgetProperties);
474
475        // For monkey patching
476        BorderContainer._Splitter = _Splitter;
477        BorderContainer._Gutter = _Gutter;
478
479        return BorderContainer;
480});
Note: See TracBrowser for help on using the repository browser.