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