Howdy_McGee
Howdy_McGee

Reputation: 10673

Chain Select2 via AJAX

I'm looking to achieve 2 things:

  1. Allow select options to be searchable and filterable.

  2. Auto-select value if the given options are only 1

The below code achieves my 1st point but I'm not sure how, given the current implementation, I can achieve my 2nd point. Selecting a value gets added to args so when a select2 is open it queries the database with the updated args and returns filtered options for that specific select2. The working version looks like this:

jQuery( 'document' ).ready( function( $ ) {
            
    let select2s = [];
    let args = {};
    
    
    /**
     * Clear select2 values
     */
    function clear_select2s( except_name ) {
        
        except_name = ( 'undefined' !== typeof except_name ) ? except_name : '';
        
        select2s.forEach( function( $select2, key ) {
            if( $select2.attr( 'name' ) == except_name ) { return; }
            $select2.val( '' );
        } );
        
    }
    
    
    /**
     * Initialize select2s
     */
    $( '#s2Container .select2' ).each( function() {
        
        let name = $( this ).attr( 'name' );
        let placeholder = $( this ).attr( 'placeholder' );
        let $select2 = $( this ).select2( {
            allowClear: true,
            placeholder: placeholder,
            ajax: {
                type: 'post',
                url: test.ajaxurl,
                dataType: 'json',
                data: function( params ) {
                    return {
                        action: 'select2_filtering',
                        return: name,
                        args: Object.assign( args, {
                            'search': params.term
                        } ),
                    };
                },
                processResults: function( response ) {
                    return {results: response.data.options};
                },
            },
        } );
        
        /* Push select into Array */
        select2s.push( $select2 );
    } );
    
    
    /**
     * Remove selected value from arguments
     */
    $( '#s2Container .select2' ).on( 'select2:unselecting', function() {
        
        let name = $( this ).attr( 'name' );
        args[ name ] = '';
        
        clear_select2s();
        
    } );
    
    /**
     * Add selected value to arguments
     */
    $( '#s2Container .select2' ).on( 'select2:select', function() {
        
        let name = $( this ).attr( 'name' );
        let value = $( this ).val();
        
        Object.assign( args, {[name]:value} );
        
        clear_select2s( name );
        
    } );
    
} );

HTML

<div id="s2Container">
    <select class="select2" name="product_id" placeholder="Select Product"></select>
    <select class="select2" name="category_id" placeholder="Select Category"></select>
    <select class="select2" name="brand_id" placeholder="Select Brand"></select>
</div>

The above code filters the select options via ajax, great. The problem is that my return is only returning options for the current select2.

If I return the data for all options for all selects then my understanding is that I need to rebuild each select2 entirely. Destroying and rebuilding the select2 then loses the ajax functionality. Doing all of this while inside the processResults seems flawed and I'm not sure the better way to go about it.


If I break this into 2 ajax calls, one for the initial load of select2 options and one that processes the on( 'select2:select' ) then I end up with way more ajax calls than I need, and my select2:select ajax gets overwritten with the initial select2 instance ajax.


What's the best way to populate multiple select2 options dynamically, based on one-another? How would I then set a selected value if the returned option count is only 1?

Upvotes: 0

Views: 508

Answers (1)

Howdy_McGee
Howdy_McGee

Reputation: 10673

Select2 makes it very difficult to switch out active options. I had to ensure the select2 was .empty() then I had to destroy the instance and recreate the entire thing with the same settings. It was a pain. In the end, I decided that it's easier to simply not work with the select2 ajax population and populate them after initialization. Here's what I came up with:

jQuery( 'document' ).ready( function( $ ) {
            
    let args = {};
    
    
    /**
     * Update all select2s with fresh options
     */
    function update_select2s() {
        
        $.ajax( {
            type: 'post',
            url: test.ajaxurl,
            dataType: 'json',
            data: {
                action: 'product_filtering',
                args: args,
            },
            success: function( response ) {
                
                let options = response.data.results;
                
                for( const [ key, values ] of Object.entries( options ) ) {
                    
                    if( ! $( '#s2Container select[name="' + key + '"]' ).length ) {
                        continue;
                    }
                    
                    let $select2    = $( '#s2Container select[name="' + key + '"]' );
                    let selected    = $select2.val();
                    let name        = $select2.attr( 'name' );
                    let placeholder = $select2.attr( 'placeholder' );
                    
                    /**
                     * If we have a select2, empty and destroy it.
                     * This will allow us to reinitialize the select2 with fresh options.
                     * Select2 instance is not yet initialized on first run.
                     */
                    if( $select2.data( 'select2' ) ) {
                        $select2.empty().select2( 'destroy' );
                    }
                    
                    $select2.select2( {
                        allowClear: true,
                        placeholder: placeholder,
                        data: values,
                    } );
                    
                    /**
                     * If there is only one option, select 2 automatically.
                     */
                    if( values.length > 1 ) {
                        $select2.val( '' ).trigger( 'change.select2' );
                    }
                    
                }
                
            }
        } );
        
    }
    
    /* Populate Select2 options */
    update_select2s();
    
    
    $( '#s2Container .select2' ).on( 'select2:unselect', function() {

        let name = $( this ).attr( 'name' );
        delete args[ name ];
        
        update_select2s();
        
    } );
    
    
    $( '#s2Container .select2' ).on( 'select2:select', function() {
        
        let name = $( this ).attr( 'name' );
        let value = $( this ).val();
        
        /**
         * If a named arg already exists, this means the user wants change
         * We need to clear our args and give our select2s fresh data.
         */
        if( args.hasOwnProperty( name ) && args[name].length ) {
            args = {[name]:value};
        } else {
            Object.assign( args, {[name]:value} );
        }
        
        update_select2s();
        
    } );
    
} );

With this update, I'm also not returning data for a specific select2 but all the select2s. Return data keyed by select names:

{
    product_id: [ {id: 1, text: 'foo'}, {id: 1,text: 'foo'} ],
    category_id: [ {id: 1,text: 'xyz'}, {id: 1,text: '123'} ],
}

Upvotes: 0

Related Questions