17

Is it possible within WooCommerce to change the variations dropdown into radio buttons without having to work with a plugin? I would like to have the following setup on the variations section:

  • 1 liter (10€)
  • 2 liter (20€)
  • 3 Liter (25€)

The price at the bottom should be automatically changed when you select an option.

Kirk Beard
  • 9,569
  • 12
  • 43
  • 47
Sam De Decker
  • 307
  • 1
  • 3
  • 12
  • 3
    Would be nice to see an answer on this that isn't "go acquire another plugin". – gan May 16 '17 at 02:07

4 Answers4

37

EDIT: added variation_check() and JS variation checking thanks to @ThomasB!

EDIT2: make sure variation_check() also checks for backorder status to allow selection when a product can be backordered.

The best way I managed to get this working is to add radio button markup directly after the select dropdowns and then hide the select dropdowns using CSS. You'll also need some custom JS to trigger the hidden select value changes so that your price will change according to the radio button you select.

Here's how I added the markup:

function variation_radio_buttons($html, $args) {
  $args = wp_parse_args(apply_filters('woocommerce_dropdown_variation_attribute_options_args', $args), array(
    'options'          => false,
    'attribute'        => false,
    'product'          => false,
    'selected'         => false,
    'name'             => '',
    'id'               => '',
    'class'            => '',
    'show_option_none' => __('Choose an option', 'woocommerce'),
  ));

  if(false === $args['selected'] && $args['attribute'] && $args['product'] instanceof WC_Product) {
    $selected_key     = 'attribute_'.sanitize_title($args['attribute']);
    $args['selected'] = isset($_REQUEST[$selected_key]) ? wc_clean(wp_unslash($_REQUEST[$selected_key])) : $args['product']->get_variation_default_attribute($args['attribute']);
  }

  $options               = $args['options'];
  $product               = $args['product'];
  $attribute             = $args['attribute'];
  $name                  = $args['name'] ? $args['name'] : 'attribute_'.sanitize_title($attribute);
  $id                    = $args['id'] ? $args['id'] : sanitize_title($attribute);
  $class                 = $args['class'];
  $show_option_none      = (bool)$args['show_option_none'];
  $show_option_none_text = $args['show_option_none'] ? $args['show_option_none'] : __('Choose an option', 'woocommerce');

  if(empty($options) && !empty($product) && !empty($attribute)) {
    $attributes = $product->get_variation_attributes();
    $options    = $attributes[$attribute];
  }

  $radios = '<div class="variation-radios">';

  if(!empty($options)) {
    if($product && taxonomy_exists($attribute)) {
      $terms = wc_get_product_terms($product->get_id(), $attribute, array(
        'fields' => 'all',
      ));

      foreach($terms as $term) {
        if(in_array($term->slug, $options, true)) {
          $id = $name.'-'.$term->slug;
          $radios .= '<input type="radio" id="'.esc_attr($id).'" name="'.esc_attr($name).'" value="'.esc_attr($term->slug).'" '.checked(sanitize_title($args['selected']), $term->slug, false).'><label for="'.esc_attr($id).'">'.esc_html(apply_filters('woocommerce_variation_option_name', $term->name)).'</label>';
        }
      }
    } else {
      foreach($options as $option) {
        $id = $name.'-'.$option;
        $checked    = sanitize_title($args['selected']) === $args['selected'] ? checked($args['selected'], sanitize_title($option), false) : checked($args['selected'], $option, false);
        $radios    .= '<input type="radio" id="'.esc_attr($id).'" name="'.esc_attr($name).'" value="'.esc_attr($option).'" id="'.sanitize_title($option).'" '.$checked.'><label for="'.esc_attr($id).'">'.esc_html(apply_filters('woocommerce_variation_option_name', $option)).'</label>';
      }
    }
  }

  $radios .= '</div>';
    
  return $html.$radios;
}
add_filter('woocommerce_dropdown_variation_attribute_options_html', 'variation_radio_buttons', 20, 2);

function variation_check($active, $variation) {
  if(!$variation->is_in_stock() && !$variation->backorders_allowed()) {
    return false;
  }
  return $active;
}
add_filter('woocommerce_variation_is_active', 'variation_check', 10, 2);

And here's the JS I used:

$(document).on('change', '.variation-radios input', function() {
  $('.variation-radios input:checked').each(function(index, element) {
    var $el = $(element);
    var thisName = $el.attr('name');
    var thisVal  = $el.attr('value');
    $('select[name="'+thisName+'"]').val(thisVal).trigger('change');
  });
});
$(document).on('woocommerce_update_variation_values', function() {
  $('.variation-radios input').each(function(index, element) {
    var $el = $(element);
    var thisName = $el.attr('name');
    var thisVal  = $el.attr('value');
    $el.removeAttr('disabled');
    if($('select[name="'+thisName+'"] option[value="'+thisVal+'"]').is(':disabled')) {
      $el.prop('disabled', true);
    }
  });
});
cfx
  • 3,311
  • 2
  • 35
  • 45
  • Hi cfx, Your code works perfect, but it adds radio buttons and don't remove default – Juraj Jul 17 '19 at 13:59
  • @Juraj I recommend that you hide the select element using CSS (it's part of my answer). – cfx Jul 17 '19 at 17:04
  • There is no way to remove it via hook? – Juraj Jul 17 '19 at 17:10
  • @Juraj removing it altogether isn't a good idea because a lot of WooCommerce Product logic depends on the presence of the ` – cfx Jul 17 '19 at 21:10
  • sadly doesn't work - if a variation is sold out, the radio will still be shown and active. – ThomasB Apr 13 '20 at 23:16
  • 1
    @ThomasB that doesn't mean that "it doesn't work," it's just been waiting for you to come along and help by providing a more ingenious, complete solution :)c – cfx Apr 14 '20 at 03:04
  • woooow @cfx your're the most positive person today. I admire you! :) <3 BTW: Still frigglin around and found no solution. Nearly about to go crazy or just keep ugly dropdowns. All those plugins... i dont like to use heavy weight plugins for simple stuff. so if you find a way, im totaly in to support the best way i can. – ThomasB Apr 14 '20 at 20:57
  • @ThomasB on my fresh Woo install, even with a dropdown, it still allows you to select the out of stock variation, but the ATC button is disabled. The result is similar with my solution and clicking on the OOS radio option keeps the ATC button disabled and even displays the OOS message. – cfx Apr 14 '20 at 21:19
  • @cfx your're absolutly right. But this just bad UX. Just because "it works" doenst mean "we should let the user use it". Just because the button is inactive after you've selcted a variation will leave you behind with question marks. I sadly found no solution to simply add special class "inactive" or something like that... – ThomasB Apr 14 '20 at 22:40
  • If you want to "greyish" and inactive the variation by default add this to your functions... pretty simple. function yp_variation_check( $active, $variation ) { if( ! $variation->is_in_stock() ) { return false; } return $active; } add_filter( 'woocommerce_variation_is_active', 'yp_variation_check', 10, 2 ); – ThomasB Apr 14 '20 at 22:42
  • 1
    @ThomasB heck yeah! That works well! I edited my solution to include that along with some JS to ensure the radio buttons are disabled as needed too! – cfx Apr 14 '20 at 23:18
  • 1
    @cfx OHBOYYY! That's why i love sof.... I told you, I admire you. Great solution now! I changed my code and will run this on production soon :) so much for love for you! – ThomasB Apr 14 '20 at 23:37
  • 1
    Thanks @ThomasB! It was a great collaboration. I did also just add one more tidbit in case the product can be backordered! :D – cfx Apr 15 '20 at 13:30
  • Awesome, thank you @cfx ! when I'm live i will add an tutorial to transform those bullets into color swatches without a plugin like yith. So we should have a nice collection over here afterwards. – ThomasB Apr 15 '20 at 14:46
  • Sorry @CFX I've broke it again :( I'n not fimiliar with JS thats my problem. I can work out the php and stylings. I've added an ID, because we need the label-function for the color-swatches, as we hide the radio input itself. Now its not working anymore jQuery(element).removeAttr('disabled'); /! jQuery(element).removeClass('disabled'); var thisName = $(element).attr('name'); ... jQuery(element).prop('disabled', true); /! jQuery(element).addClass('disabled', true); } and in functions – ThomasB Apr 16 '20 at 07:36
  • 1
    @ThomasB can you link to a gist with your complete code or post a new question? – cfx Apr 16 '20 at 12:57
  • https://codepen.io/thomasB88/pen/MWaKNpZ I hope thats enough.... – ThomasB Apr 16 '20 at 13:08
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/211823/discussion-between-cfx-and-thomasb). – cfx Apr 16 '20 at 17:59
  • plus 1 for not saying - use a plugin. – Jon Nov 04 '20 at 16:58
  • I added bit of JQuery to to remove property checked & remove inline css from the clear button ::: $("a.reset_variations").click(function(){ $('input:radio[name="attribute_size"]').prop('checked', false); $(this).css('display', ''); }); – John Montgomery Mar 03 '21 at 04:49
  • Is it possible to display only for particular product id. – Praveen Jun 06 '22 at 08:11
4

I don't know how to do that without a plugin, but I suggest you drop that requirement, and use the Woocommerce Radio Buttons plugin. This does exactly what you want:

Sajid anwar
  • 1,194
  • 14
  • 41
2

You can use Variation Swatches for WooCommerce plugin. It worked for me.

Kaizur
  • 181
  • 1
  • 10
0

I extended further the JavaScript part of cfx' answer to include cases of multiple variations. The idea is to hide and show available variations (radio buttons) based on the select inputs.

<script>
    jQuery(document).on('change', '.variation-radios input', function() {
      jQuery('.variation-radios input:checked').each(function(index, element) {
        let el = jQuery(element);
        jQuery('select[name="' + el.attr('name') + '"]').val(el.attr('value')).trigger('change');
        recreateRadioInputs();
      });
    });
    
    jQuery(document).on('woocommerce_update_variation_values', function() {
      jQuery('.variation-radios input').each(function(index, element) {
        let el = jQuery(element);
        el.removeAttr('disabled');
        if(jQuery('select[name="' + el.attr('name') + '"] option[value="' + el.attr('value') + '"]').is(':disabled')) {
          $el.prop('disabled', true);
        }
      });
    });
    
    // recreate readio inputs based on the select inputs
    function recreateRadioInputs() {
        jQuery('.variation-radios input, .variation-radios label').hide();
        jQuery('.variations select').each(function() {
            let inputName = jQuery(this).attr('name');
            jQuery(this).find('option').each(function() {
                let inputVal = jQuery(this).val();
                let radioInput = jQuery('.variation-radios input[value="' + inputVal + '"]');
                jQuery('.variation-radios label[for="' + radioInput.attr('id') + '"]').show();
                radioInput.show();
            });
        });
    }
</script>
Yasen
  • 3,400
  • 1
  • 27
  • 22