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