Roo/bootstrap/ComboBox.js
[roojs1] / Roo / bootstrap / ComboBox.js
1 /*
2  * - LGPL
3  * * 
4  */
5
6 /**
7  * @class Roo.bootstrap.ComboBox
8  * @extends Roo.bootstrap.TriggerField
9  * A combobox control with support for autocomplete, remote-loading, paging and many other features.
10  * @cfg {Boolean} append (true|false) default false
11  * @cfg {Boolean} autoFocus (true|false) auto focus the first item, default true
12  * @constructor
13  * Create a new ComboBox.
14  * @param {Object} config Configuration options
15  */
16 Roo.bootstrap.ComboBox = function(config){
17     Roo.bootstrap.ComboBox.superclass.constructor.call(this, config);
18     this.addEvents({
19         /**
20          * @event expand
21          * Fires when the dropdown list is expanded
22              * @param {Roo.bootstrap.ComboBox} combo This combo box
23              */
24         'expand' : true,
25         /**
26          * @event collapse
27          * Fires when the dropdown list is collapsed
28              * @param {Roo.bootstrap.ComboBox} combo This combo box
29              */
30         'collapse' : true,
31         /**
32          * @event beforeselect
33          * Fires before a list item is selected. Return false to cancel the selection.
34              * @param {Roo.bootstrap.ComboBox} combo This combo box
35              * @param {Roo.data.Record} record The data record returned from the underlying store
36              * @param {Number} index The index of the selected item in the dropdown list
37              */
38         'beforeselect' : true,
39         /**
40          * @event select
41          * Fires when a list item is selected
42              * @param {Roo.bootstrap.ComboBox} combo This combo box
43              * @param {Roo.data.Record} record The data record returned from the underlying store (or false on clear)
44              * @param {Number} index The index of the selected item in the dropdown list
45              */
46         'select' : true,
47         /**
48          * @event beforequery
49          * Fires before all queries are processed. Return false to cancel the query or set cancel to true.
50          * The event object passed has these properties:
51              * @param {Roo.bootstrap.ComboBox} combo This combo box
52              * @param {String} query The query
53              * @param {Boolean} forceAll true to force "all" query
54              * @param {Boolean} cancel true to cancel the query
55              * @param {Object} e The query event object
56              */
57         'beforequery': true,
58          /**
59          * @event add
60          * Fires when the 'add' icon is pressed (add a listener to enable add button)
61              * @param {Roo.bootstrap.ComboBox} combo This combo box
62              */
63         'add' : true,
64         /**
65          * @event edit
66          * Fires when the 'edit' icon is pressed (add a listener to enable add button)
67              * @param {Roo.bootstrap.ComboBox} combo This combo box
68              * @param {Roo.data.Record|false} record The data record returned from the underlying store (or false on nothing selected)
69              */
70         'edit' : true,
71         /**
72          * @event remove
73          * Fires when the remove value from the combobox array
74              * @param {Roo.bootstrap.ComboBox} combo This combo box
75              */
76         'remove' : true
77         
78     });
79     
80     
81     this.selectedIndex = -1;
82     if(this.mode == 'local'){
83         if(config.queryDelay === undefined){
84             this.queryDelay = 10;
85         }
86         if(config.minChars === undefined){
87             this.minChars = 0;
88         }
89     }
90 };
91
92 Roo.extend(Roo.bootstrap.ComboBox, Roo.bootstrap.TriggerField, {
93      
94     /**
95      * @cfg {Boolean} lazyRender True to prevent the ComboBox from rendering until requested (should always be used when
96      * rendering into an Roo.Editor, defaults to false)
97      */
98     /**
99      * @cfg {Boolean/Object} autoCreate A DomHelper element spec, or true for a default element spec (defaults to:
100      * {tag: "input", type: "text", size: "24", autocomplete: "off"})
101      */
102     /**
103      * @cfg {Roo.data.Store} store The data store to which this combo is bound (defaults to undefined)
104      */
105     /**
106      * @cfg {String} title If supplied, a header element is created containing this text and added into the top of
107      * the dropdown list (defaults to undefined, with no header element)
108      */
109
110      /**
111      * @cfg {String/Roo.Template} tpl The template to use to render the output
112      */
113      
114      /**
115      * @cfg {Number} listWidth The width in pixels of the dropdown list (defaults to the width of the ComboBox field)
116      */
117     listWidth: undefined,
118     /**
119      * @cfg {String} displayField The underlying data field name to bind to this CombBox (defaults to undefined if
120      * mode = 'remote' or 'text' if mode = 'local')
121      */
122     displayField: undefined,
123     /**
124      * @cfg {String} valueField The underlying data value name to bind to this CombBox (defaults to undefined if
125      * mode = 'remote' or 'value' if mode = 'local'). 
126      * Note: use of a valueField requires the user make a selection
127      * in order for a value to be mapped.
128      */
129     valueField: undefined,
130     
131     
132     /**
133      * @cfg {String} hiddenName If specified, a hidden form field with this name is dynamically generated to store the
134      * field's data value (defaults to the underlying DOM element's name)
135      */
136     hiddenName: undefined,
137     /**
138      * @cfg {String} listClass CSS class to apply to the dropdown list element (defaults to '')
139      */
140     listClass: '',
141     /**
142      * @cfg {String} selectedClass CSS class to apply to the selected item in the dropdown list (defaults to 'x-combo-selected')
143      */
144     selectedClass: 'active',
145     
146     /**
147      * @cfg {Boolean/String} shadow True or "sides" for the default effect, "frame" for 4-way shadow, and "drop" for bottom-right
148      */
149     shadow:'sides',
150     /**
151      * @cfg {String} listAlign A valid anchor position value. See {@link Roo.Element#alignTo} for details on supported
152      * anchor positions (defaults to 'tl-bl')
153      */
154     listAlign: 'tl-bl?',
155     /**
156      * @cfg {Number} maxHeight The maximum height in pixels of the dropdown list before scrollbars are shown (defaults to 300)
157      */
158     maxHeight: 300,
159     /**
160      * @cfg {String} triggerAction The action to execute when the trigger field is activated.  Use 'all' to run the
161      * query specified by the allQuery config option (defaults to 'query')
162      */
163     triggerAction: 'query',
164     /**
165      * @cfg {Number} minChars The minimum number of characters the user must type before autocomplete and typeahead activate
166      * (defaults to 4, does not apply if editable = false)
167      */
168     minChars : 4,
169     /**
170      * @cfg {Boolean} typeAhead True to populate and autoselect the remainder of the text being typed after a configurable
171      * delay (typeAheadDelay) if it matches a known value (defaults to false)
172      */
173     typeAhead: false,
174     /**
175      * @cfg {Number} queryDelay The length of time in milliseconds to delay between the start of typing and sending the
176      * query to filter the dropdown list (defaults to 500 if mode = 'remote' or 10 if mode = 'local')
177      */
178     queryDelay: 500,
179     /**
180      * @cfg {Number} pageSize If greater than 0, a paging toolbar is displayed in the footer of the dropdown list and the
181      * filter queries will execute with page start and limit parameters.  Only applies when mode = 'remote' (defaults to 0)
182      */
183     pageSize: 0,
184     /**
185      * @cfg {Boolean} selectOnFocus True to select any existing text in the field immediately on focus.  Only applies
186      * when editable = true (defaults to false)
187      */
188     selectOnFocus:false,
189     /**
190      * @cfg {String} queryParam Name of the query as it will be passed on the querystring (defaults to 'query')
191      */
192     queryParam: 'query',
193     /**
194      * @cfg {String} loadingText The text to display in the dropdown list while data is loading.  Only applies
195      * when mode = 'remote' (defaults to 'Loading...')
196      */
197     loadingText: 'Loading...',
198     /**
199      * @cfg {Boolean} resizable True to add a resize handle to the bottom of the dropdown list (defaults to false)
200      */
201     resizable: false,
202     /**
203      * @cfg {Number} handleHeight The height in pixels of the dropdown list resize handle if resizable = true (defaults to 8)
204      */
205     handleHeight : 8,
206     /**
207      * @cfg {Boolean} editable False to prevent the user from typing text directly into the field, just like a
208      * traditional select (defaults to true)
209      */
210     editable: true,
211     /**
212      * @cfg {String} allQuery The text query to send to the server to return all records for the list with no filtering (defaults to '')
213      */
214     allQuery: '',
215     /**
216      * @cfg {String} mode Set to 'local' if the ComboBox loads local data (defaults to 'remote' which loads from the server)
217      */
218     mode: 'remote',
219     /**
220      * @cfg {Number} minListWidth The minimum width of the dropdown list in pixels (defaults to 70, will be ignored if
221      * listWidth has a higher value)
222      */
223     minListWidth : 70,
224     /**
225      * @cfg {Boolean} forceSelection True to restrict the selected value to one of the values in the list, false to
226      * allow the user to set arbitrary text into the field (defaults to false)
227      */
228     forceSelection:false,
229     /**
230      * @cfg {Number} typeAheadDelay The length of time in milliseconds to wait until the typeahead text is displayed
231      * if typeAhead = true (defaults to 250)
232      */
233     typeAheadDelay : 250,
234     /**
235      * @cfg {String} valueNotFoundText When using a name/value combo, if the value passed to setValue is not found in
236      * the store, valueNotFoundText will be displayed as the field text if defined (defaults to undefined)
237      */
238     valueNotFoundText : undefined,
239     /**
240      * @cfg {Boolean} blockFocus Prevents all focus calls, so it can work with things like HTML edtor bar
241      */
242     blockFocus : false,
243     
244     /**
245      * @cfg {Boolean} disableClear Disable showing of clear button.
246      */
247     disableClear : false,
248     /**
249      * @cfg {Boolean} alwaysQuery  Disable caching of results, and always send query
250      */
251     alwaysQuery : false,
252     
253     /**
254      * @cfg {Boolean} multiple  (true|false) ComboBobArray, default false
255      */
256     multiple : false,
257     
258     //private
259     addicon : false,
260     editicon: false,
261     
262     page: 0,
263     hasQuery: false,
264     append: false,
265     loadNext: false,
266     autoFocus : true,
267     item: [],
268     
269     // element that contains real text value.. (when hidden is used..)
270      
271     // private
272     initEvents: function(){
273         
274         if (!this.store) {
275             throw "can not find store for combo";
276         }
277         this.store = Roo.factory(this.store, Roo.data);
278         
279         
280         
281         Roo.bootstrap.ComboBox.superclass.initEvents.call(this);
282         
283         
284         if(this.hiddenName){
285             
286             this.hiddenField = this.el.select('input.form-hidden-field',true).first();
287             
288             this.hiddenField.dom.value =
289                 this.hiddenValue !== undefined ? this.hiddenValue :
290                 this.value !== undefined ? this.value : '';
291
292             // prevent input submission
293             this.el.dom.removeAttribute('name');
294             this.hiddenField.dom.setAttribute('name', this.hiddenName);
295              
296              
297         }
298         //if(Roo.isGecko){
299         //    this.el.dom.setAttribute('autocomplete', 'off');
300         //}
301
302         var cls = 'x-combo-list';
303         this.list = this.el.select('ul.dropdown-menu',true).first();
304
305         //this.list = new Roo.Layer({
306         //    shadow: this.shadow, cls: [cls, this.listClass].join(' '), constrain:false
307         //});
308         
309         var lw = this.listWidth || Math.max(this.inputEl().getWidth(), this.minListWidth);
310         this.list.setWidth(lw);
311         
312         this.list.on('mouseover', this.onViewOver, this);
313         this.list.on('mousemove', this.onViewMove, this);
314         
315         this.list.on('scroll', this.onViewScroll, this);
316         
317         /*
318         this.list.swallowEvent('mousewheel');
319         this.assetHeight = 0;
320
321         if(this.title){
322             this.header = this.list.createChild({cls:cls+'-hd', html: this.title});
323             this.assetHeight += this.header.getHeight();
324         }
325
326         this.innerList = this.list.createChild({cls:cls+'-inner'});
327         this.innerList.on('mouseover', this.onViewOver, this);
328         this.innerList.on('mousemove', this.onViewMove, this);
329         this.innerList.setWidth(lw - this.list.getFrameWidth('lr'));
330         
331         if(this.allowBlank && !this.pageSize && !this.disableClear){
332             this.footer = this.list.createChild({cls:cls+'-ft'});
333             this.pageTb = new Roo.Toolbar(this.footer);
334            
335         }
336         if(this.pageSize){
337             this.footer = this.list.createChild({cls:cls+'-ft'});
338             this.pageTb = new Roo.PagingToolbar(this.footer, this.store,
339                     {pageSize: this.pageSize});
340             
341         }
342         
343         if (this.pageTb && this.allowBlank && !this.disableClear) {
344             var _this = this;
345             this.pageTb.add(new Roo.Toolbar.Fill(), {
346                 cls: 'x-btn-icon x-btn-clear',
347                 text: ' ',
348                 handler: function()
349                 {
350                     _this.collapse();
351                     _this.clearValue();
352                     _this.onSelect(false, -1);
353                 }
354             });
355         }
356         if (this.footer) {
357             this.assetHeight += this.footer.getHeight();
358         }
359         */
360             
361         if(!this.tpl){
362             this.tpl = '<li><a href="#">{' + this.displayField + '}</a></li>';
363         }
364
365         this.view = new Roo.View(this.el.select('ul.dropdown-menu',true).first(), this.tpl, {
366             singleSelect:true, store: this.store, selectedClass: this.selectedClass
367         });
368         //this.view.wrapEl.setDisplayed(false);
369         this.view.on('click', this.onViewClick, this);
370         
371         
372         
373         this.store.on('beforeload', this.onBeforeLoad, this);
374         this.store.on('load', this.onLoad, this);
375         this.store.on('loadexception', this.onLoadException, this);
376         /*
377         if(this.resizable){
378             this.resizer = new Roo.Resizable(this.list,  {
379                pinned:true, handles:'se'
380             });
381             this.resizer.on('resize', function(r, w, h){
382                 this.maxHeight = h-this.handleHeight-this.list.getFrameWidth('tb')-this.assetHeight;
383                 this.listWidth = w;
384                 this.innerList.setWidth(w - this.list.getFrameWidth('lr'));
385                 this.restrictHeight();
386             }, this);
387             this[this.pageSize?'footer':'innerList'].setStyle('margin-bottom', this.handleHeight+'px');
388         }
389         */
390         if(!this.editable){
391             this.editable = true;
392             this.setEditable(false);
393         }
394         
395         /*
396         
397         if (typeof(this.events.add.listeners) != 'undefined') {
398             
399             this.addicon = this.wrap.createChild(
400                 {tag: 'img', src: Roo.BLANK_IMAGE_URL, cls: 'x-form-combo-add' });  
401        
402             this.addicon.on('click', function(e) {
403                 this.fireEvent('add', this);
404             }, this);
405         }
406         if (typeof(this.events.edit.listeners) != 'undefined') {
407             
408             this.editicon = this.wrap.createChild(
409                 {tag: 'img', src: Roo.BLANK_IMAGE_URL, cls: 'x-form-combo-edit' });  
410             if (this.addicon) {
411                 this.editicon.setStyle('margin-left', '40px');
412             }
413             this.editicon.on('click', function(e) {
414                 
415                 // we fire even  if inothing is selected..
416                 this.fireEvent('edit', this, this.lastData );
417                 
418             }, this);
419         }
420         */
421         
422         this.keyNav = new Roo.KeyNav(this.inputEl(), {
423             "up" : function(e){
424                 this.inKeyMode = true;
425                 this.selectPrev();
426             },
427
428             "down" : function(e){
429                 if(!this.isExpanded()){
430                     this.onTriggerClick();
431                 }else{
432                     this.inKeyMode = true;
433                     this.selectNext();
434                 }
435             },
436
437             "enter" : function(e){
438                 this.onViewClick();
439                 //return true;
440             },
441
442             "esc" : function(e){
443                 this.collapse();
444             },
445
446             "tab" : function(e){
447                 this.collapse();
448                 
449                 if(this.fireEvent("specialkey", this, e)){
450                     this.onViewClick(false);
451                 }
452                 
453                 return true;
454             },
455
456             scope : this,
457
458             doRelay : function(foo, bar, hname){
459                 if(hname == 'down' || this.scope.isExpanded()){
460                    return Roo.KeyNav.prototype.doRelay.apply(this, arguments);
461                 }
462                 return true;
463             },
464
465             forceKeyDown: true
466         });
467         
468         
469         this.queryDelay = Math.max(this.queryDelay || 10,
470                 this.mode == 'local' ? 10 : 250);
471         
472         
473         this.dqTask = new Roo.util.DelayedTask(this.initQuery, this);
474         
475         if(this.typeAhead){
476             this.taTask = new Roo.util.DelayedTask(this.onTypeAhead, this);
477         }
478         if(this.editable !== false){
479             this.inputEl().on("keyup", this.onKeyUp, this);
480         }
481         if(this.forceSelection){
482             this.inputEl().on('blur', this.doForce, this);
483         }
484         
485         if(this.multiple){
486             this.choices = this.el.select('ul.select2-choices', true).first();
487             this.searchField = this.el.select('ul li.select2-search-field', true).first();
488         }
489     },
490
491     onDestroy : function(){
492         if(this.view){
493             this.view.setStore(null);
494             this.view.el.removeAllListeners();
495             this.view.el.remove();
496             this.view.purgeListeners();
497         }
498         if(this.list){
499             this.list.dom.innerHTML  = '';
500         }
501         if(this.store){
502             this.store.un('beforeload', this.onBeforeLoad, this);
503             this.store.un('load', this.onLoad, this);
504             this.store.un('loadexception', this.onLoadException, this);
505         }
506         Roo.bootstrap.ComboBox.superclass.onDestroy.call(this);
507     },
508
509     // private
510     fireKey : function(e){
511         if(e.isNavKeyPress() && !this.list.isVisible()){
512             this.fireEvent("specialkey", this, e);
513         }
514     },
515
516     // private
517     onResize: function(w, h){
518 //        Roo.bootstrap.ComboBox.superclass.onResize.apply(this, arguments);
519 //        
520 //        if(typeof w != 'number'){
521 //            // we do not handle it!?!?
522 //            return;
523 //        }
524 //        var tw = this.trigger.getWidth();
525 //       // tw += this.addicon ? this.addicon.getWidth() : 0;
526 //       // tw += this.editicon ? this.editicon.getWidth() : 0;
527 //        var x = w - tw;
528 //        this.inputEl().setWidth( this.adjustWidth('input', x));
529 //            
530 //        //this.trigger.setStyle('left', x+'px');
531 //        
532 //        if(this.list && this.listWidth === undefined){
533 //            var lw = Math.max(x + this.trigger.getWidth(), this.minListWidth);
534 //            this.list.setWidth(lw);
535 //            this.innerList.setWidth(lw - this.list.getFrameWidth('lr'));
536 //        }
537         
538     
539         
540     },
541
542     /**
543      * Allow or prevent the user from directly editing the field text.  If false is passed,
544      * the user will only be able to select from the items defined in the dropdown list.  This method
545      * is the runtime equivalent of setting the 'editable' config option at config time.
546      * @param {Boolean} value True to allow the user to directly edit the field text
547      */
548     setEditable : function(value){
549         if(value == this.editable){
550             return;
551         }
552         this.editable = value;
553         if(!value){
554             this.inputEl().dom.setAttribute('readOnly', true);
555             this.inputEl().on('mousedown', this.onTriggerClick,  this);
556             this.inputEl().addClass('x-combo-noedit');
557         }else{
558             this.inputEl().dom.setAttribute('readOnly', false);
559             this.inputEl().un('mousedown', this.onTriggerClick,  this);
560             this.inputEl().removeClass('x-combo-noedit');
561         }
562     },
563
564     // private
565     
566     onBeforeLoad : function(combo,opts){
567         if(!this.hasFocus){
568             return;
569         }
570          if (!opts.add) {
571             this.list.dom.innerHTML = '<li class="loading-indicator">'+(this.loadingText||'loading')+'</li>' ;
572          }
573         this.restrictHeight();
574         this.selectedIndex = -1;
575     },
576
577     // private
578     onLoad : function(){
579         
580         this.hasQuery = false;
581         
582         if(!this.hasFocus){
583             return;
584         }
585         
586         if(typeof(this.loading) !== 'undefined' && this.loading !== null){
587             this.loading.hide();
588         }
589         
590         if(this.store.getCount() > 0){
591             this.expand();
592             this.restrictHeight();
593             if(this.lastQuery == this.allQuery){
594                 if(this.editable){
595                     this.inputEl().dom.select();
596                 }
597                 if(!this.selectByValue(this.value, true) && this.selectFirst){
598                     this.select(0, true);
599                 }
600             }else{
601                 if(this.selectFirst){
602                     this.selectNext();
603                 }
604                 if(this.typeAhead && this.lastKey != Roo.EventObject.BACKSPACE && this.lastKey != Roo.EventObject.DELETE){
605                     this.taTask.delay(this.typeAheadDelay);
606                 }
607             }
608         }else{
609             this.onEmptyResults();
610         }
611         
612         //this.el.focus();
613     },
614     // private
615     onLoadException : function()
616     {
617         this.hasQuery = false;
618         
619         if(typeof(this.loading) !== 'undefined' && this.loading !== null){
620             this.loading.hide();
621         }
622         
623         this.collapse();
624         Roo.log(this.store.reader.jsonData);
625         if (this.store && typeof(this.store.reader.jsonData.errorMsg) != 'undefined') {
626             // fixme
627             //Roo.MessageBox.alert("Error loading",this.store.reader.jsonData.errorMsg);
628         }
629         
630         
631     },
632     // private
633     onTypeAhead : function(){
634         if(this.store.getCount() > 0){
635             var r = this.store.getAt(0);
636             var newValue = r.data[this.displayField];
637             var len = newValue.length;
638             var selStart = this.getRawValue().length;
639             
640             if(selStart != len){
641                 this.setRawValue(newValue);
642                 this.selectText(selStart, newValue.length);
643             }
644         }
645     },
646
647     // private
648     onSelect : function(record, index){
649         
650         if(this.fireEvent('beforeselect', this, record, index) !== false){
651         
652             this.setFromData(index > -1 ? record.data : false);
653             
654             this.collapse();
655             this.fireEvent('select', this, record, index);
656         }
657     },
658
659     /**
660      * Returns the currently selected field value or empty string if no value is set.
661      * @return {String} value The selected value
662      */
663     getValue : function(){
664         
665         if(this.multiple){
666             return (this.hiddenField) ? this.hiddenField.dom.value : this.value;
667         }
668         
669         if(this.valueField){
670             return typeof this.value != 'undefined' ? this.value : '';
671         }else{
672             return Roo.bootstrap.ComboBox.superclass.getValue.call(this);
673         }
674     },
675
676     /**
677      * Clears any text/value currently set in the field
678      */
679     clearValue : function(){
680         if(this.hiddenField){
681             this.hiddenField.dom.value = '';
682         }
683         this.value = '';
684         this.setRawValue('');
685         this.lastSelectionText = '';
686         
687     },
688
689     /**
690      * Sets the specified value into the field.  If the value finds a match, the corresponding record text
691      * will be displayed in the field.  If the value does not match the data value of an existing item,
692      * and the valueNotFoundText config option is defined, it will be displayed as the default field text.
693      * Otherwise the field will be blank (although the value will still be set).
694      * @param {String} value The value to match
695      */
696     setValue : function(v){
697         if(this.multiple){
698             this.syncValue();
699             return;
700         }
701         
702         var text = v;
703         if(this.valueField){
704             var r = this.findRecord(this.valueField, v);
705             if(r){
706                 text = r.data[this.displayField];
707             }else if(this.valueNotFoundText !== undefined){
708                 text = this.valueNotFoundText;
709             }
710         }
711         this.lastSelectionText = text;
712         if(this.hiddenField){
713             this.hiddenField.dom.value = v;
714         }
715         Roo.bootstrap.ComboBox.superclass.setValue.call(this, text);
716         this.value = v;
717     },
718     /**
719      * @property {Object} the last set data for the element
720      */
721     
722     lastData : false,
723     /**
724      * Sets the value of the field based on a object which is related to the record format for the store.
725      * @param {Object} value the value to set as. or false on reset?
726      */
727     setFromData : function(o){
728         
729         if(this.multiple){
730             this.addItem(o);
731             return;
732         }
733             
734         var dv = ''; // display value
735         var vv = ''; // value value..
736         this.lastData = o;
737         if (this.displayField) {
738             dv = !o || typeof(o[this.displayField]) == 'undefined' ? '' : o[this.displayField];
739         } else {
740             // this is an error condition!!!
741             Roo.log('no  displayField value set for '+ (this.name ? this.name : this.id));
742         }
743         
744         if(this.valueField){
745             vv = !o || typeof(o[this.valueField]) == 'undefined' ? dv : o[this.valueField];
746         }
747         
748         if(this.hiddenField){
749             this.hiddenField.dom.value = vv;
750             
751             this.lastSelectionText = dv;
752             Roo.bootstrap.ComboBox.superclass.setValue.call(this, dv);
753             this.value = vv;
754             return;
755         }
756         // no hidden field.. - we store the value in 'value', but still display
757         // display field!!!!
758         this.lastSelectionText = dv;
759         Roo.bootstrap.ComboBox.superclass.setValue.call(this, dv);
760         this.value = vv;
761         
762         
763     },
764     // private
765     reset : function(){
766         // overridden so that last data is reset..
767         this.setValue(this.originalValue);
768         this.clearInvalid();
769         this.lastData = false;
770         if (this.view) {
771             this.view.clearSelections();
772         }
773     },
774     // private
775     findRecord : function(prop, value){
776         var record;
777         if(this.store.getCount() > 0){
778             this.store.each(function(r){
779                 if(r.data[prop] == value){
780                     record = r;
781                     return false;
782                 }
783                 return true;
784             });
785         }
786         return record;
787     },
788     
789     getName: function()
790     {
791         // returns hidden if it's set..
792         if (!this.rendered) {return ''};
793         return !this.hiddenName && this.inputEl().dom.name  ? this.inputEl().dom.name : (this.hiddenName || '');
794         
795     },
796     // private
797     onViewMove : function(e, t){
798         this.inKeyMode = false;
799     },
800
801     // private
802     onViewOver : function(e, t){
803         if(this.inKeyMode){ // prevent key nav and mouse over conflicts
804             return;
805         }
806         var item = this.view.findItemFromChild(t);
807         if(item){
808             var index = this.view.indexOf(item);
809             this.select(index, false);
810         }
811     },
812
813     // private
814     onViewClick : function(doFocus)
815     {
816         var index = this.view.getSelectedIndexes()[0];
817         var r = this.store.getAt(index);
818         if(r){
819             this.onSelect(r, index);
820         }
821         if(doFocus !== false && !this.blockFocus){
822             this.inputEl().focus();
823         }
824     },
825
826     // private
827     restrictHeight : function(){
828         //this.innerList.dom.style.height = '';
829         //var inner = this.innerList.dom;
830         //var h = Math.max(inner.clientHeight, inner.offsetHeight, inner.scrollHeight);
831         //this.innerList.setHeight(h < this.maxHeight ? 'auto' : this.maxHeight);
832         //this.list.beginUpdate();
833         //this.list.setHeight(this.innerList.getHeight()+this.list.getFrameWidth('tb')+(this.resizable?this.handleHeight:0)+this.assetHeight);
834         this.list.alignTo(this.inputEl(), this.listAlign);
835         //this.list.endUpdate();
836     },
837
838     // private
839     onEmptyResults : function(){
840         this.collapse();
841     },
842
843     /**
844      * Returns true if the dropdown list is expanded, else false.
845      */
846     isExpanded : function(){
847         return this.list.isVisible();
848     },
849
850     /**
851      * Select an item in the dropdown list by its data value. This function does NOT cause the select event to fire.
852      * The store must be loaded and the list expanded for this function to work, otherwise use setValue.
853      * @param {String} value The data value of the item to select
854      * @param {Boolean} scrollIntoView False to prevent the dropdown list from autoscrolling to display the
855      * selected item if it is not currently in view (defaults to true)
856      * @return {Boolean} True if the value matched an item in the list, else false
857      */
858     selectByValue : function(v, scrollIntoView){
859         if(v !== undefined && v !== null){
860             var r = this.findRecord(this.valueField || this.displayField, v);
861             if(r){
862                 this.select(this.store.indexOf(r), scrollIntoView);
863                 return true;
864             }
865         }
866         return false;
867     },
868
869     /**
870      * Select an item in the dropdown list by its numeric index in the list. This function does NOT cause the select event to fire.
871      * The store must be loaded and the list expanded for this function to work, otherwise use setValue.
872      * @param {Number} index The zero-based index of the list item to select
873      * @param {Boolean} scrollIntoView False to prevent the dropdown list from autoscrolling to display the
874      * selected item if it is not currently in view (defaults to true)
875      */
876     select : function(index, scrollIntoView){
877         this.selectedIndex = index;
878         this.view.select(index);
879         if(scrollIntoView !== false){
880             var el = this.view.getNode(index);
881             if(el){
882                 //this.innerList.scrollChildIntoView(el, false);
883                 
884             }
885         }
886     },
887
888     // private
889     selectNext : function(){
890         var ct = this.store.getCount();
891         if(ct > 0){
892             if(this.selectedIndex == -1){
893                 this.select(0);
894             }else if(this.selectedIndex < ct-1){
895                 this.select(this.selectedIndex+1);
896             }
897         }
898     },
899
900     // private
901     selectPrev : function(){
902         var ct = this.store.getCount();
903         if(ct > 0){
904             if(this.selectedIndex == -1){
905                 this.select(0);
906             }else if(this.selectedIndex != 0){
907                 this.select(this.selectedIndex-1);
908             }
909         }
910     },
911
912     // private
913     onKeyUp : function(e){
914         if(this.editable !== false && !e.isSpecialKey()){
915             this.lastKey = e.getKey();
916             this.dqTask.delay(this.queryDelay);
917         }
918     },
919
920     // private
921     validateBlur : function(){
922         return !this.list || !this.list.isVisible();   
923     },
924
925     // private
926     initQuery : function(){
927         this.doQuery(this.getRawValue());
928     },
929
930     // private
931     doForce : function(){
932         if(this.inputEl().dom.value.length > 0){
933             this.inputEl().dom.value =
934                 this.lastSelectionText === undefined ? '' : this.lastSelectionText;
935              
936         }
937     },
938
939     /**
940      * Execute a query to filter the dropdown list.  Fires the beforequery event prior to performing the
941      * query allowing the query action to be canceled if needed.
942      * @param {String} query The SQL query to execute
943      * @param {Boolean} forceAll True to force the query to execute even if there are currently fewer characters
944      * in the field than the minimum specified by the minChars config option.  It also clears any filter previously
945      * saved in the current store (defaults to false)
946      */
947     doQuery : function(q, forceAll){
948         
949         if(q === undefined || q === null){
950             q = '';
951         }
952         var qe = {
953             query: q,
954             forceAll: forceAll,
955             combo: this,
956             cancel:false
957         };
958         if(this.fireEvent('beforequery', qe)===false || qe.cancel){
959             return false;
960         }
961         q = qe.query;
962         
963         forceAll = qe.forceAll;
964         if(forceAll === true || (q.length >= this.minChars)){
965             
966             this.hasQuery = true;
967             
968             if(this.lastQuery != q || this.alwaysQuery){
969                 this.lastQuery = q;
970                 if(this.mode == 'local'){
971                     this.selectedIndex = -1;
972                     if(forceAll){
973                         this.store.clearFilter();
974                     }else{
975                         this.store.filter(this.displayField, q);
976                     }
977                     this.onLoad();
978                 }else{
979                     this.store.baseParams[this.queryParam] = q;
980                     
981                     var options = {params : this.getParams(q)};
982                     
983                     if(this.loadNext){
984                         options.add = true;
985                         options.params.start = this.page * this.pageSize;
986                     }
987                     
988                     this.store.load(options);
989                     /*
990                      *  this code will make the page width larger, at the beginning, the list not align correctly, 
991                      *  we should expand the list on onLoad
992                      *  so command out it
993                      */
994 //                    this.expand();
995                 }
996             }else{
997                 this.selectedIndex = -1;
998                 this.onLoad();   
999             }
1000         }
1001         
1002         this.loadNext = false;
1003     },
1004
1005     // private
1006     getParams : function(q){
1007         var p = {};
1008         //p[this.queryParam] = q;
1009         
1010         if(this.pageSize){
1011             p.start = 0;
1012             p.limit = this.pageSize;
1013         }
1014         return p;
1015     },
1016
1017     /**
1018      * Hides the dropdown list if it is currently expanded. Fires the 'collapse' event on completion.
1019      */
1020     collapse : function(){
1021         if(!this.isExpanded()){
1022             return;
1023         }
1024         
1025         this.list.hide();
1026         Roo.get(document).un('mousedown', this.collapseIf, this);
1027         Roo.get(document).un('mousewheel', this.collapseIf, this);
1028         if (!this.editable) {
1029             Roo.get(document).un('keydown', this.listKeyPress, this);
1030         }
1031         this.fireEvent('collapse', this);
1032     },
1033
1034     // private
1035     collapseIf : function(e){
1036         var in_combo  = e.within(this.el);
1037         var in_list =  e.within(this.list);
1038         
1039         if (in_combo || in_list) {
1040             //e.stopPropagation();
1041             return;
1042         }
1043
1044         this.collapse();
1045         
1046     },
1047
1048     /**
1049      * Expands the dropdown list if it is currently hidden. Fires the 'expand' event on completion.
1050      */
1051     expand : function(){
1052        
1053         if(this.isExpanded() || !this.hasFocus){
1054             return;
1055         }
1056          Roo.log('expand');
1057         this.list.alignTo(this.inputEl(), this.listAlign);
1058         this.list.show();
1059         Roo.get(document).on('mousedown', this.collapseIf, this);
1060         Roo.get(document).on('mousewheel', this.collapseIf, this);
1061         if (!this.editable) {
1062             Roo.get(document).on('keydown', this.listKeyPress, this);
1063         }
1064         
1065         this.fireEvent('expand', this);
1066     },
1067
1068     // private
1069     // Implements the default empty TriggerField.onTriggerClick function
1070     onTriggerClick : function()
1071     {
1072         Roo.log('trigger click');
1073         
1074         if(this.disabled){
1075             return;
1076         }
1077         
1078         this.page = 0;
1079         this.loadNext = false;
1080         
1081         if(this.isExpanded()){
1082             this.collapse();
1083             if (!this.blockFocus) {
1084                 this.inputEl().focus();
1085             }
1086             
1087         }else {
1088             this.hasFocus = true;
1089             if(this.triggerAction == 'all') {
1090                 this.doQuery(this.allQuery, true);
1091             } else {
1092                 this.doQuery(this.getRawValue());
1093             }
1094             if (!this.blockFocus) {
1095                 this.inputEl().focus();
1096             }
1097         }
1098     },
1099     listKeyPress : function(e)
1100     {
1101         //Roo.log('listkeypress');
1102         // scroll to first matching element based on key pres..
1103         if (e.isSpecialKey()) {
1104             return false;
1105         }
1106         var k = String.fromCharCode(e.getKey()).toUpperCase();
1107         //Roo.log(k);
1108         var match  = false;
1109         var csel = this.view.getSelectedNodes();
1110         var cselitem = false;
1111         if (csel.length) {
1112             var ix = this.view.indexOf(csel[0]);
1113             cselitem  = this.store.getAt(ix);
1114             if (!cselitem.get(this.displayField) || cselitem.get(this.displayField).substring(0,1).toUpperCase() != k) {
1115                 cselitem = false;
1116             }
1117             
1118         }
1119         
1120         this.store.each(function(v) { 
1121             if (cselitem) {
1122                 // start at existing selection.
1123                 if (cselitem.id == v.id) {
1124                     cselitem = false;
1125                 }
1126                 return true;
1127             }
1128                 
1129             if (v.get(this.displayField) && v.get(this.displayField).substring(0,1).toUpperCase() == k) {
1130                 match = this.store.indexOf(v);
1131                 return false;
1132             }
1133             return true;
1134         }, this);
1135         
1136         if (match === false) {
1137             return true; // no more action?
1138         }
1139         // scroll to?
1140         this.view.select(match);
1141         var sn = Roo.get(this.view.getSelectedNodes()[0])
1142         //sn.scrollIntoView(sn.dom.parentNode, false);
1143     },
1144     
1145     onViewScroll : function(e, t){
1146         
1147         if(this.view.el.getScroll().top < this.view.el.dom.scrollHeight - this.view.el.dom.clientHeight || !this.hasFocus || !this.append || this.hasQuery){
1148             return;
1149         }
1150         
1151         this.hasQuery = true;
1152         
1153         this.loading = this.list.select('.loading', true).first();
1154         
1155         if(this.loading === null){
1156             this.list.createChild({
1157                 tag: 'div',
1158                 cls: 'loading select2-more-results select2-active',
1159                 html: 'Loading more results...'
1160             })
1161             
1162             this.loading = this.list.select('.loading', true).first();
1163             
1164             this.loading.setVisibilityMode(Roo.Element.DISPLAY);
1165             
1166             this.loading.hide();
1167         }
1168         
1169         this.loading.show();
1170         
1171         var _combo = this;
1172         
1173         this.page++;
1174         this.loadNext = true;
1175         
1176         (function() { _combo.doQuery(_combo.allQuery, true); }).defer(500);
1177         
1178         return;
1179     },
1180     
1181     addItem : function(o)
1182     {   
1183         var dv = ''; // display value
1184         
1185         if (this.displayField) {
1186             dv = !o || typeof(o[this.displayField]) == 'undefined' ? '' : o[this.displayField];
1187         } else {
1188             // this is an error condition!!!
1189             Roo.log('no  displayField value set for '+ (this.name ? this.name : this.id));
1190         }
1191         
1192         if(!dv.length){
1193             return;
1194         }
1195         
1196         var choice = this.choices.createChild({
1197             tag: 'li',
1198             cls: 'select2-search-choice',
1199             cn: [
1200                 {
1201                     tag: 'div',
1202                     html: dv
1203                 },
1204                 {
1205                     tag: 'a',
1206                     href: '#',
1207                     cls: 'select2-search-choice-close',
1208                     tabindex: '-1'
1209                 }
1210             ]
1211             
1212         }, this.searchField);
1213         
1214         var close = choice.select('a.select2-search-choice-close', true).first()
1215         
1216         close.on('click', this.onRemoveItem, this, { item : choice, data : o} );
1217         
1218         this.item.push(o);
1219         this.lastData = o;
1220         
1221         this.syncValue();
1222         
1223         this.inputEl().dom.value = '';
1224         
1225     },
1226     
1227     onRemoveItem : function(e, _self, o)
1228     {
1229         e.preventDefault();
1230         var index = this.item.indexOf(o.data) * 1;
1231         
1232         if( index < 0){
1233             Roo.log('not this item?!');
1234             return;
1235         }
1236         
1237         this.item.splice(index, 1);
1238         o.item.remove();
1239         
1240         this.syncValue();
1241         
1242         this.fireEvent('remove', this, e);
1243         
1244     },
1245     
1246     syncValue : function()
1247     {
1248         if(!this.item.length){
1249             this.clearValue();
1250             return;
1251         }
1252             
1253         var value = [];
1254         var _this = this;
1255         Roo.each(this.item, function(i){
1256             if(_this.valueField){
1257                 value.push(i[_this.valueField]);
1258                 return;
1259             }
1260
1261             value.push(i);
1262         });
1263
1264         this.value = value.join(',');
1265
1266         if(this.hiddenField){
1267             this.hiddenField.dom.value = this.value;
1268         }
1269     },
1270     
1271     clearItem : function()
1272     {
1273         if(!this.multiple){
1274             return;
1275         }
1276         
1277         this.item = [];
1278         
1279         Roo.each(this.choices.select('>li.select2-search-choice', true).elements, function(c){
1280            c.remove();
1281         });
1282         
1283         this.syncValue();
1284     }
1285     
1286     
1287
1288     /** 
1289     * @cfg {Boolean} grow 
1290     * @hide 
1291     */
1292     /** 
1293     * @cfg {Number} growMin 
1294     * @hide 
1295     */
1296     /** 
1297     * @cfg {Number} growMax 
1298     * @hide 
1299     */
1300     /**
1301      * @hide
1302      * @method autoSize
1303      */
1304 });