Roo/BorderLayout.js
[roojs1] / Roo / BorderLayout.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  * @class Roo.BorderLayout
13  * @extends Roo.LayoutManager
14  * This class represents a common layout manager used in desktop applications. For screenshots and more details,
15  * please see: <br><br>
16  * <a href="http://www.jackslocum.com/yui/2006/10/19/cross-browser-web-20-layouts-with-yahoo-ui/">Cross Browser Layouts - Part 1</a><br>
17  * <a href="http://www.jackslocum.com/yui/2006/10/28/cross-browser-web-20-layouts-part-2-ajax-feed-viewer-20/">Cross Browser Layouts - Part 2</a><br><br>
18  * Example:
19  <pre><code>
20  var layout = new Roo.BorderLayout(document.body, {
21     north: {
22         initialSize: 25,
23         titlebar: false
24     },
25     west: {
26         split:true,
27         initialSize: 200,
28         minSize: 175,
29         maxSize: 400,
30         titlebar: true,
31         collapsible: true
32     },
33     east: {
34         split:true,
35         initialSize: 202,
36         minSize: 175,
37         maxSize: 400,
38         titlebar: true,
39         collapsible: true
40     },
41     south: {
42         split:true,
43         initialSize: 100,
44         minSize: 100,
45         maxSize: 200,
46         titlebar: true,
47         collapsible: true
48     },
49     center: {
50         titlebar: true,
51         autoScroll:true,
52         resizeTabs: true,
53         minTabWidth: 50,
54         preferredTabWidth: 150
55     }
56 });
57
58 // shorthand
59 var CP = Roo.ContentPanel;
60
61 layout.beginUpdate();
62 layout.add("north", new CP("north", "North"));
63 layout.add("south", new CP("south", {title: "South", closable: true}));
64 layout.add("west", new CP("west", {title: "West"}));
65 layout.add("east", new CP("autoTabs", {title: "Auto Tabs", closable: true}));
66 layout.add("center", new CP("center1", {title: "Close Me", closable: true}));
67 layout.add("center", new CP("center2", {title: "Center Panel", closable: false}));
68 layout.getRegion("center").showPanel("center1");
69 layout.endUpdate();
70 </code></pre>
71
72 <b>The container the layout is rendered into can be either the body element or any other element.
73 If it is not the body element, the container needs to either be an absolute positioned element,
74 or you will need to add "position:relative" to the css of the container.  You will also need to specify
75 the container size if it is not the body element.</b>
76
77 * @constructor
78 * Create a new BorderLayout
79 * @param {String/HTMLElement/Element} container The container this layout is bound to
80 * @param {Object} config Configuration options
81  */
82 Roo.BorderLayout = function(container, config){
83     config = config || {};
84     Roo.BorderLayout.superclass.constructor.call(this, container, config);
85     return;
86     this.factory = config.factory || Roo.BorderLayout.RegionFactory;
87     for(var i = 0, len = this.factory.validRegions.length; i < len; i++) {
88         var target = this.factory.validRegions[i];
89         if(config[target]){
90             this.addRegion(target, config[target]);
91         }
92     }
93 };
94
95 Roo.extend(Roo.BorderLayout, Roo.LayoutManager, {
96     /**
97      * Creates and adds a new region if it doesn't already exist.
98      * @param {String} target The target region key (north, south, east, west or center).
99      * @param {Object} config The regions config object
100      * @return {BorderLayoutRegion} The new region
101      */
102     addRegion : function(target, config){
103         if(!this.regions[target]){
104             var r = this.factory.create(target, this, config);
105             this.bindRegion(target, r);
106         }
107         return this.regions[target];
108     },
109
110     // private (kinda)
111     bindRegion : function(name, r){
112         this.regions[name] = r;
113         r.on("visibilitychange", this.layout, this);
114         r.on("paneladded", this.layout, this);
115         r.on("panelremoved", this.layout, this);
116         r.on("invalidated", this.layout, this);
117         r.on("resized", this.onRegionResized, this);
118         r.on("collapsed", this.onRegionCollapsed, this);
119         r.on("expanded", this.onRegionExpanded, this);
120     },
121
122     /**
123      * Performs a layout update.
124      */
125     layout : function(){
126         if(this.updating) return;
127         var size = this.getViewSize();
128         var w = size.width;
129         var h = size.height;
130         var centerW = w;
131         var centerH = h;
132         var centerY = 0;
133         var centerX = 0;
134         //var x = 0, y = 0;
135
136         var rs = this.regions;
137         var north = rs["north"];
138         var south = rs["south"]; 
139         var west = rs["west"];
140         var east = rs["east"];
141         var center = rs["center"];
142         //if(this.hideOnLayout){ // not supported anymore
143             //c.el.setStyle("display", "none");
144         //}
145         if(north && north.isVisible()){
146             var b = north.getBox();
147             var m = north.getMargins();
148             b.width = w - (m.left+m.right);
149             b.x = m.left;
150             b.y = m.top;
151             centerY = b.height + b.y + m.bottom;
152             centerH -= centerY;
153             north.updateBox(this.safeBox(b));
154         }
155         if(south && south.isVisible()){
156             var b = south.getBox();
157             var m = south.getMargins();
158             b.width = w - (m.left+m.right);
159             b.x = m.left;
160             var totalHeight = (b.height + m.top + m.bottom);
161             b.y = h - totalHeight + m.top;
162             centerH -= totalHeight;
163             south.updateBox(this.safeBox(b));
164         }
165         if(west && west.isVisible()){
166             var b = west.getBox();
167             var m = west.getMargins();
168             b.height = centerH - (m.top+m.bottom);
169             b.x = m.left;
170             b.y = centerY + m.top;
171             var totalWidth = (b.width + m.left + m.right);
172             centerX += totalWidth;
173             centerW -= totalWidth;
174             west.updateBox(this.safeBox(b));
175         }
176         if(east && east.isVisible()){
177             var b = east.getBox();
178             var m = east.getMargins();
179             b.height = centerH - (m.top+m.bottom);
180             var totalWidth = (b.width + m.left + m.right);
181             b.x = w - totalWidth + m.left;
182             b.y = centerY + m.top;
183             centerW -= totalWidth;
184             east.updateBox(this.safeBox(b));
185         }
186         if(center){
187             var m = center.getMargins();
188             var centerBox = {
189                 x: centerX + m.left,
190                 y: centerY + m.top,
191                 width: centerW - (m.left+m.right),
192                 height: centerH - (m.top+m.bottom)
193             };
194             //if(this.hideOnLayout){
195                 //center.el.setStyle("display", "block");
196             //}
197             center.updateBox(this.safeBox(centerBox));
198         }
199         this.el.repaint();
200         this.fireEvent("layout", this);
201     },
202
203     // private
204     safeBox : function(box){
205         box.width = Math.max(0, box.width);
206         box.height = Math.max(0, box.height);
207         return box;
208     },
209
210     /**
211      * Adds a ContentPanel (or subclass) to this layout.
212      * @param {String} target The target region key (north, south, east, west or center).
213      * @param {Roo.ContentPanel} panel The panel to add
214      * @return {Roo.ContentPanel} The added panel
215      */
216     add : function(target, panel){
217          
218         target = target.toLowerCase();
219         return this.regions[target].add(panel);
220     },
221
222     /**
223      * Remove a ContentPanel (or subclass) to this layout.
224      * @param {String} target The target region key (north, south, east, west or center).
225      * @param {Number/String/Roo.ContentPanel} panel The index, id or panel to remove
226      * @return {Roo.ContentPanel} The removed panel
227      */
228     remove : function(target, panel){
229         target = target.toLowerCase();
230         return this.regions[target].remove(panel);
231     },
232
233     /**
234      * Searches all regions for a panel with the specified id
235      * @param {String} panelId
236      * @return {Roo.ContentPanel} The panel or null if it wasn't found
237      */
238     findPanel : function(panelId){
239         var rs = this.regions;
240         for(var target in rs){
241             if(typeof rs[target] != "function"){
242                 var p = rs[target].getPanel(panelId);
243                 if(p){
244                     return p;
245                 }
246             }
247         }
248         return null;
249     },
250
251     /**
252      * Searches all regions for a panel with the specified id and activates (shows) it.
253      * @param {String/ContentPanel} panelId The panels id or the panel itself
254      * @return {Roo.ContentPanel} The shown panel or null
255      */
256     showPanel : function(panelId) {
257       var rs = this.regions;
258       for(var target in rs){
259          var r = rs[target];
260          if(typeof r != "function"){
261             if(r.hasPanel(panelId)){
262                return r.showPanel(panelId);
263             }
264          }
265       }
266       return null;
267    },
268
269    /**
270      * Restores this layout's state using Roo.state.Manager or the state provided by the passed provider.
271      * @param {Roo.state.Provider} provider (optional) An alternate state provider
272      */
273     restoreState : function(provider){
274         if(!provider){
275             provider = Roo.state.Manager;
276         }
277         var sm = new Roo.LayoutStateManager();
278         sm.init(this, provider);
279     },
280
281     /**
282      * Adds a batch of multiple ContentPanels dynamically by passing a special regions config object.  This config
283      * object should contain properties for each region to add ContentPanels to, and each property's value should be
284      * a valid ContentPanel config object.  Example:
285      * <pre><code>
286 // Create the main layout
287 var layout = new Roo.BorderLayout('main-ct', {
288     west: {
289         split:true,
290         minSize: 175,
291         titlebar: true
292     },
293     center: {
294         title:'Components'
295     }
296 }, 'main-ct');
297
298 // Create and add multiple ContentPanels at once via configs
299 layout.batchAdd({
300    west: {
301        id: 'source-files',
302        autoCreate:true,
303        title:'Ext Source Files',
304        autoScroll:true,
305        fitToFrame:true
306    },
307    center : {
308        el: cview,
309        autoScroll:true,
310        fitToFrame:true,
311        toolbar: tb,
312        resizeEl:'cbody'
313    }
314 });
315 </code></pre>
316      * @param {Object} regions An object containing ContentPanel configs by region name
317      */
318     batchAdd : function(regions){
319         this.beginUpdate();
320         for(var rname in regions){
321             var lr = this.regions[rname];
322             if(lr){
323                 this.addTypedPanels(lr, regions[rname]);
324             }
325         }
326         this.endUpdate();
327     },
328
329     // private
330     addTypedPanels : function(lr, ps){
331         if(typeof ps == 'string'){
332             lr.add(new Roo.ContentPanel(ps));
333         }
334         else if(ps instanceof Array){
335             for(var i =0, len = ps.length; i < len; i++){
336                 this.addTypedPanels(lr, ps[i]);
337             }
338         }
339         else if(!ps.events){ // raw config?
340             var el = ps.el;
341             delete ps.el; // prevent conflict
342             lr.add(new Roo.ContentPanel(el || Roo.id(), ps));
343         }
344         else {  // panel object assumed!
345             lr.add(ps);
346         }
347     },
348     /**
349      * Adds a xtype elements to the layout.
350      * <pre><code>
351
352 layout.addxtype({
353        xtype : 'ContentPanel',
354        region: 'west',
355        items: [ .... ]
356    }
357 );
358
359 layout.addxtype({
360         xtype : 'NestedLayoutPanel',
361         region: 'west',
362         layout: {
363            center: { },
364            west: { }   
365         },
366         items : [ ... list of content panels or nested layout panels.. ]
367    }
368 );
369 </code></pre>
370      * @param {Object} cfg Xtype definition of item to add.
371      */
372     addxtype : function(cfg)
373     {
374         // basically accepts a pannel...
375         // can accept a layout region..!?!?
376         //Roo.log('Roo.BorderLayout add ' + cfg.xtype)
377         
378         if (!cfg.xtype.match(/Panel$/)) {
379             return false;
380         }
381         var ret = false;
382         
383         if (typeof(cfg.region) == 'undefined') {
384             Roo.log("Failed to add Panel, region was not set");
385             Roo.log(cfg);
386             return false;
387         }
388         var region = cfg.region;
389         delete cfg.region;
390         
391           
392         var xitems = [];
393         if (cfg.items) {
394             xitems = cfg.items;
395             delete cfg.items;
396         }
397         var nb = false;
398         
399         switch(cfg.xtype) 
400         {
401             case 'ContentPanel':  // ContentPanel (el, cfg)
402             case 'ScrollPanel':  // ContentPanel (el, cfg)
403             case 'ViewPanel': 
404                 if(cfg.autoCreate) {
405                     ret = new Roo[cfg.xtype](cfg); // new panel!!!!!
406                 } else {
407                     var el = this.el.createChild();
408                     ret = new Roo[cfg.xtype](el, cfg); // new panel!!!!!
409                 }
410                 
411                 this.add(region, ret);
412                 break;
413             
414             
415             case 'TreePanel': // our new panel!
416                 cfg.el = this.el.createChild();
417                 ret = new Roo[cfg.xtype](cfg); // new panel!!!!!
418                 this.add(region, ret);
419                 break;
420             
421             case 'NestedLayoutPanel': 
422                 // create a new Layout (which is  a Border Layout...
423                 var el = this.el.createChild();
424                 var clayout = cfg.layout;
425                 delete cfg.layout;
426                 clayout.items   = clayout.items  || [];
427                 // replace this exitems with the clayout ones..
428                 xitems = clayout.items;
429                  
430                 
431                 if (region == 'center' && this.active && this.getRegion('center').panels.length < 1) {
432                     cfg.background = false;
433                 }
434                 var layout = new Roo.BorderLayout(el, clayout);
435                 
436                 ret = new Roo[cfg.xtype](layout, cfg); // new panel!!!!!
437                 //console.log('adding nested layout panel '  + cfg.toSource());
438                 this.add(region, ret);
439                 nb = {}; /// find first...
440                 break;
441                 
442             case 'GridPanel': 
443             
444                 // needs grid and region
445                 
446                 //var el = this.getRegion(region).el.createChild();
447                 var el = this.el.createChild();
448                 // create the grid first...
449                 
450                 var grid = new Roo.grid[cfg.grid.xtype](el, cfg.grid);
451                 delete cfg.grid;
452                 if (region == 'center' && this.active ) {
453                     cfg.background = false;
454                 }
455                 ret = new Roo[cfg.xtype](grid, cfg); // new panel!!!!!
456                 
457                 this.add(region, ret);
458                 if (cfg.background) {
459                     ret.on('activate', function(gp) {
460                         if (!gp.grid.rendered) {
461                             gp.grid.render();
462                         }
463                     });
464                 } else {
465                     grid.render();
466                 }
467                 break;
468            
469            
470            
471                 
472                 
473                 
474             default:
475                 if (typeof(Roo[cfg.xtype]) != 'undefined') {
476                     
477                     ret = new Roo[cfg.xtype](cfg); // new panel!!!!!
478                     this.add(region, ret);
479                 } else {
480                 
481                     alert("Can not add '" + cfg.xtype + "' to BorderLayout");
482                     return null;
483                 }
484                 
485              // GridPanel (grid, cfg)
486             
487         }
488         this.beginUpdate();
489         // add children..
490         var region = '';
491         var abn = {};
492         Roo.each(xitems, function(i)  {
493             region = nb && i.region ? i.region : false;
494             
495             var add = ret.addxtype(i);
496            
497             if (region) {
498                 nb[region] = nb[region] == undefined ? 0 : nb[region]+1;
499                 if (!i.background) {
500                     abn[region] = nb[region] ;
501                 }
502             }
503             
504         });
505         this.endUpdate();
506
507         // make the last non-background panel active..
508         //if (nb) { Roo.log(abn); }
509         if (nb) {
510             
511             for(var r in abn) {
512                 region = this.getRegion(r);
513                 if (region) {
514                     // tried using nb[r], but it does not work..
515                      
516                     region.showPanel(abn[r]);
517                    
518                 }
519             }
520         }
521         return ret;
522         
523     }
524 });
525
526 /**
527  * Shortcut for creating a new BorderLayout object and adding one or more ContentPanels to it in a single step, handling
528  * the beginUpdate and endUpdate calls internally.  The key to this method is the <b>panels</b> property that can be
529  * provided with each region config, which allows you to add ContentPanel configs in addition to the region configs
530  * during creation.  The following code is equivalent to the constructor-based example at the beginning of this class:
531  * <pre><code>
532 // shorthand
533 var CP = Roo.ContentPanel;
534
535 var layout = Roo.BorderLayout.create({
536     north: {
537         initialSize: 25,
538         titlebar: false,
539         panels: [new CP("north", "North")]
540     },
541     west: {
542         split:true,
543         initialSize: 200,
544         minSize: 175,
545         maxSize: 400,
546         titlebar: true,
547         collapsible: true,
548         panels: [new CP("west", {title: "West"})]
549     },
550     east: {
551         split:true,
552         initialSize: 202,
553         minSize: 175,
554         maxSize: 400,
555         titlebar: true,
556         collapsible: true,
557         panels: [new CP("autoTabs", {title: "Auto Tabs", closable: true})]
558     },
559     south: {
560         split:true,
561         initialSize: 100,
562         minSize: 100,
563         maxSize: 200,
564         titlebar: true,
565         collapsible: true,
566         panels: [new CP("south", {title: "South", closable: true})]
567     },
568     center: {
569         titlebar: true,
570         autoScroll:true,
571         resizeTabs: true,
572         minTabWidth: 50,
573         preferredTabWidth: 150,
574         panels: [
575             new CP("center1", {title: "Close Me", closable: true}),
576             new CP("center2", {title: "Center Panel", closable: false})
577         ]
578     }
579 }, document.body);
580
581 layout.getRegion("center").showPanel("center1");
582 </code></pre>
583  * @param config
584  * @param targetEl
585  */
586 Roo.BorderLayout.create = function(config, targetEl){
587     var layout = new Roo.BorderLayout(targetEl || document.body, config);
588     layout.beginUpdate();
589     var regions = Roo.BorderLayout.RegionFactory.validRegions;
590     for(var j = 0, jlen = regions.length; j < jlen; j++){
591         var lr = regions[j];
592         if(layout.regions[lr] && config[lr].panels){
593             var r = layout.regions[lr];
594             var ps = config[lr].panels;
595             layout.addTypedPanels(r, ps);
596         }
597     }
598     layout.endUpdate();
599     return layout;
600 };
601
602 // private
603 Roo.BorderLayout.RegionFactory = {
604     // private
605     validRegions : ["north","south","east","west","center"],
606
607     // private
608     create : function(target, mgr, config){
609         target = target.toLowerCase();
610         if(config.lightweight || config.basic){
611             return new Roo.BasicLayoutRegion(mgr, config, target);
612         }
613         switch(target){
614             case "north":
615                 return new Roo.NorthLayoutRegion(mgr, config);
616             case "south":
617                 return new Roo.SouthLayoutRegion(mgr, config);
618             case "east":
619                 return new Roo.EastLayoutRegion(mgr, config);
620             case "west":
621                 return new Roo.WestLayoutRegion(mgr, config);
622             case "center":
623                 return new Roo.CenterLayoutRegion(mgr, config);
624         }
625         throw 'Layout region "'+target+'" not supported.';
626     }
627 };