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