a2593c113616d559425832bbb4e1bfe2b832c883
[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} triggerList trigger show the list or not (true|false) default true
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     triggerList : true,
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         if(this.hiddenName){
446             
447             this.hiddenField = this.el.select('input.form-hidden-field',true).first();
448             
449             this.hiddenField.dom.value =
450                 this.hiddenValue !== undefined ? this.hiddenValue :
451                 this.value !== undefined ? this.value : '';
452
453             // prevent input submission
454             this.el.dom.removeAttribute('name');
455             this.hiddenField.dom.setAttribute('name', this.hiddenName);
456              
457              
458         }
459         //if(Roo.isGecko){
460         //    this.el.dom.setAttribute('autocomplete', 'off');
461         //}
462         
463         var cls = 'x-combo-list';
464         
465         //this.list = new Roo.Layer({
466         //    shadow: this.shadow, cls: [cls, this.listClass].join(' '), constrain:false
467         //});
468         
469         var _this = this;
470         
471         (function(){
472             var lw = _this.listWidth || Math.max(_this.inputEl().getWidth(), _this.minListWidth);
473             _this.list.setWidth(lw);
474         }).defer(100);
475         
476         this.list.on('mouseover', this.onViewOver, this);
477         this.list.on('mousemove', this.onViewMove, this);
478         
479         this.list.on('scroll', this.onViewScroll, this);
480         
481         /*
482         this.list.swallowEvent('mousewheel');
483         this.assetHeight = 0;
484
485         if(this.title){
486             this.header = this.list.createChild({cls:cls+'-hd', html: this.title});
487             this.assetHeight += this.header.getHeight();
488         }
489
490         this.innerList = this.list.createChild({cls:cls+'-inner'});
491         this.innerList.on('mouseover', this.onViewOver, this);
492         this.innerList.on('mousemove', this.onViewMove, this);
493         this.innerList.setWidth(lw - this.list.getFrameWidth('lr'));
494         
495         if(this.allowBlank && !this.pageSize && !this.disableClear){
496             this.footer = this.list.createChild({cls:cls+'-ft'});
497             this.pageTb = new Roo.Toolbar(this.footer);
498            
499         }
500         if(this.pageSize){
501             this.footer = this.list.createChild({cls:cls+'-ft'});
502             this.pageTb = new Roo.PagingToolbar(this.footer, this.store,
503                     {pageSize: this.pageSize});
504             
505         }
506         
507         if (this.pageTb && this.allowBlank && !this.disableClear) {
508             var _this = this;
509             this.pageTb.add(new Roo.Toolbar.Fill(), {
510                 cls: 'x-btn-icon x-btn-clear',
511                 text: ' ',
512                 handler: function()
513                 {
514                     _this.collapse();
515                     _this.clearValue();
516                     _this.onSelect(false, -1);
517                 }
518             });
519         }
520         if (this.footer) {
521             this.assetHeight += this.footer.getHeight();
522         }
523         */
524             
525         if(!this.tpl){
526             this.tpl = '<li><a href="#">{' + this.displayField + '}</a></li>';
527         }
528
529         this.view = new Roo.View(this.list, this.tpl, {
530             singleSelect:true, store: this.store, selectedClass: this.selectedClass
531         });
532         //this.view.wrapEl.setDisplayed(false);
533         this.view.on('click', this.onViewClick, this);
534         
535         
536         
537         this.store.on('beforeload', this.onBeforeLoad, this);
538         this.store.on('load', this.onLoad, this);
539         this.store.on('loadexception', this.onLoadException, this);
540         /*
541         if(this.resizable){
542             this.resizer = new Roo.Resizable(this.list,  {
543                pinned:true, handles:'se'
544             });
545             this.resizer.on('resize', function(r, w, h){
546                 this.maxHeight = h-this.handleHeight-this.list.getFrameWidth('tb')-this.assetHeight;
547                 this.listWidth = w;
548                 this.innerList.setWidth(w - this.list.getFrameWidth('lr'));
549                 this.restrictHeight();
550             }, this);
551             this[this.pageSize?'footer':'innerList'].setStyle('margin-bottom', this.handleHeight+'px');
552         }
553         */
554         if(!this.editable){
555             this.editable = true;
556             this.setEditable(false);
557         }
558         
559         /*
560         
561         if (typeof(this.events.add.listeners) != 'undefined') {
562             
563             this.addicon = this.wrap.createChild(
564                 {tag: 'img', src: Roo.BLANK_IMAGE_URL, cls: 'x-form-combo-add' });  
565        
566             this.addicon.on('click', function(e) {
567                 this.fireEvent('add', this);
568             }, this);
569         }
570         if (typeof(this.events.edit.listeners) != 'undefined') {
571             
572             this.editicon = this.wrap.createChild(
573                 {tag: 'img', src: Roo.BLANK_IMAGE_URL, cls: 'x-form-combo-edit' });  
574             if (this.addicon) {
575                 this.editicon.setStyle('margin-left', '40px');
576             }
577             this.editicon.on('click', function(e) {
578                 
579                 // we fire even  if inothing is selected..
580                 this.fireEvent('edit', this, this.lastData );
581                 
582             }, this);
583         }
584         */
585         
586         this.keyNav = new Roo.KeyNav(this.inputEl(), {
587             "up" : function(e){
588                 this.inKeyMode = true;
589                 this.selectPrev();
590             },
591
592             "down" : function(e){
593                 if(!this.isExpanded()){
594                     this.onTriggerClick();
595                 }else{
596                     this.inKeyMode = true;
597                     this.selectNext();
598                 }
599             },
600
601             "enter" : function(e){
602 //                this.onViewClick();
603                 //return true;
604                 this.collapse();
605                 
606                 if(this.fireEvent("specialkey", this, e)){
607                     this.onViewClick(false);
608                 }
609                 
610                 return true;
611             },
612
613             "esc" : function(e){
614                 this.collapse();
615             },
616
617             "tab" : function(e){
618                 this.collapse();
619                 
620                 if(this.fireEvent("specialkey", this, e)){
621                     this.onViewClick(false);
622                 }
623                 
624                 return true;
625             },
626
627             scope : this,
628
629             doRelay : function(foo, bar, hname){
630                 if(hname == 'down' || this.scope.isExpanded()){
631                    return Roo.KeyNav.prototype.doRelay.apply(this, arguments);
632                 }
633                 return true;
634             },
635
636             forceKeyDown: true
637         });
638         
639         
640         this.queryDelay = Math.max(this.queryDelay || 10,
641                 this.mode == 'local' ? 10 : 250);
642         
643         
644         this.dqTask = new Roo.util.DelayedTask(this.initQuery, this);
645         
646         if(this.typeAhead){
647             this.taTask = new Roo.util.DelayedTask(this.onTypeAhead, this);
648         }
649         if(this.editable !== false){
650             this.inputEl().on("keyup", this.onKeyUp, this);
651         }
652         if(this.forceSelection){
653             this.inputEl().on('blur', this.doForce, this);
654         }
655         
656         if(this.multiple){
657             this.choices = this.el.select('ul.select2-choices', true).first();
658             this.searchField = this.el.select('ul li.select2-search-field', true).first();
659         }
660     },
661     
662     initTickableEvnets: function()
663     {   
664         this.createList();
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.triggerList){
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.list, 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             // if(typeof o.display_name !== 'string'){
1040             //     for(var i=0;i<o.display_name.length;i++){
1041             //         this.addItem({'id':o.id[i],'display_name':o.display_name[i]});
1042             //     }
1043             // }
1044             this.addItem(o);
1045             return;
1046         }
1047             
1048         var dv = ''; // display value
1049         var vv = ''; // value value..
1050         this.lastData = o;
1051         if (this.displayField) {
1052             dv = !o || typeof(o[this.displayField]) == 'undefined' ? '' : o[this.displayField];
1053         } else {
1054             // this is an error condition!!!
1055             Roo.log('no  displayField value set for '+ (this.name ? this.name : this.id));
1056         }
1057         
1058         if(this.valueField){
1059             vv = !o || typeof(o[this.valueField]) == 'undefined' ? dv : o[this.valueField];
1060         }
1061         
1062         if(this.hiddenField){
1063             this.hiddenField.dom.value = vv;
1064             
1065             this.lastSelectionText = dv;
1066             Roo.bootstrap.ComboBox.superclass.setValue.call(this, dv);
1067             this.value = vv;
1068             return;
1069         }
1070         // no hidden field.. - we store the value in 'value', but still display
1071         // display field!!!!
1072         this.lastSelectionText = dv;
1073         Roo.bootstrap.ComboBox.superclass.setValue.call(this, dv);
1074         this.value = vv;
1075         
1076         
1077     },
1078     // private
1079     reset : function(){
1080         // overridden so that last data is reset..
1081         this.setValue(this.originalValue);
1082         this.clearInvalid();
1083         this.lastData = false;
1084         if (this.view) {
1085             this.view.clearSelections();
1086         }
1087     },
1088     // private
1089     findRecord : function(prop, value){
1090         var record;
1091         if(this.store.getCount() > 0){
1092             this.store.each(function(r){
1093                 if(r.data[prop] == value){
1094                     record = r;
1095                     return false;
1096                 }
1097                 return true;
1098             });
1099         }
1100         return record;
1101     },
1102     
1103     getName: function()
1104     {
1105         // returns hidden if it's set..
1106         if (!this.rendered) {return ''};
1107         return !this.hiddenName && this.inputEl().dom.name  ? this.inputEl().dom.name : (this.hiddenName || '');
1108         
1109     },
1110     // private
1111     onViewMove : function(e, t){
1112         this.inKeyMode = false;
1113     },
1114
1115     // private
1116     onViewOver : function(e, t){
1117         if(this.inKeyMode){ // prevent key nav and mouse over conflicts
1118             return;
1119         }
1120         var item = this.view.findItemFromChild(t);
1121         
1122         if(item){
1123             var index = this.view.indexOf(item);
1124             this.select(index, false);
1125         }
1126     },
1127
1128     // private
1129     onViewClick : function(view, doFocus, el, e)
1130     {
1131         var index = this.view.getSelectedIndexes()[0];
1132         
1133         var r = this.store.getAt(index);
1134         
1135         if(this.tickable){
1136             
1137             if(e.getTarget().nodeName.toLowerCase() != 'input'){
1138                 return;
1139             }
1140             
1141             var rm = false;
1142             var _this = this;
1143             
1144             Roo.each(this.tickItems, function(v,k){
1145                 
1146                 if(typeof(v) != 'undefined' && v[_this.valueField] == r.data[_this.valueField]){
1147                     _this.tickItems.splice(k, 1);
1148                     rm = true;
1149                     return;
1150                 }
1151             })
1152             
1153             if(rm){
1154                 return;
1155             }
1156             
1157             this.tickItems.push(r.data);
1158             return;
1159         }
1160         
1161         if(r){
1162             this.onSelect(r, index);
1163         }
1164         if(doFocus !== false && !this.blockFocus){
1165             this.inputEl().focus();
1166         }
1167     },
1168
1169     // private
1170     restrictHeight : function(){
1171         //this.innerList.dom.style.height = '';
1172         //var inner = this.innerList.dom;
1173         //var h = Math.max(inner.clientHeight, inner.offsetHeight, inner.scrollHeight);
1174         //this.innerList.setHeight(h < this.maxHeight ? 'auto' : this.maxHeight);
1175         //this.list.beginUpdate();
1176         //this.list.setHeight(this.innerList.getHeight()+this.list.getFrameWidth('tb')+(this.resizable?this.handleHeight:0)+this.assetHeight);
1177         this.list.alignTo(this.inputEl(), this.listAlign);
1178         this.list.alignTo(this.inputEl(), this.listAlign);
1179         //this.list.endUpdate();
1180     },
1181
1182     // private
1183     onEmptyResults : function(){
1184         this.collapse();
1185     },
1186
1187     /**
1188      * Returns true if the dropdown list is expanded, else false.
1189      */
1190     isExpanded : function(){
1191         return this.list.isVisible();
1192     },
1193
1194     /**
1195      * Select an item in the dropdown list by its data value. This function does NOT cause the select event to fire.
1196      * The store must be loaded and the list expanded for this function to work, otherwise use setValue.
1197      * @param {String} value The data value of the item to select
1198      * @param {Boolean} scrollIntoView False to prevent the dropdown list from autoscrolling to display the
1199      * selected item if it is not currently in view (defaults to true)
1200      * @return {Boolean} True if the value matched an item in the list, else false
1201      */
1202     selectByValue : function(v, scrollIntoView){
1203         if(v !== undefined && v !== null){
1204             var r = this.findRecord(this.valueField || this.displayField, v);
1205             if(r){
1206                 this.select(this.store.indexOf(r), scrollIntoView);
1207                 return true;
1208             }
1209         }
1210         return false;
1211     },
1212
1213     /**
1214      * Select an item in the dropdown list by its numeric index in the list. This function does NOT cause the select event to fire.
1215      * The store must be loaded and the list expanded for this function to work, otherwise use setValue.
1216      * @param {Number} index The zero-based index of the list item to select
1217      * @param {Boolean} scrollIntoView False to prevent the dropdown list from autoscrolling to display the
1218      * selected item if it is not currently in view (defaults to true)
1219      */
1220     select : function(index, scrollIntoView){
1221         this.selectedIndex = index;
1222         this.view.select(index);
1223         if(scrollIntoView !== false){
1224             var el = this.view.getNode(index);
1225             if(el && !this.multiple && !this.tickable){
1226                 this.list.scrollChildIntoView(el, false);
1227             }
1228         }
1229     },
1230
1231     // private
1232     selectNext : function(){
1233         var ct = this.store.getCount();
1234         if(ct > 0){
1235             if(this.selectedIndex == -1){
1236                 this.select(0);
1237             }else if(this.selectedIndex < ct-1){
1238                 this.select(this.selectedIndex+1);
1239             }
1240         }
1241     },
1242
1243     // private
1244     selectPrev : function(){
1245         var ct = this.store.getCount();
1246         if(ct > 0){
1247             if(this.selectedIndex == -1){
1248                 this.select(0);
1249             }else if(this.selectedIndex != 0){
1250                 this.select(this.selectedIndex-1);
1251             }
1252         }
1253     },
1254
1255     // private
1256     onKeyUp : function(e){
1257         if(this.editable !== false && !e.isSpecialKey()){
1258             this.lastKey = e.getKey();
1259             this.dqTask.delay(this.queryDelay);
1260         }
1261     },
1262
1263     // private
1264     validateBlur : function(){
1265         return !this.list || !this.list.isVisible();   
1266     },
1267
1268     // private
1269     initQuery : function(){
1270         this.doQuery(this.getRawValue());
1271     },
1272
1273     // private
1274     doForce : function(){
1275         if(this.inputEl().dom.value.length > 0){
1276             this.inputEl().dom.value =
1277                 this.lastSelectionText === undefined ? '' : this.lastSelectionText;
1278              
1279         }
1280     },
1281
1282     /**
1283      * Execute a query to filter the dropdown list.  Fires the beforequery event prior to performing the
1284      * query allowing the query action to be canceled if needed.
1285      * @param {String} query The SQL query to execute
1286      * @param {Boolean} forceAll True to force the query to execute even if there are currently fewer characters
1287      * in the field than the minimum specified by the minChars config option.  It also clears any filter previously
1288      * saved in the current store (defaults to false)
1289      */
1290     doQuery : function(q, forceAll){
1291         
1292         if(q === undefined || q === null){
1293             q = '';
1294         }
1295         var qe = {
1296             query: q,
1297             forceAll: forceAll,
1298             combo: this,
1299             cancel:false
1300         };
1301         if(this.fireEvent('beforequery', qe)===false || qe.cancel){
1302             return false;
1303         }
1304         q = qe.query;
1305         
1306         forceAll = qe.forceAll;
1307         if(forceAll === true || (q.length >= this.minChars)){
1308             
1309             this.hasQuery = true;
1310             
1311             if(this.lastQuery != q || this.alwaysQuery){
1312                 this.lastQuery = q;
1313                 if(this.mode == 'local'){
1314                     this.selectedIndex = -1;
1315                     if(forceAll){
1316                         this.store.clearFilter();
1317                     }else{
1318                         this.store.filter(this.displayField, q);
1319                     }
1320                     this.onLoad();
1321                 }else{
1322                     this.store.baseParams[this.queryParam] = q;
1323                     
1324                     var options = {params : this.getParams(q)};
1325                     
1326                     if(this.loadNext){
1327                         options.add = true;
1328                         options.params.start = this.page * this.pageSize;
1329                     }
1330                     
1331                     this.store.load(options);
1332                     /*
1333                      *  this code will make the page width larger, at the beginning, the list not align correctly, 
1334                      *  we should expand the list on onLoad
1335                      *  so command out it
1336                      */
1337 //                    this.expand();
1338                 }
1339             }else{
1340                 this.selectedIndex = -1;
1341                 this.onLoad();   
1342             }
1343         }
1344         
1345         this.loadNext = false;
1346     },
1347
1348     // private
1349     getParams : function(q){
1350         var p = {};
1351         //p[this.queryParam] = q;
1352         
1353         if(this.pageSize){
1354             p.start = 0;
1355             p.limit = this.pageSize;
1356         }
1357         return p;
1358     },
1359
1360     /**
1361      * Hides the dropdown list if it is currently expanded. Fires the 'collapse' event on completion.
1362      */
1363     collapse : function(){
1364         if(!this.isExpanded()){
1365             return;
1366         }
1367         
1368         this.hasFocus = false;
1369         
1370         this.list.hide();
1371         
1372         if(this.tickable){
1373             this.okBtn.hide();
1374             this.cancelBtn.hide();
1375             this.trigger.show();
1376         }
1377         
1378         Roo.get(document).un('mousedown', this.collapseIf, this);
1379         Roo.get(document).un('mousewheel', this.collapseIf, this);
1380         if (!this.editable) {
1381             Roo.get(document).un('keydown', this.listKeyPress, this);
1382         }
1383         this.fireEvent('collapse', this);
1384     },
1385
1386     // private
1387     collapseIf : function(e){
1388         var in_combo  = e.within(this.el);
1389         var in_list =  e.within(this.list);
1390         var is_list = (Roo.get(e.getTarget()).id == this.list.id) ? true : false;
1391         
1392         if (in_combo || in_list || is_list) {
1393             //e.stopPropagation();
1394             return;
1395         }
1396         
1397         if(this.tickable){
1398             this.onTickableFooterButtonClick(e, false, false);
1399         }
1400
1401         this.collapse();
1402         
1403     },
1404
1405     /**
1406      * Expands the dropdown list if it is currently hidden. Fires the 'expand' event on completion.
1407      */
1408     expand : function(){
1409        
1410         if(this.isExpanded() || !this.hasFocus){
1411             return;
1412         }
1413          Roo.log('expand');
1414         
1415         this.list.show();
1416         
1417         this.restrictHeight();
1418         
1419         if(this.tickable){
1420             
1421             this.tickItems = Roo.apply([], this.item);
1422             
1423             this.okBtn.show();
1424             this.cancelBtn.show();
1425             this.trigger.hide();
1426             
1427         }
1428         
1429         Roo.get(document).on('mousedown', this.collapseIf, this);
1430         Roo.get(document).on('mousewheel', this.collapseIf, this);
1431         if (!this.editable) {
1432             Roo.get(document).on('keydown', this.listKeyPress, this);
1433         }
1434         
1435         this.fireEvent('expand', this);
1436     },
1437
1438     // private
1439     // Implements the default empty TriggerField.onTriggerClick function
1440     onTriggerClick : function(e)
1441     {
1442         Roo.log('trigger click');
1443         
1444         if(this.disabled || !this.triggerList){
1445             return;
1446         }
1447         
1448         this.page = 0;
1449         this.loadNext = false;
1450         
1451         if(this.isExpanded()){
1452             this.collapse();
1453             if (!this.blockFocus) {
1454                 this.inputEl().focus();
1455             }
1456             
1457         }else {
1458             this.hasFocus = true;
1459             if(this.triggerAction == 'all') {
1460                 this.doQuery(this.allQuery, true);
1461             } else {
1462                 this.doQuery(this.getRawValue());
1463             }
1464             if (!this.blockFocus) {
1465                 this.inputEl().focus();
1466             }
1467         }
1468     },
1469     
1470     onTickableTriggerClick : function(e)
1471     {
1472         if(this.disabled){
1473             return;
1474         }
1475         
1476         this.page = 0;
1477         this.loadNext = false;
1478         this.hasFocus = true;
1479         
1480         if(this.triggerAction == 'all') {
1481             this.doQuery(this.allQuery, true);
1482         } else {
1483             this.doQuery(this.getRawValue());
1484         }
1485     },
1486     
1487     onSearchFieldClick : function(e)
1488     {
1489         if(this.hasFocus || this.disabled || e.getTarget().nodeName.toLowerCase() == 'button'){
1490             return;
1491         }
1492         
1493         this.page = 0;
1494         this.loadNext = false;
1495         this.hasFocus = true;
1496         
1497         if(this.triggerAction == 'all') {
1498             this.doQuery(this.allQuery, true);
1499         } else {
1500             this.doQuery(this.getRawValue());
1501         }
1502     },
1503     
1504     listKeyPress : function(e)
1505     {
1506         //Roo.log('listkeypress');
1507         // scroll to first matching element based on key pres..
1508         if (e.isSpecialKey()) {
1509             return false;
1510         }
1511         var k = String.fromCharCode(e.getKey()).toUpperCase();
1512         //Roo.log(k);
1513         var match  = false;
1514         var csel = this.view.getSelectedNodes();
1515         var cselitem = false;
1516         if (csel.length) {
1517             var ix = this.view.indexOf(csel[0]);
1518             cselitem  = this.store.getAt(ix);
1519             if (!cselitem.get(this.displayField) || cselitem.get(this.displayField).substring(0,1).toUpperCase() != k) {
1520                 cselitem = false;
1521             }
1522             
1523         }
1524         
1525         this.store.each(function(v) { 
1526             if (cselitem) {
1527                 // start at existing selection.
1528                 if (cselitem.id == v.id) {
1529                     cselitem = false;
1530                 }
1531                 return true;
1532             }
1533                 
1534             if (v.get(this.displayField) && v.get(this.displayField).substring(0,1).toUpperCase() == k) {
1535                 match = this.store.indexOf(v);
1536                 return false;
1537             }
1538             return true;
1539         }, this);
1540         
1541         if (match === false) {
1542             return true; // no more action?
1543         }
1544         // scroll to?
1545         this.view.select(match);
1546         var sn = Roo.get(this.view.getSelectedNodes()[0])
1547         //sn.scrollIntoView(sn.dom.parentNode, false);
1548     },
1549     
1550     onViewScroll : function(e, t){
1551         
1552         if(this.view.el.getScroll().top < this.view.el.dom.scrollHeight - this.view.el.dom.clientHeight || !this.hasFocus || !this.append || this.hasQuery){
1553             return;
1554         }
1555         
1556         this.hasQuery = true;
1557         
1558         this.loading = this.list.select('.loading', true).first();
1559         
1560         if(this.loading === null){
1561             this.list.createChild({
1562                 tag: 'div',
1563                 cls: 'loading select2-more-results select2-active',
1564                 html: 'Loading more results...'
1565             })
1566             
1567             this.loading = this.list.select('.loading', true).first();
1568             
1569             this.loading.setVisibilityMode(Roo.Element.DISPLAY);
1570             
1571             this.loading.hide();
1572         }
1573         
1574         this.loading.show();
1575         
1576         var _combo = this;
1577         
1578         this.page++;
1579         this.loadNext = true;
1580         
1581         (function() { _combo.doQuery(_combo.allQuery, true); }).defer(500);
1582         
1583         return;
1584     },
1585     
1586     addItem : function(o)
1587     {   
1588         var dv = ''; // display value
1589         
1590         if (this.displayField) {
1591             dv = !o || typeof(o[this.displayField]) == 'undefined' ? '' : o[this.displayField];
1592         } else {
1593             // this is an error condition!!!
1594             Roo.log('no  displayField value set for '+ (this.name ? this.name : this.id));
1595         }
1596         
1597         if(!dv.length){
1598             return;
1599         }
1600         
1601         var choice = this.choices.createChild({
1602             tag: 'li',
1603             cls: 'select2-search-choice',
1604             cn: [
1605                 {
1606                     tag: 'div',
1607                     html: dv
1608                 },
1609                 {
1610                     tag: 'a',
1611                     href: '#',
1612                     cls: 'select2-search-choice-close',
1613                     tabindex: '-1'
1614                 }
1615             ]
1616             
1617         }, this.searchField);
1618         
1619         var close = choice.select('a.select2-search-choice-close', true).first()
1620         
1621         close.on('click', this.onRemoveItem, this, { item : choice, data : o} );
1622         
1623         this.item.push(o);
1624         
1625         this.lastData = o;
1626         
1627         this.syncValue();
1628         
1629         this.inputEl().dom.value = '';
1630         
1631     },
1632     
1633     onRemoveItem : function(e, _self, o)
1634     {
1635         e.preventDefault();
1636         var index = this.item.indexOf(o.data) * 1;
1637         
1638         if( index < 0){
1639             Roo.log('not this item?!');
1640             return;
1641         }
1642         
1643         this.item.splice(index, 1);
1644         o.item.remove();
1645         
1646         this.syncValue();
1647         
1648         this.fireEvent('remove', this, e);
1649         
1650     },
1651     
1652     syncValue : function()
1653     {
1654         if(!this.item.length){
1655             this.clearValue();
1656             return;
1657         }
1658             
1659         var value = [];
1660         var _this = this;
1661         Roo.each(this.item, function(i){
1662             if(_this.valueField){
1663                 value.push(i[_this.valueField]);
1664                 return;
1665             }
1666
1667             value.push(i);
1668         });
1669
1670         this.value = value.join(',');
1671
1672         if(this.hiddenField){
1673             this.hiddenField.dom.value = this.value;
1674         }
1675     },
1676     
1677     clearItem : function()
1678     {
1679         if(!this.multiple){
1680             return;
1681         }
1682         
1683         this.item = [];
1684         
1685         Roo.each(this.choices.select('>li.select2-search-choice', true).elements, function(c){
1686            c.remove();
1687         });
1688         
1689         this.syncValue();
1690     },
1691     
1692     inputEl: function ()
1693     {
1694         if(this.tickable){
1695             return this.searchField;
1696         }
1697         return this.el.select('input.form-control',true).first();
1698     },
1699     
1700     
1701     onTickableFooterButtonClick : function(e, btn, el)
1702     {
1703         e.preventDefault();
1704         
1705         if(btn && btn.name == 'cancel'){
1706             this.tickItems = Roo.apply([], this.item);
1707             this.collapse();
1708             return;
1709         }
1710         
1711         this.clearItem();
1712         
1713         var _this = this;
1714         
1715         Roo.each(this.tickItems, function(o){
1716             _this.addItem(o);
1717         });
1718         
1719         this.collapse();
1720         
1721     }
1722     
1723     
1724
1725     /** 
1726     * @cfg {Boolean} grow 
1727     * @hide 
1728     */
1729     /** 
1730     * @cfg {Number} growMin 
1731     * @hide 
1732     */
1733     /** 
1734     * @cfg {Number} growMax 
1735     * @hide 
1736     */
1737     /**
1738      * @hide
1739      * @method autoSize
1740      */
1741 });