1 Ext.namespace('Ext.ux.form');
\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
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
12 * @license TBA (To be announced)
\r
15 Ext.ux.form.SuperBoxSelect = function(config) {
\r
16 Ext.ux.form.SuperBoxSelect.superclass.constructor.call(this,config);
\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
28 * Fires after a new item is added to the component.
\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
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
40 * @memberOf Ext.ux.form.SuperBoxSelect
\r
41 * @param {SuperBoxSelect} this
\r
42 * @param {Mixed} value The new item's value
\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
56 * Fires after an item has been removed.
\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
65 * Fires after the component values have been cleared.
\r
67 * @memberOf Ext.ux.form.SuperBoxSelect
\r
68 * @param {SuperBoxSelect} this
\r
75 * @private hide from doc gen
\r
77 Ext.ux.form.SuperBoxSelect = Ext.extend(Ext.ux.form.SuperBoxSelect,Ext.form.ComboBox,{
\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
81 allowAddNewData: false,
\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
86 backspaceDeletesLastItem: true,
\r
89 * @cfg {String} classField The underlying data field that will be used to supply an additional class to each item.
\r
94 * @cfg {String} clearBtnCls An additional class to add to the in-field clear button.
\r
99 * @cfg {String/XTemplate} displayFieldTpl A template for rendering the displayField in each selected item. Defaults to null.
\r
101 displayFieldTpl: null,
\r
104 * @cfg {String} extraItemCls An additional css class to apply to each item.
\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
111 extraItemStyle: '',
\r
114 * @cfg {String} expandBtnCls An additional class to add to the in-field expand button.
\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
121 fixFocusOnTabSelect: true,
\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
126 forceFormValue: true,
\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
131 itemDelimiterKey: Ext.EventObject.ENTER,
\r
133 * @cfg {Boolean} navigateItemsWithTab When set to true the tab key will navigate between selected items. Defaults to true.
\r
135 navigateItemsWithTab: true,
\r
138 * @cfg {Boolean} pinList When set to true the select list will be pinned to allow for multiple selections. Defaults to true.
\r
143 * @cfg {Boolean} preventDuplicates When set to true unique item values will be enforced. Defaults to true.
\r
145 preventDuplicates: true,
\r
148 * @cfg {String} queryValuesDelimiter Used to delimit multiple values queried from the server when mode is remote.
\r
150 queryValuesDelimiter: '|',
\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
155 queryValuesIndicator: 'valuesqry',
\r
158 * @cfg {Boolean} removeValuesFromStore When set to true, selected records will be removed from the store. Defaults to true.
\r
160 removeValuesFromStore: true,
\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
165 renderFieldBtns: true,
\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
173 * @cfg {String} styleField The underlying data field that will be used to supply additional css styles to each item.
\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
180 supressClearValueRemoveEvents : false,
\r
183 * @cfg {String/Boolean} validationEvent The event that should initiate field validation. Set to false to disable automatic validation (defaults to 'blur').
\r
185 validationEvent : 'blur',
\r
188 * @cfg {String} valueDelimiter The delimiter to use when joining and splitting value arrays and strings.
\r
190 valueDelimiter: ',',
\r
191 initComponent:function() {
\r
193 items : new Ext.util.MixedCollection(false),
\r
194 usedRecords : new Ext.util.MixedCollection(false),
\r
197 hideTrigger : true,
\r
200 multiSelectMode : false,
\r
201 preRenderValue : null
\r
204 if(this.transform){
\r
205 this.doTransform();
\r
207 if(this.forceFormValue){
\r
209 add: this.manageNameAttribute,
\r
210 remove: this.manageNameAttribute,
\r
211 clear: this.manageNameAttribute,
\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
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
228 var extraClass = (this.stackItems === true) ? 'x-superboxselect-stacked' : '';
\r
229 if(this.renderFieldBtns){
\r
230 extraClass += ' x-superboxselect-display-btns';
\r
232 this.el.removeClass('x-form-text').addClass('x-superboxselect-input-field');
\r
234 this.wrapEl = this.el.wrap({
\r
238 this.outerWrapEl = this.wrapEl.wrap({
\r
240 cls: 'x-form-text x-superboxselect ' + extraClass
\r
243 this.inputEl = this.el.wrap({
\r
245 cls : 'x-superboxselect-input'
\r
248 if(this.renderFieldBtns){
\r
249 this.setupFieldButtons().manageClearBtn();
\r
252 this.setupFormInterception();
\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
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
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
273 this.addRecord(rec);
\r
277 if(this.setOriginal){
\r
278 this.setOriginal = false;
\r
279 this.originalValue = this.getValue();
\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
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
297 if(this.isExpanded()){
\r
308 Ext.each(this.addedRecords,function(rec){
\r
309 if(this.preventDuplicates && this.usedRecords.containsKey(rec.get(this.valueField))){
\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
322 if(re.test(rec.get(this.displayField))){
\r
327 this.store.add(toAdd);
\r
328 this.store.sort(this.displayField, 'ASC');
\r
330 if(this.store.getCount() === 0 && this.isExpanded()){
\r
335 doTransform : function() {
\r
336 var s = Ext.getDom(this.transform), transformValues = [];
\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
346 transformValues.push(value);
\r
348 d.push([value, o.text, cls, typeof(style) === "string" ? style : style.cssText]);
\r
350 this.store = new Ext.data.SimpleStore({
\r
352 fields: ['value', 'text', 'cls', 'style'],
\r
356 valueField: 'value',
\r
357 displayField: 'text',
\r
359 styleField: 'style'
\r
363 if(transformValues.length){
\r
364 this.value = transformValues.join(',');
\r
367 setupFieldButtons : function(){
\r
368 this.buttonWrap = this.outerWrapEl.createChild({
\r
369 cls: 'x-superboxselect-btns'
\r
372 this.buttonClear = this.buttonWrap.createChild({
\r
374 cls: 'x-superboxselect-btn-clear ' + this.clearBtnCls
\r
377 this.buttonExpand = this.buttonWrap.createChild({
\r
379 cls: 'x-superboxselect-btn-expand ' + this.expandBtnCls
\r
382 this.initButtonEvents();
\r
386 initButtonEvents : function() {
\r
387 this.buttonClear.addClassOnOver('x-superboxselect-btn-over').on('click', function(e) {
\r
389 if (this.disabled) {
\r
396 this.buttonExpand.addClassOnOver('x-superboxselect-btn-over').on('click', function(e) {
\r
398 if (this.disabled) {
\r
401 if (this.isExpanded()) {
\r
402 this.multiSelectMode = false;
\r
403 } else if (this.pinList) {
\r
404 this.multiSelectMode = true;
\r
406 this.onTriggerClick();
\r
409 removeButtonEvents : function() {
\r
410 this.buttonClear.removeAllListeners();
\r
411 this.buttonExpand.removeAllListeners();
\r
414 clearCurrentFocus : function(){
\r
415 if(this.currentFocus){
\r
416 this.currentFocus.onLnkBlur();
\r
417 this.currentFocus = null;
\r
421 initEvents : function() {
\r
425 click : this.onClick,
\r
426 focus : this.clearCurrentFocus,
\r
427 blur : this.onBlur,
\r
429 keydown : this.onKeyDownHandler,
\r
430 keyup : this.onKeyUpBuffered,
\r
436 collapse: this.onCollapse,
\r
437 expand: this.clearCurrentFocus,
\r
441 this.wrapEl.on('click', this.onWrapClick, this);
\r
442 this.outerWrapEl.on('click', this.onWrapClick, this);
\r
444 this.inputEl.focus = function() {
\r
448 Ext.ux.form.SuperBoxSelect.superclass.initEvents.call(this);
\r
450 Ext.apply(this.keyNav, {
\r
452 if (this.fixFocusOnTabSelect && this.isExpanded()) {
\r
455 this.onViewClick(false);
\r
456 this.focus(false, 10);
\r
460 this.onViewClick(false);
\r
461 if (el.dom.value !== '') {
\r
462 this.setRawValue('');
\r
468 down: function(e) {
\r
469 if (!this.isExpanded() && !this.currentFocus) {
\r
470 this.onTriggerClick();
\r
472 this.inKeyMode = true;
\r
477 enter: function(){}
\r
481 onClick: function() {
\r
482 this.clearCurrentFocus();
\r
487 beforeBlur: Ext.form.ComboBox.superclass.beforeBlur,
\r
489 onFocus: function() {
\r
490 this.outerWrapEl.addClass(this.focusClass);
\r
492 Ext.ux.form.SuperBoxSelect.superclass.onFocus.call(this);
\r
495 onBlur: function() {
\r
496 this.outerWrapEl.removeClass(this.focusClass);
\r
498 this.clearCurrentFocus();
\r
500 if (this.el.dom.value !== '') {
\r
501 this.applyEmptyText();
\r
505 Ext.ux.form.SuperBoxSelect.superclass.onBlur.call(this);
\r
508 onCollapse: function() {
\r
509 this.view.clearSelections();
\r
510 this.multiSelectMode = false;
\r
513 onWrapClick: function(e) {
\r
517 this.clearCurrentFocus();
\r
519 markInvalid : function(msg) {
\r
522 if (!this.rendered || this.preventMark ) {
\r
525 this.outerWrapEl.addClass(this.invalidClass);
\r
526 msg = msg || this.invalidText;
\r
528 switch (this.msgTarget) {
\r
530 Ext.apply(this.el.dom, {
\r
532 qclass : 'x-form-invalid-tip'
\r
534 Ext.apply(this.wrapEl.dom, {
\r
536 qclass : 'x-form-invalid-tip'
\r
538 if (Ext.QuickTips) { // fix for floating editors interacting with DND
\r
539 Ext.QuickTips.enable();
\r
543 this.el.dom.title = msg;
\r
544 this.wrapEl.dom.title = msg;
\r
545 this.outerWrapEl.dom.title = msg;
\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
554 this.errorEl = elp.createChild({cls:'x-form-invalid-msg'});
\r
555 this.errorEl.setWidth(elp.getWidth(true) - 20);
\r
557 this.errorEl.update(msg);
\r
558 Ext.form.Field.msgFx[this.msgFx].show(this.errorEl, this);
\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
567 this.errorIcon = elp.createChild({cls:'x-form-invalid-icon'});
\r
569 this.alignErrorIcon();
\r
570 Ext.apply(this.errorIcon.dom, {
\r
572 qclass : 'x-form-invalid-tip'
\r
574 this.errorIcon.show();
\r
575 this.on('resize', this.alignErrorIcon, this);
\r
578 t = Ext.getDom(this.msgTarget);
\r
580 t.style.display = this.msgDisplay;
\r
583 this.fireEvent('invalid', this, msg);
\r
585 clearInvalid : function(){
\r
586 if(!this.rendered || this.preventMark){ // not rendered
\r
589 this.outerWrapEl.removeClass(this.invalidClass);
\r
590 switch(this.msgTarget){
\r
592 this.el.dom.qtip = '';
\r
593 this.wrapEl.dom.qtip ='';
\r
596 this.el.dom.title = '';
\r
597 this.wrapEl.dom.title = '';
\r
598 this.outerWrapEl.dom.title = '';
\r
602 Ext.form.Field.msgFx[this.msgFx].hide(this.errorEl, this);
\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
613 var t = Ext.getDom(this.msgTarget);
\r
615 t.style.display = 'none';
\r
618 this.fireEvent('valid', this);
\r
620 alignErrorIcon : function(){
\r
622 this.errorIcon.alignTo(this.wrap, 'tl-tr', [Ext.isIE ? 5 : 2, 3]);
\r
625 expand : function(){
\r
626 if (this.isExpanded() || !this.hasFocus) {
\r
629 this.list.alignTo(this.outerWrapEl, this.listAlign).show();
\r
630 this.innerList.setOverflow('auto'); // necessary for FF 2.0/Mac
\r
632 mousewheel: this.collapseIf,
\r
633 mousedown: this.collapseIf,
\r
636 this.fireEvent('expand', this);
\r
638 restrictHeight : function(){
\r
639 var inner = this.innerList.dom,
\r
640 st = inner.scrollTop,
\r
643 inner.style.height = '';
\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
651 h = Math.min(h, space, this.maxHeight);
\r
652 this.innerList.setHeight(h);
\r
654 list.beginUpdate();
\r
655 list.setHeight(h+pad);
\r
656 list.alignTo(this.outerWrapEl, this.listAlign);
\r
659 if(this.multiSelectMode){
\r
660 inner.scrollTop = st;
\r
664 validateValue: function(val){
\r
665 if(this.items.getCount() === 0){
\r
666 if(this.allowBlank){
\r
667 this.clearInvalid();
\r
670 this.markInvalid(this.blankText);
\r
675 this.clearInvalid();
\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
683 this.el.dom.removeAttribute('name');
\r
686 setupFormInterception : function(){
\r
688 this.findParentBy(function(p){
\r
690 form = p.getForm();
\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
706 return asString ? Ext.urlEncode(vals) : vals ;
\r
707 }.createDelegate(this);
\r
710 onResize : function(w, h, rw, rh) {
\r
711 var reduce = Ext.isIE6 ? 4 : Ext.isIE7 ? 1 : Ext.isIE8 ? 1 : 0;
\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
720 Ext.ux.form.SuperBoxSelect.superclass.onResize.call(this, w, h, rw, rh);
\r
723 onEnable: function(){
\r
724 Ext.ux.form.SuperBoxSelect.superclass.onEnable.call(this);
\r
725 this.items.each(function(item){
\r
728 if (this.renderFieldBtns) {
\r
729 this.initButtonEvents();
\r
732 onDisable: function(){
\r
733 Ext.ux.form.SuperBoxSelect.superclass.onDisable.call(this);
\r
734 this.items.each(function(item){
\r
737 if(this.renderFieldBtns){
\r
738 this.removeButtonEvents();
\r
742 * Clears all values from the component.
\r
743 * @methodOf Ext.ux.form.SuperBoxSelect
\r
745 * @param {Boolean} supressRemoveEvent [Optional] When true, the 'removeitem' event will not fire for each item that is removed.
\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
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
761 onKeyDownHandler : function(e,t) {
\r
763 var toDestroy,nextFocus,idx;
\r
764 if ((e.getKey() === e.DELETE || e.getKey() === e.SPACE) && this.currentFocus){
\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
770 this.clearCurrentFocus();
\r
772 if(idx < (this.items.getCount() -1)){
\r
773 nextFocus = this.items.itemAt(idx+1);
\r
776 toDestroy.preDestroy(true);
\r
779 nextFocus.onLnkFocus();
\r
780 this.currentFocus = nextFocus;
\r
781 }).defer(200,this);
\r
787 var val = this.el.dom.value, it, ctrl = e.ctrlKey;
\r
788 if(e.getKey() === this.itemDelimiterKey){
\r
791 if (ctrl || !this.isExpanded()) { //ctrl+enter for new items
\r
792 this.view.clearSelections();
\r
794 this.setRawValue('');
\r
795 this.fireEvent('newitem', this, val);
\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
806 if(!this.isExpanded()){
\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
824 //select first item
\r
825 if(e.getKey() === e.HOME){
\r
827 if(this.items.getCount() > 0){
\r
829 it = this.items.get(0);
\r
836 if(e.getKey() === e.BACKSPACE){
\r
838 if(this.currentFocus) {
\r
839 toDestroy = this.currentFocus;
\r
840 this.on('expand',function(){
\r
842 },this,{single: true});
\r
844 idx = this.items.indexOfKey(toDestroy.key);
\r
846 this.clearCurrentFocus();
\r
847 if(idx < (this.items.getCount() -1)){
\r
848 nextFocus = this.items.itemAt(idx+1);
\r
851 toDestroy.preDestroy(true);
\r
855 nextFocus.onLnkFocus();
\r
856 this.currentFocus = nextFocus;
\r
857 }).defer(200,this);
\r
862 it = this.items.get(this.items.getCount() -1);
\r
864 if(this.backspaceDeletesLastItem){
\r
865 this.on('expand',function(){this.collapse();},this,{single: true});
\r
866 it.preDestroy(true);
\r
868 if(this.navigateItemsWithTab){
\r
871 this.on('expand',function(){
\r
873 this.currentFocus = it;
\r
874 this.currentFocus.onLnkFocus.defer(20,this.currentFocus);
\r
875 },this,{single: true});
\r
883 if(!e.isNavKeyPress()){
\r
884 this.multiSelectMode = false;
\r
885 this.clearCurrentFocus();
\r
889 if(e.getKey() === e.LEFT || (e.getKey() === e.UP && !this.isExpanded())){
\r
893 it = this.items.get(this.items.getCount()-1);
\r
894 if(this.navigateItemsWithTab){
\r
901 if(this.currentFocus){
\r
902 idx = this.items.indexOfKey(this.currentFocus.key);
\r
903 this.clearCurrentFocus();
\r
906 this.currentFocus = this.items.itemAt(idx-1);
\r
907 this.currentFocus.onLnkFocus();
\r
910 this.currentFocus = it;
\r
918 if(e.getKey() === e.DOWN){
\r
919 if(this.currentFocus){
\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
926 this.clearCurrentFocus();
\r
927 this.currentFocus = this.items.itemAt(idx+1);
\r
928 if(this.currentFocus){
\r
929 this.currentFocus.onLnkFocus();
\r
935 if(e.getKey() === e.RIGHT){
\r
937 it = this.items.itemAt(0);
\r
938 if(this.navigateItemsWithTab){
\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
954 this.currentFocus = it;
\r
962 onKeyUpBuffered : function(e){
\r
963 if(!e.isNavKeyPress()){
\r
967 reset : function(){
\r
969 Ext.ux.form.SuperBoxSelect.superclass.reset.call(this);
\r
970 this.addedRecords = [];
\r
971 this.autoSize().setRawValue('');
\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
980 if(this.rendered && this.emptyText && this.getRawValue().length < 1){
\r
981 this.setRawValue(this.emptyText);
\r
982 this.el.addClass(this.emptyClass);
\r
989 * Use clearValue instead
\r
991 removeAllItems: function(){
\r
992 this.items.each(function(item){
\r
993 item.preDestroy(true);
\r
995 this.manageClearBtn();
\r
998 killItems : function(){
\r
999 this.items.each(function(item){
\r
1002 this.resetStore();
\r
1003 this.items.clear();
\r
1004 this.manageClearBtn();
\r
1007 resetStore: function(){
\r
1008 this.store.clearFilter();
\r
1009 if(!this.removeValuesFromStore){
\r
1012 this.usedRecords.each(function(rec){
\r
1013 this.store.add(rec);
\r
1015 this.usedRecords.clear();
\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
1026 getCaption: function(dataObject){
\r
1027 if(typeof this.displayFieldTpl === 'string') {
\r
1028 this.displayFieldTpl = new Ext.XTemplate(this.displayFieldTpl);
\r
1030 var caption, recordData = dataObject instanceof Ext.data.Record ? dataObject.data : dataObject;
\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
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
1047 if (this.removeValuesFromStore) {
\r
1048 this.usedRecords.add(val, record);
\r
1049 this.store.remove(record);
\r
1052 this.addItemBox(val, display, caption, cls, style);
\r
1053 this.fireEvent('additem', this, val, record);
\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
1061 if(this.classField){
\r
1062 recordFields.push({name: this.classField});
\r
1064 if(this.styleField){
\r
1065 recordFields.push({name: this.styleField});
\r
1067 this.recordConstructor = Ext.data.Record.create(recordFields);
\r
1069 return new this.recordConstructor(recordData);
\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
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
1077 addItems : function(newItemObjects){
\r
1078 if (Ext.isArray(newItemObjects)) {
\r
1079 Ext.each(newItemObjects, function(item) {
\r
1080 this.addItem(item);
\r
1083 this.addItem(newItemObjects);
\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
1093 addNewItem : function(newItemObject){
\r
1094 this.addItem(newItemObject,true);
\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
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
1102 addItem : function(newItemObject, /*hidden param*/ forcedAdd){
\r
1104 var val = newItemObject[this.valueField];
\r
1106 if(this.disabled) {
\r
1109 if(this.preventDuplicates && this.hasValue(val)){
\r
1113 //use existing record if found
\r
1114 var record = this.findRecord(this.valueField, val);
\r
1116 this.addRecord(record);
\r
1118 } else if (!this.allowAddNewData) { // else it's a new item
\r
1122 if(this.mode === 'remote'){
\r
1123 this.remoteLookup.push(newItemObject);
\r
1124 this.doQuery(val,false,false,forcedAdd);
\r
1128 var rec = this.createRecord(newItemObject);
\r
1129 this.store.add(rec);
\r
1130 this.addRecord(rec);
\r
1134 addItemBox : function(itemVal,itemDisplay,itemCaption, itemClass, itemStyle) {
\r
1135 var hConfig, parseStyle = function(s){
\r
1137 if(typeof s == 'function'){
\r
1139 }else if(typeof s == 'object'){
\r
1141 ret+= p +':'+s[p]+';';
\r
1143 }else if(typeof s == 'string'){
\r
1147 }, itemKey = Ext.id(null,'sbx-item'), box = new Ext.ux.form.SuperBoxSelectItem({
\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
1158 'remove': function(item){
\r
1159 if(this.fireEvent('beforeremoveitem',this,item.value) === false){
\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
1169 this.view.render();
\r
1173 if(!this.preventMultipleRemoveEvents){
\r
1174 this.fireEvent.defer(250,this,['removeitem',this,item.value, this.findInStore(item.value)]);
\r
1177 destroy: function(){
\r
1179 this.autoSize().manageClearBtn().validateValue();
\r
1190 name : (this.hiddenName || this.name)
\r
1193 if(this.disabled){
\r
1194 Ext.apply(hConfig,{
\r
1195 disabled : 'disabled'
\r
1198 box.hidden = this.el.insertSibling(hConfig,'before');
\r
1200 this.items.add(itemKey,box);
\r
1201 this.applyEmptyText().autoSize().manageClearBtn().validateValue();
\r
1203 manageClearBtn : function() {
\r
1204 if (!this.renderFieldBtns || !this.rendered) {
\r
1207 var cls = 'x-superboxselect-btn-hide';
\r
1208 if (this.items.getCount() === 0) {
\r
1209 this.buttonClear.addClass(cls);
\r
1211 this.buttonClear.removeClass(cls);
\r
1215 findInStore : function(val){
\r
1216 var index = this.store.find(this.valueField, val);
\r
1218 return this.store.getAt(index);
\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
1226 * @return {String} a String value containing a concatenated list of item values.
\r
1228 getValue : function() {
\r
1230 this.items.each(function(item){
\r
1231 ret.push(item.value);
\r
1233 return ret.join(this.valueDelimiter);
\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
1241 getValueEx : function() {
\r
1243 this.items.each(function(item){
\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
1250 if(this.styleField){
\r
1251 newItem[this.styleField] = item.style || '';
\r
1253 ret.push(newItem);
\r
1258 initValue : function(){
\r
1260 Ext.ux.form.SuperBoxSelect.superclass.initValue.call(this);
\r
1261 if(this.mode === 'remote') {
\r
1262 this.setOriginal = true;
\r
1266 * Sets the value of the SuperBoxSelect component.
\r
1267 * @methodOf Ext.ux.form.SuperBoxSelect
\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
1271 setValue : function(value){
\r
1272 if(!this.rendered){
\r
1273 this.value = value;
\r
1277 this.removeAllItems().resetStore();
\r
1278 this.remoteLookup = [];
\r
1280 if(Ext.isEmpty(value)){
\r
1284 var values = value;
\r
1285 if(!Ext.isArray(value)){
\r
1286 value = '' + value;
\r
1287 values = value.split(this.valueDelimiter);
\r
1290 Ext.each(values,function(val){
\r
1291 var record = this.findRecord(this.valueField, val);
\r
1293 this.addRecord(record);
\r
1294 }else if(this.mode === 'remote'){
\r
1295 this.remoteLookup.push(val);
\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
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
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
1311 setValueEx : function(data){
\r
1312 this.removeAllItems().resetStore();
\r
1314 if(!Ext.isArray(data)){
\r
1317 this.remoteLookup = [];
\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
1327 Ext.each(data,function(item){
\r
1328 this.addItem(item);
\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
1335 * @param {Mixed} val The value to test.
\r
1336 * @return {Boolean} true if the component has the selected value, false otherwise.
\r
1338 hasValue: function(val){
\r
1340 this.items.each(function(item){
\r
1341 if(item.value == val){
\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
1352 if(this.preventDuplicates && this.hasValue(val)){
\r
1356 this.setRawValue('');
\r
1357 this.lastSelectionText = '';
\r
1359 if(this.fireEvent('beforeadditem',this,val) !== false){
\r
1360 this.addRecord(record);
\r
1362 if(this.store.getCount() === 0 || !this.multiSelectMode){
\r
1365 this.restrictHeight();
\r
1369 onDestroy : function() {
\r
1370 this.items.purgeListeners();
\r
1372 if (this.renderFieldBtns) {
\r
1375 this.buttonExpand,
\r
1386 Ext.ux.form.SuperBoxSelect.superclass.onDestroy.call(this);
\r
1388 autoSize : function(){
\r
1389 if(!this.rendered){
\r
1392 if(!this.metrics){
\r
1393 this.metrics = Ext.util.TextMetrics.createInstance(this.el);
\r
1397 d = document.createElement('div');
\r
1399 if(v === "" && this.emptyText && this.items.getCount() < 1){
\r
1400 v = this.emptyText;
\r
1402 d.appendChild(document.createTextNode(v));
\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
1410 this.el.setWidth(w);
\r
1413 this.el.dom.style.top='0';
\r
1417 doQuery : function(q, forceAll,valuesQuery, forcedAdd){
\r
1418 q = Ext.isEmpty(q) ? '' : q;
\r
1421 forceAll: forceAll,
\r
1425 if(this.fireEvent('beforequery', qe)===false || qe.cancel){
\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
1436 this.store.clearFilter();
\r
1438 this.store.filter(this.displayField, q);
\r
1443 this.store.baseParams[this.queryParam] = q;
\r
1444 this.store.baseParams[this.queryValuesIndicator] = valuesQuery;
\r
1446 params: this.getParams(q)
\r
1453 this.selectedIndex = -1;
\r
1459 Ext.reg('superboxselect', Ext.ux.form.SuperBoxSelect);
\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
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
1474 onElClick : function(e){
\r
1475 var o = this.owner;
\r
1476 o.clearCurrentFocus().collapse();
\r
1477 if(o.navigateItemsWithTab){
\r
1483 this.onLnkFocus();
\r
1484 o.currentFocus = this;
\r
1485 }).defer(10,this);
\r
1489 onLnkClick : function(e){
\r
1493 this.preDestroy();
\r
1494 if(!this.owner.navigateItemsWithTab){
\r
1495 this.owner.el.focus();
\r
1498 onLnkFocus : function(){
\r
1499 this.el.addClass("x-superboxselect-item-focus");
\r
1500 this.owner.outerWrapEl.addClass("x-form-focus");
\r
1503 onLnkBlur : function(){
\r
1504 this.el.removeClass("x-superboxselect-item-focus");
\r
1505 this.owner.outerWrapEl.removeClass("x-form-focus");
\r
1508 enableElListeners : function() {
\r
1509 this.el.on('click', this.onElClick, this, {stopEvent:true});
\r
1511 this.el.addClassOnOver('x-superboxselect-item x-superboxselect-item-hover');
\r
1514 enableLnkListeners : function() {
\r
1516 click: this.onLnkClick,
\r
1517 focus: this.onLnkFocus,
\r
1518 blur: this.onLnkBlur,
\r
1523 enableAllListeners : function() {
\r
1524 this.enableElListeners();
\r
1525 this.enableLnkListeners();
\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
1533 onRender : function(ct, position){
\r
1535 Ext.ux.form.SuperBoxSelectItem.superclass.onRender.call(this, ct, position);
\r
1542 this.el = el = ct.createChild({ tag: 'li' }, ct.last());
\r
1543 el.addClass('x-superboxselect-item');
\r
1545 var btnEl = this.owner.navigateItemsWithTab ? ( Ext.isSafari ? 'button' : 'a') : 'span';
\r
1546 var itemKey = this.key;
\r
1549 focus: function(){
\r
1550 var c = this.down(btnEl +'.x-superboxselect-item-close');
\r
1555 preDestroy: function(){
\r
1556 this.preDestroy();
\r
1557 }.createDelegate(this)
\r
1560 this.enableElListeners();
\r
1562 el.update(this.caption);
\r
1566 'class': 'x-superboxselect-item-close',
\r
1567 tabIndex : this.owner.navigateItemsWithTab ? '0' : '-1'
\r
1569 if(btnEl === 'a'){
\r
1572 this.lnk = el.createChild(cfg);
\r
1575 if(!this.disabled) {
\r
1576 this.enableLnkListeners();
\r
1578 this.disableAllListeners();
\r
1582 disable: this.disableAllListeners,
\r
1583 enable: this.enableAllListeners,
\r
1587 this.setupKeyMap();
\r
1589 setupKeyMap : function(){
\r
1590 this.keyMap = new Ext.KeyMap(this.lnk, [
\r
1593 Ext.EventObject.BACKSPACE,
\r
1594 Ext.EventObject.DELETE,
\r
1595 Ext.EventObject.SPACE
\r
1597 fn: this.preDestroy,
\r
1601 Ext.EventObject.RIGHT,
\r
1602 Ext.EventObject.DOWN
\r
1605 this.moveFocus('right');
\r
1610 key: [Ext.EventObject.LEFT,Ext.EventObject.UP],
\r
1612 this.moveFocus('left');
\r
1617 key: [Ext.EventObject.HOME],
\r
1619 var l = this.owner.items.get(0).el.focus();
\r
1627 key: [Ext.EventObject.END],
\r
1629 this.owner.el.focus();
\r
1634 key: Ext.EventObject.ENTER,
\r
1639 this.keyMap.stopEvent = true;
\r
1641 moveFocus : function(dir) {
\r
1642 var el = this.el[dir == 'left' ? 'prev' : 'next']() || this.owner.el;
\r
1644 el.focus.defer(100,el);
\r
1647 preDestroy : function(supressEffect) {
\r
1648 if(this.fireEvent('remove', this) === false){
\r
1651 var actionDestroy = function(){
\r
1652 if(this.owner.navigateItemsWithTab){
\r
1653 this.moveFocus('right');
\r
1655 this.hidden.remove();
\r
1656 this.hidden = null;
\r
1660 if(supressEffect){
\r
1661 actionDestroy.call(this);
\r
1665 callback: actionDestroy,
\r
1671 kill : function(){
\r
1672 this.hidden.remove();
\r
1673 this.hidden = null;
\r
1674 this.purgeListeners();
\r
1677 onDisable : function() {
\r
1679 this.hidden.dom.setAttribute('disabled', 'disabled');
\r
1681 this.keyMap.disable();
\r
1682 Ext.ux.form.SuperBoxSelectItem.superclass.onDisable.call(this);
\r
1684 onEnable : function() {
\r
1686 this.hidden.dom.removeAttribute('disabled');
\r
1688 this.keyMap.enable();
\r
1689 Ext.ux.form.SuperBoxSelectItem.superclass.onEnable.call(this);
\r
1691 onDestroy : function() {
\r
1697 Ext.ux.form.SuperBoxSelectItem.superclass.onDestroy.call(this);
\r