handle revert on event handling - use createDelegate
[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     loadingChildren : false,
62     
63     onRender : function(ct, position)
64     {
65         Roo.form.ComboBox.superclass.onRender.call(this, ct, position); // skip parent call - got to above..
66         
67         if(this.hiddenName){
68             this.hiddenField = this.el.insertSibling({tag:'input', type:'hidden', name: this.hiddenName, id:  (this.hiddenId||this.hiddenName)},
69                     'before', true);
70             this.hiddenField.value =
71                 this.hiddenValue !== undefined ? this.hiddenValue :
72                 this.value !== undefined ? this.value : '';
73
74             // prevent input submission
75             this.el.dom.removeAttribute('name');
76              
77              
78         }
79         
80         if(Roo.isGecko){
81             this.el.dom.setAttribute('autocomplete', 'off');
82         }
83
84         var cls = 'x-combo-list';
85
86         this.list = new Roo.Layer({
87             shadow: this.shadow, cls: [cls, this.listClass].join(' '), constrain:false
88         });
89
90         var lw = this.listWidth || Math.max(this.wrap.getWidth(), this.minListWidth);
91         this.list.setWidth(lw);
92         this.list.swallowEvent('mousewheel');
93         this.assetHeight = 0;
94
95         if(this.title){
96             this.header = this.list.createChild({cls:cls+'-hd', html: this.title});
97             this.assetHeight += this.header.getHeight();
98         }
99         this.innerLists = [];
100         this.views = [];
101         this.stores = [];
102         for (var i =0 ; i < this.maxColumns; i++) {
103             this.onRenderList( cls, i);
104         }
105         
106         // always needs footer, as we are going to have an 'OK' button.
107         this.footer = this.list.createChild({cls:cls+'-ft'});
108         this.pageTb = new Roo.Toolbar(this.footer);  
109         var _this = this;
110         this.pageTb.add(  {
111             
112             text: 'Done',
113             handler: function()
114             {
115                 _this.collapse();
116             }
117         });
118         
119         if ( this.allowBlank && !this.disableClear) {
120             
121             this.pageTb.add(new Roo.Toolbar.Fill(), {
122                 cls: 'x-btn-icon x-btn-clear',
123                 text: '&#160;',
124                 handler: function()
125                 {
126                     _this.collapse();
127                     _this.clearValue();
128                     _this.onSelect(false, -1);
129                 }
130             });
131         }
132         if (this.footer) {
133             this.assetHeight += this.footer.getHeight();
134         }
135         
136     },
137     onRenderList : function (  cls, i)
138     {
139         
140         var lw = Math.floor(
141                 ((this.listWidth * this.maxColumns || Math.max(this.wrap.getWidth(), this.minListWidth)) - this.list.getFrameWidth('lr')) / this.maxColumns
142         );
143         
144         this.list.setWidth(lw); // default to '1'
145
146         var il = this.innerLists[i] = this.list.createChild({cls:cls+'-inner'});
147         //il.on('mouseover', this.onViewOver, this, { list:  i });
148         //il.on('mousemove', this.onViewMove, this, { list:  i });
149         il.setWidth(lw);
150         il.setStyle({ 'overflow-x' : 'hidden'});
151
152         if(!this.tpl){
153             this.tpl = new Roo.Template({
154                 html :  '<div class="'+cls+'-item '+cls+'-item-{cn:this.isEmpty}">{' + this.displayField + '}</div>',
155                 isEmpty: function (value, allValues) {
156                     //Roo.log(value);
157                     var dl = typeof(value.data) != 'undefined' ? value.data.length : value.length; ///json is a nested response..
158                     return dl ? 'has-children' : 'no-children'
159                 }
160             });
161         }
162         
163         var store  = this.store;
164         if (i > 0) {
165             store  = new Roo.data.SimpleStore({
166                 //fields : this.store.reader.meta.fields,
167                 reader : this.store.reader,
168                 data : [ ]
169             });
170         }
171         this.stores[i]  = store;
172                   
173         var view = this.views[i] = new Roo.View(
174             il,
175             this.tpl,
176             {
177                 singleSelect:true,
178                 store: store,
179                 selectedClass: this.selectedClass
180             }
181         );
182         view.getEl().setWidth(lw);
183         view.getEl().setStyle({
184             position: i < 1 ? 'relative' : 'absolute',
185             top: 0,
186             left: (i * lw ) + 'px',
187             display : i > 0 ? 'none' : 'block'
188         });
189         view.on('selectionchange', this.onSelectChange.createDelegate(this, {list : i }, true));
190         view.on('dblclick', this.onDoubleClick.createDelegate(this, {list : i }, true));
191         //view.on('click', this.onViewClick, this, { list : i });
192
193         store.on('beforeload', this.onBeforeLoad, this);
194         store.on('load',  this.onLoad, this, { list  : i});
195         store.on('loadexception', this.onLoadException, this);
196
197         // hide the other vies..
198         
199         
200         
201     },
202       
203     restrictHeight : function()
204     {
205         var mh = 0;
206         Roo.each(this.innerLists, function(il,i) {
207             var el = this.views[i].getEl();
208             el.dom.style.height = '';
209             var inner = el.dom;
210             var h = Math.max(il.clientHeight, il.offsetHeight, il.scrollHeight);
211             // only adjust heights on other ones..
212             mh = Math.max(h, mh);
213             if (i < 1) {
214                 
215                 el.setHeight(h < this.maxHeight ? 'auto' : this.maxHeight);
216                 il.setHeight(h < this.maxHeight ? 'auto' : this.maxHeight);
217                
218             }
219             
220             
221         }, this);
222         
223         this.list.beginUpdate();
224         this.list.setHeight(mh+this.list.getFrameWidth('tb')+this.assetHeight);
225         this.list.alignTo(this.el, this.listAlign);
226         this.list.endUpdate();
227         
228     },
229      
230     
231     // -- store handlers..
232     // private
233     onBeforeLoad : function()
234     {
235         if(!this.hasFocus){
236             return;
237         }
238         this.innerLists[0].update(this.loadingText ?
239                '<div class="loading-indicator">'+this.loadingText+'</div>' : '');
240         this.restrictHeight();
241         this.selectedIndex = -1;
242     },
243     // private
244     onLoad : function(a,b,c,d)
245     {
246         if (!this.loadingChildren) {
247             // then we are loading the top level. - hide the children
248             for (var i = 1;i < this.views.length; i++) {
249                 this.views[i].getEl().setStyle({ display : 'none' });
250             }
251             var lw = Math.floor(
252                 ((this.listWidth * this.maxColumns || Math.max(this.wrap.getWidth(), this.minListWidth)) - this.list.getFrameWidth('lr')) / this.maxColumns
253             );
254         
255              this.list.setWidth(lw); // default to '1'
256
257             
258         }
259         if(!this.hasFocus){
260             return;
261         }
262         
263         if(this.store.getCount() > 0) {
264             this.expand();
265             this.restrictHeight();   
266         } else {
267             this.onEmptyResults();
268         }
269         
270         if (!this.loadingChildren) {
271             this.selectActive();
272         }
273         /*
274         this.stores[1].loadData([]);
275         this.stores[2].loadData([]);
276         this.views
277         */    
278     
279         //this.el.focus();
280     },
281     
282     
283     // private
284     onLoadException : function()
285     {
286         this.collapse();
287         Roo.log(this.store.reader.jsonData);
288         if (this.store && typeof(this.store.reader.jsonData.errorMsg) != 'undefined') {
289             Roo.MessageBox.alert("Error loading",this.store.reader.jsonData.errorMsg);
290         }
291         
292         
293     },
294     // no cleaning of leading spaces on blur here.
295     cleanLeadingSpace : function(e) { },
296     
297
298     onSelectChange : function (view, sels, opts )
299     {
300         var ix = view.getSelectedIndexes();
301          
302         if (opts.list > this.maxColumns - 2) {
303             if (view.store.getCount()<  1) {
304                 this.views[opts.list ].getEl().setStyle({ display :   'none' });
305
306             } else  {
307                 if (ix.length) {
308                     // used to clear ?? but if we are loading unselected 
309                     this.setFromData(view.store.getAt(ix[0]).data);
310                 }
311                 
312             }
313             
314             return;
315         }
316         
317         if (!ix.length) {
318             // this get's fired when trigger opens..
319            // this.setFromData({});
320             var str = this.stores[opts.list+1];
321             str.data.clear(); // removeall wihtout the fire events..
322             return;
323         }
324         
325         var rec = view.store.getAt(ix[0]);
326          
327         this.setFromData(rec.data);
328         this.fireEvent('select', this, rec, ix[0]);
329         
330         var lw = Math.floor(
331              (
332                 (this.listWidth * this.maxColumns || Math.max(this.wrap.getWidth(), this.minListWidth)) - this.list.getFrameWidth('lr')
333              ) / this.maxColumns
334         );
335         this.loadingChildren = true;
336         this.stores[opts.list+1].loadDataFromChildren( rec );
337         this.loadingChildren = false;
338         var dl = this.stores[opts.list+1]. getTotalCount();
339         
340         this.views[opts.list+1].getEl().setHeight( this.innerLists[0].getHeight());
341         
342         this.views[opts.list+1].getEl().setStyle({ display : dl ? 'block' : 'none' });
343         for (var i = opts.list+2; i < this.views.length;i++) {
344             this.views[i].getEl().setStyle({ display : 'none' });
345         }
346         
347         this.innerLists[opts.list+1].setHeight( this.innerLists[0].getHeight());
348         this.list.setWidth(lw * (opts.list + (dl ? 2 : 1)));
349         
350         if (this.isLoading) {
351            // this.selectActive(opts.list);
352         }
353          
354     },
355     
356     
357     
358     
359     onDoubleClick : function()
360     {
361         this.collapse(); //??
362     },
363     
364      
365     
366     
367     
368     // private
369     recordToStack : function(store, prop, value, stack)
370     {
371         var cstore = new Roo.data.SimpleStore({
372             //fields : this.store.reader.meta.fields, // we need array reader.. for
373             reader : this.store.reader,
374             data : [ ]
375         });
376         var _this = this;
377         var record  = false;
378         var srec = false;
379         if(store.getCount() < 1){
380             return false;
381         }
382         store.each(function(r){
383             if(r.data[prop] == value){
384                 record = r;
385             srec = r;
386                 return false;
387             }
388             if (r.data.cn && r.data.cn.length) {
389                 cstore.loadDataFromChildren( r);
390                 var cret = _this.recordToStack(cstore, prop, value, stack);
391                 if (cret !== false) {
392                     record = cret;
393                     srec = r;
394                     return false;
395                 }
396             }
397              
398             return true;
399         });
400         if (record == false) {
401             return false
402         }
403         stack.unshift(srec);
404         return record;
405     },
406     
407     /*
408      * find the stack of stores that match our value.
409      *
410      * 
411      */
412     
413     selectActive : function ()
414     {
415         // if store is not loaded, then we will need to wait for that to happen first.
416         var stack = [];
417         this.recordToStack(this.store, this.valueField, this.getValue(), stack);
418         for (var i = 0; i < stack.length; i++ ) {
419             this.views[i].select(stack[i].store.indexOf(stack[i]), false, false );
420         }
421         
422     }
423         
424          
425     
426     
427     
428     
429 });