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