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