Dmitry Pashkevich
Dmitry Pashkevich

Reputation: 13516

Extjs combobox: hide selected value from dropdown list

I'm using ExtJS 4 and looking for a way I can hide currently selected value from combo's dropdown list?

So instead of this ("Alaska" currently selected in combobox):

default combobox behaviour

I want the list of values look like this:

enter image description here

In my case the combobox is not editable (i.e. you can't input an arbitrary value), I don't think it makes much sense to display the selected value two times: once in the input field and once in the dropdown list. I already see what is selected, I want the dropdown list to only show me other options I can select.

So far I don't see an easy way to do it. Probably the best place to start at is filtering combobox store but combobox uses its own filters for live search functionality.

Anybody considered this problem? Am I trying to do something weird? I'm surprised that I haven't been able to find any related topics.

Upvotes: 8

Views: 10243

Answers (5)

bpinaya
bpinaya

Reputation: 609

So I used @sra's solution, but with a small modification, to add the content in the right place below the if(!me.hideActive):

if(me.selectedRecords[0]) {
    me.store.insert(me.selectedRecords[0].index,me.selectedRecords);
}

That way you just don't add them to the botton. I know it is an old post, but I hope it helps people looking for a solution.

Upvotes: 0

Jake Steele
Jake Steele

Reputation: 497

ExtJS 3 I wrote this answer based on the others. Works great for me, its a little modified from what your looking for though.

Name.space.name = new Ext.extend(Ext.form.ComboBox, {
    type: 'all',
    oldrec: null,
    store: null,
    constructor: function (config) {
        var me = this;
        if (config.type === 'all') {
            me.store = AllConditionStore;
        } else {
            me.store = ?.?('RuleParameterType');
        }
        config = Ext.apply({
            store: me.store,
            valueField: 'id',
            hideActive: true,
            triggerAction: 'all',
            lazyRender: true,
            allowBlank: false,
            mode: 'local',
            displayField: 'text',
            listeners: {
                select: function (me, recs, index) {
                    if (me.oldrec !== null)
                        me.store.add(me.oldrec);
                    me.oldrec = recs;
                    // remove the selected from the store
                    me.store.remove(recs);
                    // redo store
                }
            }
        }, config);
        ?.?.Parameter.superclass.constructor.call(this, config);
    }
});

Upvotes: 2

Dmitry Pashkevich
Dmitry Pashkevich

Reputation: 13516

I ended up using a modified version of @sra's solution:

Ext.define('My.ComboBox',  {
    extend: 'Ext.form.field.ComboBox',

    /**
     * @cfg {Boolean} hideActive=true
     * When true, hides the currently selected value from the dropdown list
     */
    hideActive: true,

    /**
    * @private {Ext.data.Model[]} selectedRecords
    * A Array of selected records, used when hideActive is true
    */

    initComponent: function() {
        this.selectedRecords = [];

        this.callParent();
    },


    setValue: function(value, doSelect) {
        var store = this.store;

        if(this.hideActive) {
            store.suspendEvents(false);
            // write the current selected back to the store (you need to suspend autoSync if active)
            // do this BEFORE callParent so the currently selected record would be found in the store
            store.add(this.selectedRecords);
        }

        this.callParent(arguments);

        if(this.hideActive) {
            // set the selected as new recordlist
            this.selectedRecords = this.valueModels;
            // remove the selected from the store
            store.remove(this.valueModels);
            store.resumeEvents();
            store.fireEvent('refresh', store);
        }

        return this;
    }

});

The 'hiding' logic is the same, only I perform it in the setValue method to make sure that it also works when programmatically setting combo's value, including the case where the combobox is initialized with a value.

UPD Also, looks like store.add(this.selectedRecords); has to be called before this.callParent(arguments);, otherwise combobox will act weird if we attempt to set the same value twice (it simply wouldn't find the active record in the store cause we removed it, so it'll reset to blank). I suspend store's events to prevent some quirks caused by combobox trying to synchronize with its dropdown list in the middle of my manipulations with selected record(s) and manually trigger store's 'refresh' event when I'm done so the list gets eventually updated. This may have performance impact but so far I don't know a better solution.

Upvotes: 2

Dmitry Pashkevich
Dmitry Pashkevich

Reputation: 13516

I came up with another solution that looks even simpler and a quick testing reveals no side effects:

We can leave Combobox logic untouched but simply hide the selected item via CSS:

.x-boundlist-selected {
    display: none;
}

And voila, we don't see the selected item! Don't know how reliable this would be in production code but still worth considering, I think...


UPDATE. Here is the complete solution if you want to control this behavior via config flag of Combobox:

Ext.define('My.ComboBox',  {
    extend: 'Ext.form.field.ComboBox',

    /**
     * @cfg {Boolean} hideActive=true
     * When true, hides the currently selected value from the dropdown list
     */
    hideActive: true,

    /**
     * Internal method that creates the BoundList
     */
    createPicker: function() {
      var picker = this.callParent(arguments);

      // honor the hideActive flag
      if(this.hideActive) {
        picker.addCls('x-boundlist-hideactive');
      }

      return picker;
    }
});

Somewhere in your CSS:

.x-boundlist-hideactive .x-boundlist-selected {
    display: none;
}

UPDATE 2. Found a UI problem with my approach!

Hiding the selected item from the dropdown list introduces a quirk to keyboard navigation: though the element is visually hidden, it still exists and Ext will select it when you press UP/DOWN keys. Visually that means that the your selection will disappear at some point and you will have to press UP/DOWN one more time to get it back on the next visible element.

So far I was unable to find an easy fix for this. My best bet would be to modify itemSelector of the bound list (which is a Data View), setting it to something like .x-boundlist-item:not(.x-boundlist-selected) so the selected element doesn't make it into the query.

While the selector itself works, it doesn't solve the problem because the View performs this selector query before any additional classes (including the selected item class) get applied to items (this happens in Ext.view.AbstractView.refresh().

Also, this solution causes misplacement of the dropdown list when it appears above the combobox!

I had a feeling that my approach was way too easy to work flawlessly :)

Upvotes: 4

sra
sra

Reputation: 23975

I don't think you have to much options here... maybe you could do something like this:

Ext.define('Your.company.Combo', {
    extend: 'Ext.form.field.ComboBox',
    alias: 'widget.specialcombo',

    /**
    * @cfg {boolean} hideActive
    * True to hide any selected record. Defaults to <tt>true</tt>.
    */
    hideActive: true,

    /**
    * @private {Ext.data.Model[]} hideActive
    * A Array of selected records.
    */


    initComponent: function () {
        this.selectedRecords = [];

        this.callParent(arguments);

        this.on('select', this.onSelectionChange, this);
    },

    /**
    * @private onChangeSelection
    * eventhandler selections
    */
    onSelectionChange: function (me, recs) {
        if(!me.hideActive)
            return;
        // write the current selected back to the store (you need to suspend autoSync if active)
        me.store.add(me.selectedRecords);
        // set the selected as new recordlist
        me.selectedRecords = recs;
        // remove the selected from the store
        me.store.remove(recs);
    }
});

That example is totally untested. But as the store is mainly bound to the BoundList which is not direct connected to the textfield this should work. You are doing a sort of caching here.

Upvotes: 6

Related Questions