roojs-ui.js
[roojs1] / Roo / LayoutRegion.js
1 /*
2  * Based on:
3  * Ext JS Library 1.1.1
4  * Copyright(c) 2006-2007, Ext JS, LLC.
5  *
6  * Originally Released Under LGPL - original licence link has changed is not relivant.
7  *
8  * Fork - LGPL
9  * <script type="text/javascript">
10  */
11  
12 /**
13  * @class Roo.LayoutRegion
14  * @extends Roo.BasicLayoutRegion
15  * This class represents a region in a layout manager.
16  * @cfg {Boolean}   collapsible     False to disable collapsing (defaults to true)
17  * @cfg {Boolean}   collapsed       True to set the initial display to collapsed (defaults to false)
18  * @cfg {Boolean}   floatable       False to disable floating (defaults to true)
19  * @cfg {Object}    margins         Margins for the element (defaults to {top: 0, left: 0, right:0, bottom: 0})
20  * @cfg {Object}    cmargins        Margins for the element when collapsed (defaults to: north/south {top: 2, left: 0, right:0, bottom: 2} or east/west {top: 0, left: 2, right:2, bottom: 0})
21  * @cfg {String}    tabPosition     "top" or "bottom" (defaults to "bottom")
22  * @cfg {String}    collapsedTitle  Optional string message to display in the collapsed block of a north or south region
23  * @cfg {Boolean}   alwaysShowTabs  True to always display tabs even when there is only 1 panel (defaults to false)
24  * @cfg {Boolean}   autoScroll      True to enable overflow scrolling (defaults to false)
25  * @cfg {Boolean}   titlebar        True to display a title bar (defaults to true)
26  * @cfg {String}    title           The title for the region (overrides panel titles)
27  * @cfg {Boolean}   animate         True to animate expand/collapse (defaults to false)
28  * @cfg {Boolean}   autoHide        False to disable auto hiding when the mouse leaves the "floated" region (defaults to true)
29  * @cfg {Boolean}   preservePanels  True to preserve removed panels so they can be readded later (defaults to false)
30  * @cfg {Boolean}   closeOnTab      True to place the close icon on the tabs instead of the region titlebar (defaults to false)
31  * @cfg {Boolean}   hideTabs        True to hide the tab strip (defaults to false)
32  * @cfg {Boolean}   resizeTabs      True to enable automatic tab resizing. This will resize the tabs so they are all the same size and fit within
33  *                      the space available, similar to FireFox 1.5 tabs (defaults to false)
34  * @cfg {Number}    minTabWidth     The minimum tab width (defaults to 40)
35  * @cfg {Number}    preferredTabWidth The preferred tab width (defaults to 150)
36  * @cfg {Boolean}   showPin         True to show a pin button
37  * @cfg {Boolean}   hidden          True to start the region hidden (defaults to false)
38  * @cfg {Boolean}   hideWhenEmpty   True to hide the region when it has no panels
39  * @cfg {Boolean}   disableTabTips  True to disable tab tooltips
40  * @cfg {Number}    width           For East/West panels
41  * @cfg {Number}    height          For North/South panels
42  * @cfg {Boolean}   split           To show the splitter
43  * @cfg {Boolean}   toolbar         xtype configuration for a toolbar - shows on right of tabbar
44  */
45 Roo.LayoutRegion = function(mgr, config, pos){
46     Roo.LayoutRegion.superclass.constructor.call(this, mgr, config, pos, true);
47     var dh = Roo.DomHelper;
48     /** This region's container element 
49     * @type Roo.Element */
50     this.el = dh.append(mgr.el.dom, {tag: "div", cls: "x-layout-panel x-layout-panel-" + this.position}, true);
51     /** This region's title element 
52     * @type Roo.Element */
53
54     this.titleEl = dh.append(this.el.dom, {tag: "div", unselectable: "on", cls: "x-unselectable x-layout-panel-hd x-layout-title-"+this.position, children:[
55         {tag: "span", cls: "x-unselectable x-layout-panel-hd-text", unselectable: "on", html: "&#160;"},
56         {tag: "div", cls: "x-unselectable x-layout-panel-hd-tools", unselectable: "on"}
57     ]}, true);
58     this.titleEl.enableDisplayMode();
59     /** This region's title text element 
60     * @type HTMLElement */
61     this.titleTextEl = this.titleEl.dom.firstChild;
62     this.tools = Roo.get(this.titleEl.dom.childNodes[1], true);
63     this.closeBtn = this.createTool(this.tools.dom, "x-layout-close");
64     this.closeBtn.enableDisplayMode();
65     this.closeBtn.on("click", this.closeClicked, this);
66     this.closeBtn.hide();
67
68     this.createBody(config);
69     this.visible = true;
70     this.collapsed = false;
71
72     if(config.hideWhenEmpty){
73         this.hide();
74         this.on("paneladded", this.validateVisibility, this);
75         this.on("panelremoved", this.validateVisibility, this);
76     }
77     this.applyConfig(config);
78 };
79
80 Roo.extend(Roo.LayoutRegion, Roo.BasicLayoutRegion, {
81
82     createBody : function(){
83         /** This region's body element 
84         * @type Roo.Element */
85         this.bodyEl = this.el.createChild({tag: "div", cls: "x-layout-panel-body"});
86     },
87
88     applyConfig : function(c){
89         if(c.collapsible && this.position != "center" && !this.collapsedEl){
90             var dh = Roo.DomHelper;
91             if(c.titlebar !== false){
92                 this.collapseBtn = this.createTool(this.tools.dom, "x-layout-collapse-"+this.position);
93                 this.collapseBtn.on("click", this.collapse, this);
94                 this.collapseBtn.enableDisplayMode();
95
96                 if(c.showPin === true || this.showPin){
97                     this.stickBtn = this.createTool(this.tools.dom, "x-layout-stick");
98                     this.stickBtn.enableDisplayMode();
99                     this.stickBtn.on("click", this.expand, this);
100                     this.stickBtn.hide();
101                 }
102             }
103             /** This region's collapsed element
104             * @type Roo.Element */
105             this.collapsedEl = dh.append(this.mgr.el.dom, {cls: "x-layout-collapsed x-layout-collapsed-"+this.position, children:[
106                 {cls: "x-layout-collapsed-tools", children:[{cls: "x-layout-ctools-inner"}]}
107             ]}, true);
108             if(c.floatable !== false){
109                this.collapsedEl.addClassOnOver("x-layout-collapsed-over");
110                this.collapsedEl.on("click", this.collapseClick, this);
111             }
112
113             if(c.collapsedTitle && (this.position == "north" || this.position== "south")) {
114                 this.collapsedTitleTextEl = dh.append(this.collapsedEl.dom, {tag: "div", cls: "x-unselectable x-layout-panel-hd-text",
115                    id: "message", unselectable: "on", style:{"float":"left"}});
116                this.collapsedTitleTextEl.innerHTML = c.collapsedTitle;
117              }
118             this.expandBtn = this.createTool(this.collapsedEl.dom.firstChild.firstChild, "x-layout-expand-"+this.position);
119             this.expandBtn.on("click", this.expand, this);
120         }
121         if(this.collapseBtn){
122             this.collapseBtn.setVisible(c.collapsible == true);
123         }
124         this.cmargins = c.cmargins || this.cmargins ||
125                          (this.position == "west" || this.position == "east" ?
126                              {top: 0, left: 2, right:2, bottom: 0} :
127                              {top: 2, left: 0, right:0, bottom: 2});
128         this.margins = c.margins || this.margins || {top: 0, left: 0, right:0, bottom: 0};
129         this.bottomTabs = c.tabPosition != "top";
130         this.autoScroll = c.autoScroll || false;
131         if(this.autoScroll){
132             this.bodyEl.setStyle("overflow", "auto");
133         }else{
134             this.bodyEl.setStyle("overflow", "hidden");
135         }
136         //if(c.titlebar !== false){
137             if((!c.titlebar && !c.title) || c.titlebar === false){
138                 this.titleEl.hide();
139             }else{
140                 this.titleEl.show();
141                 if(c.title){
142                     this.titleTextEl.innerHTML = c.title;
143                 }
144             }
145         //}
146         this.duration = c.duration || .30;
147         this.slideDuration = c.slideDuration || .45;
148         this.config = c;
149         if(c.collapsed){
150             this.collapse(true);
151         }
152         if(c.hidden){
153             this.hide();
154         }
155     },
156     /**
157      * Returns true if this region is currently visible.
158      * @return {Boolean}
159      */
160     isVisible : function(){
161         return this.visible;
162     },
163
164     /**
165      * Updates the title for collapsed north/south regions (used with {@link #collapsedTitle} config option)
166      * @param {String} title (optional) The title text (accepts HTML markup, defaults to the numeric character reference for a non-breaking space, "&amp;#160;")
167      */
168     setCollapsedTitle : function(title){
169         title = title || "&#160;";
170         if(this.collapsedTitleTextEl){
171             this.collapsedTitleTextEl.innerHTML = title;
172         }
173     },
174
175     getBox : function(){
176         var b;
177         if(!this.collapsed){
178             b = this.el.getBox(false, true);
179         }else{
180             b = this.collapsedEl.getBox(false, true);
181         }
182         return b;
183     },
184
185     getMargins : function(){
186         return this.collapsed ? this.cmargins : this.margins;
187     },
188
189     highlight : function(){
190         this.el.addClass("x-layout-panel-dragover");
191     },
192
193     unhighlight : function(){
194         this.el.removeClass("x-layout-panel-dragover");
195     },
196
197     updateBox : function(box){
198         this.box = box;
199         if(!this.collapsed){
200             this.el.dom.style.left = box.x + "px";
201             this.el.dom.style.top = box.y + "px";
202             this.updateBody(box.width, box.height);
203         }else{
204             this.collapsedEl.dom.style.left = box.x + "px";
205             this.collapsedEl.dom.style.top = box.y + "px";
206             this.collapsedEl.setSize(box.width, box.height);
207         }
208         if(this.tabs){
209             this.tabs.autoSizeTabs();
210         }
211     },
212
213     updateBody : function(w, h){
214         if(w !== null){
215             this.el.setWidth(w);
216             w -= this.el.getBorderWidth("rl");
217             if(this.config.adjustments){
218                 w += this.config.adjustments[0];
219             }
220         }
221         if(h !== null){
222             this.el.setHeight(h);
223             h = this.titleEl && this.titleEl.isDisplayed() ? h - (this.titleEl.getHeight()||0) : h;
224             h -= this.el.getBorderWidth("tb");
225             if(this.config.adjustments){
226                 h += this.config.adjustments[1];
227             }
228             this.bodyEl.setHeight(h);
229             if(this.tabs){
230                 h = this.tabs.syncHeight(h);
231             }
232         }
233         if(this.panelSize){
234             w = w !== null ? w : this.panelSize.width;
235             h = h !== null ? h : this.panelSize.height;
236         }
237         if(this.activePanel){
238             var el = this.activePanel.getEl();
239             w = w !== null ? w : el.getWidth();
240             h = h !== null ? h : el.getHeight();
241             this.panelSize = {width: w, height: h};
242             this.activePanel.setSize(w, h);
243         }
244         if(Roo.isIE && this.tabs){
245             this.tabs.el.repaint();
246         }
247     },
248
249     /**
250      * Returns the container element for this region.
251      * @return {Roo.Element}
252      */
253     getEl : function(){
254         return this.el;
255     },
256
257     /**
258      * Hides this region.
259      */
260     hide : function(){
261         if(!this.collapsed){
262             this.el.dom.style.left = "-2000px";
263             this.el.hide();
264         }else{
265             this.collapsedEl.dom.style.left = "-2000px";
266             this.collapsedEl.hide();
267         }
268         this.visible = false;
269         this.fireEvent("visibilitychange", this, false);
270     },
271
272     /**
273      * Shows this region if it was previously hidden.
274      */
275     show : function(){
276         if(!this.collapsed){
277             this.el.show();
278         }else{
279             this.collapsedEl.show();
280         }
281         this.visible = true;
282         this.fireEvent("visibilitychange", this, true);
283     },
284
285     closeClicked : function(){
286         if(this.activePanel){
287             this.remove(this.activePanel);
288         }
289     },
290
291     collapseClick : function(e){
292         if(this.isSlid){
293            e.stopPropagation();
294            this.slideIn();
295         }else{
296            e.stopPropagation();
297            this.slideOut();
298         }
299     },
300
301     /**
302      * Collapses this region.
303      * @param {Boolean} skipAnim (optional) true to collapse the element without animation (if animate is true)
304      */
305     collapse : function(skipAnim){
306         if(this.collapsed) return;
307         this.collapsed = true;
308         if(this.split){
309             this.split.el.hide();
310         }
311         if(this.config.animate && skipAnim !== true){
312             this.fireEvent("invalidated", this);
313             this.animateCollapse();
314         }else{
315             this.el.setLocation(-20000,-20000);
316             this.el.hide();
317             this.collapsedEl.show();
318             this.fireEvent("collapsed", this);
319             this.fireEvent("invalidated", this);
320         }
321     },
322
323     animateCollapse : function(){
324         // overridden
325     },
326
327     /**
328      * Expands this region if it was previously collapsed.
329      * @param {Roo.EventObject} e The event that triggered the expand (or null if calling manually)
330      * @param {Boolean} skipAnim (optional) true to expand the element without animation (if animate is true)
331      */
332     expand : function(e, skipAnim){
333         if(e) e.stopPropagation();
334         if(!this.collapsed || this.el.hasActiveFx()) return;
335         if(this.isSlid){
336             this.afterSlideIn();
337             skipAnim = true;
338         }
339         this.collapsed = false;
340         if(this.config.animate && skipAnim !== true){
341             this.animateExpand();
342         }else{
343             this.el.show();
344             if(this.split){
345                 this.split.el.show();
346             }
347             this.collapsedEl.setLocation(-2000,-2000);
348             this.collapsedEl.hide();
349             this.fireEvent("invalidated", this);
350             this.fireEvent("expanded", this);
351         }
352     },
353
354     animateExpand : function(){
355         // overridden
356     },
357
358     initTabs : function()
359     {
360         this.bodyEl.setStyle("overflow", "hidden");
361         var ts = new Roo.TabPanel(
362                 this.bodyEl.dom,
363                 {
364                     tabPosition: this.bottomTabs ? 'bottom' : 'top',
365                     disableTooltips: this.config.disableTabTips,
366                     toolbar : this.config.toolbar
367                 }
368         );
369         if(this.config.hideTabs){
370             ts.stripWrap.setDisplayed(false);
371         }
372         this.tabs = ts;
373         ts.resizeTabs = this.config.resizeTabs === true;
374         ts.minTabWidth = this.config.minTabWidth || 40;
375         ts.maxTabWidth = this.config.maxTabWidth || 250;
376         ts.preferredTabWidth = this.config.preferredTabWidth || 150;
377         ts.monitorResize = false;
378         ts.bodyEl.setStyle("overflow", this.config.autoScroll ? "auto" : "hidden");
379         ts.bodyEl.addClass('x-layout-tabs-body');
380         this.panels.each(this.initPanelAsTab, this);
381     },
382
383     initPanelAsTab : function(panel){
384         var ti = this.tabs.addTab(panel.getEl().id, panel.getTitle(), null,
385                     this.config.closeOnTab && panel.isClosable());
386         if(panel.tabTip !== undefined){
387             ti.setTooltip(panel.tabTip);
388         }
389         ti.on("activate", function(){
390               this.setActivePanel(panel);
391         }, this);
392         if(this.config.closeOnTab){
393             ti.on("beforeclose", function(t, e){
394                 e.cancel = true;
395                 this.remove(panel);
396             }, this);
397         }
398         return ti;
399     },
400
401     updatePanelTitle : function(panel, title){
402         if(this.activePanel == panel){
403             this.updateTitle(title);
404         }
405         if(this.tabs){
406             var ti = this.tabs.getTab(panel.getEl().id);
407             ti.setText(title);
408             if(panel.tabTip !== undefined){
409                 ti.setTooltip(panel.tabTip);
410             }
411         }
412     },
413
414     updateTitle : function(title){
415         if(this.titleTextEl && !this.config.title){
416             this.titleTextEl.innerHTML = (typeof title != "undefined" && title.length > 0 ? title : "&#160;");
417         }
418     },
419
420     setActivePanel : function(panel){
421         panel = this.getPanel(panel);
422         if(this.activePanel && this.activePanel != panel){
423             this.activePanel.setActiveState(false);
424         }
425         this.activePanel = panel;
426         panel.setActiveState(true);
427         if(this.panelSize){
428             panel.setSize(this.panelSize.width, this.panelSize.height);
429         }
430         if(this.closeBtn){
431             this.closeBtn.setVisible(!this.config.closeOnTab && !this.isSlid && panel.isClosable());
432         }
433         this.updateTitle(panel.getTitle());
434         if(this.tabs){
435             this.fireEvent("invalidated", this);
436         }
437         this.fireEvent("panelactivated", this, panel);
438     },
439
440     /**
441      * Shows the specified panel.
442      * @param {Number/String/ContentPanel} panelId The panel's index, id or the panel itself
443      * @return {Roo.ContentPanel} The shown panel, or null if a panel could not be found from panelId
444      */
445     showPanel : function(panel){
446         if(panel = this.getPanel(panel)){
447             if(this.tabs){
448                 var tab = this.tabs.getTab(panel.getEl().id);
449                 if(tab.isHidden()){
450                     this.tabs.unhideTab(tab.id);
451                 }
452                 tab.activate();
453             }else{
454                 this.setActivePanel(panel);
455             }
456         }
457         return panel;
458     },
459
460     /**
461      * Get the active panel for this region.
462      * @return {Roo.ContentPanel} The active panel or null
463      */
464     getActivePanel : function(){
465         return this.activePanel;
466     },
467
468     validateVisibility : function(){
469         if(this.panels.getCount() < 1){
470             this.updateTitle("&#160;");
471             this.closeBtn.hide();
472             this.hide();
473         }else{
474             if(!this.isVisible()){
475                 this.show();
476             }
477         }
478     },
479
480     /**
481      * Adds the passed ContentPanel(s) to this region.
482      * @param {ContentPanel...} panel The ContentPanel(s) to add (you can pass more than one)
483      * @return {Roo.ContentPanel} The panel added (if only one was added; null otherwise)
484      */
485     add : function(panel){
486         if(arguments.length > 1){
487             for(var i = 0, len = arguments.length; i < len; i++) {
488                 this.add(arguments[i]);
489             }
490             return null;
491         }
492         if(this.hasPanel(panel)){
493             this.showPanel(panel);
494             return panel;
495         }
496         panel.setRegion(this);
497         this.panels.add(panel);
498         if(this.panels.getCount() == 1 && !this.config.alwaysShowTabs){
499             this.bodyEl.dom.appendChild(panel.getEl().dom);
500             if(panel.background !== true){
501                 this.setActivePanel(panel);
502             }
503             this.fireEvent("paneladded", this, panel);
504             return panel;
505         }
506         if(!this.tabs){
507             this.initTabs();
508         }else{
509             this.initPanelAsTab(panel);
510         }
511         if(panel.background !== true){
512             this.tabs.activate(panel.getEl().id);
513         }
514         this.fireEvent("paneladded", this, panel);
515         return panel;
516     },
517
518     /**
519      * Hides the tab for the specified panel.
520      * @param {Number/String/ContentPanel} panel The panel's index, id or the panel itself
521      */
522     hidePanel : function(panel){
523         if(this.tabs && (panel = this.getPanel(panel))){
524             this.tabs.hideTab(panel.getEl().id);
525         }
526     },
527
528     /**
529      * Unhides the tab for a previously hidden panel.
530      * @param {Number/String/ContentPanel} panel The panel's index, id or the panel itself
531      */
532     unhidePanel : function(panel){
533         if(this.tabs && (panel = this.getPanel(panel))){
534             this.tabs.unhideTab(panel.getEl().id);
535         }
536     },
537
538     clearPanels : function(){
539         while(this.panels.getCount() > 0){
540              this.remove(this.panels.first());
541         }
542     },
543
544     /**
545      * Removes the specified panel. If preservePanel is not true (either here or in the config), the panel is destroyed.
546      * @param {Number/String/ContentPanel} panel The panel's index, id or the panel itself
547      * @param {Boolean} preservePanel Overrides the config preservePanel option
548      * @return {Roo.ContentPanel} The panel that was removed
549      */
550     remove : function(panel, preservePanel){
551         panel = this.getPanel(panel);
552         if(!panel){
553             return null;
554         }
555         var e = {};
556         this.fireEvent("beforeremove", this, panel, e);
557         if(e.cancel === true){
558             return null;
559         }
560         preservePanel = (typeof preservePanel != "undefined" ? preservePanel : (this.config.preservePanels === true || panel.preserve === true));
561         var panelId = panel.getId();
562         this.panels.removeKey(panelId);
563         if(preservePanel){
564             document.body.appendChild(panel.getEl().dom);
565         }
566         if(this.tabs){
567             this.tabs.removeTab(panel.getEl().id);
568         }else if (!preservePanel){
569             this.bodyEl.dom.removeChild(panel.getEl().dom);
570         }
571         if(this.panels.getCount() == 1 && this.tabs && !this.config.alwaysShowTabs){
572             var p = this.panels.first();
573             var tempEl = document.createElement("div"); // temp holder to keep IE from deleting the node
574             tempEl.appendChild(p.getEl().dom);
575             this.bodyEl.update("");
576             this.bodyEl.dom.appendChild(p.getEl().dom);
577             tempEl = null;
578             this.updateTitle(p.getTitle());
579             this.tabs = null;
580             this.bodyEl.setStyle("overflow", this.config.autoScroll ? "auto" : "hidden");
581             this.setActivePanel(p);
582         }
583         panel.setRegion(null);
584         if(this.activePanel == panel){
585             this.activePanel = null;
586         }
587         if(this.config.autoDestroy !== false && preservePanel !== true){
588             try{panel.destroy();}catch(e){}
589         }
590         this.fireEvent("panelremoved", this, panel);
591         return panel;
592     },
593
594     /**
595      * Returns the TabPanel component used by this region
596      * @return {Roo.TabPanel}
597      */
598     getTabs : function(){
599         return this.tabs;
600     },
601
602     createTool : function(parentEl, className){
603         var btn = Roo.DomHelper.append(parentEl, {tag: "div", cls: "x-layout-tools-button",
604             children: [{tag: "div", cls: "x-layout-tools-button-inner " + className, html: "&#160;"}]}, true);
605         btn.addClassOnOver("x-layout-tools-button-over");
606         return btn;
607     }
608 });