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