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