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