All of my work from commits: dd4a194, 692644a, 4a60203, 5de46bc, 152042d, 64a2d4e...
[philo.git] / contrib / gilbert / media / gilbert / superboxselect / SuperBoxSelect.js
1 Ext.namespace('Ext.ux.form');\r
2 /**\r
3  * <p>SuperBoxSelect is an extension of the ComboBox component that displays selected items as labelled boxes within the form field. As seen on facebook, hotmail and other sites.</p>\r
4  * <p>The SuperBoxSelect component was inspired by the BoxSelect component found here: http://efattal.fr/en/extjs/extuxboxselect/</p>\r
5  * \r
6  * @author <a href="mailto:dan.humphrey@technomedia.co.uk">Dan Humphrey</a>\r
7  * @class Ext.ux.form.SuperBoxSelect\r
8  * @extends Ext.form.ComboBox\r
9  * @constructor\r
10  * @component\r
11  * @version 1.0\r
12  * @license TBA (To be announced)\r
13  * \r
14  */\r
15 Ext.ux.form.SuperBoxSelect = function(config) {\r
16     Ext.ux.form.SuperBoxSelect.superclass.constructor.call(this,config);\r
17     this.addEvents(\r
18         /**\r
19          * Fires before an item is added to the component via user interaction. Return false from the callback function to prevent the item from being added.\r
20          * @event beforeadditem\r
21          * @memberOf Ext.ux.form.SuperBoxSelect\r
22          * @param {SuperBoxSelect} this\r
23          * @param {Mixed} value The value of the item to be added\r
24          */\r
25         'beforeadditem',\r
26 \r
27         /**\r
28          * Fires after a new item is added to the component.\r
29          * @event additem\r
30          * @memberOf Ext.ux.form.SuperBoxSelect\r
31          * @param {SuperBoxSelect} this\r
32          * @param {Mixed} value The value of the item which was added\r
33          * @param {Record} record The store record which was added\r
34          */\r
35         'additem',\r
36 \r
37         /**\r
38          * Fires when the allowAddNewData config is set to true, and a user attempts to add an item that is not in the data store.\r
39          * @event newitem\r
40          * @memberOf Ext.ux.form.SuperBoxSelect\r
41          * @param {SuperBoxSelect} this\r
42          * @param {Mixed} value The new item's value\r
43          */\r
44         'newitem',\r
45 \r
46         /**\r
47          * Fires when an item's remove button is clicked. Return false from the callback function to prevent the item from being removed.\r
48          * @event beforeremoveitem\r
49          * @memberOf Ext.ux.form.SuperBoxSelect\r
50          * @param {SuperBoxSelect} this\r
51          * @param {Mixed} value The value of the item to be removed\r
52          */\r
53         'beforeremoveitem',\r
54 \r
55         /**\r
56          * Fires after an item has been removed.\r
57          * @event removeitem\r
58          * @memberOf Ext.ux.form.SuperBoxSelect\r
59          * @param {SuperBoxSelect} this\r
60          * @param {Mixed} value The value of the item which was removed\r
61          * @param {Record} record The store record which was removed\r
62          */\r
63         'removeitem',\r
64         /**\r
65          * Fires after the component values have been cleared.\r
66          * @event clear\r
67          * @memberOf Ext.ux.form.SuperBoxSelect\r
68          * @param {SuperBoxSelect} this\r
69          */\r
70         'clear'\r
71     );\r
72     \r
73 };\r
74 /**\r
75  * @private hide from doc gen\r
76  */\r
77 Ext.ux.form.SuperBoxSelect = Ext.extend(Ext.ux.form.SuperBoxSelect,Ext.form.ComboBox,{\r
78     /**\r
79      * @cfg {Boolean} allowAddNewData When set to true, allows items to be added (via the setValueEx and addItem methods) that do not already exist in the data store. Defaults to false.\r
80      */\r
81     allowAddNewData: false,\r
82 \r
83     /**\r
84      * @cfg {Boolean} backspaceDeletesLastItem When set to false, the BACKSPACE key will focus the last selected item. When set to true, the last item will be immediately deleted. Defaults to true.\r
85      */\r
86     backspaceDeletesLastItem: true,\r
87 \r
88     /**\r
89      * @cfg {String} classField The underlying data field that will be used to supply an additional class to each item.\r
90      */\r
91     classField: null,\r
92 \r
93     /**\r
94      * @cfg {String} clearBtnCls An additional class to add to the in-field clear button.\r
95      */\r
96     clearBtnCls: '',\r
97 \r
98     /**\r
99      * @cfg {String/XTemplate} displayFieldTpl A template for rendering the displayField in each selected item. Defaults to null.\r
100      */\r
101     displayFieldTpl: null,\r
102 \r
103     /**\r
104      * @cfg {String} extraItemCls An additional css class to apply to each item.\r
105      */\r
106     extraItemCls: '',\r
107 \r
108     /**\r
109      * @cfg {String/Object/Function} extraItemStyle Additional css style(s) to apply to each item. Should be a valid argument to Ext.Element.applyStyles.\r
110      */\r
111     extraItemStyle: '',\r
112 \r
113     /**\r
114      * @cfg {String} expandBtnCls An additional class to add to the in-field expand button.\r
115      */\r
116     expandBtnCls: '',\r
117 \r
118     /**\r
119      * @cfg {Boolean} fixFocusOnTabSelect When set to true, the component will not lose focus when a list item is selected with the TAB key. Defaults to true.\r
120      */\r
121     fixFocusOnTabSelect: true,\r
122     \r
123      /**\r
124      * @cfg {Boolean} forceFormValue When set to true, the component will always return a value to the parent form getValues method, and when the parent form is submitted manually. Defaults to false, meaning the component will only be included in the parent form submission (or getValues) if at least 1 item has been selected.  \r
125      */\r
126     forceFormValue: true,\r
127     /**\r
128      * @cfg {Number} itemDelimiterKey The key code which terminates keying in of individual items, and adds the current\r
129      * item to the list. Defaults to the ENTER key.\r
130      */\r
131     itemDelimiterKey: Ext.EventObject.ENTER,    \r
132     /**\r
133      * @cfg {Boolean} navigateItemsWithTab When set to true the tab key will navigate between selected items. Defaults to true.\r
134      */\r
135     navigateItemsWithTab: true,\r
136 \r
137     /**\r
138      * @cfg {Boolean} pinList When set to true the select list will be pinned to allow for multiple selections. Defaults to true.\r
139      */\r
140     pinList: true,\r
141 \r
142     /**\r
143      * @cfg {Boolean} preventDuplicates When set to true unique item values will be enforced. Defaults to true.\r
144      */\r
145     preventDuplicates: true,\r
146     \r
147     /**\r
148      * @cfg {String} queryValuesDelimiter Used to delimit multiple values queried from the server when mode is remote.\r
149      */\r
150     queryValuesDelimiter: '|',\r
151     \r
152     /**\r
153      * @cfg {String} queryValuesIndicator A request variable that is sent to the server (as true) to indicate that we are querying values rather than display data (as used in autocomplete) when mode is remote.\r
154      */\r
155     queryValuesIndicator: 'valuesqry',\r
156 \r
157     /**\r
158      * @cfg {Boolean} removeValuesFromStore When set to true, selected records will be removed from the store. Defaults to true.\r
159      */\r
160     removeValuesFromStore: true,\r
161 \r
162     /**\r
163      * @cfg {String} renderFieldBtns When set to true, will render in-field buttons for clearing the component, and displaying the list for selection. Defaults to true.\r
164      */\r
165     renderFieldBtns: true,\r
166 \r
167     /**\r
168      * @cfg {Boolean} stackItems When set to true, the items will be stacked 1 per line. Defaults to false which displays the items inline.\r
169      */\r
170     stackItems: false,\r
171 \r
172     /**\r
173      * @cfg {String} styleField The underlying data field that will be used to supply additional css styles to each item.\r
174      */\r
175     styleField : null,\r
176     \r
177      /**\r
178      * @cfg {Boolean} supressClearValueRemoveEvents When true, the removeitem event will not be fired for each item when the clearValue method is called, or when the clear button is used. Defaults to false.\r
179      */\r
180     supressClearValueRemoveEvents : false,\r
181     \r
182     /**\r
183      * @cfg {String/Boolean} validationEvent The event that should initiate field validation. Set to false to disable automatic validation (defaults to 'blur').\r
184      */\r
185         validationEvent : 'blur',\r
186         \r
187     /**\r
188      * @cfg {String} valueDelimiter The delimiter to use when joining and splitting value arrays and strings.\r
189      */\r
190     valueDelimiter: ',',\r
191     initComponent:function() {\r
192        Ext.apply(this, {\r
193             items           : new Ext.util.MixedCollection(false),\r
194             usedRecords     : new Ext.util.MixedCollection(false),\r
195             addedRecords        : [],\r
196             remoteLookup        : [],\r
197             hideTrigger     : true,\r
198             grow            : false,\r
199             resizable       : false,\r
200             multiSelectMode : false,\r
201             preRenderValue  : null\r
202         });\r
203         \r
204         if(this.transform){\r
205             this.doTransform();\r
206         }\r
207         if(this.forceFormValue){\r
208                 this.items.on({\r
209                    add: this.manageNameAttribute,\r
210                    remove: this.manageNameAttribute,\r
211                    clear: this.manageNameAttribute,\r
212                    scope: this\r
213                 });\r
214         }\r
215         \r
216         Ext.ux.form.SuperBoxSelect.superclass.initComponent.call(this);\r
217         if(this.mode === 'remote' && this.store){\r
218                 this.store.on('load', this.onStoreLoad, this);\r
219         }\r
220     },\r
221     onRender:function(ct, position) {\r
222         var h = this.hiddenName;\r
223         this.hiddenName = null;\r
224         Ext.ux.form.SuperBoxSelect.superclass.onRender.call(this, ct, position);\r
225         this.hiddenName = h;\r
226         this.manageNameAttribute();\r
227        \r
228         var extraClass = (this.stackItems === true) ? 'x-superboxselect-stacked' : '';\r
229         if(this.renderFieldBtns){\r
230             extraClass += ' x-superboxselect-display-btns';\r
231         }\r
232         this.el.removeClass('x-form-text').addClass('x-superboxselect-input-field');\r
233         \r
234         this.wrapEl = this.el.wrap({\r
235             tag : 'ul'\r
236         });\r
237         \r
238         this.outerWrapEl = this.wrapEl.wrap({\r
239             tag : 'div',\r
240             cls: 'x-form-text x-superboxselect ' + extraClass\r
241         });\r
242        \r
243         this.inputEl = this.el.wrap({\r
244             tag : 'li',\r
245             cls : 'x-superboxselect-input'\r
246         });\r
247         \r
248         if(this.renderFieldBtns){\r
249             this.setupFieldButtons().manageClearBtn();\r
250         }\r
251         \r
252         this.setupFormInterception();\r
253     },\r
254     onStoreLoad : function(store, records, options){\r
255         //accomodating for bug in Ext 3.0.0 where options.params are empty\r
256         var q = options.params[this.queryParam] || store.baseParams[this.queryParam] || "",\r
257                 isValuesQuery = options.params[this.queryValuesIndicator] || store.baseParams[this.queryValuesIndicator];\r
258         \r
259         if(this.removeValuesFromStore){\r
260                 this.store.each(function(record) {\r
261                                 if(this.usedRecords.containsKey(record.get(this.valueField))){\r
262                                         this.store.remove(record);\r
263                                 }\r
264                         }, this);\r
265         }\r
266         //queried values\r
267         if(isValuesQuery){\r
268                 var params = q.split(this.queryValuesDelimiter);\r
269                 Ext.each(params,function(p){\r
270                         this.remoteLookup.remove(p);\r
271                         var rec = this.findRecord(this.valueField,p);\r
272                         if(rec){\r
273                                 this.addRecord(rec);\r
274                         }\r
275                 },this);\r
276                 \r
277                 if(this.setOriginal){\r
278                         this.setOriginal = false;\r
279                         this.originalValue = this.getValue();\r
280                 }\r
281         }\r
282 \r
283         //queried display (autocomplete) & addItem\r
284         if(q !== '' && this.allowAddNewData){\r
285                 Ext.each(this.remoteLookup,function(r){\r
286                         if(typeof r == "object" && r[this.displayField] == q){\r
287                                 this.remoteLookup.remove(r);\r
288                                         if(records.length && records[0].get(this.displayField) === q) {\r
289                                                 this.addRecord(records[0]);\r
290                                                 return;\r
291                                         }\r
292                                         var rec = this.createRecord(r);\r
293                                         this.store.add(rec);\r
294                                 this.addRecord(rec);\r
295                                 this.addedRecords.push(rec); //keep track of records added to store\r
296                                 (function(){\r
297                                         if(this.isExpanded()){\r
298                                                 this.collapse();\r
299                                         }\r
300                                 }).defer(10,this);\r
301                                 return;\r
302                         }\r
303                 },this);\r
304         }\r
305         \r
306         var toAdd = [];\r
307         if(q === ''){\r
308                 Ext.each(this.addedRecords,function(rec){\r
309                         if(this.preventDuplicates && this.usedRecords.containsKey(rec.get(this.valueField))){\r
310                                         return;                         \r
311                         }\r
312                         toAdd.push(rec);\r
313                         \r
314                 },this);\r
315                 \r
316         }else{\r
317                 var re = new RegExp(Ext.escapeRe(q) + '.*','i');\r
318                 Ext.each(this.addedRecords,function(rec){\r
319                         if(this.preventDuplicates && this.usedRecords.containsKey(rec.get(this.valueField))){\r
320                                         return;                         \r
321                         }\r
322                         if(re.test(rec.get(this.displayField))){\r
323                                 toAdd.push(rec);\r
324                         }\r
325                 },this);\r
326             }\r
327         this.store.add(toAdd);\r
328         this.store.sort(this.displayField, 'ASC');\r
329         \r
330                 if(this.store.getCount() === 0 && this.isExpanded()){\r
331                         this.collapse();\r
332                 }\r
333                 \r
334         },\r
335     doTransform : function() {\r
336         var s = Ext.getDom(this.transform), transformValues = [];\r
337             if(!this.store){\r
338                 this.mode = 'local';\r
339                 var d = [], opts = s.options;\r
340                 for(var i = 0, len = opts.length;i < len; i++){\r
341                     var o = opts[i], oe = Ext.get(o),\r
342                         value = oe.getAttributeNS(null,'value') || '',\r
343                         cls = oe.getAttributeNS(null,'className') || '',\r
344                         style = oe.getAttributeNS(null,'style') || '';\r
345                     if(o.selected) {\r
346                         transformValues.push(value);\r
347                     }\r
348                     d.push([value, o.text, cls, typeof(style) === "string" ? style : style.cssText]);\r
349                 }\r
350                 this.store = new Ext.data.SimpleStore({\r
351                     'id': 0,\r
352                     fields: ['value', 'text', 'cls', 'style'],\r
353                     data : d\r
354                 });\r
355                 Ext.apply(this,{\r
356                     valueField: 'value',\r
357                     displayField: 'text',\r
358                     classField: 'cls',\r
359                     styleField: 'style'\r
360                 });\r
361             }\r
362            \r
363             if(transformValues.length){\r
364                 this.value = transformValues.join(',');\r
365             }\r
366     },\r
367     setupFieldButtons : function(){\r
368         this.buttonWrap = this.outerWrapEl.createChild({\r
369             cls: 'x-superboxselect-btns'\r
370         });\r
371         \r
372         this.buttonClear = this.buttonWrap.createChild({\r
373             tag:'div',\r
374             cls: 'x-superboxselect-btn-clear ' + this.clearBtnCls\r
375         });\r
376         \r
377         this.buttonExpand = this.buttonWrap.createChild({\r
378             tag:'div',\r
379             cls: 'x-superboxselect-btn-expand ' + this.expandBtnCls\r
380         });\r
381         \r
382         this.initButtonEvents();\r
383         \r
384         return this;\r
385     },\r
386     initButtonEvents : function() {\r
387         this.buttonClear.addClassOnOver('x-superboxselect-btn-over').on('click', function(e) {\r
388             e.stopEvent();\r
389             if (this.disabled) {\r
390                 return;\r
391             }\r
392             this.clearValue();\r
393             this.el.focus();\r
394         }, this);\r
395 \r
396         this.buttonExpand.addClassOnOver('x-superboxselect-btn-over').on('click', function(e) {\r
397             e.stopEvent();\r
398             if (this.disabled) {\r
399                 return;\r
400             }\r
401             if (this.isExpanded()) {\r
402                 this.multiSelectMode = false;\r
403             } else if (this.pinList) {\r
404                 this.multiSelectMode = true;\r
405             }\r
406             this.onTriggerClick();\r
407         }, this);\r
408     },\r
409     removeButtonEvents : function() {\r
410         this.buttonClear.removeAllListeners();\r
411         this.buttonExpand.removeAllListeners();\r
412         return this;\r
413     },\r
414     clearCurrentFocus : function(){\r
415         if(this.currentFocus){\r
416             this.currentFocus.onLnkBlur();\r
417             this.currentFocus = null;\r
418         }  \r
419         return this;        \r
420     },\r
421     initEvents : function() {\r
422         var el = this.el;\r
423 \r
424         el.on({\r
425             click   : this.onClick,\r
426             focus   : this.clearCurrentFocus,\r
427             blur    : this.onBlur,\r
428 \r
429             keydown : this.onKeyDownHandler,\r
430             keyup   : this.onKeyUpBuffered,\r
431 \r
432             scope   : this\r
433         });\r
434 \r
435         this.on({\r
436             collapse: this.onCollapse,\r
437             expand: this.clearCurrentFocus,\r
438             scope: this\r
439         });\r
440 \r
441         this.wrapEl.on('click', this.onWrapClick, this);\r
442         this.outerWrapEl.on('click', this.onWrapClick, this);\r
443         \r
444         this.inputEl.focus = function() {\r
445             el.focus();\r
446         };\r
447 \r
448         Ext.ux.form.SuperBoxSelect.superclass.initEvents.call(this);\r
449 \r
450         Ext.apply(this.keyNav, {\r
451             tab: function(e) {\r
452                 if (this.fixFocusOnTabSelect && this.isExpanded()) {\r
453                     e.stopEvent();\r
454                     el.blur();\r
455                     this.onViewClick(false);\r
456                     this.focus(false, 10);\r
457                     return true;\r
458                 }\r
459 \r
460                 this.onViewClick(false);\r
461                 if (el.dom.value !== '') {\r
462                     this.setRawValue('');\r
463                 }\r
464 \r
465                 return true;\r
466             },\r
467 \r
468             down: function(e) {\r
469                 if (!this.isExpanded() && !this.currentFocus) {\r
470                     this.onTriggerClick();\r
471                 } else {\r
472                     this.inKeyMode = true;\r
473                     this.selectNext();\r
474                 }\r
475             },\r
476 \r
477             enter: function(){}\r
478         });\r
479     },\r
480 \r
481     onClick: function() {\r
482         this.clearCurrentFocus();\r
483         this.collapse();\r
484         this.autoSize();\r
485     },\r
486 \r
487     beforeBlur: Ext.form.ComboBox.superclass.beforeBlur,\r
488 \r
489     onFocus: function() {\r
490         this.outerWrapEl.addClass(this.focusClass);\r
491 \r
492         Ext.ux.form.SuperBoxSelect.superclass.onFocus.call(this);\r
493     },\r
494 \r
495     onBlur: function() {\r
496         this.outerWrapEl.removeClass(this.focusClass);\r
497 \r
498         this.clearCurrentFocus();\r
499 \r
500         if (this.el.dom.value !== '') {\r
501             this.applyEmptyText();\r
502             this.autoSize();\r
503         }\r
504 \r
505         Ext.ux.form.SuperBoxSelect.superclass.onBlur.call(this);\r
506     },\r
507 \r
508     onCollapse: function() {\r
509         this.view.clearSelections();\r
510         this.multiSelectMode = false;\r
511     },\r
512 \r
513     onWrapClick: function(e) {\r
514         e.stopEvent();\r
515         this.collapse();\r
516         this.el.focus();\r
517         this.clearCurrentFocus();\r
518     },\r
519     markInvalid : function(msg) {\r
520         var elp, t;\r
521 \r
522         if (!this.rendered || this.preventMark ) {\r
523             return;\r
524         }\r
525         this.outerWrapEl.addClass(this.invalidClass);\r
526         msg = msg || this.invalidText;\r
527 \r
528         switch (this.msgTarget) {\r
529             case 'qtip':\r
530                 Ext.apply(this.el.dom, {\r
531                     qtip    : msg,\r
532                     qclass  : 'x-form-invalid-tip'\r
533                 });\r
534                 Ext.apply(this.wrapEl.dom, {\r
535                     qtip    : msg,\r
536                     qclass  : 'x-form-invalid-tip'\r
537                 });\r
538                 if (Ext.QuickTips) { // fix for floating editors interacting with DND\r
539                     Ext.QuickTips.enable();\r
540                 }\r
541                 break;\r
542             case 'title':\r
543                 this.el.dom.title = msg;\r
544                 this.wrapEl.dom.title = msg;\r
545                 this.outerWrapEl.dom.title = msg;\r
546                 break;\r
547             case 'under':\r
548                 if (!this.errorEl) {\r
549                     elp = this.getErrorCt();\r
550                     if (!elp) { // field has no container el\r
551                         this.el.dom.title = msg;\r
552                         break;\r
553                     }\r
554                     this.errorEl = elp.createChild({cls:'x-form-invalid-msg'});\r
555                     this.errorEl.setWidth(elp.getWidth(true) - 20);\r
556                 }\r
557                 this.errorEl.update(msg);\r
558                 Ext.form.Field.msgFx[this.msgFx].show(this.errorEl, this);\r
559                 break;\r
560             case 'side':\r
561                 if (!this.errorIcon) {\r
562                     elp = this.getErrorCt();\r
563                     if (!elp) { // field has no container el\r
564                         this.el.dom.title = msg;\r
565                         break;\r
566                     }\r
567                     this.errorIcon = elp.createChild({cls:'x-form-invalid-icon'});\r
568                 }\r
569                 this.alignErrorIcon();\r
570                 Ext.apply(this.errorIcon.dom, {\r
571                     qtip    : msg,\r
572                     qclass  : 'x-form-invalid-tip'\r
573                 });\r
574                 this.errorIcon.show();\r
575                 this.on('resize', this.alignErrorIcon, this);\r
576                 break;\r
577             default:\r
578                 t = Ext.getDom(this.msgTarget);\r
579                 t.innerHTML = msg;\r
580                 t.style.display = this.msgDisplay;\r
581                 break;\r
582         }\r
583         this.fireEvent('invalid', this, msg);\r
584     },\r
585     clearInvalid : function(){\r
586         if(!this.rendered || this.preventMark){ // not rendered\r
587             return;\r
588         }\r
589         this.outerWrapEl.removeClass(this.invalidClass);\r
590         switch(this.msgTarget){\r
591             case 'qtip':\r
592                 this.el.dom.qtip = '';\r
593                 this.wrapEl.dom.qtip ='';\r
594                 break;\r
595             case 'title':\r
596                 this.el.dom.title = '';\r
597                 this.wrapEl.dom.title = '';\r
598                 this.outerWrapEl.dom.title = '';\r
599                 break;\r
600             case 'under':\r
601                 if(this.errorEl){\r
602                     Ext.form.Field.msgFx[this.msgFx].hide(this.errorEl, this);\r
603                 }\r
604                 break;\r
605             case 'side':\r
606                 if(this.errorIcon){\r
607                     this.errorIcon.dom.qtip = '';\r
608                     this.errorIcon.hide();\r
609                     this.un('resize', this.alignErrorIcon, this);\r
610                 }\r
611                 break;\r
612             default:\r
613                 var t = Ext.getDom(this.msgTarget);\r
614                 t.innerHTML = '';\r
615                 t.style.display = 'none';\r
616                 break;\r
617         }\r
618         this.fireEvent('valid', this);\r
619     },\r
620     alignErrorIcon : function(){\r
621         if(this.wrap){\r
622             this.errorIcon.alignTo(this.wrap, 'tl-tr', [Ext.isIE ? 5 : 2, 3]);\r
623         }\r
624     },\r
625     expand : function(){\r
626         if (this.isExpanded() || !this.hasFocus) {\r
627             return;\r
628         }\r
629         this.list.alignTo(this.outerWrapEl, this.listAlign).show();\r
630         this.innerList.setOverflow('auto'); // necessary for FF 2.0/Mac\r
631         Ext.getDoc().on({\r
632             mousewheel: this.collapseIf,\r
633             mousedown: this.collapseIf,\r
634             scope: this\r
635         });\r
636         this.fireEvent('expand', this);\r
637     },\r
638     restrictHeight : function(){\r
639         var inner = this.innerList.dom,\r
640             st = inner.scrollTop, \r
641             list = this.list;\r
642         \r
643         inner.style.height = '';\r
644         \r
645         var pad = list.getFrameWidth('tb')+(this.resizable?this.handleHeight:0)+this.assetHeight,\r
646             h = Math.max(inner.clientHeight, inner.offsetHeight, inner.scrollHeight),\r
647             ha = this.getPosition()[1]-Ext.getBody().getScroll().top,\r
648             hb = Ext.lib.Dom.getViewHeight()-ha-this.getSize().height,\r
649             space = Math.max(ha, hb, this.minHeight || 0)-list.shadowOffset-pad-5;\r
650         \r
651         h = Math.min(h, space, this.maxHeight);\r
652         this.innerList.setHeight(h);\r
653 \r
654         list.beginUpdate();\r
655         list.setHeight(h+pad);\r
656         list.alignTo(this.outerWrapEl, this.listAlign);\r
657         list.endUpdate();\r
658         \r
659         if(this.multiSelectMode){\r
660             inner.scrollTop = st;\r
661         }\r
662     },\r
663     \r
664     validateValue: function(val){\r
665         if(this.items.getCount() === 0){\r
666              if(this.allowBlank){\r
667                  this.clearInvalid();\r
668                  return true;\r
669              }else{\r
670                  this.markInvalid(this.blankText);\r
671                  return false;\r
672              }\r
673         }\r
674         \r
675         this.clearInvalid();\r
676         return true;\r
677     },\r
678 \r
679     manageNameAttribute :  function(){\r
680         if(this.items.getCount() === 0 && this.forceFormValue){\r
681            this.el.dom.setAttribute('name', this.hiddenName || this.name);\r
682         }else{\r
683                 this.el.dom.removeAttribute('name');\r
684         }\r
685     },\r
686     setupFormInterception : function(){\r
687         var form;\r
688         this.findParentBy(function(p){ \r
689             if(p.getForm){\r
690                 form = p.getForm();\r
691             }\r
692         });\r
693         if(form){\r
694                 \r
695                 var formGet = form.getValues;\r
696             form.getValues = function(asString){\r
697                 this.el.dom.disabled = true;\r
698                 var oldVal = this.el.dom.value;\r
699                 this.setRawValue('');\r
700                 var vals = formGet.call(form);\r
701                 this.el.dom.disabled = false;\r
702                 this.setRawValue(oldVal);\r
703                 if(this.forceFormValue && this.items.getCount() === 0){\r
704                         vals[this.name] = '';\r
705                 }\r
706                 return asString ? Ext.urlEncode(vals) : vals ;\r
707             }.createDelegate(this);\r
708         }\r
709     },\r
710     onResize : function(w, h, rw, rh) {\r
711         var reduce = Ext.isIE6 ? 4 : Ext.isIE7 ? 1 : Ext.isIE8 ? 1 : 0;\r
712         if(this.wrapEl){\r
713             this._width = w;\r
714             this.outerWrapEl.setWidth(w - reduce);\r
715             if (this.renderFieldBtns) {\r
716                 reduce += (this.buttonWrap.getWidth() + 20);\r
717                 this.wrapEl.setWidth(w - reduce);\r
718         }\r
719         }\r
720         Ext.ux.form.SuperBoxSelect.superclass.onResize.call(this, w, h, rw, rh);\r
721         this.autoSize();\r
722     },\r
723     onEnable: function(){\r
724         Ext.ux.form.SuperBoxSelect.superclass.onEnable.call(this);\r
725         this.items.each(function(item){\r
726             item.enable();\r
727         });\r
728         if (this.renderFieldBtns) {\r
729             this.initButtonEvents();\r
730         }\r
731     },\r
732     onDisable: function(){\r
733         Ext.ux.form.SuperBoxSelect.superclass.onDisable.call(this);\r
734         this.items.each(function(item){\r
735             item.disable();\r
736         });\r
737         if(this.renderFieldBtns){\r
738             this.removeButtonEvents();\r
739         }\r
740     },\r
741     /**\r
742      * Clears all values from the component.\r
743      * @methodOf Ext.ux.form.SuperBoxSelect\r
744      * @name clearValue\r
745      * @param {Boolean} supressRemoveEvent [Optional] When true, the 'removeitem' event will not fire for each item that is removed.    \r
746      */\r
747     clearValue : function(supressRemoveEvent){\r
748         Ext.ux.form.SuperBoxSelect.superclass.clearValue.call(this);\r
749         this.preventMultipleRemoveEvents = supressRemoveEvent || this.supressClearValueRemoveEvents || false;\r
750         this.removeAllItems();\r
751         this.preventMultipleRemoveEvents = false;\r
752         this.fireEvent('clear',this);\r
753         return this;\r
754     },\r
755     onKeyUp : function(e) {\r
756         if (this.editable !== false && (!e.isSpecialKey() || e.getKey() === e.BACKSPACE) && e.getKey() !== this.itemDelimiterKey && (!e.hasModifier() || e.shiftKey)) {\r
757             this.lastKey = e.getKey();\r
758             this.dqTask.delay(this.queryDelay);\r
759         }        \r
760     },\r
761     onKeyDownHandler : function(e,t) {\r
762                 \r
763         var toDestroy,nextFocus,idx;\r
764         if ((e.getKey() === e.DELETE || e.getKey() === e.SPACE) && this.currentFocus){\r
765             e.stopEvent();\r
766             toDestroy = this.currentFocus;\r
767             this.on('expand',function(){this.collapse();},this,{single: true});\r
768             idx = this.items.indexOfKey(this.currentFocus.key);\r
769             \r
770             this.clearCurrentFocus();\r
771             \r
772             if(idx < (this.items.getCount() -1)){\r
773                 nextFocus = this.items.itemAt(idx+1);\r
774             }\r
775             \r
776             toDestroy.preDestroy(true);\r
777             if(nextFocus){\r
778                 (function(){\r
779                     nextFocus.onLnkFocus();\r
780                     this.currentFocus = nextFocus;\r
781                 }).defer(200,this);\r
782             }\r
783         \r
784             return true;\r
785         }\r
786         \r
787         var val = this.el.dom.value, it, ctrl = e.ctrlKey;\r
788         if(e.getKey() === this.itemDelimiterKey){\r
789             e.stopEvent();\r
790             if (val !== "") {\r
791                 if (ctrl || !this.isExpanded())  {  //ctrl+enter for new items\r
792                         this.view.clearSelections();\r
793                     this.collapse();\r
794                     this.setRawValue('');\r
795                     this.fireEvent('newitem', this, val);\r
796                 }\r
797                 else {\r
798                         this.onViewClick();\r
799                     //removed from 3.0.1\r
800                     if(this.unsetDelayCheck){\r
801                         this.delayedCheck = true;\r
802                         this.unsetDelayCheck.defer(10, this);\r
803                     }\r
804                 }\r
805             }else{\r
806                 if(!this.isExpanded()){\r
807                     return;\r
808                 }\r
809                 this.onViewClick();\r
810                 //removed from 3.0.1\r
811                 if(this.unsetDelayCheck){\r
812                     this.delayedCheck = true;\r
813                     this.unsetDelayCheck.defer(10, this);\r
814                 }\r
815             }\r
816             return true;\r
817         }\r
818         \r
819         if(val !== '') {\r
820             this.autoSize();\r
821             return;\r
822         }\r
823         \r
824         //select first item\r
825         if(e.getKey() === e.HOME){\r
826             e.stopEvent();\r
827             if(this.items.getCount() > 0){\r
828                 this.collapse();\r
829                 it = this.items.get(0);\r
830                 it.el.focus();\r
831                 \r
832             }\r
833             return true;\r
834         }\r
835         //backspace remove\r
836         if(e.getKey() === e.BACKSPACE){\r
837             e.stopEvent();\r
838             if(this.currentFocus) {\r
839                 toDestroy = this.currentFocus;\r
840                 this.on('expand',function(){\r
841                     this.collapse();\r
842                 },this,{single: true});\r
843                 \r
844                 idx = this.items.indexOfKey(toDestroy.key);\r
845                 \r
846                 this.clearCurrentFocus();\r
847                 if(idx < (this.items.getCount() -1)){\r
848                     nextFocus = this.items.itemAt(idx+1);\r
849                 }\r
850                 \r
851                 toDestroy.preDestroy(true);\r
852                 \r
853                 if(nextFocus){\r
854                     (function(){\r
855                         nextFocus.onLnkFocus();\r
856                         this.currentFocus = nextFocus;\r
857                     }).defer(200,this);\r
858                 }\r
859                 \r
860                 return;\r
861             }else{\r
862                 it = this.items.get(this.items.getCount() -1);\r
863                 if(it){\r
864                     if(this.backspaceDeletesLastItem){\r
865                         this.on('expand',function(){this.collapse();},this,{single: true});\r
866                         it.preDestroy(true);\r
867                     }else{\r
868                         if(this.navigateItemsWithTab){\r
869                             it.onElClick();\r
870                         }else{\r
871                             this.on('expand',function(){\r
872                                 this.collapse();\r
873                                 this.currentFocus = it;\r
874                                 this.currentFocus.onLnkFocus.defer(20,this.currentFocus);\r
875                             },this,{single: true});\r
876                         }\r
877                     }\r
878                 }\r
879                 return true;\r
880             }\r
881         }\r
882         \r
883         if(!e.isNavKeyPress()){\r
884             this.multiSelectMode = false;\r
885             this.clearCurrentFocus();\r
886             return;\r
887         }\r
888         //arrow nav\r
889         if(e.getKey() === e.LEFT || (e.getKey() === e.UP && !this.isExpanded())){\r
890             e.stopEvent();\r
891             this.collapse();\r
892             //get last item\r
893             it = this.items.get(this.items.getCount()-1);\r
894             if(this.navigateItemsWithTab){ \r
895                 //focus last el\r
896                 if(it){\r
897                     it.focus(); \r
898                 }\r
899             }else{\r
900                 //focus prev item\r
901                 if(this.currentFocus){\r
902                     idx = this.items.indexOfKey(this.currentFocus.key);\r
903                     this.clearCurrentFocus();\r
904                     \r
905                     if(idx !== 0){\r
906                         this.currentFocus = this.items.itemAt(idx-1);\r
907                         this.currentFocus.onLnkFocus();\r
908                     }\r
909                 }else{\r
910                     this.currentFocus = it;\r
911                     if(it){\r
912                         it.onLnkFocus();\r
913                     }\r
914                 }\r
915             }\r
916             return true;\r
917         }\r
918         if(e.getKey() === e.DOWN){\r
919             if(this.currentFocus){\r
920                 this.collapse();\r
921                 e.stopEvent();\r
922                 idx = this.items.indexOfKey(this.currentFocus.key);\r
923                 if(idx == (this.items.getCount() -1)){\r
924                     this.clearCurrentFocus.defer(10,this);\r
925                 }else{\r
926                     this.clearCurrentFocus();\r
927                     this.currentFocus = this.items.itemAt(idx+1);\r
928                     if(this.currentFocus){\r
929                         this.currentFocus.onLnkFocus();\r
930                     }\r
931                 }\r
932                 return true;\r
933             }\r
934         }\r
935         if(e.getKey() === e.RIGHT){\r
936             this.collapse();\r
937             it = this.items.itemAt(0);\r
938             if(this.navigateItemsWithTab){ \r
939                 //focus first el\r
940                 if(it){\r
941                     it.focus(); \r
942                 }\r
943             }else{\r
944                 if(this.currentFocus){\r
945                     idx = this.items.indexOfKey(this.currentFocus.key);\r
946                     this.clearCurrentFocus();\r
947                     if(idx < (this.items.getCount() -1)){\r
948                         this.currentFocus = this.items.itemAt(idx+1);\r
949                         if(this.currentFocus){\r
950                             this.currentFocus.onLnkFocus();\r
951                         }\r
952                     }\r
953                 }else{\r
954                     this.currentFocus = it;\r
955                     if(it){\r
956                         it.onLnkFocus();\r
957                     }\r
958                 }\r
959             }\r
960         }\r
961     },\r
962     onKeyUpBuffered : function(e){\r
963         if(!e.isNavKeyPress()){\r
964             this.autoSize();\r
965         }\r
966     },\r
967     reset :  function(){\r
968         this.killItems();\r
969         Ext.ux.form.SuperBoxSelect.superclass.reset.call(this);\r
970         this.addedRecords = [];\r
971         this.autoSize().setRawValue('');\r
972     },\r
973     applyEmptyText : function(){\r
974                 this.setRawValue('');\r
975         if(this.items.getCount() > 0){\r
976             this.el.removeClass(this.emptyClass);\r
977             this.setRawValue('');\r
978             return this;\r
979         }\r
980         if(this.rendered && this.emptyText && this.getRawValue().length < 1){\r
981             this.setRawValue(this.emptyText);\r
982             this.el.addClass(this.emptyClass);\r
983         }\r
984         return this;\r
985     },\r
986     /**\r
987      * @private\r
988      * \r
989      * Use clearValue instead\r
990      */\r
991     removeAllItems: function(){\r
992         this.items.each(function(item){\r
993             item.preDestroy(true);\r
994         },this);\r
995         this.manageClearBtn();\r
996         return this;\r
997     },\r
998     killItems : function(){\r
999         this.items.each(function(item){\r
1000             item.kill();\r
1001         },this);\r
1002         this.resetStore();\r
1003         this.items.clear();\r
1004         this.manageClearBtn();\r
1005         return this;\r
1006     },\r
1007     resetStore: function(){\r
1008         this.store.clearFilter();\r
1009         if(!this.removeValuesFromStore){\r
1010             return this;\r
1011         }\r
1012         this.usedRecords.each(function(rec){\r
1013             this.store.add(rec);\r
1014         },this);\r
1015         this.usedRecords.clear();\r
1016         this.sortStore();\r
1017         return this;\r
1018     },\r
1019     sortStore: function(){\r
1020         var ss = this.store.getSortState();\r
1021         if(ss && ss.field){\r
1022             this.store.sort(ss.field, ss.direction);\r
1023         }\r
1024         return this;\r
1025     },\r
1026     getCaption: function(dataObject){\r
1027         if(typeof this.displayFieldTpl === 'string') {\r
1028             this.displayFieldTpl = new Ext.XTemplate(this.displayFieldTpl);\r
1029         }\r
1030         var caption, recordData = dataObject instanceof Ext.data.Record ? dataObject.data : dataObject;\r
1031       \r
1032         if(this.displayFieldTpl) {\r
1033             caption = this.displayFieldTpl.apply(recordData);\r
1034         } else if(this.displayField) {\r
1035             caption = recordData[this.displayField];\r
1036         }\r
1037         \r
1038         return caption;\r
1039     },\r
1040     addRecord : function(record) {\r
1041         var display = record.data[this.displayField],\r
1042             caption = this.getCaption(record),\r
1043             val = record.data[this.valueField],\r
1044             cls = this.classField ? record.data[this.classField] : '',\r
1045             style = this.styleField ? record.data[this.styleField] : '';\r
1046 \r
1047         if (this.removeValuesFromStore) {\r
1048             this.usedRecords.add(val, record);\r
1049             this.store.remove(record);\r
1050         }\r
1051         \r
1052         this.addItemBox(val, display, caption, cls, style);\r
1053         this.fireEvent('additem', this, val, record);\r
1054     },\r
1055     createRecord : function(recordData){\r
1056         if(!this.recordConstructor){\r
1057             var recordFields = [\r
1058                 {name: this.valueField},\r
1059                 {name: this.displayField}\r
1060             ];\r
1061             if(this.classField){\r
1062                 recordFields.push({name: this.classField});\r
1063             }\r
1064             if(this.styleField){\r
1065                 recordFields.push({name: this.styleField});\r
1066             }\r
1067             this.recordConstructor = Ext.data.Record.create(recordFields);\r
1068         }\r
1069         return new this.recordConstructor(recordData);\r
1070     },\r
1071     /**\r
1072      * Adds an array of items to the SuperBoxSelect component if the {@link #Ext.ux.form.SuperBoxSelect-allowAddNewData} config is set to true.\r
1073      * @methodOf Ext.ux.form.SuperBoxSelect\r
1074      * @name addItem\r
1075      * @param {Array} newItemObjects An Array of object literals containing the property names and values for an item. The property names must match those specified in {@link #Ext.ux.form.SuperBoxSelect-displayField}, {@link #Ext.ux.form.SuperBoxSelect-valueField} and {@link #Ext.ux.form.SuperBoxSelect-classField} \r
1076      */\r
1077     addItems : function(newItemObjects){\r
1078         if (Ext.isArray(newItemObjects)) {\r
1079                         Ext.each(newItemObjects, function(item) {\r
1080                                 this.addItem(item);\r
1081                         }, this);\r
1082                 } else {\r
1083                         this.addItem(newItemObjects);\r
1084                 }\r
1085     },\r
1086     /**\r
1087      * Adds a new non-existing item to the SuperBoxSelect component if the {@link #Ext.ux.form.SuperBoxSelect-allowAddNewData} config is set to true.\r
1088      * This method should be used in place of addItem from within the newitem event handler.\r
1089      * @methodOf Ext.ux.form.SuperBoxSelect\r
1090      * @name addNewItem\r
1091      * @param {Object} newItemObject An object literal containing the property names and values for an item. The property names must match those specified in {@link #Ext.ux.form.SuperBoxSelect-displayField}, {@link #Ext.ux.form.SuperBoxSelect-valueField} and {@link #Ext.ux.form.SuperBoxSelect-classField} \r
1092      */\r
1093     addNewItem : function(newItemObject){\r
1094         this.addItem(newItemObject,true);\r
1095     },\r
1096     /**\r
1097      * Adds an item to the SuperBoxSelect component if the {@link #Ext.ux.form.SuperBoxSelect-allowAddNewData} config is set to true.\r
1098      * @methodOf Ext.ux.form.SuperBoxSelect\r
1099      * @name addItem\r
1100      * @param {Object} newItemObject An object literal containing the property names and values for an item. The property names must match those specified in {@link #Ext.ux.form.SuperBoxSelect-displayField}, {@link #Ext.ux.form.SuperBoxSelect-valueField} and {@link #Ext.ux.form.SuperBoxSelect-classField} \r
1101      */\r
1102     addItem : function(newItemObject, /*hidden param*/ forcedAdd){\r
1103         \r
1104         var val = newItemObject[this.valueField];\r
1105 \r
1106         if(this.disabled) {\r
1107             return false;\r
1108         }\r
1109         if(this.preventDuplicates && this.hasValue(val)){\r
1110             return;\r
1111         }\r
1112         \r
1113         //use existing record if found\r
1114         var record = this.findRecord(this.valueField, val);\r
1115         if (record) {\r
1116             this.addRecord(record);\r
1117             return;\r
1118         } else if (!this.allowAddNewData) { // else it's a new item\r
1119             return;\r
1120         }\r
1121         \r
1122         if(this.mode === 'remote'){\r
1123                 this.remoteLookup.push(newItemObject); \r
1124                 this.doQuery(val,false,false,forcedAdd);\r
1125                 return;\r
1126         }\r
1127         \r
1128         var rec = this.createRecord(newItemObject);\r
1129         this.store.add(rec);\r
1130         this.addRecord(rec);\r
1131         \r
1132         return true;\r
1133     },\r
1134     addItemBox : function(itemVal,itemDisplay,itemCaption, itemClass, itemStyle) {\r
1135         var hConfig, parseStyle = function(s){\r
1136             var ret = '';\r
1137             if(typeof s == 'function'){\r
1138                 ret = s.call();\r
1139             }else if(typeof s == 'object'){\r
1140                 for(var p in s){\r
1141                     ret+= p +':'+s[p]+';';\r
1142                 }\r
1143             }else if(typeof s == 'string'){\r
1144                 ret = s + ';';\r
1145             }\r
1146             return ret;\r
1147         }, itemKey = Ext.id(null,'sbx-item'), box = new Ext.ux.form.SuperBoxSelectItem({\r
1148             owner: this,\r
1149             disabled: this.disabled,\r
1150             renderTo: this.wrapEl,\r
1151             cls: this.extraItemCls + ' ' + itemClass,\r
1152             style: parseStyle(this.extraItemStyle) + ' ' + itemStyle,\r
1153             caption: itemCaption,\r
1154             display: itemDisplay,\r
1155             value:  itemVal,\r
1156             key: itemKey,\r
1157             listeners: {\r
1158                 'remove': function(item){\r
1159                     if(this.fireEvent('beforeremoveitem',this,item.value) === false){\r
1160                         return;\r
1161                     }\r
1162                     this.items.removeKey(item.key);\r
1163                     if(this.removeValuesFromStore){\r
1164                         if(this.usedRecords.containsKey(item.value)){\r
1165                             this.store.add(this.usedRecords.get(item.value));\r
1166                             this.usedRecords.removeKey(item.value);\r
1167                             this.sortStore();\r
1168                             if(this.view){\r
1169                                 this.view.render();\r
1170                             }\r
1171                         }\r
1172                     }\r
1173                     if(!this.preventMultipleRemoveEvents){\r
1174                         this.fireEvent.defer(250,this,['removeitem',this,item.value, this.findInStore(item.value)]);\r
1175                     }\r
1176                 },\r
1177                 destroy: function(){\r
1178                     this.collapse();\r
1179                     this.autoSize().manageClearBtn().validateValue();\r
1180                 },\r
1181                 scope: this\r
1182             }\r
1183         });\r
1184         box.render();\r
1185         \r
1186         hConfig = {\r
1187             tag :'input', \r
1188             type :'hidden', \r
1189             value : itemVal,\r
1190             name : (this.hiddenName || this.name)\r
1191         };\r
1192         \r
1193         if(this.disabled){\r
1194                 Ext.apply(hConfig,{\r
1195                    disabled : 'disabled'\r
1196                 })\r
1197         }\r
1198         box.hidden = this.el.insertSibling(hConfig,'before');\r
1199 \r
1200         this.items.add(itemKey,box);\r
1201         this.applyEmptyText().autoSize().manageClearBtn().validateValue();\r
1202     },\r
1203     manageClearBtn : function() {\r
1204         if (!this.renderFieldBtns || !this.rendered) {\r
1205             return this;\r
1206         }\r
1207         var cls = 'x-superboxselect-btn-hide';\r
1208         if (this.items.getCount() === 0) {\r
1209             this.buttonClear.addClass(cls);\r
1210         } else {\r
1211             this.buttonClear.removeClass(cls);\r
1212         }\r
1213         return this;\r
1214     },\r
1215     findInStore : function(val){\r
1216         var index = this.store.find(this.valueField, val);\r
1217         if(index > -1){\r
1218             return this.store.getAt(index);\r
1219         }\r
1220         return false;\r
1221     },\r
1222     /**\r
1223      * Returns a String value containing a concatenated list of item values. The list is concatenated with the {@link #Ext.ux.form.SuperBoxSelect-valueDelimiter}.\r
1224      * @methodOf Ext.ux.form.SuperBoxSelect\r
1225      * @name getValue\r
1226      * @return {String} a String value containing a concatenated list of item values. \r
1227      */\r
1228     getValue : function() {\r
1229         var ret = [];\r
1230         this.items.each(function(item){\r
1231             ret.push(item.value);\r
1232         });\r
1233         return ret.join(this.valueDelimiter);\r
1234     },\r
1235     /**\r
1236      * Returns an Array of item objects containing the {@link #Ext.ux.form.SuperBoxSelect-displayField}, {@link #Ext.ux.form.SuperBoxSelect-valueField}, {@link #Ext.ux.form.SuperBoxSelect-classField} and {@link #Ext.ux.form.SuperBoxSelect-styleField} properties.\r
1237      * @methodOf Ext.ux.form.SuperBoxSelect\r
1238      * @name getValueEx\r
1239      * @return {Array} an array of item objects. \r
1240      */\r
1241     getValueEx : function() {\r
1242         var ret = [];\r
1243         this.items.each(function(item){\r
1244             var newItem = {};\r
1245             newItem[this.valueField] = item.value;\r
1246             newItem[this.displayField] = item.display;\r
1247             if(this.classField){\r
1248                 newItem[this.classField] = item.cls || '';\r
1249             }\r
1250             if(this.styleField){\r
1251                 newItem[this.styleField] = item.style || '';\r
1252             }\r
1253             ret.push(newItem);\r
1254         },this);\r
1255         return ret;\r
1256     },\r
1257     // private\r
1258     initValue : function(){\r
1259  \r
1260         Ext.ux.form.SuperBoxSelect.superclass.initValue.call(this);\r
1261         if(this.mode === 'remote') {\r
1262                 this.setOriginal = true;\r
1263         }\r
1264     },\r
1265     /**\r
1266      * Sets the value of the SuperBoxSelect component.\r
1267      * @methodOf Ext.ux.form.SuperBoxSelect\r
1268      * @name setValue\r
1269      * @param {String|Array} value An array of item values, or a String value containing a delimited list of item values. (The list should be delimited with the {@link #Ext.ux.form.SuperBoxSelect-valueDelimiter) \r
1270      */\r
1271     setValue : function(value){\r
1272         if(!this.rendered){\r
1273             this.value = value;\r
1274             return;\r
1275         }\r
1276             \r
1277         this.removeAllItems().resetStore();\r
1278         this.remoteLookup = [];\r
1279         \r
1280         if(Ext.isEmpty(value)){\r
1281                 return;\r
1282         }\r
1283         \r
1284         var values = value;\r
1285         if(!Ext.isArray(value)){\r
1286             value = '' + value;\r
1287             values = value.split(this.valueDelimiter); \r
1288         }\r
1289         \r
1290         Ext.each(values,function(val){\r
1291             var record = this.findRecord(this.valueField, val);\r
1292             if(record){\r
1293                 this.addRecord(record);\r
1294             }else if(this.mode === 'remote'){\r
1295                                 this.remoteLookup.push(val);                    \r
1296             }\r
1297         },this);\r
1298         \r
1299         if(this.mode === 'remote'){\r
1300                 var q = this.remoteLookup.join(this.queryValuesDelimiter); \r
1301                 this.doQuery(q,false, true); //3rd param to specify a values query\r
1302         }\r
1303         \r
1304     },\r
1305     /**\r
1306      * Sets the value of the SuperBoxSelect component, adding new items that don't exist in the data store if the {@link #Ext.ux.form.SuperBoxSelect-allowAddNewData} config is set to true.\r
1307      * @methodOf Ext.ux.form.SuperBoxSelect\r
1308      * @name setValue\r
1309      * @param {Array} data An Array of item objects containing the {@link #Ext.ux.form.SuperBoxSelect-displayField}, {@link #Ext.ux.form.SuperBoxSelect-valueField} and {@link #Ext.ux.form.SuperBoxSelect-classField} properties.  \r
1310      */\r
1311     setValueEx : function(data){\r
1312         this.removeAllItems().resetStore();\r
1313         \r
1314         if(!Ext.isArray(data)){\r
1315             data = [data];\r
1316         }\r
1317         this.remoteLookup = [];\r
1318         \r
1319         if(this.allowAddNewData && this.mode === 'remote'){ // no need to query\r
1320             Ext.each(data, function(d){\r
1321                 var r = this.findRecord(this.valueField, d[this.valueField]) || this.createRecord(d);\r
1322                 this.addRecord(r);\r
1323             },this);\r
1324             return;\r
1325         }\r
1326         \r
1327         Ext.each(data,function(item){\r
1328             this.addItem(item);\r
1329         },this);\r
1330     },\r
1331     /**\r
1332      * Returns true if the SuperBoxSelect component has a selected item with a value matching the 'val' parameter.\r
1333      * @methodOf Ext.ux.form.SuperBoxSelect\r
1334      * @name hasValue\r
1335      * @param {Mixed} val The value to test.\r
1336      * @return {Boolean} true if the component has the selected value, false otherwise.\r
1337      */\r
1338     hasValue: function(val){\r
1339         var has = false;\r
1340         this.items.each(function(item){\r
1341             if(item.value == val){\r
1342                 has = true;\r
1343                 return false;\r
1344             }\r
1345         },this);\r
1346         return has;\r
1347     },\r
1348     onSelect : function(record, index) {\r
1349         if (this.fireEvent('beforeselect', this, record, index) !== false){\r
1350             var val = record.data[this.valueField];\r
1351             \r
1352             if(this.preventDuplicates && this.hasValue(val)){\r
1353                 return;\r
1354             }\r
1355             \r
1356             this.setRawValue('');\r
1357             this.lastSelectionText = '';\r
1358             \r
1359             if(this.fireEvent('beforeadditem',this,val) !== false){\r
1360                 this.addRecord(record);\r
1361             }\r
1362             if(this.store.getCount() === 0 || !this.multiSelectMode){\r
1363                 this.collapse();\r
1364             }else{\r
1365                 this.restrictHeight();\r
1366             }\r
1367         }\r
1368     },\r
1369     onDestroy : function() {\r
1370         this.items.purgeListeners();\r
1371         this.killItems();\r
1372         if (this.renderFieldBtns) {\r
1373             Ext.destroy(\r
1374                 this.buttonClear,\r
1375                 this.buttonExpand,\r
1376                 this.buttonWrap\r
1377             );\r
1378         }\r
1379 \r
1380         Ext.destroy(\r
1381             this.inputEl,\r
1382             this.wrapEl,\r
1383             this.outerWrapEl\r
1384         );\r
1385 \r
1386         Ext.ux.form.SuperBoxSelect.superclass.onDestroy.call(this);\r
1387     },\r
1388     autoSize : function(){\r
1389         if(!this.rendered){\r
1390             return this;\r
1391         }\r
1392         if(!this.metrics){\r
1393             this.metrics = Ext.util.TextMetrics.createInstance(this.el);\r
1394         }\r
1395         var el = this.el,\r
1396             v = el.dom.value,\r
1397             d = document.createElement('div');\r
1398 \r
1399         if(v === "" && this.emptyText && this.items.getCount() < 1){\r
1400             v = this.emptyText;\r
1401         }\r
1402         d.appendChild(document.createTextNode(v));\r
1403         v = d.innerHTML;\r
1404         d = null;\r
1405         v += "&#160;";\r
1406         var w = Math.max(this.metrics.getWidth(v) +  24, 24);\r
1407         if(typeof this._width != 'undefined'){\r
1408             w = Math.min(this._width, w);\r
1409         }\r
1410         this.el.setWidth(w);\r
1411         \r
1412         if(Ext.isIE){\r
1413             this.el.dom.style.top='0';\r
1414         }\r
1415         return this;\r
1416     },\r
1417     doQuery : function(q, forceAll,valuesQuery, forcedAdd){\r
1418         q = Ext.isEmpty(q) ? '' : q;\r
1419         var qe = {\r
1420             query: q,\r
1421             forceAll: forceAll,\r
1422             combo: this,\r
1423             cancel:false\r
1424         };\r
1425         if(this.fireEvent('beforequery', qe)===false || qe.cancel){\r
1426             return false;\r
1427         }\r
1428         q = qe.query;\r
1429         forceAll = qe.forceAll;\r
1430         if(forceAll === true || (q.length >= this.minChars) || valuesQuery && !Ext.isEmpty(q)){\r
1431             if(this.lastQuery !== q || forcedAdd){\r
1432                 this.lastQuery = q;\r
1433                 if(this.mode == 'local'){\r
1434                     this.selectedIndex = -1;\r
1435                     if(forceAll){\r
1436                         this.store.clearFilter();\r
1437                     }else{\r
1438                         this.store.filter(this.displayField, q);\r
1439                     }\r
1440                     this.onLoad();\r
1441                 }else{\r
1442                         \r
1443                     this.store.baseParams[this.queryParam] = q;\r
1444                     this.store.baseParams[this.queryValuesIndicator] = valuesQuery;\r
1445                     this.store.load({\r
1446                         params: this.getParams(q)\r
1447                     });\r
1448                     if(!forcedAdd){\r
1449                         this.expand();\r
1450                     }\r
1451                 }\r
1452             }else{\r
1453                 this.selectedIndex = -1;\r
1454                 this.onLoad();\r
1455             }\r
1456         }\r
1457     }\r
1458 });\r
1459 Ext.reg('superboxselect', Ext.ux.form.SuperBoxSelect);\r
1460 /*\r
1461  * @private\r
1462  */\r
1463 Ext.ux.form.SuperBoxSelectItem = function(config){\r
1464     Ext.apply(this,config);\r
1465     Ext.ux.form.SuperBoxSelectItem.superclass.constructor.call(this); \r
1466 };\r
1467 /*\r
1468  * @private\r
1469  */\r
1470 Ext.ux.form.SuperBoxSelectItem = Ext.extend(Ext.ux.form.SuperBoxSelectItem,Ext.Component, {\r
1471     initComponent : function(){\r
1472         Ext.ux.form.SuperBoxSelectItem.superclass.initComponent.call(this); \r
1473     },\r
1474     onElClick : function(e){\r
1475         var o = this.owner;\r
1476         o.clearCurrentFocus().collapse();\r
1477         if(o.navigateItemsWithTab){\r
1478             this.focus();\r
1479         }else{\r
1480             o.el.dom.focus();\r
1481             var that = this;\r
1482             (function(){\r
1483                 this.onLnkFocus();\r
1484                 o.currentFocus = this;\r
1485             }).defer(10,this);\r
1486         }\r
1487     },\r
1488     \r
1489     onLnkClick : function(e){\r
1490         if(e) {\r
1491             e.stopEvent();\r
1492         }\r
1493         this.preDestroy();\r
1494         if(!this.owner.navigateItemsWithTab){\r
1495             this.owner.el.focus();\r
1496         }\r
1497     },\r
1498     onLnkFocus : function(){\r
1499         this.el.addClass("x-superboxselect-item-focus");\r
1500         this.owner.outerWrapEl.addClass("x-form-focus");\r
1501     },\r
1502     \r
1503     onLnkBlur : function(){\r
1504         this.el.removeClass("x-superboxselect-item-focus");\r
1505         this.owner.outerWrapEl.removeClass("x-form-focus");\r
1506     },\r
1507     \r
1508     enableElListeners : function() {\r
1509         this.el.on('click', this.onElClick, this, {stopEvent:true});\r
1510        \r
1511         this.el.addClassOnOver('x-superboxselect-item x-superboxselect-item-hover');\r
1512     },\r
1513 \r
1514     enableLnkListeners : function() {\r
1515         this.lnk.on({\r
1516             click: this.onLnkClick,\r
1517             focus: this.onLnkFocus,\r
1518             blur:  this.onLnkBlur,\r
1519             scope: this\r
1520         });\r
1521     },\r
1522     \r
1523     enableAllListeners : function() {\r
1524         this.enableElListeners();\r
1525         this.enableLnkListeners();\r
1526     },\r
1527     disableAllListeners : function() {\r
1528         this.el.removeAllListeners();\r
1529         this.lnk.un('click', this.onLnkClick, this);\r
1530         this.lnk.un('focus', this.onLnkFocus, this);\r
1531         this.lnk.un('blur', this.onLnkBlur, this);\r
1532     },\r
1533     onRender : function(ct, position){\r
1534         \r
1535         Ext.ux.form.SuperBoxSelectItem.superclass.onRender.call(this, ct, position);\r
1536         \r
1537         var el = this.el;\r
1538         if(el){\r
1539             el.remove();\r
1540         }\r
1541         \r
1542         this.el = el = ct.createChild({ tag: 'li' }, ct.last());\r
1543         el.addClass('x-superboxselect-item');\r
1544         \r
1545         var btnEl = this.owner.navigateItemsWithTab ? ( Ext.isSafari ? 'button' : 'a') : 'span';\r
1546         var itemKey = this.key;\r
1547         \r
1548         Ext.apply(el, {\r
1549             focus: function(){\r
1550                 var c = this.down(btnEl +'.x-superboxselect-item-close');\r
1551                 if(c){\r
1552                         c.focus();\r
1553                 }\r
1554             },\r
1555             preDestroy: function(){\r
1556                 this.preDestroy();\r
1557             }.createDelegate(this)\r
1558         });\r
1559         \r
1560         this.enableElListeners();\r
1561 \r
1562         el.update(this.caption);\r
1563 \r
1564         var cfg = {\r
1565             tag: btnEl,\r
1566             'class': 'x-superboxselect-item-close',\r
1567             tabIndex : this.owner.navigateItemsWithTab ? '0' : '-1'\r
1568         };\r
1569         if(btnEl === 'a'){\r
1570             cfg.href = '#';\r
1571         }\r
1572         this.lnk = el.createChild(cfg);\r
1573         \r
1574         \r
1575         if(!this.disabled) {\r
1576             this.enableLnkListeners();\r
1577         }else {\r
1578             this.disableAllListeners();\r
1579         }\r
1580         \r
1581         this.on({\r
1582             disable: this.disableAllListeners,\r
1583             enable: this.enableAllListeners,\r
1584             scope: this\r
1585         });\r
1586 \r
1587         this.setupKeyMap();\r
1588     },\r
1589     setupKeyMap : function(){\r
1590         this.keyMap = new Ext.KeyMap(this.lnk, [\r
1591             {\r
1592                 key: [\r
1593                     Ext.EventObject.BACKSPACE, \r
1594                     Ext.EventObject.DELETE, \r
1595                     Ext.EventObject.SPACE\r
1596                 ],\r
1597                 fn: this.preDestroy,\r
1598                 scope: this\r
1599             }, {\r
1600                 key: [\r
1601                     Ext.EventObject.RIGHT,\r
1602                     Ext.EventObject.DOWN\r
1603                 ],\r
1604                 fn: function(){\r
1605                     this.moveFocus('right');\r
1606                 },\r
1607                 scope: this\r
1608             },\r
1609             {\r
1610                 key: [Ext.EventObject.LEFT,Ext.EventObject.UP],\r
1611                 fn: function(){\r
1612                     this.moveFocus('left');\r
1613                 },\r
1614                 scope: this\r
1615             },\r
1616             {\r
1617                 key: [Ext.EventObject.HOME],\r
1618                 fn: function(){\r
1619                     var l = this.owner.items.get(0).el.focus();\r
1620                     if(l){\r
1621                         l.el.focus();\r
1622                     }\r
1623                 },\r
1624                 scope: this\r
1625             },\r
1626             {\r
1627                 key: [Ext.EventObject.END],\r
1628                 fn: function(){\r
1629                     this.owner.el.focus();\r
1630                 },\r
1631                 scope: this\r
1632             },\r
1633             {\r
1634                 key: Ext.EventObject.ENTER,\r
1635                 fn: function(){\r
1636                 }\r
1637             }\r
1638         ]);\r
1639         this.keyMap.stopEvent = true;\r
1640     },\r
1641     moveFocus : function(dir) {\r
1642         var el = this.el[dir == 'left' ? 'prev' : 'next']() || this.owner.el;\r
1643         \r
1644         el.focus.defer(100,el);\r
1645     },\r
1646 \r
1647     preDestroy : function(supressEffect) {\r
1648         if(this.fireEvent('remove', this) === false){\r
1649                 return;\r
1650             }   \r
1651         var actionDestroy = function(){\r
1652             if(this.owner.navigateItemsWithTab){\r
1653                 this.moveFocus('right');\r
1654             }\r
1655             this.hidden.remove();\r
1656             this.hidden = null;\r
1657             this.destroy();\r
1658         };\r
1659         \r
1660         if(supressEffect){\r
1661             actionDestroy.call(this);\r
1662         } else {\r
1663             this.el.hide({\r
1664                 duration: 0.2,\r
1665                 callback: actionDestroy,\r
1666                 scope: this\r
1667             });\r
1668         }\r
1669         return this;\r
1670     },\r
1671     kill : function(){\r
1672         this.hidden.remove();\r
1673         this.hidden = null;\r
1674         this.purgeListeners();\r
1675         this.destroy();\r
1676     },\r
1677     onDisable : function() {\r
1678         if(this.hidden){\r
1679             this.hidden.dom.setAttribute('disabled', 'disabled');\r
1680         }\r
1681         this.keyMap.disable();\r
1682         Ext.ux.form.SuperBoxSelectItem.superclass.onDisable.call(this);\r
1683     },\r
1684     onEnable : function() {\r
1685         if(this.hidden){\r
1686             this.hidden.dom.removeAttribute('disabled');\r
1687         }\r
1688         this.keyMap.enable();\r
1689         Ext.ux.form.SuperBoxSelectItem.superclass.onEnable.call(this);\r
1690     },\r
1691     onDestroy : function() {\r
1692         Ext.destroy(\r
1693             this.lnk,\r
1694             this.el\r
1695         );\r
1696         \r
1697         Ext.ux.form.SuperBoxSelectItem.superclass.onDestroy.call(this);\r
1698     }\r
1699 });