roojs-core.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|bottom) "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, skipCheck){
306         if(this.collapsed) {
307             return;
308         }
309         
310         if(skipCheck || this.fireEvent("beforecollapse", this) != false){
311             
312             this.collapsed = true;
313             if(this.split){
314                 this.split.el.hide();
315             }
316             if(this.config.animate && skipAnim !== true){
317                 this.fireEvent("invalidated", this);
318                 this.animateCollapse();
319             }else{
320                 this.el.setLocation(-20000,-20000);
321                 this.el.hide();
322                 this.collapsedEl.show();
323                 this.fireEvent("collapsed", this);
324                 this.fireEvent("invalidated", this);
325             }
326         }
327         
328     },
329
330     animateCollapse : function(){
331         // overridden
332     },
333
334     /**
335      * Expands this region if it was previously collapsed.
336      * @param {Roo.EventObject} e The event that triggered the expand (or null if calling manually)
337      * @param {Boolean} skipAnim (optional) true to expand the element without animation (if animate is true)
338      */
339     expand : function(e, skipAnim){
340         if(e) {
341             e.stopPropagation();
342         }
343         if(!this.collapsed || this.el.hasActiveFx()) {
344             return;
345         }
346         if(this.isSlid){
347             this.afterSlideIn();
348             skipAnim = true;
349         }
350         this.collapsed = false;
351         if(this.config.animate && skipAnim !== true){
352             this.animateExpand();
353         }else{
354             this.el.show();
355             if(this.split){
356                 this.split.el.show();
357             }
358             this.collapsedEl.setLocation(-2000,-2000);
359             this.collapsedEl.hide();
360             this.fireEvent("invalidated", this);
361             this.fireEvent("expanded", this);
362         }
363     },
364
365     animateExpand : function(){
366         // overridden
367     },
368
369     initTabs : function()
370     {
371         this.bodyEl.setStyle("overflow", "hidden");
372         var ts = new Roo.TabPanel(
373                 this.bodyEl.dom,
374                 {
375                     tabPosition: this.bottomTabs ? 'bottom' : 'top',
376                     disableTooltips: this.config.disableTabTips,
377                     toolbar : this.config.toolbar
378                 }
379         );
380         if(this.config.hideTabs){
381             ts.stripWrap.setDisplayed(false);
382         }
383         this.tabs = ts;
384         ts.resizeTabs = this.config.resizeTabs === true;
385         ts.minTabWidth = this.config.minTabWidth || 40;
386         ts.maxTabWidth = this.config.maxTabWidth || 250;
387         ts.preferredTabWidth = this.config.preferredTabWidth || 150;
388         ts.monitorResize = false;
389         ts.bodyEl.setStyle("overflow", this.config.autoScroll ? "auto" : "hidden");
390         ts.bodyEl.addClass('x-layout-tabs-body');
391         this.panels.each(this.initPanelAsTab, this);
392     },
393
394     initPanelAsTab : function(panel){
395         var ti = this.tabs.addTab(panel.getEl().id, panel.getTitle(), null,
396                     this.config.closeOnTab && panel.isClosable());
397         if(panel.tabTip !== undefined){
398             ti.setTooltip(panel.tabTip);
399         }
400         ti.on("activate", function(){
401               this.setActivePanel(panel);
402         }, this);
403         if(this.config.closeOnTab){
404             ti.on("beforeclose", function(t, e){
405                 e.cancel = true;
406                 this.remove(panel);
407             }, this);
408         }
409         return ti;
410     },
411
412     updatePanelTitle : function(panel, title){
413         if(this.activePanel == panel){
414             this.updateTitle(title);
415         }
416         if(this.tabs){
417             var ti = this.tabs.getTab(panel.getEl().id);
418             ti.setText(title);
419             if(panel.tabTip !== undefined){
420                 ti.setTooltip(panel.tabTip);
421             }
422         }
423     },
424
425     updateTitle : function(title){
426         if(this.titleTextEl && !this.config.title){
427             this.titleTextEl.innerHTML = (typeof title != "undefined" && title.length > 0 ? title : "&#160;");
428         }
429     },
430
431     setActivePanel : function(panel){
432         panel = this.getPanel(panel);
433         if(this.activePanel && this.activePanel != panel){
434             this.activePanel.setActiveState(false);
435         }
436         this.activePanel = panel;
437         panel.setActiveState(true);
438         if(this.panelSize){
439             panel.setSize(this.panelSize.width, this.panelSize.height);
440         }
441         if(this.closeBtn){
442             this.closeBtn.setVisible(!this.config.closeOnTab && !this.isSlid && panel.isClosable());
443         }
444         this.updateTitle(panel.getTitle());
445         if(this.tabs){
446             this.fireEvent("invalidated", this);
447         }
448         this.fireEvent("panelactivated", this, panel);
449     },
450
451     /**
452      * Shows the specified panel.
453      * @param {Number/String/ContentPanel} panelId The panel's index, id or the panel itself
454      * @return {Roo.ContentPanel} The shown panel, or null if a panel could not be found from panelId
455      */
456     showPanel : function(panel)
457     {
458         panel = this.getPanel(panel);
459         if(panel){
460             if(this.tabs){
461                 var tab = this.tabs.getTab(panel.getEl().id);
462                 if(tab.isHidden()){
463                     this.tabs.unhideTab(tab.id);
464                 }
465                 tab.activate();
466             }else{
467                 this.setActivePanel(panel);
468             }
469         }
470         return panel;
471     },
472
473     /**
474      * Get the active panel for this region.
475      * @return {Roo.ContentPanel} The active panel or null
476      */
477     getActivePanel : function(){
478         return this.activePanel;
479     },
480
481     validateVisibility : function(){
482         if(this.panels.getCount() < 1){
483             this.updateTitle("&#160;");
484             this.closeBtn.hide();
485             this.hide();
486         }else{
487             if(!this.isVisible()){
488                 this.show();
489             }
490         }
491     },
492
493     /**
494      * Adds the passed ContentPanel(s) to this region.
495      * @param {ContentPanel...} panel The ContentPanel(s) to add (you can pass more than one)
496      * @return {Roo.ContentPanel} The panel added (if only one was added; null otherwise)
497      */
498     add : function(panel){
499         if(arguments.length > 1){
500             for(var i = 0, len = arguments.length; i < len; i++) {
501                 this.add(arguments[i]);
502             }
503             return null;
504         }
505         if(this.hasPanel(panel)){
506             this.showPanel(panel);
507             return panel;
508         }
509         panel.setRegion(this);
510         this.panels.add(panel);
511         if(this.panels.getCount() == 1 && !this.config.alwaysShowTabs){
512             this.bodyEl.dom.appendChild(panel.getEl().dom);
513             if(panel.background !== true){
514                 this.setActivePanel(panel);
515             }
516             this.fireEvent("paneladded", this, panel);
517             return panel;
518         }
519         if(!this.tabs){
520             this.initTabs();
521         }else{
522             this.initPanelAsTab(panel);
523         }
524         if(panel.background !== true){
525             this.tabs.activate(panel.getEl().id);
526         }
527         this.fireEvent("paneladded", this, panel);
528         return panel;
529     },
530
531     /**
532      * Hides the tab for the specified panel.
533      * @param {Number/String/ContentPanel} panel The panel's index, id or the panel itself
534      */
535     hidePanel : function(panel){
536         if(this.tabs && (panel = this.getPanel(panel))){
537             this.tabs.hideTab(panel.getEl().id);
538         }
539     },
540
541     /**
542      * Unhides the tab for a previously hidden panel.
543      * @param {Number/String/ContentPanel} panel The panel's index, id or the panel itself
544      */
545     unhidePanel : function(panel){
546         if(this.tabs && (panel = this.getPanel(panel))){
547             this.tabs.unhideTab(panel.getEl().id);
548         }
549     },
550
551     clearPanels : function(){
552         while(this.panels.getCount() > 0){
553              this.remove(this.panels.first());
554         }
555     },
556
557     /**
558      * Removes the specified panel. If preservePanel is not true (either here or in the config), the panel is destroyed.
559      * @param {Number/String/ContentPanel} panel The panel's index, id or the panel itself
560      * @param {Boolean} preservePanel Overrides the config preservePanel option
561      * @return {Roo.ContentPanel} The panel that was removed
562      */
563     remove : function(panel, preservePanel){
564         panel = this.getPanel(panel);
565         if(!panel){
566             return null;
567         }
568         var e = {};
569         this.fireEvent("beforeremove", this, panel, e);
570         if(e.cancel === true){
571             return null;
572         }
573         preservePanel = (typeof preservePanel != "undefined" ? preservePanel : (this.config.preservePanels === true || panel.preserve === true));
574         var panelId = panel.getId();
575         this.panels.removeKey(panelId);
576         if(preservePanel){
577             document.body.appendChild(panel.getEl().dom);
578         }
579         if(this.tabs){
580             this.tabs.removeTab(panel.getEl().id);
581         }else if (!preservePanel){
582             this.bodyEl.dom.removeChild(panel.getEl().dom);
583         }
584         if(this.panels.getCount() == 1 && this.tabs && !this.config.alwaysShowTabs){
585             var p = this.panels.first();
586             var tempEl = document.createElement("div"); // temp holder to keep IE from deleting the node
587             tempEl.appendChild(p.getEl().dom);
588             this.bodyEl.update("");
589             this.bodyEl.dom.appendChild(p.getEl().dom);
590             tempEl = null;
591             this.updateTitle(p.getTitle());
592             this.tabs = null;
593             this.bodyEl.setStyle("overflow", this.config.autoScroll ? "auto" : "hidden");
594             this.setActivePanel(p);
595         }
596         panel.setRegion(null);
597         if(this.activePanel == panel){
598             this.activePanel = null;
599         }
600         if(this.config.autoDestroy !== false && preservePanel !== true){
601             try{panel.destroy();}catch(e){}
602         }
603         this.fireEvent("panelremoved", this, panel);
604         return panel;
605     },
606
607     /**
608      * Returns the TabPanel component used by this region
609      * @return {Roo.TabPanel}
610      */
611     getTabs : function(){
612         return this.tabs;
613     },
614
615     createTool : function(parentEl, className){
616         var btn = Roo.DomHelper.append(parentEl, {tag: "div", cls: "x-layout-tools-button",
617             children: [{tag: "div", cls: "x-layout-tools-button-inner " + className, html: "&#160;"}]}, true);
618         btn.addClassOnOver("x-layout-tools-button-over");
619         return btn;
620     }
621 });