]> git.wh0rd.org Git - tt-rss.git/blob - lib/dijit/layout/BorderContainer.js.uncompressed.js
update dojo to 1.7.3
[tt-rss.git] / lib / dijit / layout / BorderContainer.js.uncompressed.js
1 define("dijit/layout/BorderContainer", [
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/_base/event", // event.stop
10         "dojo/keys",
11         "dojo/_base/lang", // lang.getObject lang.hitch
12         "dojo/on",
13         "dojo/touch",
14         "dojo/_base/window", // win.body win.doc win.doc.createElement
15         "../_WidgetBase",
16         "../_Widget",
17         "../_TemplatedMixin",
18         "./_LayoutWidget",
19         "./utils"               // layoutUtils.layoutChildren
20 ], function(array, cookie, declare, domClass, domConstruct, domGeometry, domStyle, event, keys, lang, on, touch, win,
21                         _WidgetBase, _Widget, _TemplatedMixin, _LayoutWidget, layoutUtils){
22
23 /*=====
24         var _WidgetBase = dijit._WidgetBase;
25         var _Widget = dijit._Widget;
26         var _TemplatedMixin = dijit._TemplatedMixin;
27         var _LayoutWidget = dijit.layout._LayoutWidget;
28 =====*/
29
30 // module:
31 //              dijit/layout/BorderContainer
32 // summary:
33 //              Provides layout in up to 5 regions, a mandatory center with optional borders along its 4 sides.
34
35 var _Splitter = declare("dijit.layout._Splitter", [_Widget, _TemplatedMixin ],
36 {
37         // summary:
38         //              A draggable spacer between two items in a `dijit.layout.BorderContainer`.
39         // description:
40         //              This is instantiated by `dijit.layout.BorderContainer`.  Users should not
41         //              create it directly.
42         // tags:
43         //              private
44
45 /*=====
46         // container: [const] dijit.layout.BorderContainer
47         //              Pointer to the parent BorderContainer
48         container: null,
49
50         // child: [const] dijit.layout._LayoutWidget
51         //              Pointer to the pane associated with this splitter
52         child: null,
53
54         // region: [const] String
55         //              Region of pane associated with this splitter.
56         //              "top", "bottom", "left", "right".
57         region: null,
58 =====*/
59
60         // live: [const] Boolean
61         //              If true, the child's size changes and the child widget is redrawn as you drag the splitter;
62         //              otherwise, the size doesn't change until you drop the splitter (by mouse-up)
63         live: true,
64
65         templateString: '<div class="dijitSplitter" data-dojo-attach-event="onkeypress:_onKeyPress,press:_startDrag,onmouseenter:_onMouse,onmouseleave:_onMouse" tabIndex="0" role="separator"><div class="dijitSplitterThumb"></div></div>',
66
67         constructor: function(){
68                 this._handlers = [];
69         },
70
71         postMixInProperties: function(){
72                 this.inherited(arguments);
73
74                 this.horizontal = /top|bottom/.test(this.region);
75                 this._factor = /top|left/.test(this.region) ? 1 : -1;
76                 this._cookieName = this.container.id + "_" + this.region;
77         },
78
79         buildRendering: function(){
80                 this.inherited(arguments);
81
82                 domClass.add(this.domNode, "dijitSplitter" + (this.horizontal ? "H" : "V"));
83
84                 if(this.container.persist){
85                         // restore old size
86                         var persistSize = cookie(this._cookieName);
87                         if(persistSize){
88                                 this.child.domNode.style[this.horizontal ? "height" : "width"] = persistSize;
89                         }
90                 }
91         },
92
93         _computeMaxSize: function(){
94                 // summary:
95                 //              Return the maximum size that my corresponding pane can be set to
96
97                 var dim = this.horizontal ? 'h' : 'w',
98                         childSize = domGeometry.getMarginBox(this.child.domNode)[dim],
99                         center = array.filter(this.container.getChildren(), function(child){ return child.region == "center";})[0],
100                         spaceAvailable = domGeometry.getMarginBox(center.domNode)[dim]; // can expand until center is crushed to 0
101
102                 return Math.min(this.child.maxSize, childSize + spaceAvailable);
103         },
104
105         _startDrag: function(e){
106                 if(!this.cover){
107                         this.cover = win.doc.createElement('div');
108                         domClass.add(this.cover, "dijitSplitterCover");
109                         domConstruct.place(this.cover, this.child.domNode, "after");
110                 }
111                 domClass.add(this.cover, "dijitSplitterCoverActive");
112
113                 // Safeguard in case the stop event was missed.  Shouldn't be necessary if we always get the mouse up.
114                 if(this.fake){ domConstruct.destroy(this.fake); }
115                 if(!(this._resize = this.live)){ //TODO: disable live for IE6?
116                         // create fake splitter to display at old position while we drag
117                         (this.fake = this.domNode.cloneNode(true)).removeAttribute("id");
118                         domClass.add(this.domNode, "dijitSplitterShadow");
119                         domConstruct.place(this.fake, this.domNode, "after");
120                 }
121                 domClass.add(this.domNode, "dijitSplitterActive dijitSplitter" + (this.horizontal ? "H" : "V") + "Active");
122                 if(this.fake){
123                         domClass.remove(this.fake, "dijitSplitterHover dijitSplitter" + (this.horizontal ? "H" : "V") + "Hover");
124                 }
125
126                 //Performance: load data info local vars for onmousevent function closure
127                 var factor = this._factor,
128                         isHorizontal = this.horizontal,
129                         axis = isHorizontal ? "pageY" : "pageX",
130                         pageStart = e[axis],
131                         splitterStyle = this.domNode.style,
132                         dim = isHorizontal ? 'h' : 'w',
133                         childStart = domGeometry.getMarginBox(this.child.domNode)[dim],
134                         max = this._computeMaxSize(),
135                         min = this.child.minSize || 20,
136                         region = this.region,
137                         splitterAttr = region == "top" || region == "bottom" ? "top" : "left",  // style attribute of splitter to adjust
138                         splitterStart = parseInt(splitterStyle[splitterAttr], 10),
139                         resize = this._resize,
140                         layoutFunc = lang.hitch(this.container, "_layoutChildren", this.child.id),
141                         de = win.doc;
142
143                 this._handlers = this._handlers.concat([
144                         on(de, touch.move, this._drag = function(e, forceResize){
145                                 var delta = e[axis] - pageStart,
146                                         childSize = factor * delta + childStart,
147                                         boundChildSize = Math.max(Math.min(childSize, max), min);
148
149                                 if(resize || forceResize){
150                                         layoutFunc(boundChildSize);
151                                 }
152                                 // TODO: setting style directly (usually) sets content box size, need to set margin box size
153                                 splitterStyle[splitterAttr] = delta + splitterStart + factor*(boundChildSize - childSize) + "px";
154                         }),
155                         on(de, "dragstart", event.stop),
156                         on(win.body(), "selectstart", event.stop),
157                         on(de, touch.release, lang.hitch(this, "_stopDrag"))
158                 ]);
159                 event.stop(e);
160         },
161
162         _onMouse: function(e){
163                 // summary:
164                 //              Handler for onmouseenter / onmouseleave events
165                 var o = (e.type == "mouseover" || e.type == "mouseenter");
166                 domClass.toggle(this.domNode, "dijitSplitterHover", o);
167                 domClass.toggle(this.domNode, "dijitSplitter" + (this.horizontal ? "H" : "V") + "Hover", o);
168         },
169
170         _stopDrag: function(e){
171                 try{
172                         if(this.cover){
173                                 domClass.remove(this.cover, "dijitSplitterCoverActive");
174                         }
175                         if(this.fake){ domConstruct.destroy(this.fake); }
176                         domClass.remove(this.domNode, "dijitSplitterActive dijitSplitter"
177                                 + (this.horizontal ? "H" : "V") + "Active dijitSplitterShadow");
178                         this._drag(e); //TODO: redundant with onmousemove?
179                         this._drag(e, true);
180                 }finally{
181                         this._cleanupHandlers();
182                         delete this._drag;
183                 }
184
185                 if(this.container.persist){
186                         cookie(this._cookieName, this.child.domNode.style[this.horizontal ? "height" : "width"], {expires:365});
187                 }
188         },
189
190         _cleanupHandlers: function(){
191                 var h;
192                 while(h = this._handlers.pop()){ h.remove(); }
193         },
194
195         _onKeyPress: function(/*Event*/ e){
196                 // should we apply typematic to this?
197                 this._resize = true;
198                 var horizontal = this.horizontal;
199                 var tick = 1;
200                 switch(e.charOrCode){
201                         case horizontal ? keys.UP_ARROW : keys.LEFT_ARROW:
202                                 tick *= -1;
203 //                              break;
204                         case horizontal ? keys.DOWN_ARROW : keys.RIGHT_ARROW:
205                                 break;
206                         default:
207 //                              this.inherited(arguments);
208                                 return;
209                 }
210                 var childSize = domGeometry.getMarginSize(this.child.domNode)[ horizontal ? 'h' : 'w' ] + this._factor * tick;
211                 this.container._layoutChildren(this.child.id, Math.max(Math.min(childSize, this._computeMaxSize()), this.child.minSize));
212                 event.stop(e);
213         },
214
215         destroy: function(){
216                 this._cleanupHandlers();
217                 delete this.child;
218                 delete this.container;
219                 delete this.cover;
220                 delete this.fake;
221                 this.inherited(arguments);
222         }
223 });
224
225 var _Gutter = declare("dijit.layout._Gutter", [_Widget, _TemplatedMixin],
226 {
227         // summary:
228         //              Just a spacer div to separate side pane from center pane.
229         //              Basically a trick to lookup the gutter/splitter width from the theme.
230         // description:
231         //              Instantiated by `dijit.layout.BorderContainer`.  Users should not
232         //              create directly.
233         // tags:
234         //              private
235
236         templateString: '<div class="dijitGutter" role="presentation"></div>',
237
238         postMixInProperties: function(){
239                 this.inherited(arguments);
240                 this.horizontal = /top|bottom/.test(this.region);
241         },
242
243         buildRendering: function(){
244                 this.inherited(arguments);
245                 domClass.add(this.domNode, "dijitGutter" + (this.horizontal ? "H" : "V"));
246         }
247 });
248
249 var BorderContainer = declare("dijit.layout.BorderContainer", _LayoutWidget, {
250         // summary:
251         //              Provides layout in up to 5 regions, a mandatory center with optional borders along its 4 sides.
252         //
253         // description:
254         //              A BorderContainer is a box with a specified size, such as style="width: 500px; height: 500px;",
255         //              that contains a child widget marked region="center" and optionally children widgets marked
256         //              region equal to "top", "bottom", "leading", "trailing", "left" or "right".
257         //              Children along the edges will be laid out according to width or height dimensions and may
258         //              include optional splitters (splitter="true") to make them resizable by the user.  The remaining
259         //              space is designated for the center region.
260         //
261         //              The outer size must be specified on the BorderContainer node.  Width must be specified for the sides
262         //              and height for the top and bottom, respectively.  No dimensions should be specified on the center;
263         //              it will fill the remaining space.  Regions named "leading" and "trailing" may be used just like
264         //              "left" and "right" except that they will be reversed in right-to-left environments.
265         //
266         //              For complex layouts, multiple children can be specified for a single region.   In this case, the
267         //              layoutPriority flag on the children determines which child is closer to the edge (low layoutPriority)
268         //              and which child is closer to the center (high layoutPriority).   layoutPriority can also be used
269         //              instead of the design attribute to control layout precedence of horizontal vs. vertical panes.
270         // example:
271         // |    <div data-dojo-type="dijit.layout.BorderContainer" data-dojo-props="design: 'sidebar', gutters: false"
272         // |            style="width: 400px; height: 300px;">
273         // |            <div data-dojo-type="dijit.layout.ContentPane" data-dojo-props="region: 'top'">header text</div>
274         // |            <div data-dojo-type="dijit.layout.ContentPane" data-dojo-props="region: 'right', splitter: true" style="width: 200px;">table of contents</div>
275         // |            <div data-dojo-type="dijit.layout.ContentPane" data-dojo-props="region: 'center'">client area</div>
276         // |    </div>
277
278         // design: String
279         //              Which design is used for the layout:
280         //                      - "headline" (default) where the top and bottom extend
281         //                              the full width of the container
282         //                      - "sidebar" where the left and right sides extend from top to bottom.
283         design: "headline",
284
285         // gutters: [const] Boolean
286         //              Give each pane a border and margin.
287         //              Margin determined by domNode.paddingLeft.
288         //              When false, only resizable panes have a gutter (i.e. draggable splitter) for resizing.
289         gutters: true,
290
291         // liveSplitters: [const] Boolean
292         //              Specifies whether splitters resize as you drag (true) or only upon mouseup (false)
293         liveSplitters: true,
294
295         // persist: Boolean
296         //              Save splitter positions in a cookie.
297         persist: false,
298
299         baseClass: "dijitBorderContainer",
300
301         // _splitterClass: Function||String
302         //              Optional hook to override the default Splitter widget used by BorderContainer
303         _splitterClass: _Splitter,
304
305         postMixInProperties: function(){
306                 // change class name to indicate that BorderContainer is being used purely for
307                 // layout (like LayoutContainer) rather than for pretty formatting.
308                 if(!this.gutters){
309                         this.baseClass += "NoGutter";
310                 }
311                 this.inherited(arguments);
312         },
313
314         startup: function(){
315                 if(this._started){ return; }
316                 array.forEach(this.getChildren(), this._setupChild, this);
317                 this.inherited(arguments);
318         },
319
320         _setupChild: function(/*dijit._Widget*/ child){
321                 // Override _LayoutWidget._setupChild().
322
323                 var region = child.region;
324                 if(region){
325                         this.inherited(arguments);
326
327                         domClass.add(child.domNode, this.baseClass+"Pane");
328
329                         var ltr = this.isLeftToRight();
330                         if(region == "leading"){ region = ltr ? "left" : "right"; }
331                         if(region == "trailing"){ region = ltr ? "right" : "left"; }
332
333                         // Create draggable splitter for resizing pane,
334                         // or alternately if splitter=false but BorderContainer.gutters=true then
335                         // insert dummy div just for spacing
336                         if(region != "center" && (child.splitter || this.gutters) && !child._splitterWidget){
337                                 var _Splitter = child.splitter ? this._splitterClass : _Gutter;
338                                 if(lang.isString(_Splitter)){
339                                         _Splitter = lang.getObject(_Splitter);  // for back-compat, remove in 2.0
340                                 }
341                                 var splitter = new _Splitter({
342                                         id: child.id + "_splitter",
343                                         container: this,
344                                         child: child,
345                                         region: region,
346                                         live: this.liveSplitters
347                                 });
348                                 splitter.isSplitter = true;
349                                 child._splitterWidget = splitter;
350
351                                 domConstruct.place(splitter.domNode, child.domNode, "after");
352
353                                 // Splitters aren't added as Contained children, so we need to call startup explicitly
354                                 splitter.startup();
355                         }
356                         child.region = region;  // TODO: technically wrong since it overwrites "trailing" with "left" etc.
357                 }
358         },
359
360         layout: function(){
361                 // Implement _LayoutWidget.layout() virtual method.
362                 this._layoutChildren();
363         },
364
365         addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){
366                 // Override _LayoutWidget.addChild().
367                 this.inherited(arguments);
368                 if(this._started){
369                         this.layout(); //OPT
370                 }
371         },
372
373         removeChild: function(/*dijit._Widget*/ child){
374                 // Override _LayoutWidget.removeChild().
375
376                 var region = child.region;
377                 var splitter = child._splitterWidget;
378                 if(splitter){
379                         splitter.destroy();
380                         delete child._splitterWidget;
381                 }
382                 this.inherited(arguments);
383
384                 if(this._started){
385                         this._layoutChildren();
386                 }
387                 // Clean up whatever style changes we made to the child pane.
388                 // Unclear how height and width should be handled.
389                 domClass.remove(child.domNode, this.baseClass+"Pane");
390                 domStyle.set(child.domNode, {
391                         top: "auto",
392                         bottom: "auto",
393                         left: "auto",
394                         right: "auto",
395                         position: "static"
396                 });
397                 domStyle.set(child.domNode, region == "top" || region == "bottom" ? "width" : "height", "auto");
398         },
399
400         getChildren: function(){
401                 // Override _LayoutWidget.getChildren() to only return real children, not the splitters.
402                 return array.filter(this.inherited(arguments), function(widget){
403                         return !widget.isSplitter;
404                 });
405         },
406
407         // TODO: remove in 2.0
408         getSplitter: function(/*String*/region){
409                 // summary:
410                 //              Returns the widget responsible for rendering the splitter associated with region
411                 // tags:
412                 //              deprecated
413                 return array.filter(this.getChildren(), function(child){
414                         return child.region == region;
415                 })[0]._splitterWidget;
416         },
417
418         resize: function(newSize, currentSize){
419                 // Overrides _LayoutWidget.resize().
420
421                 // resetting potential padding to 0px to provide support for 100% width/height + padding
422                 // TODO: this hack doesn't respect the box model and is a temporary fix
423                 if(!this.cs || !this.pe){
424                         var node = this.domNode;
425                         this.cs = domStyle.getComputedStyle(node);
426                         this.pe = domGeometry.getPadExtents(node, this.cs);
427                         this.pe.r = domStyle.toPixelValue(node, this.cs.paddingRight);
428                         this.pe.b = domStyle.toPixelValue(node, this.cs.paddingBottom);
429
430                         domStyle.set(node, "padding", "0px");
431                 }
432
433                 this.inherited(arguments);
434         },
435
436         _layoutChildren: function(/*String?*/ changedChildId, /*Number?*/ changedChildSize){
437                 // summary:
438                 //              This is the main routine for setting size/position of each child.
439                 // description:
440                 //              With no arguments, measures the height of top/bottom panes, the width
441                 //              of left/right panes, and then sizes all panes accordingly.
442                 //
443                 //              With changedRegion specified (as "left", "top", "bottom", or "right"),
444                 //              it changes that region's width/height to changedRegionSize and
445                 //              then resizes other regions that were affected.
446                 // changedChildId:
447                 //              Id of the child which should be resized because splitter was dragged.
448                 // changedChildSize:
449                 //              The new width/height (in pixels) to make specified child
450
451                 if(!this._borderBox || !this._borderBox.h){
452                         // We are currently hidden, or we haven't been sized by our parent yet.
453                         // Abort.   Someone will resize us later.
454                         return;
455                 }
456
457                 // Generate list of wrappers of my children in the order that I want layoutChildren()
458                 // to process them (i.e. from the outside to the inside)
459                 var wrappers = array.map(this.getChildren(), function(child, idx){
460                         return {
461                                 pane: child,
462                                 weight: [
463                                         child.region == "center" ? Infinity : 0,
464                                         child.layoutPriority,
465                                         (this.design == "sidebar" ? 1 : -1) * (/top|bottom/.test(child.region) ? 1 : -1),
466                                         idx
467                                 ]
468                         };
469                 }, this);
470                 wrappers.sort(function(a, b){
471                         var aw = a.weight, bw = b.weight;
472                         for(var i=0; i<aw.length; i++){
473                                 if(aw[i] != bw[i]){
474                                         return aw[i] - bw[i];
475                                 }
476                         }
477                         return 0;
478                 });
479
480                 // Make new list, combining the externally specified children with splitters and gutters
481                 var childrenAndSplitters = [];
482                 array.forEach(wrappers, function(wrapper){
483                         var pane = wrapper.pane;
484                         childrenAndSplitters.push(pane);
485                         if(pane._splitterWidget){
486                                 childrenAndSplitters.push(pane._splitterWidget);
487                         }
488                 });
489
490                 // Compute the box in which to lay out my children
491                 var dim = {
492                         l: this.pe.l,
493                         t: this.pe.t,
494                         w: this._borderBox.w - this.pe.w,
495                         h: this._borderBox.h - this.pe.h
496                 };
497
498                 // Layout the children, possibly changing size due to a splitter drag
499                 layoutUtils.layoutChildren(this.domNode, dim, childrenAndSplitters,
500                         changedChildId, changedChildSize);
501         },
502
503         destroyRecursive: function(){
504                 // Destroy splitters first, while getChildren() still works
505                 array.forEach(this.getChildren(), function(child){
506                         var splitter = child._splitterWidget;
507                         if(splitter){
508                                 splitter.destroy();
509                         }
510                         delete child._splitterWidget;
511                 });
512
513                 // Then destroy the real children, and myself
514                 this.inherited(arguments);
515         }
516 });
517
518 // This argument can be specified for the children of a BorderContainer.
519 // Since any widget can be specified as a LayoutContainer child, mix it
520 // into the base widget class.  (This is a hack, but it's effective.)
521 lang.extend(_WidgetBase, {
522         // region: [const] String
523         //              Parameter for children of `dijit.layout.BorderContainer`.
524         //              Values: "top", "bottom", "leading", "trailing", "left", "right", "center".
525         //              See the `dijit.layout.BorderContainer` description for details.
526         region: '',
527
528         // layoutPriority: [const] Number
529         //              Parameter for children of `dijit.layout.BorderContainer`.
530         //              Children with a higher layoutPriority will be placed closer to the BorderContainer center,
531         //              between children with a lower layoutPriority.
532         layoutPriority: 0,
533
534         // splitter: [const] Boolean
535         //              Parameter for child of `dijit.layout.BorderContainer` where region != "center".
536         //              If true, enables user to resize the widget by putting a draggable splitter between
537         //              this widget and the region=center widget.
538         splitter: false,
539
540         // minSize: [const] Number
541         //              Parameter for children of `dijit.layout.BorderContainer`.
542         //              Specifies a minimum size (in pixels) for this widget when resized by a splitter.
543         minSize: 0,
544
545         // maxSize: [const] Number
546         //              Parameter for children of `dijit.layout.BorderContainer`.
547         //              Specifies a maximum size (in pixels) for this widget when resized by a splitter.
548         maxSize: Infinity
549 });
550
551 // For monkey patching
552 BorderContainer._Splitter = _Splitter;
553 BorderContainer._Gutter = _Gutter;
554
555 return BorderContainer;
556 });