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:
I want to change this to use drop down menus for each option type, like this:
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:
I wanted to know the best way to split the option values into individual dropdown menus?
Any assistance is my much appreciated, thank you.
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
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>
Related
I have a dashboard with a date range, the user can search activy for the period he wants.
I have different models that I manage with the same Dasboard
For example I want to find:
the users registration in a date range
the sales in a date range
etc...
I have buttons that represent the models, on click they render the appropriated partial.
I can search for date range but it only works for the first partial sales in this case, if I click on the next button which is users the search redirect on the first partial sales
This is because the dashboard action show renders by default sales I would need something that renders the current active button...
Could you help please me to find how to do this?
below some code:
dashboard.rb
class Dashboard
attr_reader :date_from, :date_to
def initialize(params)
params ||= {}
#date_from = parsed_date(params[:date_from],Time.now.beginning_of_month.to_date.to_s)
#date_to = parsed_date(params[:date_to], (Date.today + 1).to_s)
end
def article_date_range
Article.where('created_at BETWEEN ? AND ?', #date_from, #date_to)
end
private
def parsed_date(date_string, default)
Date.parse(date_string)
rescue ArgumentError, TypeError
default
end
end
the show action in dashboards_controller.rb
def show
#button ||= set_button || :sales
#dashboard = Dashboard.new(params[:search])
#articles = #dashboard.article_date_range
#articles = #articles.order('created_at ASC')
end
private
def set_button
button = dashboard__params[:button]&.to_sym and [:sales, :users].include?(button) and button
end
this form and buttons are in the dashboard show.html.erb
<%= form_tag admin_dashboard_path, method: :get do %>
<%= text_field_tag "search[date_from]", #dashboard.date_from, class: "datepicker" %>
<%= text_field_tag "search[date_to]", #dashboard.date_to, class: "datepicker" %>
<%= submit_tag "Rechercher", class: "btn btn-main" %>
<% end %>
#the buttons that "redirect" to the appropriated partial
<% [:sales, :users, :articles].each do |button| %>
<li class="tabs list-inline-item <%= params[:button] == button.to_s ? "tabs__item--active" : "tabs__item--inactive" %>">
<%= link_to admin_dashboard_url(button: button) do %>
<%= button.capitalize %>
<% end %>
</li>
<% end %>
Update
The idea is that the user select a date range and then click on the buttons to see the activity in the selected date range for either articles sales and users (without selecting again the date range)
I think this is because, dashboard__params[:button] is always nil. Because in your search form there is no input field with name set to button. If you are thinking about <%= link_to admin_dashboard_url(button: button) do %>, it's outside the html form.
So method set_button is returning nil.
So the first line of your show action always returns :sales as this is the default value if set_button returns nil.
I would suggest to add an hidden input field in your search form. Then use javascript to prevent the default form submit behavior, set the value of that hidden input field with appropriate value (sales or user), then submit the form using javascript.
Update 1
show.html.erb
<%= form_tag admin_dashboard_path, method: :get do %>
<%= text_field_tag "search[date_from]", #dashboard.date_from, class: "datepicker" %>
<%= text_field_tag "search[date_to]", #dashboard.date_to, class: "datepicker" %>
<%= hidden_field_tag 'button', 'sales', id: 'button' %>
<%= submit_tag "Rechercher", class: "btn btn-main" %>
<% end %>
#the buttons that "redirect" to the appropriated partial
<% [:sales, :users, :articles].each do |button| %>
<li class="tabs list-inline-item <%= params[:button] == button.to_s ? "tabs__item--active" : "tabs__item--inactive" %>">
<%= link_to admin_dashboard_url(button: button) id: "#{button}_click" do %>
<%= button.capitalize %>
<% end %>
</li>
<% end %>
Now using javascript before submitting the search form, set the value of the hidden_filed.
Update 2
show.html.erb
<%= link_to '#', id: "#{button}_click", class: 'model_link' do %>
<%= button.capitalize %>
<% end %>
javascript (assuming you have jQuery)
$(document).ready(function() {
$('.model_link').click(function() {
var buttonName = $(this).attr('id').split('_')[0];
$('#button').val(buttonName);
$('form').submit();
});
});
I have been hitting my head against a brick wall so it is time to seek smarter people.
I am trying to create multiple records of one model using form_tag and fields_for. I have been following all the help/issues/guides I can find but it doesn't seem to work for me. I am wondering if it something that changed going to Rails 5 but more likely it is me.
Basically I want a new/create version of the task system listed at the bottom of the api page, similar to this guys puppy creator.
The "new" page loads fine with as many records as I like, so that part is ok but it doesn't seem to be creating a collection to send through, it is just overriding and thus sending through the last set of params so only creating one record.
What I have.
# routes
resources :container_returns
controller
# container returns controller
def new
#containers = Container.where(id: params[:container_ids])
#container_returns = []
#containers.each do |container|
#container_returns << ContainerReturn.new(
{
container_id: container.id,
quantity: container.amount,
uom: container.uom,
material_restriction_id: container.material_restriction_id
}
)
end
end
view
# new.html.erb
<%= form_tag container_returns_path, method: :post do %>
<% #container_returns.each do |container_return| %>
<%= fields_for 'returns[]', container_return, hidden_field_id: true do |cr| %>
<div class="field">
<%= cr.label :container_id %>
<%= cr.number_field :container_id %>
</div>
<div class="field">
<%= cr.label :material_restriction_id %>
<%= cr.number_field :material_restriction_id %>
</div>
<div class="field">
<%= cr.label :quantity %>
<%= cr.text_field :quantity %>
</div>
<div class="field">
<%= cr.label :uom %>
<%= cr.text_field :uom %>
</div>
<% end %>
<% end %>
<%= submit_tag "lots of returns" %>
<% end %>
which submits
# params submitted
Started POST "/container_returns" for 127.0.0.1 at 2018-10-19 11:00:48 +0200
Processing by ContainerReturnsController#create as HTML
Parameters: {
"utf8"=>"✓", "authenticity_token"=>[removed],
"returns"=>{"container_id"=>"405", "material_restriction_id"=>"", "quantity"=>"100.0", "uom"=>"kg"}, "commit"=>"lots of returns"
}
hopefully it is just something stupid that I missed.
UPDATE:
if I add an index to the form it now believes me that my objects are different and sends through all the params I need.
<% #container_returns.each_with_index do |container_return, index| %>
<%= fields_for 'returns[]', container_return, index: index do |cr| %>
[...]
as mentioned in the update, if I add an ID to the initial create it builds the correct array that I was expecting. What I also found was if I send through an index position that also works.
<% #container_returns.each_with_index do |container_return, index| %>
<%= fields_for 'returns[]', container_return, index: index do |cr| %>
[...]
gives me what I was expecting
Parameters: {
"returns"=>{"0"=>{"container_id"=>"400",...},
"1"=>{"container_id"=>"401",...},
etc.
},
"commit"=>"lots of returns"
}
I want site visitors to be able to view nearby shows within a radius that can be input via dropdown form. I have a view that displays nearby shows using the Geocoder gem:
<h3> Shows near <%= request.location.city %> </h3>
<%= form_for #nearby_shows.first do |f| %>
<p> Radius (in miles): <%= f.select(:radii, [10,20,30,40,50], {},
:style => "width:50px", :selected => f.object.radii, :onchange =>
"location.href = '#{shows_path}'") %> </p>
<% end %>
<ul class="users">
<% #nearby_shows.each do |nearby_show| %>
<li>
<%= link_to nearby_show.show_name, nearby_show %>
</li>
<% end %>
</ul>
Right now the selection doesn't affect anything, and the selection isn't remembered in the form when the page refreshes.
The model, show.rb contains:
attr_accessor :radii
And the shows controller contains:
def index
#shows = Show.all
#radii = 50
#nearby_venues = Venue.near("Boulder, CO",#radii,:select =>
"id").to_a
#nearby_shows = Show.where(show_venue: #nearby_venues)
end
In production, I'll be using request.location.city, but in development I'm just using "Boulder, CO" as an example.
How can I set #radii using the input select form? I am concerned that form_for will not permit me to change a variable for the list of entities #nearby_shows.
If you want a fast AJAX solution, here's what I would do
First, add an ID to your list so it's easy to manipulate
<ul id="my_id" class="users"></ul>
I really don't understand why you need that <%= form_for #nearby_shows.first %> for ? If I understand well, you just want to show a select, and update the list of nearby shows based on what the user selects ?
routes.rb
resource :shows do
get 'update_nearby', on: :collection, constraints: { format: 'js' }
end
# GET /shows/update_nearby, meant to be used only with AJAX
your_view.html.erb
<%= form_tag update_nearby_shows_path, remote: :true do |f| %>
<p> Radius (in miles): <%= select_tag(:radii, [10,20,30,40,50], {},
:style => "width:50px", :selected => #radii, :onchange =>
"location.href = '#{shows_path}'") %> </p>
<% end %>
<!-- On Submit, it will request a JS response
You can add some JS to submit the form everytime the select changes -->
Add some JS specific respone
your_controller.rb
def update_nearby
find_nearby_shows
end
private
def find_nearby_shows
#radii = params[:radii] ? params[:radii] : 50
#nearby_venues = Venue.near("Boulder, CO",#radii,:select =>
"id").to_a
#nearby_shows = Show.where(show_venue: #nearby_venues)
end
update_nearby.js.erb
<% if #nearby_shows %>
// Empty the list
var id = $("#my_id").empty()
<% #nearby_shows.each do %>
// Add one <li> per show
$("<li>", { 'html': "<%= escape_javascript(link_to(nearby_show.show_name, nearby_show)) %>"}).appendTo(id)
<% end %>
<% end %>
Bonus : you said you wanted to save the radius ? You can actually try to add it to the user session
def index
...
#radii = session[:saved_radii] ? session[:saved_radii] : DEFAULT_RADIUS
...
end
def update_nearby
find_nearby_shows
session[:saved_radii] = #radii
end
But if you really want to save it for the user, you should have a preferred_radii field in your User model
I am trying to create a checklist in rails using form_for. This checklist is taken from a table which I gained in the create action of my sign_ins controller:
#content = OrientationContent.where(site_id: session[:site_id])
In my view I want to use the form_for helper to iterate through the list in #content:
<%= form_for(:sign_ups ) do |f| %>
<% #content.each do |c| %>
<%= f.check_box nil %> <%= c %> <br>
<% end %>
<% end %>
However this is not working and it produces two square brackets on the page: [].
How do I go through the list and print the name while creating a check box on the left of it? The check box does not have any meaning or data, I just need it present for reference.
Solved:
In the controller, need to pluck an individual field:
#content = OrientationContent.where(site_id: 1).pluck(:content)
In the view, structure as so:
<%= form_for(:sign_ups) do |f| %>
<% #content.each do |c| %>
<%= f.check_box nil %> <%= c %> <br>
<% end %>
<% end %>
Everything is posting correctly, but I do not see any labels in my checkboxes, just blanks. My form looks like this:
<%= form_for #itemrecord do |f| %>
<div class="col-xs-12">
<p><b>Items people are asking for</b></p>
</div>
<% #wishlist.each do |category, list| %>
<div class="col-xs-2">
<div class="form-group box">
<h5> <%="#{category}"%> </h5>
<% list.each do |thing| %>
<%= check_box_tag ":item_name[]", "#{thing}" %>
</br>
<% end %>
</div>
</div>
<% end %>
<%= f.submit "Go!", class: "btn btn-primary btn-large btn-block" %>
</div>
<% end %>
What's happening is that wishlist is a hash of categories and items within those categories that I set in the controller, and is then called by multiple form builders to build checkboxes. The challenge is that right now in the current implementation, the checkboxes params are passed through properly (FYI controller code is at the bottom), but beside each checkbox, there is no text that shows the thing (i.e., no label so that people know what they're checking.
Here's the html generated for one checkbox (it's the same for all checkboxes)
Basically, I need to make the value the label.
FYI what's happening is that for every item checked, a record is being created. Here's the controller code:
def create
items_to_be_saved = []
inventory_params.each do |i|
items_to_be_saved << ({ :signup_id => Signup.find_by_email(session[:signup_email]).id, :item_name => i })
end
if Inventory.create items_to_be_saved
flash[:success] = "Thanks!"
redirect_to root_path
else
render new_inventory_path
end
end
def inventory_params
params.require(":item_name")
end
In your code:
<%= check_box_tag ":item_name[]", "#{thing}" %>
Second parameter for check_box_tag is not a label value, it is just a value which goes to controller in parameters. If you wan't to display label within your checkbox you will need to call label_tag in your view:
= label_tag ':item_name[]', thing
= check_box_tag ':item_name[]'
But you definitely should check simple_form gem which allows you to render checkboxes in much cleaner way:
f.input :field, as: :boolean, inline_label: 'Label'
can you please try it and let me know
<%= check_box_tag 'item_name[]', thing %>