Take the 2-minute tour ×
WordPress Development Stack Exchange is a question and answer site for WordPress developers and administrators. It's 100% free, no registration required.

I am trying to change the checkboxes for the terms on the backend to radiobuttons. I found this topic: Altering the appearance of custom taxonomy inputs however wich helped me doing this. However, this will turn ALL terms checkboxes to radio buttons.

Is it possible to apply this only for one taxonomy?

My code:

add_action('add_meta_boxes','mysite_add_meta_boxes',10,2);
function mysite_add_meta_boxes($post_type, $post) {
  ob_start();
}
add_action('dbx_post_sidebar','mysite_dbx_post_sidebar');
function mysite_dbx_post_sidebar() {
  $html = ob_get_clean();
  $html = str_replace('"checkbox"','"radio"',$html);
  echo $html;
}

thanks

share|improve this question

3 Answers 3

up vote 6 down vote accepted

However, this will turn ALL terms checkboxes to radio buttons.

Not only that, it'll turn any checkbox in a meta box - not ideal!

Instead, let's specifically target the wp_terms_checklist() function, which is used to generate the list of checkboxes across the admin (including quick edit).

/**
 * Use radio inputs instead of checkboxes for term checklists in specified taxonomies.
 *
 * @param   array   $args
 * @return  array
 */
function wpse_139269_term_radio_checklist( $args ) {
    if ( ! empty( $args['taxonomy'] ) && $args['taxonomy'] === 'category' /* <== Change to your required taxonomy */ ) {
        if ( empty( $args['walker'] ) || is_a( $args['walker'], 'Walker' ) ) { // Don't override 3rd party walkers.
            if ( ! class_exists( 'WPSE_139269_Walker_Category_Radio_Checklist' ) ) {
                /**
                 * Custom walker for switching checkbox inputs to radio.
                 *
                 * @see Walker_Category_Checklist
                 */
                class WPSE_139269_Walker_Category_Radio_Checklist extends Walker_Category_Checklist {
                    function walk( $elements, $max_depth, $args = array() ) {
                        $output = parent::walk( $elements, $max_depth, $args );
                        $output = str_replace(
                            array( 'type="checkbox"', "type='checkbox'" ),
                            array( 'type="radio"', "type='radio'" ),
                            $output
                        );

                        return $output;
                    }
                }
            }

            $args['walker'] = new WPSE_139269_Walker_Category_Radio_Checklist;
        }
    }

    return $args;
}

add_filter( 'wp_terms_checklist_args', 'wpse_139269_term_radio_checklist' );

We hook onto the wp_terms_checklist_args filter, then implement our own custom "walker" (a family of classes used to generate hierarchical lists). From there, it's a simply string replace of type="checkbox" with type="radio" if the taxonomy is whatever we've configured it to match (in this case "category").

share|improve this answer
2  
Beware, this is a great answer but poses an issue for Quick Edit. While it does transform quick edit checkboxes into radio buttons it doesn't select the checked radio, it instead clears the list. If you quick-edit you have to check the category each time otherwise it gets removed. –  Howdy_McGee Jun 2 at 18:02
1  
@Howdy_McGee See my answer for an approach with some additional changes to overcome the limitation. –  ialocin Jul 15 at 13:30

The following does pretty much what @TheDeadMedic did at his answer good answer, brought me there half the way, so this is kind of just an addition to it. Out of personal preference I opted to do it with start_el.

→ make sure to replace YOUR-TAXONOMY in below code according to your needs

add_filter( 'wp_terms_checklist_args', 'wpse_139269_term_radio_checklist_start_el_version', 10, 2 );
function wpse_139269_term_radio_checklist_start_el_version( $args, $post_id ) {
    if ( ! empty( $args['taxonomy'] ) && $args['taxonomy'] === 'YOUR-TAXONOMY' ) {
        if ( empty( $args['walker'] ) || is_a( $args['walker'], 'Walker' ) ) { // Don't override 3rd party walkers.
            if ( ! class_exists( 'WPSE_139269_Walker_Category_Radio_Checklist_Start_El_Version' ) ) {
                class WPSE_139269_Walker_Category_Radio_Checklist_Start_El_Version extends Walker_Category_Checklist {
                    public function start_el( &$output, $category, $depth = 0, $args = array(), $id = 0 ) {
                        if ( empty( $args['taxonomy'] ) ) {
                            $taxonomy = 'category';
                        } else {
                            $taxonomy = $args['taxonomy'];
                        }

                        if ( $taxonomy == 'category' ) {
                            $name = 'post_category';
                        } else {
                            $name = 'tax_input[' . $taxonomy . ']';
                        }

                        $args['popular_cats'] = empty( $args['popular_cats'] ) ? array() : $args['popular_cats'];
                        $class = in_array( $category->term_id, $args['popular_cats'] ) ? ' class="popular-category"' : '';

                        $args['selected_cats'] = empty( $args['selected_cats'] ) ? array() : $args['selected_cats'];

                        /** This filter is documented in wp-includes/category-template.php */
                        if ( ! empty( $args['list_only'] ) ) {
                            $aria_cheched = 'false';
                            $inner_class = 'category';

                            if ( in_array( $category->term_id, $args['selected_cats'] ) ) {
                                $inner_class .= ' selected';
                                $aria_cheched = 'true';
                            }

                            $output .= "\n" . '<li' . $class . '>' .
                                '<div class="' . $inner_class . '" data-term-id=' . $category->term_id .
                                ' tabindex="0" role="checkbox" aria-checked="' . $aria_cheched . '">' .
                                esc_html( apply_filters( 'the_category', $category->name ) ) . '</div>';
                        } else {
                            $output .= "\n<li id='{$taxonomy}-{$category->term_id}'$class>" .
                            '<label class="selectit"><input value="' . $category->term_id . '" type="radio" name="'.$name.'[]" id="in-'.$taxonomy.'-' . $category->term_id . '"' .
                            checked( in_array( $category->term_id, $args['selected_cats'] ), true, false ) .
                            disabled( empty( $args['disabled'] ), false, false ) . ' /> ' .
                            esc_html( apply_filters( 'the_category', $category->name ) ) . '</label>';
                        }
                    }
                }
            }
            $args['walker'] = new WPSE_139269_Walker_Category_Radio_Checklist_Start_El_Version;
        }
    }
    return $args;
}

Now as @ Howdy_McGee correctly stated in his comment this doesn't work nicely, correctly with the quick/inline edit. The above code handles the saving correctly, but the radio at the inline edit isn't checked. Of course we want that, for this I have done this:

→ write some JQuery code to handle the checked state
→ file name: editphp-inline-edit-tax-radio-hack.js - used below for enqueueing

jQuery(document).ready(function($) {
    var taxonomy = 'status',
        post_id = null,
        term_id = null,
        li_ele_id = null;
    $('a.editinline').on('click', function() {
        post_id = inlineEditPost.getId(this);
        $.ajax({
            url: ajaxurl,
            data: {
                action: 'wpse_139269_inline_edit_radio_checked_hack',
                'ajax-taxonomy': taxonomy,
                'ajax-post-id': post_id
            },
            type: 'POST',
            dataType: 'json',
            success: function (response) {
                term_id = response;
                li_ele_id = 'in-' + taxonomy + '-' + term_id;
                $( 'input[id="'+li_ele_id+'"]' ).attr( 'checked', 'checked' );
            }
        });
    });

});

→ we need an AJAX action - as seen in above code block

add_action( 'wp_ajax_wpse_139269_inline_edit_radio_checked_hack', 'wpse_139269_inline_edit_radio_checked_hack' );
add_action( 'wp_ajax_nopriv_wpse_139269_inline_edit_radio_checked_hack', 'wpse_139269_inline_edit_radio_checked_hack' );
function wpse_139269_inline_edit_radio_checked_hack() {
    $terms = wp_get_object_terms(
        $_POST[ 'ajax-post-id' ],
        $_POST[ 'ajax-taxonomy' ],
        array( 'fields' => 'ids' )
    );
    $result = $terms[ 0 ];
    echo json_encode($result);
    exit;
    die();
}

→ enqueueing above script
→ change the path information according to your needs

add_action( 'admin_enqueue_scripts', 'wpse_139269_inline_edit_radio_checked_hack_enqueue_script' );
function wpse_139269_inline_edit_radio_checked_hack_enqueue_script() {
    wp_enqueue_script(
        'editphp-inline-edit-tax-radio-hack-js',
        get_template_directory_uri() . '/your/path/editphp-inline-edit-tax-radio-hack.js',
        array( 'jquery' )
    );
}

This is working quite nicely so far, but only for the first time, when opening the inline edit a second time we have lost the checked state again. We obviously don't want that. To get around it I used a method I've found here by @brasofilo. What it does is reloading the updated inline edit section. This leads to the radio checkbox being correctly shown, no matter how often it is changed.

→ make sure to replace YOUR-POST-TYPE in below code according to your needs

add_action( 'wp_ajax_inline-save', 'wpse_139269_wp_ajax_inline_save', 0 );
function wpse_139269_wp_ajax_inline_save() {
    global $wp_list_table;

    check_ajax_referer( 'inlineeditnonce', '_inline_edit' );

    if ( ! isset($_POST['post_ID']) || ! ( $post_ID = (int) $_POST['post_ID'] ) )
        wp_die();

        if ( 'page' == $_POST['post_type'] ) {
            if ( ! current_user_can( 'edit_page', $post_ID ) )
                wp_die( __( 'You are not allowed to edit this page.' ) );
        } else {
            if ( ! current_user_can( 'edit_post', $post_ID ) )
                wp_die( __( 'You are not allowed to edit this post.' ) );
        }

        if ( $last = wp_check_post_lock( $post_ID ) ) {
            $last_user = get_userdata( $last );
            $last_user_name = $last_user ? $last_user->display_name : __( 'Someone' );
            printf( $_POST['post_type'] == 'page' ? __( 'Saving is disabled: %s is currently editing this page.' ) : __( 'Saving is disabled: %s is currently editing this post.' ),    esc_html( $last_user_name ) );
            wp_die();
        }

        $data = &$_POST;

        $post = get_post( $post_ID, ARRAY_A );

        // Since it's coming from the database.
        $post = wp_slash($post);

        $data['content'] = $post['post_content'];
        $data['excerpt'] = $post['post_excerpt'];

        // Rename.
        $data['user_ID'] = get_current_user_id();

        if ( isset($data['post_parent']) )
            $data['parent_id'] = $data['post_parent'];

            // Status.
            if ( isset($data['keep_private']) && 'private' == $data['keep_private'] )
                $data['post_status'] = 'private';
            else
                $data['post_status'] = $data['_status'];

            if ( empty($data['comment_status']) )
                $data['comment_status'] = 'closed';
            if ( empty($data['ping_status']) )
                $data['ping_status'] = 'closed';

            // Exclude terms from taxonomies that are not supposed to appear in Quick Edit.
            if ( ! empty( $data['tax_input'] ) ) {
                foreach ( $data['tax_input'] as $taxonomy => $terms ) {
                    $tax_object = get_taxonomy( $taxonomy );
                    /** This filter is documented in wp-admin/includes/class-wp-posts-list-table.php */
                    if ( ! apply_filters( 'quick_edit_show_taxonomy', $tax_object->show_in_quick_edit, $taxonomy, $post['post_type'] ) ) {
                        unset( $data['tax_input'][ $taxonomy ] );
                    }
                }
            }

            // Hack: wp_unique_post_slug() doesn't work for drafts, so we will fake that our post is published.
            if ( ! empty( $data['post_name'] ) && in_array( $post['post_status'], array( 'draft', 'pending' ) ) ) {
                $post['post_status'] = 'publish';
                $data['post_name'] = wp_unique_post_slug( $data['post_name'], $post['ID'], $post['post_status'], $post['post_type'], $post['post_parent'] );
            }

            // Update the post.
            edit_post();

            $wp_list_table = _get_list_table( 'WP_Posts_List_Table', array( 'screen' => $_POST['screen'] ) );

            $level = 0;
            $request_post = array( get_post( $_POST['post_ID'] ) );
            $parent = $request_post[0]->post_parent;

            while ( $parent > 0 ) {
                $parent_post = get_post( $parent );
                $parent = $parent_post->post_parent;
                $level++;
            }

            $wp_list_table->display_rows( array( get_post( $_POST['post_ID'] ) ), $level );

            if( $_POST['post_type'] == 'YOUR-POST-TYPE' ) {
                ?>
                    <script type="text/javascript">
                        document.location.reload(true);
                    </script>
                <?php
            }

    wp_die();
}



Note: Not extensively tested, but so far working well

share|improve this answer

You can use the meta_box_cb parameter of the register_taxonomy function to define your own function for the meta_box. With the help of this article I have created this snippet:

function YOUR_TAXONOMY_NAME_meta_box($post, $meta_box_properties){
  $taxonomy = $meta_box_properties['args']['taxonomy'];
  $tax = get_taxonomy($taxonomy);
  $terms = get_terms($taxonomy, array('hide_empty' => 0));
  $name = 'tax_input[' . $taxonomy . ']';
  $postterms = get_the_terms( $post->ID, $taxonomy );
  $current = ($postterms ? array_pop($postterms) : false);
  $current = ($current ? $current->term_id : 0);
?>
<div id="taxonomy-<?php echo $taxonomy; ?>" class="categorydiv">
  <ul id="<?php echo $taxonomy; ?>-tabs" class="category-tabs">
    <li class="tabs"><a href="#<?php echo $taxonomy; ?>-all"><?php echo $tax->labels->all_items; ?></a></li>
  </ul>

  <div id="<?php echo $taxonomy; ?>-all" class="tabs-panel">
    <input name="tax_input[<?php echo $taxonomy; ?>][]" value="0" type="hidden">            
    <ul id="<?php echo $taxonomy; ?>checklist" data-wp-lists="list:symbol" class="categorychecklist form-no-clear">
<?php   foreach($terms as $term){
      $id = $taxonomy.'-'.$term->term_id;?>
      <li id="<?php echo $id?>"><label class="selectit"><input value="<?php echo $term->term_id; ?>" name="tax_input[<?php echo $taxonomy; ?>][]" id="in-<?php echo $id; ?>"<?php if( $current === (int)$term->term_id ){?> checked="checked"<?php } ?> type="radio"> <?php echo show_symbol( $term->name ); ?></label></li>
<?php   }?>
    </ul>
  </div>
</div>
<?php
}

To make use of this meta_box, you have to pass this parameter to the register_taxonomy function:

'meta_box_cb'                => 'YOUR_TAXONOMY_NAME_meta_box'

The beauty of this code is that you have to pass in no parameters at all, because it relies on the parameters passed to it by the register_taxonomy function. These are the post object and an array containing info on the metabox itself.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.