I'm trying to create an inventory form that sorts the products based on their category. Ideally I'll be able to have these products listed by category, and will put in some javascript to hide/show products of a given category.
Currently, I'm having trouble wrapping my head around how to split my products up by category in my form. Here's what I currently have (modeled after Ryan Bates' Railscasts for nested attributes):
class InventoriesController < ApplicationController
def new
#title = "Take Inventory"
#vendor = ProductVendor.find(params[:product_vendor_id])
#inventory = Inventory.new()
#products = #vendor.products.active
#products.each do |product|
#inventory_line_item = #inventory.inventory_line_items.build({:product_id => product.id})
end
end
My form for a new inventory:
<%= form_for #inventory do |f| %>
<table>
<tr>
<th>Item</th>
<th>Quantity</th>
</tr>
<% f.fields_for :inventory_line_items do |builder| %>
<tr>
<td><%= builder.object.product.name %></td>
<td><%= builder.text_field(:quantity, :size => 3) %></td>
<%= builder.hidden_field :product_id %>
</tr>
<% end %>
</table>
<%= f.hidden_field(:user_id, :value => current_user.id) %>
<%= f.hidden_field(:location_id, :value => current_location.id) %>
<%= f.hidden_field(:product_vendor_id, :value => #vendor.id) %>
<%= f.submit "Submit" %>
<% end %>
So I have another model called product_category that has_many products. How do I sort and separate my products by category in the controller and form? Also, is there a way to do this using Formtastic? Thanks!
It's actually pretty simple to implement. Add a hidden field in the nest_form field with the attribute position (add this attr to your model of course) and add this to your js along with using the jquery-ui.js
$('#task_fields').sortable({
items: '.fields',
dropOnEmpty: true,
cursor: 'move',
handle: '.move',
opacity: 0.4,
scroll: true,
axis:'y',
placeholder: 'ui-state-highlight',
start: function(e, ui) {
ui.placeholder.height(ui.item.height());
},
update: function() {
$(this).children(".fields").each(function(index) {
$(this).children('input.task_position').val(index + 1);
});
}
});
here's what I have in my _form.html.haml
= f.fields_for :tasks do |t|
= t.text_field :name
= t.hidden_field :position, :class => :task_position
= t.collection_select :account_id, Account.all, :id, :full_name, :prompt => 'Assign task to...'
.button-group
%span{:class => "button icon no-text move pill"}
= t.link_to_remove "", :class => "button icon no-text remove pill"
= f.link_to_add "Add a Task", :tasks, :class => "button icon add"
This worked really well.
Related
I have the following form:
<div class='panel' id='panel-advanced-search'>
<%= form_tag contacts_path, method: :get do %>
<div>
<div class='advanced_search'>
<div>
<%= label_tag "Contact Type" %>
<%= select_tag(:contact_type, options_for_select(Contact::TYPES, selected: params[:contact_type]), prompt: 'Any', class: 'customSelect') %>
</div>
<div>
<%= label_tag "Prospect Strength" %>
<%= select_tag(:prospect_strength, options_for_select(Contact::PROSPECT_STRENGTHS, selected: params[:prospect_strength]), prompt: 'Any', class: 'customSelect') %>
</div>
<div>
<%= label_tag "Customer" %>
<%= collection_select(:customer_account_id, :customer_account_id, Customer.order(:name), :id, :name, { prompt: '' }, class: 'customSelect select2') %>
</div>
<div>
<%= label_tag "Supplier" %>
<%= collection_select(:supplier_account_id, :supplier_account_id, Supplier.order(:name), :id, :name, { prompt: '' }, class: 'customSelect select2') %>
</div>
<div>
<%= label_tag "Company Name" %>
<%= text_field_tag :company_name %>
</div>
<div>
<%= label_tag "Name" %>
<%= text_field_tag :name %>
</div>
<div>
<%= label_tag "Job Title" %>
<%= text_field_tag :title %>
</div>
<div>
<%= label_tag "Postcode" %>
<%= text_field_tag :postcode %>
</div>
<div>
<%= label_tag :created_at, 'Created From' %>
<div class="date-picker">
<%= text_field_tag :created_at, nil, class: 'date-picker-select' %>
<span class="date-picker-btn">
<span class="icon-calendar" aria-hidden="true"></span>
</span>
</div>
</div>
<div>
<%= label_tag :updated_at, 'Created To' %>
<div class="date-picker">
<%= text_field_tag :updated_at, nil, class: 'date-picker-select' %>
<span class="date-picker-btn">
<span class="icon-calendar" aria-hidden="true"></span>
</span>
</div>
</div>
<div>
<%= label_tag "Tags" %>
<%= collection_select(:tag_list, :tag_list, #tags.order(:name), :name, :name, {}, { class: 'select2', multiple: true }) %>
</div>
<div>
<%= label_tag "Assignee" %>
<%= collection_select(:assigned_to, :assigned_to, User.all, :id, :name, { prompt: 'Any' }, class: 'customSelect select2') %>
</div>
<div>
<%= label_tag "Obsolete?" %>
<%= select_tag(:obsolete, options_for_select(['Obsolete', 'All'], selected: params[:obsolete]), prompt: 'Not obsolete?', class: 'customSelect') %>
</div>
<div>
<%= label_tag "Send Results To" %>
<%= select_tag(:subsequent_action, options_for_select([
['Report', 'report'],
['CSV Export', 'csv_export'],
['New Event', 'new_event']
]), prompt: 'None', class: 'customSelect') %>
</div>
</div>
<div class="advanced_search_btns">
<%= submit_tag submit_text %>
<%= link_to secondary_btn, contacts_path, class: 'btn-medium' %>
</div>
</div>
<% end %>
</div>
and the following method in the model
def self.advanced_search
Contact.where('
contact_type LIKE :search OR
prospect_strength LIKE :search OR
customer_account_id LIKE :search OR
supplier_account_id LIKE :search OR
company_name LIKE :search OR
name LIKE :search OR
title LIKE :search OR
postcode LIKE :search OR
created_at LIKE :search OR
updated_at LIKE :search OR
tag_list LIKE :search OR
assigned_to LIKE :search OR
obsolete LIKE :search
', search: "%#{search}%"
)
end
How do I go about using this method so the user can use this search form with multiple params? I already have the following in the index method for a basic search so I need to have both search forms
def index
#per_page = params[:per_page] || 20
#tags = ActsAsTaggableOn::Tag.all
if params[:search].present?
#contacts = Contact.search(params[:qs], params[:search]).order(sort_column + ' ' + sort_direction).paginate(page: params[:page], per_page: #per_page)
else
#contacts = Contact.all.order(sort_column + ' ' + sort_direction).paginate(page: params[:page], per_page: #per_page)
end
end
Edit Updated the form above to the complete form, ideally, I would like the forms functionality to be entirely in the model.
Edit #2
This is the basic search:
model
QUICK_SEARCH_FIELDS = {
name: {
column_names: 'contacts.name'
},
customers: {
joins_table: :customer,
column_names: 'contacts.name',
filters: { contact_type: 'customer' }
},
suppliers: {
joins_table: :supplier,
column_names: 'contacts.name',
filters: { contact_type: 'supplier' }
},
tags: {
tagged_with: true
}
}.with_indifferent_access
def self.search(field, query)
field = QUICK_SEARCH_FIELDS[field]
contact = all
contact = contact.joins(field[:joins_table]) if field[:joins_table]
contact = contact.where(field[:filters]) if field[:filters]
contact = contact.where("#{field[:column_names]} LIKE ?", "%#{query}%") if field[:column_names]
contact = contact.tagged_with(query) if field[:tagged_with]
contact
end
form
<div class='panel' id='panel-search'>
<%= form_tag contacts_path, method: :get do %>
<div class='l-inline-row-block'>
<div class='l-inline-col width_120px'>
<%= select_tag(:qs, options_for_select(Contact::QUICK_SEARCH_FIELDS.keys(), selected: params[:qs]), class: 'customSelect') %>
</div>
<div class='l-inline-col'>
<%= search_field_tag :search, params[:search] %>
</div>
<div class='l-inline-col' style='width: 100px;'>
<%= submit_tag submit_text %>
</div>
</div>
<% end %>
</div>
you may need to make view adjustments and you cannot just use the search term/query for all fields:
1.make sure the fields inside the form in your view are all the same names of the columns in the Contact model that need to be searched
2.add a hidden field inside each form so you can determine which search this is (there is another option but this one is less work)
3.change the collection_select where user can only select one field to select_tag in order to not mess up query building
every field that is filled will pass a value- you can build a hash including column/form-field entered and their value e.g { contact_type: "value1", prospect_strength: "value2" }
after that, you would need to build a query using that hash and query the DB
this would look like so:
In your view:
add this in the advanced search form
<%= hidden_field_tag :search_type, :advanced %>
and in your normal search add this
<%= hidden_field_tag :search_type, :normal %>
In your model:
def self.advanced_search(values_by_column_name) # values_by_column_name is a hash passed from the controller
tag_list = values_by_column_name.delete("tag_list")
sql_query = values_by_column_name.keys.map { |column| "#{column} LIKE :#{column}" }
sql_query = sql_query.join(" OR ")
values_by_column_name = values_by_column_name.transform_values { |value| "%#{value}%" }.symbolize_keys
relation = Contact.where(sql_query, values_by_column_name)
relation = relation.tagged_with(tag_list) if tag_list
relation
end
in your controller:
ADVANCED_SEARCH_FIELDS = [
:contact_type,
:prospect_strength,
:customer_account_id,
:supplier_account_id,
:company_name,
:name,
:title,
:postcode,
:created_at,
:updated_at,
{tag_list: []},
:assigned_to,
:obsolete,
]
def index
#per_page = params[:per_page] || 20
#tags = ActsAsTaggableOn::Tag.all
if advanced_search?
#contacts = Contact.advanced_search(advanced_search_params)
elsif normal_search?
#contacts = Contact.search(params[:qs], params[:search])
else
#contacts = Contact.all
end
#contacts = #contacts.order(sort_column + ' ' + sort_direction).paginate(page: params[:page], per_page: #per_page)
end
def advanced_search?
params[:search_type] == "advanced" && advanced_search_params.any?
end
def normal_search?
params[:search_type] == "normal" && params[:search].present?
end
def advanced_search_params
#_advanced_search_params ||= params.permit(ADVANCED_SEARCH_FIELDS).select { |_, v| v.present? }.to_h
end
I got the following code, which I want when people choose the OPTIONS, then show the Add an option button
<%= f.fields_for :questions do |question_form| %>
<%= question_form.text_field :question_text %>
<%= question_form.select :question_type, [ 'TEXT', 'OPTIONS', 'UPLOAD' ], :prompt => 'Select One', :id => "my_id", :onchange => "myFunction()" %>
<%= question_form.link_to_remove "Remove this Question" %>
<%= question_form.fields_for :options do |option_form| %>
<%= option_form.text_field :option_text %>
<%= option_form.link_to_remove "Remove this option" %>
<% end %>
<p id = "test" hidden><%= question_form.link_to_add "Add a option", :options %></p>
<script>
function myFunction(){
var x = document.getElementById("my_id").value;
if (x == "OPTIONS") {
document.getElementById("test").hidden = false;
}
}
</script>
<% end %>
First of all, you can't send a value from Rails to Js during Js execution. In that moment you are already in the browser context unless you ask something via Ajax to your server.
In this case everything can and has to be done in your browser context. Just change this line:
var x = document.getElementById("my_id").value
For:
var e = document.getElementById("my_id");
var x = e.options[e.selectedIndex].value;
This should do the trick.
I'm attempting to a create a simple form that allows me to run a new search on the existing 'results' page using simple form.
The exact same code works fine when performing the search from another (landing) page. However, when I try to insert it into the results page I'm getting
undefined method 'vtype' for #
<Venue::ActiveRecord_Relation:0x007fd6860eb730>
vtype is both a table column and also the name of the param i'm passing to search on.
The simple_form looks like this:
<%= simple_form_for :results, html: { class: "form-inline justify-content-center" } do |f| %>
<%= f.error_notification %>
<%= f.select :vtype, options_for_select([['Happy Hours Anywhere','Anywhere'],['After Work Drinks','After Work Drinks'],['Somewhere to Relax with Friends', 'Relaxing with Friends'], ['A Club Night','Club Night'], ['Somewhere for Date Night','Date Night'], ['A Place to Watch Sports', 'Watching Sports']]),{} ,:class => "form-control select-box font-lightweight" %>
starting
<%= f.select :date, options_for_select(dates_for_search_select.each_with_index.map{|d, i| [d[1],d[0]]}), {}, :class => "form-control select-box font-lightweight" %>
at
<%= f.select :time, options_for_select(times_for_search_select.each_with_index.map{|d, i| [d[1],d[0]]}), {}, :class => "form-control select-box font-lightweight" %>
</h4>
<%= f.button :submit, 'Discover', :class => 'btn btn-block btn-danger btn-embossed top-margin ' %>
<%end%>
My controller looks like this:
def results
if params.has_key?(:results)
##results = Venue.joins(:offers).where("offers.offertype = '2-4-1'")
#finddate = (params[:results][:date]).to_date
#findtime = (params[:results][:time]).to_time
#resultsdate = DateTime.new(#finddate.year,#finddate.month,#finddate.day,#findtime.hour)
#results = Venue.joins(:offers).where(["venues.vtype = ? and offers.start >= ?", params[:results][:vtype], #resultsdate])
else
#results = Venue.joins(:offers).where(["offers.start = ?", Date.today])
end
end
What's wrong with this form running on the 'results' page?
I am using filterrific gem in my rails app to filter the result, i have events to be filtered based on the options
my code is as follows
event.rb
filterrific :default_filter_params => { :sorted_by => 'created_at_desc' },
:available_filters => %w[
sorted_by
search_query
with_category_id
with_area
]
# default for will_paginate
self.per_page = 9
scope :with_category_id, lambda { |category_ids|
where(category_id: [*category_ids])
}
events_controller.rb
def index
#filterrific = initialize_filterrific(
Event,
params[:filterrific],
:select_options => {
sorted_by: Event.options_for_sorted_by,
with_category_id: Category.options_for_select
}
) or return
#events = #filterrific.find.paginate(page: params[:page], per_page: 6).where(is_job: false)
respond_to do |format|
format.html
format.js
end
end
index.html.erb
<%= form_for_filterrific #filterrific, :class => 'col s12' do |f| %>
<%= render_filterrific_spinner %>
<%= f.hidden_field :source , :value => 'event' %>
<div class="col s12 m3 ">
<div class="filter-container">
<li>
<%= f.select(
:with_category_id,
#filterrific.select_options[:with_category_id],
{ include_blank: ' Any ' },
class: 'select-filter'
) %>
<label>select category</label>
</li>
</div>
<%= link_to(
'Reset filters',
reset_filterrific_url,
) %>
</div>
<% end %>
<%= render(
partial: 'events/list',
) %>
index.js.erb
<% js = escape_javascript(
render(partial: 'events/list')
) %>
$("#filterrific_results").html("<%= js %>");
$('#filterrific_results').load(document.URL + ' #filterrific_results');
_list.html.erb
<div id="filterrific_results">
<% #events.each do |event| %>
<%= link_to event.name, company_event_path(event.company.id, event.id) %>
<% end %>
</div>
so what i am doing here is i filtered the events based on their category with a select box
but now i want to add the feature in the select box that the select box must contain the checkbox elements so that i can filter multiple category result, so that when i check on one category it will filter the result by that single category and when i click another category it will filter the result with that category and previous category checked
Would be a great help to fix this , Thankx in advance
you can use multiple: true, like this:
<%= f.select(
:with_category_id,
#filterrific.select_options[:with_category_id],
{ include_blank: ' Any ' },
{ multiple: true, class: 'select-filter'}
) %>
I have a parent - child relationship between Repairs & RepairItems.
A repair must have a least 1 RepairItem to be saved. I've made a nested (simple) form to create a Repair and display 3 blank repair_items.
I'm trying to work out how to make sure that a a Repair has at least 1 repair_item entered to allow the user to save the Repair. Otherwise I need to prompt the user that the repair can't be saved until at least 1 repair_item is entered..
Can anyone point me in the right direction for validation so that a user can't save a Repair without any Repair items entered? Thanks
class Repair < ActiveRecord::Base
attr_accessible :repair_id, :repairer_id, :fault_num, :vehicle_id, :date_reported, :date_closed, :hours_open, :mileage_open, :reported_to, :reported_by,
:repair_items_attributes
belongs_to :vehicle
belongs_to :repairer
has_many :repair_items, :dependent => :destroy
validates_presence_of :vehicle_id
validates_associated :repair_items
accepts_nested_attributes_for :repair_items, :reject_if => lambda { |a| a[:repair_type_id].blank? }, :allow_destroy => true
end
class RepairItem < ActiveRecord::Base
attr_accessible :repair_id, :problem, :solution, :repair_type_id, :priority, :repairer_id, :invoice, :cost, :tax,
:item_state_id, :mileage_closed, :hours_closed, :date_closed
belongs_to :repair
belongs_to :repairer
belongs_to :repair_type
belongs_to :item_state
#validates_presence_of :repair_id
validates_presence_of :repair_type_id
scope :open, where(:item_state_id => 1)
scope :monitor, where(:item_state_id=> 2)
scope :deferred, where(:item_state_id => 3)
scope :closed, where(:item_state_id => 4)
scope :cancelled, where(:item_state_id => 5)
end
class RepairsController < ApplicationController
before_filter :authorise
layout :resolve_layout
def index
#status = 1
#repairItems = RepairItem.open
end
def monitor
#status = 2
#repairItems = RepairItem.monitor
end
def deferred
#status = 3
#repairItems = RepairItem.deferred
end
def closed
#status = 4
#repairItems = RepairItem.closed
end
def cancelled
#status = 5
#repairItems = RepairItem.cancelled
end
def new
#repair = Repair.new
3.times { #repair.repair_items.build }
end
def create
# Instantiate a new object using form parameters
#repair = Repair.new(params[:repair])
# Save the object
if #repair.save
# If the save suceeds, redirect to the list action
redirect_to(repairs_path, :notice => 'Repair Created.')
else
# If the save fails, redisplay the form so user can fix problems
render :action => :new
end
end
<%= simple_form_for( #repair, :defaults => { :disabled => #current_user.read_only, :input_html => { :class => "span10" } }) do |f| %>
<fieldset>
<!-- This will display some text in red at the top of the form telling the user -->
<%= f.error_notification %>
<div class="st-row-fluid">
<div class="span2">
<%= f.association :vehicle, label_method: :fleet_num, value_method: :id, include_blank: true, label: 'Vehicle'%>
<p>Current Kms</p>
<p>Current Hours</p>
<p class="muted">Warranty Expires</p>
<p class="muted">Contract Maintenance</p>
</div>
<div class="span2">
<%= f.input :date_reported, :as => :date_picker, :input_html => { :class => "span10 st-datepicker"} %>
<%= f.input :mileage_open, :label => "Km/Miles" %>
<%= f.input :hours_open %>
</div>
<div class="span2">
<%= f.input :fault_num %>
<%= f.input :reported_to %>
<%= f.input :reported_by %>
</div>
</div>
<div class="row-fluid">
<h4> Items</h4>
<%= f.simple_fields_for :repair_items do |p| %>
<table class="table table-condensed">
<tr>
<%= render "repair_items", :p => p %>
</tr>
</table>
<% end %>
</div>
<%= f.error :base %>
<div class="st-form-actions">
<% if #current_user.read_only == false %>
<%= f.submit nil, :class => 'btn btn-success pull-right' %>
<% end %>
<%= link_to 'Cancel', repairs_path, :class => 'btn btn-danger pull-right' %>
</div>
</fieldset>
<% end %>
partial
<td><%= p.association :repair_type, label_method: :repair_type_label, value_method: :id, include_blank: true, label: 'Repair Type'%></td>
<td><%= p.input :problem %></td>
<td><%= p.input :solution %></td>
<td><%= p.input :priority %></td>
<td><%= p.association :repairer, label_method: :rep_name, value_method: :id, include_blank: true, label: 'Repairer'%></td>
</tr>
<tr>
<td><%= p.input :invoice %></td>
<td><%= p.input :cost %></td>
<td><%= p.input :tax %></td>
<td><%= p.input :date_closed, :as => :date_picker, :input_html => { :class => "span10 st-datepicker"} %></td>
<td><%= p.input :mileage_closed, :label => "Km/Miles" %></td>
<td><%= p.input :hours_closed %></td>
<td><%= p.association :item_state, label_method: :state_label, value_method: :id, label: 'Status', :default => 1 %></td>
Use validates_associated :repair_items in the repair to validate the repair items. The action has to construct the repair items using repair.RepairItem.build, so that the items will be associated with the repair, before attempting to save the repair. The repair is constructed before the repair items, but the repair_items will block the associated repair from saving.