Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spree Dropdown boxes for variant option values

I'm learning Spree 3.0 and I have a setup a test shop that sells shorts.

Shorts has multiple option types: Size, Color, Length

I wanted to change the way it displays the variant options on the frontend from a radio checkbox to a drop down box.

Currently, Spree displays the option types as radio buttons:

Current Spree setup

I want to change this to use drop down menus for each option type, like this:

What I want Spree to do

I've tried the following:

<%= select_tag "variant_id", options_for_select(@product.variants_and_option_values(current_currency).collect{ |v| ["#{variant_options(v)}  #{variant_price(v)}", v.id] })%>

But it simply displays the values of all option types in each tag:

Dropdown with all the option types 1

Dropdown with all the option types 2

I wanted to know the best way to split the option values into individual dropdown menus?

Any assistance is my much appreciated, thank you.

like image 976
Wasabi Developer Avatar asked Jan 20 '16 11:01

Wasabi Developer


2 Answers

This is not as easy as it looks since you will be using Spree::OptionValue records instead of variants and at some point you will want to convert back to variants in order to add it to your cart. Combinations might not be possible and/or out of stock so it is highly unpractical to work with option_values.

But nonetheless, you wanted to know how so i set up the following:

@options = Spree::OptionValue.joins(:option_value_variants).where(spree_option_value_variants: { variant_id: @product.variant_ids }).group_by(&:option_type)

This will give you a hash with the keys of the hash being option_types (Size, Color, Length in your case) and the values being arrays of option_values.

You can easily form this into radios like this:

<% @options.each do |option_type, option_values| %>
  <%= content_tag :h2, option_type.presentation %>
  <% option_values.each do |option_value| %>
    <%= radio_button_tag option_type.name, option_value.id %>
    <%= label_tag option_value.presentation %>
  <% end %>
<% end %>

Or for dropdowns:

<% @options.each do |option_type, option_values| %>
  <%= content_tag :h2, option_type.presentation %>
  <%= collection_select :variants, option_type.name, option_values, :id, :presentation %>
<% end %>

And in your controller you would want to find a variant matching those 3 criteria, check if it is in_stock, backorderable or track_inventory? is false and respond with errors or an updated cart :)

I hope this helped

like image 200
Fabian de Pabian Avatar answered Oct 28 '22 15:10

Fabian de Pabian


This is what I did to solve this problem. It basically takes the variant_id parameter that was controlled by the radio buttons and turns it into a hidden field controlled by jQuery and AJAX with additional notifications.

I hope this helps someone.

config/routes.rb

# Mount the core routes
Rails.application.routes.draw do 
  mount Spree::Core::Engine, at: '/'
end

# Create new routes
Spree::Core::Engine.routes.draw do 
  post "products/:product_id/get_variant",
       to: "products#toggle_like",
       as: "get_variant",
       constraints: { :format => /(js)/ }
end

app/models/spree/product_decorator.rb

Spree::Product.class_eval do

  # Find the Product's Variant from an array of OptionValue ids
  def find_variant_by_options(array)
    option_values = Spree::OptionValue.where(id: array)
    variants = []
    option_values.each do |option_value|
      variants.push(option_value.variants.ids)
    end
    self.variants.find_by(:id => variants.inject(:&).first)
  end
end

app/controllers/spree/products_controller_decorator.rb

Spree::ProductsController.class_eval do

  # Get the Variant from params[:ids], respond with JavaScript
  def get_variant
    @product = Spree::Product.find_by :slug => params[:product_id]
    @variant = @product.find_variant_by_options(params[:ids].split(','))

    respond_to do |format|
      format.js
    end
  end
end

app/views/spree/products/get_variant.js.erb

// Update hidden field #varient_id's value. 
$("#variant_id").val("<%= @variant.id %>") 
// Update price
$(".price.selling").html("<%= number_to_currency @variant.price %>");
<% if @variant.in_stock? && @variant.available? %> 
// If in stock and available
  $("#add-to-cart-button").prop("disabled", false); // Enable button
  $(".out-of-stock").hide(); // Hide 'out of stock' message
<% else %> 
// Otherwise
  $("#add-to-cart-button").prop("disabled", true); // Disable button
  $(".out-of-stock").show(); // Show 'out of stock' message
<% end %>

app/views/spree/products/_cart_form.html.erb

<%= form_for order, url: populates_orders_path do |f| %>
  ...
  <% if @product.variants_and_option_values(current_currency).any? %>
    <div id="product_variants" class="col-md-6">
      <h3 class="product-section-title"><%= Spree.t(:variants) %></h3>
      <% @product.option_types.each do |option_type| %>
        <%= f.label "option_type_#{option_type.id}", option_type.name %>
        <br>
        <%= f.select "option_type_value_#{option_type.id}",
                     option_type.option_values.all.collect { |v| [ v.name, v.id ] },
                     { include_blank: true },
                     { class: "form-control" } %>
        <br>
      <% end %>
      <%= hidden_field_tag "variant_id", value: "0" %>
      ...
     </div>
  <% else %>
    <%= hidden_field_tag "variant_id", @product.master.id %>
  <% end %>
  ...
    <span class="price selling" 
          itemprop="price" 
          content="<%= @product.price_in(current_currency).amount.to_d %>">
      <%= display_price(@product) %>
    </span>
  ...
    <%= button_tag class: "btn btn-success",
                      id: "add-to-cart-button",
                disabled: @product.variants.any?,
                    type: :submit do %>
      <%= Spree.t(:add_to_cart) %>
    <% end %>
  ...
  <span class="out-of-stock" style="display: none;">
    <%= Spree.(:out_of_stock) %>
  </span>
<% end %>

<script>
  // Call AJAX if all options are selected, otherwise clean up.
  $("#product-variants select").change(function(){
    var option_value_ids = [];
    $("#product-variants select").each(function(){
      option_value_ids.push($(this).val());
    });
    if(option_value_ids.indexOf("") == -1){
      $.ajax({
        url: "<%= get_variant_path(@product) %>?ids=" + option_value_ids.join(','),
        method: "post"
      });
    }else{
      $("#variant_id").val("0");
      $("#add-to-cart-button").prop("disabled", true);
      $(".out-of-stock").hide();
    }
  });
</script>
like image 42
Okomikeruko Avatar answered Oct 28 '22 14:10

Okomikeruko