b7aa8e5b098c60974025eb920d9007a935854e9a
[roojs1] / Roo / form / ComboNested.js
1 /*
2  * RooJS Library 1.1.1
3  * Copyright(c) 2008-2011  Alan Knowles
4  *
5  * License - LGPL
6  */
7  
8
9 /**
10  * @class Roo.form.ComboNested
11  * @extends Roo.form.ComboBox
12  * A combobox for that allows selection of nested items in a list,
13  * eg.
14  *
15  *  Book
16  *    -> red
17  *    -> green
18  *  Table
19  *    -> square
20  *      ->red
21  *      ->green
22  *    -> rectangle
23  *      ->green
24  *      
25  * 
26  * @constructor
27  * Create a new ComboNested
28  * @param {Object} config Configuration options
29  */
30 Roo.form.ComboNested = function(config){
31     Roo.form.ComboCheck.superclass.constructor.call(this, config);
32     // should verify some data...
33     // like
34     // hiddenName = required..
35     // displayField = required
36     // valudField == required
37     var req= [ 'hiddenName', 'displayField', 'valueField' ];
38     var _t = this;
39     Roo.each(req, function(e) {
40         if ((typeof(_t[e]) == 'undefined' ) || !_t[e].length) {
41             throw "Roo.form.ComboNested : missing value for: " + e;
42         }
43     });
44      
45     
46 };
47
48 Roo.extend(Roo.form.ComboNested, Roo.form.ComboBox, {
49    
50     /*
51      * @config {Number} max Number of columns to show
52      */
53     
54     maxColumns : 3,
55    
56     list : null, // the outermost div..
57     innerLists : null, // the
58     views : null,
59     stores : null,
60     // private
61     onRender : function(ct, position)
62     {
63         Roo.form.ComboBox.superclass.onRender.call(this, ct, position); // skip parent call - got to above..
64         
65         if(this.hiddenName){
66             this.hiddenField = this.el.insertSibling({tag:'input', type:'hidden', name: this.hiddenName, id:  (this.hiddenId||this.hiddenName)},
67                     'before', true);
68             this.hiddenField.value =
69                 this.hiddenValue !== undefined ? this.hiddenValue :
70                 this.value !== undefined ? this.value : '';
71
72             // prevent input submission
73             this.el.dom.removeAttribute('name');
74              
75              
76         }
77         
78         if(Roo.isGecko){
79             this.el.dom.setAttribute('autocomplete', 'off');
80         }
81
82         var cls = 'x-combo-list';
83
84         this.list = new Roo.Layer({
85             shadow: this.shadow, cls: [cls, this.listClass].join(' '), constrain:false
86         });
87
88         var lw = this.listWidth || Math.max(this.wrap.getWidth(), this.minListWidth);
89         this.list.setWidth(lw);
90         this.list.swallowEvent('mousewheel');
91         this.assetHeight = 0;
92
93         if(this.title){
94             this.header = this.list.createChild({cls:cls+'-hd', html: this.title});
95             this.assetHeight += this.header.getHeight();
96         }
97         this.innerLists = [];
98         this.views = [];
99         this.stores = [];
100         for (var i =0 ; i < this.maxColumns; i++) {
101             this.onRenderList( cls, i);
102         }
103         
104         // always needs footer, as we are going to have an 'OK' button.
105         this.footer = this.list.createChild({cls:cls+'-ft'});
106         this.pageTb = new Roo.Toolbar(this.footer);  
107         var _this = this;
108         this.pageTb.add(  {
109             
110             text: 'Done',
111             handler: function()
112             {
113                 _this.collapse();
114             }
115         });
116         
117         if ( this.allowBlank && !this.disableClear) {
118             
119             this.pageTb.add(new Roo.Toolbar.Fill(), {
120                 cls: 'x-btn-icon x-btn-clear',
121                 text: '&#160;',
122                 handler: function()
123                 {
124                     _this.collapse();
125                     _this.clearValue();
126                     _this.onSelect(false, -1);
127                 }
128             });
129         }
130         if (this.footer) {
131             this.assetHeight += this.footer.getHeight();
132         }
133         
134     },
135     onRenderList : function (  cls, i)
136     {
137         
138         var lw = Math.floor(
139                 ((this.listWidth * this.maxColumns || Math.max(this.wrap.getWidth(), this.minListWidth)) - this.list.getFrameWidth('lr')) / this.maxColumns
140         );
141         
142         this.list.setWidth(lw); // default to '1'
143
144         var il = this.innerLists[i] = this.list.createChild({cls:cls+'-inner'});
145         //il.on('mouseover', this.onViewOver, this, { list:  i });
146         //il.on('mousemove', this.onViewMove, this, { list:  i });
147         il.setWidth(lw);
148         il.setStyle({ 'overflow-x' : 'hidden'});
149
150         if(!this.tpl){
151             this.tpl = new Roo.Template({
152                 html :  '<div class="'+cls+'-item '+cls+'-item-{cn:this.isEmpty}">{' + this.displayField + '}</div>',
153                 isEmpty: function (value, allValues) {
154                     //Roo.log(value);
155                     var dl = typeof(value.data) != 'undefined' ? value.data.length : value.length; ///json is a nested response..
156                     return dl ? 'has-children' : 'no-children'
157                 }
158             });
159         }
160         
161         var store  = this.store;
162         if (i > 0) {
163             store  = new Roo.data.SimpleStore({
164                 //fields : this.store.reader.meta.fields,
165                 reader : this.store.reader,
166                 data : [ ]
167             });
168         }
169         this.stores[i]  = store;
170                   
171         var view = this.views[i] = new Roo.View(
172             il,
173             this.tpl,
174             {
175                 singleSelect:true,
176                 store: store,
177                 selectedClass: this.selectedClass
178             }
179         );
180         view.getEl().setWidth(lw);
181         view.getEl().setStyle({
182             position: i < 1 ? 'relative' : 'absolute',
183             top: 0,
184             left: (i * lw ) + 'px',
185             display : i > 0 ? 'none' : 'block'
186         });
187         view.on('selectionchange', this.onSelectChange, this, {list : i });
188         view.on('dblclick', this.onDoubleClick, this, {list : i });
189         //view.on('click', this.onViewClick, this, { list : i });
190
191         store.on('beforeload', this.onBeforeLoad, this);
192         store.on('load',  this.onLoad, this, { list  : i});
193         store.on('loadexception', this.onLoadException, this);
194
195         // hide the other vies..
196         
197         
198         
199     },
200     onResize : function()  {},
201     
202     restrictHeight : function()
203     {
204         var mh = 0;
205         Roo.each(this.innerLists, function(il,i) {
206             var el = this.views[i].getEl();
207             el.dom.style.height = '';
208             var inner = el.dom;
209             var h = Math.max(inner.clientHeight, inner.offsetHeight, inner.scrollHeight);
210             // only adjust heights on other ones..
211             if (i < 1) {
212                 
213                 el.setHeight(h < this.maxHeight ? 'auto' : this.maxHeight);
214                 il.setHeight(h < this.maxHeight ? 'auto' : this.maxHeight);
215                 mh = Math.max(el.getHeight(), mh);
216             }
217             
218             
219         }, this);
220         
221         this.list.beginUpdate();
222         this.list.setHeight(mh+this.list.getFrameWidth('tb')+this.assetHeight);
223         this.list.alignTo(this.el, this.listAlign);
224         this.list.endUpdate();
225         
226     },
227      
228     
229     // -- store handlers..
230     // private
231     onBeforeLoad : function()
232     {
233         if(!this.hasFocus){
234             return;
235         }
236         this.innerLists[0].update(this.loadingText ?
237                '<div class="loading-indicator">'+this.loadingText+'</div>' : '');
238         this.restrictHeight();
239         this.selectedIndex = -1;
240     },
241     // private
242     onLoad : function(a,b,c,d)
243     {
244         
245         if(!this.hasFocus){
246             return;
247         }
248         
249         if(this.store.getCount() > 0) {
250             this.expand();
251             this.restrictHeight();   
252         } else {
253             this.onEmptyResults();
254         }
255         /*
256         this.stores[1].loadData([]);
257         this.stores[2].loadData([]);
258         this.views
259         */    
260     
261         //this.el.focus();
262     },
263     
264     
265     // private
266     onLoadException : function()
267     {
268         this.collapse();
269         Roo.log(this.store.reader.jsonData);
270         if (this.store && typeof(this.store.reader.jsonData.errorMsg) != 'undefined') {
271             Roo.MessageBox.alert("Error loading",this.store.reader.jsonData.errorMsg);
272         }
273         
274         
275     } ,
276      
277      
278
279     onSelectChange : function (view, sels, opts )
280     {
281         var ix = view.getSelectedIndexes();
282          
283         if (opts.list > this.maxColumns - 2) {
284             this.setFromData(ix.length ? view.store.getAt(ix[0]).data : {});
285             return;
286         }
287         
288         if (!ix.length) {
289             this.setFromData({});
290             var str = this.stores[opts.list+1];
291             str.removeAll();
292             return;
293         }
294         
295         var rec = view.store.getAt(ix[0]);
296         if (!this.isLoading) {
297             this.setFromData(rec.data);
298         }
299         
300         
301         var lw = Math.floor(
302                 ((this.listWidth * this.maxColumns || Math.max(this.wrap.getWidth(), this.minListWidth)) - this.list.getFrameWidth('lr')) / this.maxColumns
303         );
304         
305         this.stores[opts.list+1].loadDataFromChildren( rec );
306         var dl = this.stores[opts.list+1]. getTotalCount();
307         this.views[opts.list+1].getEl().setHeight( this.innerLists[0].getHeight());
308         this.views[opts.list+1].getEl().setStyle({ display : dl ? 'block' : 'none' });
309         this.innerLists[opts.list+1].setHeight( this.innerLists[0].getHeight());
310         this.list.setWidth(lw * (opts.list + (dl ? 2 : 1)));
311         
312         if (this.isLoading) {
313             this.selectActive(opts.list);
314         }
315          
316     },
317     onDoubleClick : function()
318     {
319         this.collapse(); //??
320     },
321     
322      
323     
324     findRecord : function (prop,value)
325     {
326         return this.findRecordInStore(this.store, prop,value);
327     },
328     
329     // private
330     findRecordInStore : function(store, prop, value)
331     {
332         var cstore = new Roo.data.SimpleStore({
333             //fields : this.store.reader.meta.fields, // we need array reader.. for
334             reader : this.store.reader,
335             data : [ ]
336         });
337         var _this = this;
338         var record  = false;
339         if(store.getCount() < 1){
340             return false;
341         }
342         store.each(function(r){
343             if(r.data[prop] == value){
344                 record = r;
345                 return false;
346             }
347             if (r.data.cn && r.data.cn.length) {
348                 cstore.loadDataFromChildren( r);
349                 var cret = _this.findRecordInStore(cstore, prop, value);
350                 if (cret !== false) {
351                     record = cret;
352                     return false;
353                 }
354             }
355              
356             return true;
357         });
358         
359         return record;
360     },
361     
362     
363     
364     selectActive : function (lvl)
365     {
366         var cstore = new Roo.data.SimpleStore({
367             //fields : this.store.reader.meta.fields, // we need array reader.. for
368             reader : this.store.reader,
369             data : [ ]
370         });
371         // just need to determine which of the current level is selected if any..
372         var value = this.getValue();
373         var prop = this.hiddenName;
374         var store = this.stores[lvl];
375         if(store.getCount() < 1){
376             return;
377         }
378         
379         store.each(function(r){
380             // selected is at this level
381             if(r.data[prop] == value){
382                 var ix = store.getIndexOf(r);
383                 this.views[lvl].select(ix, false, true); // do not trigger set active..
384                 return false;
385             }
386             
387             if (r.data.cn && r.data.cn.length) {
388                 cstore.loadDataFromChildren(r);
389                 var cret = _this.findRecordInStore(cstore, prop, value);
390                 if (cret !== false) {
391                     var ix = store.getIndexOf(r);
392                     this.views[lvl].select(ix, false, false); // will trigger select change..
393                     return false;
394                 }
395             }
396              
397             return true;
398         });
399         
400     }
401     
402     
403     
404     
405 });