3 * Copyright(c) 2008-2011 Alan Knowles
10 * @class Roo.form.ComboNested
11 * @extends Roo.form.ComboBox
12 * A combobox for that allows selection of nested items in a list,
27 * Create a new ComboNested
28 * @param {Object} config Configuration options
30 Roo.form.ComboNested = function(config){
31 Roo.form.ComboCheck.superclass.constructor.call(this, config);
32 // should verify some data...
34 // hiddenName = required..
35 // displayField = required
36 // valudField == required
37 var req= [ 'hiddenName', 'displayField', 'valueField' ];
39 Roo.each(req, function(e) {
40 if ((typeof(_t[e]) == 'undefined' ) || !_t[e].length) {
41 throw "Roo.form.ComboNested : missing value for: " + e;
48 Roo.extend(Roo.form.ComboNested, Roo.form.ComboBox, {
51 * @config {Number} max Number of columns to show
56 list : null, // the outermost div..
57 innerLists : null, // the
61 loadingChildren : false,
63 onRender : function(ct, position)
65 Roo.form.ComboBox.superclass.onRender.call(this, ct, position); // skip parent call - got to above..
68 this.hiddenField = this.el.insertSibling({tag:'input', type:'hidden', name: this.hiddenName, id: (this.hiddenId||this.hiddenName)},
70 this.hiddenField.value =
71 this.hiddenValue !== undefined ? this.hiddenValue :
72 this.value !== undefined ? this.value : '';
74 // prevent input submission
75 this.el.dom.removeAttribute('name');
81 this.el.dom.setAttribute('autocomplete', 'off');
84 var cls = 'x-combo-list';
86 this.list = new Roo.Layer({
87 shadow: this.shadow, cls: [cls, this.listClass].join(' '), constrain:false
90 var lw = this.listWidth || Math.max(this.wrap.getWidth(), this.minListWidth);
91 this.list.setWidth(lw);
92 this.list.swallowEvent('mousewheel');
96 this.header = this.list.createChild({cls:cls+'-hd', html: this.title});
97 this.assetHeight += this.header.getHeight();
102 for (var i =0 ; i < this.maxColumns; i++) {
103 this.onRenderList( cls, i);
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);
119 if ( this.allowBlank && !this.disableClear) {
121 this.pageTb.add(new Roo.Toolbar.Fill(), {
122 cls: 'x-btn-icon x-btn-clear',
128 _this.onSelect(false, -1);
133 this.assetHeight += this.footer.getHeight();
137 onRenderList : function ( cls, i)
141 ((this.listWidth * this.maxColumns || Math.max(this.wrap.getWidth(), this.minListWidth)) - this.list.getFrameWidth('lr')) / this.maxColumns
144 this.list.setWidth(lw); // default to '1'
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 });
150 il.setStyle({ 'overflow-x' : 'hidden'});
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) {
157 var dl = typeof(value.data) != 'undefined' ? value.data.length : value.length; ///json is a nested response..
158 return dl ? 'has-children' : 'no-children'
163 var store = this.store;
165 store = new Roo.data.SimpleStore({
166 //fields : this.store.reader.meta.fields,
167 reader : this.store.reader,
171 this.stores[i] = store;
173 var view = this.views[i] = new Roo.View(
179 selectedClass: this.selectedClass
182 view.getEl().setWidth(lw);
183 view.getEl().setStyle({
184 position: i < 1 ? 'relative' : 'absolute',
186 left: (i * lw ) + 'px',
187 display : i > 0 ? 'none' : 'block'
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 });
193 store.on('beforeload', this.onBeforeLoad, this);
194 store.on('load', this.onLoad, this, { list : i});
195 store.on('loadexception', this.onLoadException, this);
197 // hide the other vies..
203 restrictHeight : function()
206 Roo.each(this.innerLists, function(il,i) {
207 var el = this.views[i].getEl();
208 el.dom.style.height = '';
210 var h = Math.max(il.clientHeight, il.offsetHeight, il.scrollHeight);
211 // only adjust heights on other ones..
212 mh = Math.max(h, mh);
215 el.setHeight(h < this.maxHeight ? 'auto' : this.maxHeight);
216 il.setHeight(h < this.maxHeight ? 'auto' : this.maxHeight);
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();
231 // -- store handlers..
233 onBeforeLoad : function()
238 this.innerLists[0].update(this.loadingText ?
239 '<div class="loading-indicator">'+this.loadingText+'</div>' : '');
240 this.restrictHeight();
241 this.selectedIndex = -1;
244 onLoad : function(a,b,c,d)
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' });
252 ((this.listWidth * this.maxColumns || Math.max(this.wrap.getWidth(), this.minListWidth)) - this.list.getFrameWidth('lr')) / this.maxColumns
255 this.list.setWidth(lw); // default to '1'
263 if(this.store.getCount() > 0) {
265 this.restrictHeight();
267 this.onEmptyResults();
270 if (!this.loadingChildren) {
274 this.stores[1].loadData([]);
275 this.stores[2].loadData([]);
284 onLoadException : function()
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);
294 // no cleaning of leading spaces on blur here.
295 cleanLeadingSpace : function(e) { },
298 onSelectChange : function (view, sels, opts )
300 var ix = view.getSelectedIndexes();
302 if (opts.list > this.maxColumns - 2) {
303 if (view.store.getCount()< 1) {
304 this.views[opts.list ].getEl().setStyle({ display : 'none' });
308 // used to clear ?? but if we are loading unselected
309 this.setFromData(view.store.getAt(ix[0]).data);
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..
325 var rec = view.store.getAt(ix[0]);
327 this.setFromData(rec.data);
328 this.fireEvent('select', this, rec, ix[0]);
332 (this.listWidth * this.maxColumns || Math.max(this.wrap.getWidth(), this.minListWidth)) - this.list.getFrameWidth('lr')
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();
340 this.views[opts.list+1].getEl().setHeight( this.innerLists[0].getHeight());
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' });
347 this.innerLists[opts.list+1].setHeight( this.innerLists[0].getHeight());
348 this.list.setWidth(lw * (opts.list + (dl ? 2 : 1)));
350 if (this.isLoading) {
351 // this.selectActive(opts.list);
359 onDoubleClick : function()
361 this.collapse(); //??
369 recordToStack : function(store, prop, value, stack)
371 var cstore = new Roo.data.SimpleStore({
372 //fields : this.store.reader.meta.fields, // we need array reader.. for
373 reader : this.store.reader,
379 if(store.getCount() < 1){
382 store.each(function(r){
383 if(r.data[prop] == value){
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) {
400 if (record == false) {
408 * find the stack of stores that match our value.
413 selectActive : function ()
415 // if store is not loaded, then we will need to wait for that to happen first.
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 );