Support

Account

Home Forums Backend Issues (wp-admin) How can I send an additional field during ajax requests?

Solved

How can I send an additional field during ajax requests?

    • radgh

    • June 25, 2015 at 5:56 am

    I have two fields, both are “Taxonomy” relationship fields (using a select2 dropdown) and both use the same taxonomy.

    The taxonomy is called “Make and Model”, and as you might have guessed, it lists makes and models of vehicles. For example:

    • Dodge
      • Challenger
      • Charger
    • Ford
      • Mustang
      • (etc)

    The idea is, I have one dropdown to select MAKE and one to select MODEL.

    The problem is ACF doesn’t do this out of the box, so both dropdowns just show all makes and models, all the time.

    I’ve fixed the first dropdown, using this code to only show makes:

    function oss_filter_make_dropdown_admin( $args, $field, $post_id ) {
    	$args['parent'] = 0;
    
    	return $args;
    }
    add_filter( 'acf/fields/taxonomy/query/name=make_term', 'oss_filter_make_dropdown_admin', 20, 3 );

    Very straightforward, it filters the dropdown to only show items with no parent.

    The difficult part is models. Now, theoretically it’s the same solution, but instead of using 0 you use the ID from Make. Therein lies the problem.

    Here is what I have so far:

    function oss_filter_model_dropdown_admin( $args, $field, $post_id ) {
    	$make = get_field( 'make_term', $post_id );
    
    	if ( !$make ) $args['parent'] = -1;
    	else $args['parent'] = (int) $make;
    
    	return $args;
    }
    add_filter( 'acf/fields/taxonomy/query/name=model_term', 'oss_filter_model_dropdown_admin', 20, 3 );

    This works, but only when you first visit the page. The problem is, if you change the make from Dodge to Ford, you still get the same dropdown items for Model (you get Challenger/Charger instead of Mustang/etc).

    This happens because the function is using get_field, but the new value “ford” has not been saved yet. So it pulls in Dodge every time, until you save the post.

    My question:

    How can I send the value from “Make” with the ajax request that reaches my function, “oss_filter_model_dropdown_admin”?

    I’ve inspected the data and it only sends the value for the Model field.

    I’ve tried to use this JavaScript code to filter the args, but it doesn’t seem to get fired:

    acf.add_filter(
    	'prepare_for_ajax',
    	function(a,b,c) {
    		// This never gets triggered! :(
    		console.log('Filter args:', a,b,c);
    		return a;
    	}
    );

    I’ve also tried using “wp.hooks.addFilter( ‘prepare_for_ajax’, … )” and “wp.hooks.addFilter( ‘acf.prepare_for_ajax’, … )”. These get triggered AFTER the ajax call though, so I assume this is incorrect usage.

  • Not sure this will help but, this appears to be working. I don’t know that much about how this works to be honest, I used your JS and added it using the instructions here http://www.advancedcustomfields.com/resources/adding-custom-javascript-fields/

    Only to discover that the script was not actually included anywhere in the page.
    Tried quite a few variations and found that setting a priority of 1 got it to load. And I can’t tell you why the script is not loaded without setting the priority.

    PHP

    
    function my_admin_enqueue_scripts() {
        wp_enqueue_script(
            'my_admin_script',
            get_template_directory_uri().'/js/test.js',
            array(),
            '1.1.0',
            true
        );
    }
    add_action('admin_enqueue_scripts', 'my_admin_enqueue_scripts', 1);
    

    JS File:

    
    acf.add_filter(
    	'prepare_for_ajax',
    	function(a,b,c) {
    		// This never gets triggered! :(
    		console.log('Filter args:', a,b,c);
    		return a;
    	}
    );
    
    • radgh

    • June 26, 2015 at 12:07 pm

    Thanks for the attempt John. I’ve already got the javascript file included properly, though I did not mention this.

    The problem with the second piece of code is that it does fire, but it fires after you select an option.

    Here is a demonstration: http://gfycat.com/IcyBaggyDore

    In that recording, I expect the console to log the event when the ajax data is prepared (as the filter name describes). This would allow me to say “hey ajax call, why don’t you take the value of MAKE with you!”.

    The problem though, is that it doesn’t fire immediately. It fires after you select an option.

    The reason this is a problem is because I want to filter what shows up in the dropdown, to do this I need to send that value when you click on the dropdown – when it is loading the results – so that I can access that value in my PHP filter (acf/fields/taxonomy/query/name=model_term)

    Here’s what SHOULD happen

    1. User clicks on dropdown
    2. Value of “Make” is added to ajax request
    3. Ajax request is fired, return a list of options
    4. Dropdown is populated with the returned options
    5. User selects one of the “Models” displayed, which are direct descendents of the “Make” from step 2.

    And here is what is CURRENTLY happening:

    1. User clicks dropdown
    2. The make is absent from AJAX request
    3. Ajax request is fired, but we cannot filter by the make
    4. Dropdown is populated with either ALL terms, or terms from the PREVIOUSLY SAVED MAKE.
    5. User is confused because either the wrong models are appearing, or a tremendous list (1300 items) is loaded.

    • radgh

    • June 26, 2015 at 12:58 pm

    (EDIT: This solution stopped working for me, but I found a new one with Elliot’s help. See my new comment.)

    PROBLEM SOLVED!!

    I found a solution.

    It’s not very pretty, but it certainly works. None of the filters I found in the source code offered a way to extend the arguments sent in a select2 ajax request.

    However, there is a filter to modify the select2 arguments. The annoying part is that the select2 arguments are actually a callback to an ACF function. So the magic here is that we replace that function with a “wrapper” function. This new function still calls the old function, then it adds the “Make” to the results. This data is then sent via ajax, and our filter can now show the correctly models!

    Here is the JavaScript, hard-coded by field ID for the MAKE:

    // Update the ajax arguments for select2 objects to include the Make value
    // This is done by replacing the default ajax.data function with a wrapper, which calls the old function and then appends "vehicle_make" to the results.
    acf.add_filter(
        'select2_args',
        function( args ) {
    
            if ( typeof args.ajax.data == 'function' ) {
                var old_data_func = args.ajax.data; // We'll keep this for maximum compatibility, and extend it.
    
                args.ajax.data = function(term, page) {
                    var default_response = old_data_func( term, page ); // Call the old, default function.
    
                    default_response.vehicle_make = function() {
                        // Add the vehicle make to the ajax function. This happens for all select 2 objects, even if it's blank.
                        return jQuery('#acf-field_55890984e822f').val();
                    };
    
                    // Return the default args with our vehicle_make function.
                    return default_response;
                }
            }
    
            return args;
        }
    );

    Here is the PHP Filter which limits the MODEL by term parent:

    function oss_filter_model_dropdown_admin( $args, $field, $post_id ) {
        // Look for the vehicle make in the AJAX request.
        $make = isset($_REQUEST['vehicle_make']) ? (int) $_REQUEST['vehicle_make'] : false;
    
        // If not found, use the previous vehicle's value.
        if ( $make === false ) $make = get_field( 'make_term', $post_id );
    
        // If no make has been selected, do not show any terms (-1 will never give results)
        // Otherwise, use the make as the parent term. Models are always a child of a make.
        if ( !$make ) $args['parent'] = -1;
        else $args['parent'] = (int) $make;
    
        return $args;
    }
    add_filter( 'acf/fields/taxonomy/query/name=model_term', 'oss_filter_model_dropdown_admin', 20, 3 );

    Here is a video demonstrating the end result, showing that the “Model” field’s options are directly tied to the “Make”.

    http://gfycat.com/RevolvingSleepyBarasingha

  • Thanks for this – very helpful!

  • Thanks this helped me get to my own solution 🙂

    For anyone in need this is how I solved a scenario where the user first selects a post object (post object field) and then selects a term from a taxonomy field which should only show terms connected to the previously chosen post.

    This works on 5.3.n and the JS is a bit different now so I think you might need to change the JS even for the solution above.

    *php*

    
    /**
     * Filters the idrott taxonomy dropdown by selected club.
     *
     * @since	1.0.0
     */
    public function filter_idrott_dropdown_admin( $args, $field, $post_id ){
    
        $club = isset($_REQUEST['club_sport']) ? (int) $_REQUEST['club_sport'] : false;
        if ( !$club ){
    	    return $args;
        }
    
    	$sports = get_the_terms( $club, 'idrotter' );
    	$sport_ids = array();
    	if( $sports ){
    		foreach( $sports as $sport ){
    			$sport_ids[] = $sport->term_id;
    		}
    	}
    	
    	if( !empty( $sport_ids ) ){
    		$args['include'] = $sport_ids;
    	}
    	
        return $args;
    
    }
    
    

    *Jquery/javascript*

    
    /**
     * Update the ajax arguments for select2 objects to include the chosen club id
     * This is done by replacing the default ajax.data function with a wrapper, which calls the old function and then appends "club_sport" to the results.
     */
    acf.add_filter(
        'select2_args',
        function( args ) {
            if ( typeof args.ajax.data == 'function' ) {
                var old_data_func = args.ajax.data; // We'll keep this for maximum compatibility, and extend it.
    
                args.ajax.data = function(term, page) {
                    var default_response = old_data_func( term, page ); // Call the old, default function.
                    default_response.club_sport = $('#acf-field_577e458143db1-input').val();
                    // Return the default args with our club_sport value.
                    return default_response;
                }
            }
    
            return args;
        }
    );
    
    
  • I should perhaps note that the significant difference is that I’m not running a function for the extra js parameter but rather just assign it the value of the hidden input that goes along with the select2 dropdown (not the value of the empty select element).

    • ayama

    • February 8, 2017 at 4:52 am

    Thnaks

    • ayama

    • February 8, 2017 at 2:01 pm

    @radgh I have a problem, the values are selected, but they do not save the data 🙁

    ACF: Versión 5.5.6

    • radgh

    • February 10, 2017 at 10:40 am

    @ayama I actually needed to use this again today and it seems this solution doesn’t work at all in ACF Version 5.5.7.

    I’ve sent in a support ticket to hopefully get some more information, and hopefully a better implementation. The one I used before was quite a hack. It seems to be the only way, though.

    • radgh

    • February 11, 2017 at 11:31 am

    @ayama I’ve received a response from Elliot and, although I’m still waiting for some final clarification, I’ve got this to work again.

    Here is the result:

    https://s3-us-west-2.amazonaws.com/elasticbeanstalk-us-west-2-868470985522/ShareX/LimelightDept/2017/02/2017-02-10_16-27-24.mp4

    Here is how to implement this code:

    1. Have a repeater field with two dropdowns (other fields could work but need alteration).

    2. CHANGE: field_589ccdb199add— This is the dropdown’s field key that needs to have the custom data added to the ajax request. It is the one dependent on a different field.

    3. CHANGE: td.acf-field-589cc27c617ab — The TD containing the field which value needs to go to the second field’s ajax request.

    Here is the code:

    // THIS BLOCK MUST COME BEFORE DOCUMENT READY, AFTER ACF JS HAS LOADED.
    if ( typeof acf != 'undefined' ) {
    	// Send ProductID with variation ajax request calls
    	lbd_send_product_id_with_variation_ajax_query();
    }
    	
    function lbd_send_product_id_with_variation_ajax_query() {
    	var fn_originalAjaxData = false;
    
    	var fn_ajaxDataWithProductID = function( e, i ) {
    		// Call the original args.ajax.data() function, which was stored in the element
    		var data = fn_originalAjaxData(e,i);
    
    		// Add the Product ID to the data
    		data.product_id = jQuery(this).closest('.acf-row').find('td.acf-field-589cc27c617ab select').val();
    
    		return data;
    	};
    
    	// Whenever a select2 is initialized, inject our own custom ajax.data() function.
    	acf.add_filter( 'select2_args', function( args, $select, settings ) {
    		// Only apply to the "Variation" dropdowns
    		var select_is_variation = $select.attr('id').indexOf('field_589ccdb199add') >= 0;
    		if ( ! select_is_variation ) return args;
    
    		// Store the original ajax.data function for later
    		if ( fn_originalAjaxData === false ) {
    			fn_originalAjaxData = args.ajax.data;
    		}
    
    		// Overwrite the ajax.data function with one that will get the Product ID.
    		args.ajax.data = fn_ajaxDataWithProductID;
    
    		return args;
    	} );
    }

    EDIT:

    Elliot has responded yet again and explained that there is a filter to modify the ajax data. The filter is undocumented. I wasn’t able to find it in the source code, but it does work. It must be added programmatically. The filter can be used as such:

    acf.add_filter( 'select2_ajax_data', function( data, settings, query ) {
        // note: I don't know what "settings" and "query" are actually called
    
        // "this" is the window object, not the field. I don't know how to get the field!
    
        data.my_new_field = 1;
        return data;
    });

    I’m not sure how to get the field that is being triggered. “this” is the window. No reference to the field is passed in any of the parameters. So unfortunately, this filter seems useless for my original purpose. I asked Elliot if there is a way. But I think the hack from the beginning of this comment is still the best way.

    • ayama

    • February 16, 2017 at 2:03 pm

    Thanks for your code

    An additional question if it can be done

    Is it possible to have 3 selects?

    
    Region1
    	Provincia1
    		Distrito1
    		Distrito2
            Provincia2
    Region2
    	Provincia1
    		Distrito1
    		Distrito2
            Provincia2

    Excuse my English, I speak Spanish.

    • radgh

    • February 16, 2017 at 6:46 pm

    @ayama Yeah it should be pretty simple. Just find this line and copy it for each field you need to pass in:

    data.product_id = jQuery(this).closest('.acf-row').find('td.acf-field-589cc27c617ab select').val();

    To keep things simple you probably want to pass all 3 values all the time, then make the serverside logic based on the field that ACF is querying for. Hope that makes sense.

    • klas_e

    • February 17, 2017 at 9:19 am

    Thanks for this @radgh . I used the ‘select2_ajax_data’ filter and checked the data.field_key to determine what field triggered the event and when to pass additional params:

    
    if (typeof acf !== 'undefined') {
        acf.add_filter('select2_ajax_data', (data) => {
          // field_587843455430a is the post select field containing the model
          if (data.field_key === 'field_587843455430a') {
                           // field_586ac90dfd68b contains the make value
            data.make_id = document.getElementById('acf-field_586ac90dfd68b').value;
          }
          return data;
        });
    }
    

    Then on the backend I used the ‘acf/fields/post_object/query/key={field_key}’ filter to filter the post query based on make_id:

    
    // filter models based on their make meta_field
    function my_post_object_query( $args, $field, $post_id ) {
        $make_id = $_POST['make_id']; //the new query param from client-side
    
        // add additional constraints to the post query
        $args['meta_key'] = 'make';
        $args['meta_value'] = $make_id;	
    	
        return $args;
    }
    add_filter('acf/fields/post_object/query/key=field_587843455430a', 'my_post_object_query', 10, 3);
    

    I’m using a slightly different approach since I’m using a separate post object field for make but should work similarly with Taxonomy Relationship fields if you use the ‘acf/fields/taxonomy/query’ filter on the backend instead.

    • radgh

    • February 17, 2017 at 9:31 am

    @klas_e Nice work. I’d just like to point out that getting the field by ID, using the field_key, wouldn’t work in a repeater. There can be multiple of the same field. It doesn’t seem to pass an index to identify what row was invoked either. But for a single field on the page I supposed it would work just fine.

    • klas_e

    • February 17, 2017 at 10:32 am

    @radgh ah that’s a bummer 🙁 Would be good if a future update would include the select2 container id in addition to the field id as part of the args..

  • Hello everyone,

    I was looking at this and it is exactly what I am looking to do but with a multiselect taxonomy field.

    What would be the procedure? Would it be possible to sum up the working solution?

    Thanks for your help.

  • Hello, I have the following problem:

    I have 4 categories hierarchies:
      Category
       – Subcategory
         — Mark
           — Model
    With your help I managed to make the subcategories display normally, however, from the mark I can no longer follow the hierarchy updating the query.

    Do you have any idea how I can get it?
    Attached is an example of the fields I need.

    • davcet

    • July 18, 2018 at 11:07 pm

    Hi, i have two taxonomy fields (dropdownlist), i’m trying this solution, but it seems does not working ..how can i check if i’m using 2select ? .. Otherwise i don’t understand why ..
    Thank’s

  • @radgh (or anyone else) could you help me with my situation please, which is even simpler than yours but still I haven’t been able to fully figure it out…

    So, in my case I only use one Post Object (cousin of Relationship), that pulls data from a hierarchical (parent-child) CPT. Very simple and stock up to here. Ah, the Post Object is within a Repeater, so that the admin will be able to add rows of Post Objects, but that doesn’t affect the case anyway.

    The problem is I want to prevent the admin who will use the system from choosing top categories (where parent=0), but rather choose only child categories, by disabling the top categories within the select2.

    At this point let me tell you, in case you don’t already know, that it’s a select2 stock functionality to disable specific options by adding a property disabled:true for that option.

    So, I did a lot of research and found that the only JS filter that is being triggered every time the select2 gets populated is acf.add_filter(‘select2_ajax_results’, function( json, params, instance ) (https://www.advancedcustomfields.com/resources/javascript-api/#filters-select2_ajax_results), where json param holds only the id and text properties of the results.

    For testing purposes I added a third param disabled:true to each option, and indeed all options where greyed out…

    add_action('acf/input/admin_footer', 'my_acf_input_admin_footer');
    function my_acf_input_admin_footer() {
        ?>
        <script type = "text/javascript">
        (function($) {
            // JS here
            acf.add_filter('select2_ajax_results', function(json, params, instance) {
                $.each($(json.results), function() {
                    $(this).prop("disabled", true);
                });
                return json;
            });
        })(jQuery);
        </script>
        <?php
    }

    The problem is I don’t know at the time select2_ajax_results filter gets triggered which options are top categories and which are children. So, I need somehow to add a third param, like top:true, to json at an earlier stage. But what filter would that be? Probably it should be server side, but what exactly filter?

    The following screenshot was taken with the test code above in place, which disabled ALL options…

    Screenshot

    Any insight would be greatly appreciated.

  • @princeofabyss

    Do you have to show the parent posts in the field? If not then I would filter the query using acf/fields/post_object/query by adding $args['post_parent__in'] = array(0);

    If you must show the parents and have them disabled in the field, Unfortunately, there are no hooks that will let you filter the json response from the server. In this case what you would have to do is do a query to get a list of post IDs that have parent = 0 and output those as a JS variable so that you can compare the values returned from the request to determine disabled or not.

    
    add_action('acf/input/admin_footer', 'my_acf_input_admin_footer');
    function my_acf_input_admin_footer() {
        $args = array(
          'post_type' => 'your post type',
          'posts_per_page' => -1,
          'post_parent' => 0,
          'fields' => 'ids'
        );
        $parents = new WP_Query($args);
        ?>
        <script type = "text/javascript">
        (function($) {
            var parents = <?php echo json_encode($parents); ?>;
            // JS here
            acf.add_filter('select2_ajax_results', function(json, params, instance) {
                $.each($(json.results), function() {
    
                    // compare returned id with parents array here
    
                    $(this).prop("disabled", true);
                });
                return json;
            });
        })(jQuery);
        </script>
        <?php
    }
    
  • @hube2 thanks a ton for your reply. Truth is I solved this earlier today, slightly differently than your proposed solution, but still it’s solved. I’ll post the code that I used, and I’ll elaborate on it later.

    add_filter('acf/fields/post_object/result/name=screening_cinema', 'add_parent_to_cinema_options', 10, 4);
    function add_parent_to_cinema_options($text, $post, $field, $post_id)
    {
        if ($post->post_parent) {
            $text .= ' | ' . __('Parent', 'acf') . ': ' . get_the_title($post->post_parent);
        }
    
        return $text;
    }
    
    add_action('acf/input/admin_footer', 'add_js_for_disabling_cinema_options');
    function add_js_for_disabling_cinema_options() {
        ?>
        <script type = "text/javascript">
        (function($) {
            acf.add_filter('select2_ajax_results', function(json, params, instance) {
                if (instance.data.field.data.name === 'screening_cinema') {
                    json.results.forEach(function(element) {
                        if (typeof element.description === "undefined") {
                            element.disabled = "true";
                        }
                    });
                }
    
                return json;
            });
        })(jQuery);
        </script>
        <?php
    }

    First of all, I noticed in includes/fields/class-acf-field-post_object.php at lines ~259-275 the following piece of code:

    		// vars
    		$result = array(
    			'id'	=> $id,
    			'text'	=> $text
    		);
    		
    		
    		// look for parent
    		$search = '| ' . __('Parent', 'acf') . ':';
    		$pos = strpos($text, $search);
    		
    		if( $pos !== false ) {
    			
    			$result['description'] = substr($text, $pos+2);
    			$result['text'] = substr($text, 0, $pos);
    			
    		}

    According to this, it’s possible to add a description and a text param in the json object used in acf.add_filter(‘select2_ajax_results’, function(json, params, instance)

    That’s why I used the filter add_filter(‘acf/fields/post_object/result/name=screening_cinema’) to first add the parent part in the text shown, and then in the JS function I searched for the existence of the description param to decide whether I’ll disable the option or not.

    Different approach than yours, the same effect and maybe a bit faster, as I’m not creating that whole bunch of top-category IDs. Plus, I liked that for the selected option, I get this | Parent: <parent title> in the dropdown so that I know exactly what parent the selection belongs to.

    screenshot

    PS: The website is localized to my language that’s why you see that ” | Γονέας:” term in place of the ” | Parent:”.

    Anyway, thanks a million for your help! Regards.

  • How pass multiple parameters in Ajax call jQuery in MVC?

  • @hube2 , John, sorry for hi-jacking the thread with this off-topic, I’m totally clueless about the issue below, and for a fact I know that if anyone is to help me figure it out, most probably that one will be you.

    So I noticed that 3-4 out of 10 times I make some edits in a post (any post) that has an ACF multi-date picker, after the post is re-published, the date picker calendar will lose its localization, until I interact somehow with it. Once I change the month, select an unselected date, or unselect a selected one, the calendar magically gets localized again. And because in my localization settings, Monday is starting the week, even that small detail gets changed as well… And ALL that is not happening every time… As I said, somehow it’s happening 3-4 times out of 10 maybe…

    In the video below, until @1:30 I published the post 3-4 times and the calendar didn’t lose its localization. Then suddenly, on @1:30, it lost it two times in a row (where you can see all things I described above). Then, at the end of the video, again it didn’t lose it after one more re-Publish…

    Any idea why this may be happening? It’s not super serious, as it’s happening in the backend, but still, I’d like to figure out why!

    https://www.youtube.com/watch?v=yKodqaCkrsg

  • @princeofabyss

    The plugin that you mention was created by a 3rd party and not the developers of ACF.

    However, I took a quick look. Going to be honest, I don’t know the answer, but my guess is that this is happening because load_plugin_textdomain() is being called too early in this plugin and this is causing the erratic behavior. I see that this plugin has not been updated in some time and this could also be part of the issue.

    https://developer.wordpress.org/reference/functions/load_plugin_textdomain/

    At any rate…

    If you look at the code for that pluing you’ll see a call to load_plugin_textdomain() inside of the plugin’s __construct() method. I would try moving this call into the plugin’s include_field_types() method as can be see in the field type starter code that the ACF developer provides here https://github.com/AdvancedCustomFields/acf-field-type-template/blob/master/acf-FIELD-NAME/acf-FIELD-NAME.php.

    Don’t know if this will fix your issue or not.

Viewing 25 posts - 1 through 25 (of 26 total)

You must be logged in to reply to this topic.

We use cookies to offer you a better browsing experience, analyze site traffic and personalize content. Read about how we use cookies and how you can control them in our Cookie Policy. If you continue to use this site, you consent to our use of cookies.