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