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