Development

How to Replace WordPress Default Taxonomy Metabox with a Radio Select Metabox

There have been many times we have wanted to use taxonomies as a 1-to-1 ratio with a post or custom post types. This is arguably more effective than using metadata because as Alex King pointed out, using a taxonomy query is more efficient than using a meta query, not to mention all the other cool things that are done by default with custom taxonomies (i.e. admin columns).

This snippet replaces the default taxonomy checkbox select metabox for hierarchical taxonomies with a radio select metabox, which effectively limits your taxonomy selection to one term. Just include this file from your functions.php or plugin file and update the parameters at the bottom.

Note: A radio select metabox will get out of hand quickly if more than a few terms are needed.

Update:  Helen Housandi was kind enough to look over our snippet and provide some feedback. Thanks to that, there are some pretty awesome improvements to the class.

Update 2: Code is now on Github. Forks/pull requests welcome.

<?php

if ( !class_exists( 'WDS_Taxonomy_Radio' ) ) {
   /**
    * Removes and replaces the built-in taxonomy metabox with our radio-select metabox.
    * @link  http://codex.wordpress.org/Function_Reference/add_meta_box#Parameters
    */
   class WDS_Taxonomy_Radio {

      // Post types where metabox should be replaced (defaults to all post_types associated with taxonomy)
      public $post_types = array();
      // Taxonomy slug
      public $slug = '';
      // Taxonomy object
      public $taxonomy = false;
      // New metabox title. Defaults to Taxonomy name
      public $metabox_title = '';
      // Metabox priority. (vertical placement)
      // 'high', 'core', 'default' or 'low'
      public $priority = 'high';
      // Metabox position. (column placement)
      // 'normal', 'advanced', or 'side'
      public $context = 'side';
      // Set to true to hide "None" option & force a term selection
      public $force_selection = false;


      /**
       * Initiates our metabox action
       * @param string $tax_slug      Taxonomy slug
       * @param array  $post_types    post-types to display custom metabox
       */
      public function __construct( $tax_slug, $post_types = array() ) {

         $this->slug = $tax_slug;
         $this->post_types = is_array( $post_types ) ? $post_types : array( $post_types );

         add_action( 'add_meta_boxes', array( $this, 'add_radio_box' ) );
      }

      /**
       * Removes and replaces the built-in taxonomy metabox with our own.
       */
      public function add_radio_box() {
         foreach ( $this->post_types() as $key => $cpt ) {
            // remove default category type metabox
            remove_meta_box( $this->slug .'div', $cpt, 'side' );
            // remove default tag type metabox
            remove_meta_box( 'tagsdiv-'.$this->slug, $cpt, 'side' );
            // add our custom radio box
            add_meta_box( $this->slug .'_radio', $this->metabox_title(), array( $this, 'radio_box' ), $cpt, $this->context, $this->priority );
         }
      }

      /**
       * Displays our taxonomy radio box metabox
       */
      public function radio_box() {

         // uses same noncename as default box so no save_post hook needed
         wp_nonce_field( 'taxonomy_'. $this->slug, 'taxonomy_noncename' );

         // get terms associated with this post
         $names = wp_get_object_terms( get_the_ID(), $this->slug );
         // get all terms in this taxonomy
         $terms = (array) get_terms( $this->slug, 'hide_empty=0' );
         // filter the ids out of the terms
         $existing = ( !is_wp_error( $names ) && !empty( $names ) )
            ? (array) wp_list_pluck( $names, 'term_id' )
            : array();
         // Check if taxonomy is hierarchical
         // Terms are saved differently between types
         $h = $this->taxonomy()->hierarchical;

         // default value
         $default_val = $h ? 0 : '';
         // input name
         $name = $h ? 'tax_input['. $this->slug .'][]' : 'tax_input['. $this->slug .']';

         echo '<div style="margin-bottom: 5px;">
         <ul id="'. $this->slug .'_taxradiolist" data-wp-lists="list:'. $this->slug .'_tax" class="categorychecklist form-no-clear">';

            // If 'category,' force a selection, or force_selection is true
            if ( $this->slug != 'category' && !$this->force_selection ) {
               // our radio for selecting none
               echo '<li id="'. $this->slug .'_tax-0"><label><input value="'. $default_val .'" type="radio" name="'. $name .'" id="in-'. $this->slug .'_tax-0" ';
               checked( empty( $existing ) );
               echo '> '. sprintf( __( 'No %s', 'wds' ), $this->taxonomy()->labels->singular_name ) .'</label></li>';
            }

         // loop our terms and check if they're associated with this post
         foreach ( $terms as $term ) {

            $val = $h ? $term->term_id : $term->slug;

            echo '<li id="'. $this->slug .'_tax-'. $term->term_id .'"><label><input value="'. $val .'" type="radio" name="'. $name .'" id="in-'. $this->slug .'_tax-'. $term->term_id .'" ';
            // if so, they get "checked"
            checked( !empty( $existing ) && in_array( $term->term_id, $existing ) );
            echo '> '. $term->name .'</label></li>';
         }
         echo '</ul></div>';

      }

      /**
       * Gets the taxonomy object from the slug
       * @return object Taxonomy object
       */
      public function taxonomy() {
         $this->taxonomy = $this->taxonomy ? $this->taxonomy : get_taxonomy( $this->slug );
         return $this->taxonomy;
      }

      /**
       * Gets the taxonomy's associated post_types
       * @return array Taxonomy's associated post_types
       */
      public function post_types() {
         $this->post_types = !empty( $this->post_types ) ? $this->post_types : $this->taxonomy()->object_type;
         return $this->post_types;
      }

      /**
       * Gets the metabox title from the taxonomy object's labels (or uses the passed in title)
       * @return string Metabox title
       */
      public function metabox_title() {
         $this->metabox_title = !empty( $this->metabox_title ) ? $this->metabox_title : $this->taxonomy()->labels->name;
         return $this->metabox_title;
      }


   }

   $custom_tax_mb = new WDS_Taxonomy_Radio( 'custom-tax-slug' );

   // Update optional properties

   // $custom_tax_mb->priority = 'low';
   // $custom_tax_mb->context = 'normal';
   // $custom_tax_mb->metabox_title = __( 'Custom Metabox Title', 'yourtheme' );
   // $custom_tax_mb->force_selection = true;

}

View or fork on github.

8 thoughts on “How to Replace WordPress Default Taxonomy Metabox with a Radio Select Metabox

  1. Your code within code.webdevstudios.com has a problem with:

    &&

    it converted the first ‘&’ into its entity number:

    &&

    Also, your code is incorrect here:


    public function add_radio_box() {
    foreach ( $this->post_types() as $key => $cpt ) {
    // remove default category type metabox
    remove_meta_box( $this->slug .'div', $cpt, 'core' );
    // remove default tag type metabox
    remove_meta_box( 'tagsdiv-'.$this->slug, $cpt, 'core' );
    // add our custom radio box
    add_meta_box( $this->slug .'_radio', $this->metabox_title(), array( $this, 'radio_box' ), $cpt, $this->context, $this->priority );
    }
    }

    it should be


    public function add_radio_box() {
    foreach ( $this->post_types() as $key => $cpt ) {
    // remove default category type metabox
    remove_meta_box( $this->slug .'div', $cpt, 'side' );
    // remove default tag type metabox
    remove_meta_box( 'tagsdiv-'.$this->slug, $cpt, 'side' );
    // add our custom radio box
    add_meta_box( $this->slug .'_radio', $this->metabox_title(), array( $this, 'radio_box' ), $cpt, $this->context, $this->priority );
    }
    }

    notice how I’ve changed the last variable on each remove_meta_box from ‘core’ to ‘side’

  2. Pretty cool!

    Personally tho, I’ve been using jquery added to admin_head to change the input type to radio buttons


    jQuery("#categorychecklist>li>label input").each(function(){
    this.type = "radio";
    });

    Specify the ID’s of each UL (separate ULs for each tab of a tax; All/Most Used) and its tag (input) for each list of tax terms that you want to change to radio buttons

  3. An alternative approach is to use javascript to replace the checkboxes with radios. Here’s an example (using JQuery)


    $('form#post #categorychecklist input[type=checkbox]').each(function(){
    var value = $(this).val();
    var id = $(this).attr('id');
    var name = $(this).attr('name');
    var checked = '';
    if( $(this).is(':checked') ){
    checked = ' checked="checked"';
    }
    $(this).replaceWith('');
    });

Have a comment?

Your email address will not be published. Required fields are marked *