It is a description of creating the following advanced search form of Mercari. It is not a summary, but a detailed notice.
I hope it will serve as a reference for those who have encountered similar issues.
▼ Sort
▼ Category
▼ Price range, check box
▼ Clear button
Please refer to Github and the following articles for explanation and usage of ransack.
Github - ransack Summary of various search form creation methods using [Rails] ransack
As shown in the above figure, since the search form is placed in the header, it is necessary to set the query (@q) in before_action on most controllers.
Otherwise, the following error (No Ransack :: Search object was provided to search_form_for!
) Will occur.
It is necessary to describe the index action in Qiita mentioned above.
items_controller.rb
class ItemsController < ApplicationController
def index
@q = Item.ransack(params[:q])
#Defined here@Without q, search_form_The above error occurs in for
@items = @q.result(distinct: true)
end
def search
@q = Item.search(search_params)
@items = @q.result(distinct: true)
end
private
def search_params
params.require(:q).permit!
end
end
Initially, each controller defined a private method and did before_action.
tops_controller.rb
class TopsController < ApplicationController
skip_before_action :authenticate_user!
before_action :set_item_search_query
def index
@items = Item.including.limit(5).desc.trading_not
end
private
def set_item_search_query
@q = Student.ransack(params[:q])
@items = @q.result(distinct: true)
end
end
However, with this method, it is necessary to define a private method of set_item_search_query
in all the controllers that call ** the page that has the search form (= display the header) **, which violates the DRY principle **. (The description is troublesome above all).
In conclusion, I defined a method in ʻapplication_controller.rb` and called it on each required controller.
application_controller.rb
class ApplicationController < ActionController::Base
~~
def set_item_search_query
@q = Item.ransack(params[:q])
@items = @q.result(distinct: true)
end
~~
end
tops_controller.rb
class TopsController < ApplicationController
skip_before_action :authenticate_user!
before_action :set_item_search_query #Add this description
def index
@items = Item.including.limit(5).desc.trading_not
end
#The following description is deleted
# private
# def set_item_search_query
# @q = Student.ransack(params[:q])
# @items = @q.result(distinct: true)
# end
end
The set_item_search_query
method could be called from another controller regardless of whether it was defined above or below private in ʻapplication_controller.rb`.
I'm wondering what this means because private is originally meant to define methods that I don't want to be called by other controllers.
I had a hard time sorting in the order of the most "favorites!" Associated with Items (the order of the most related child models). Other than that, the following sorting was relatively easy to implement because it simply sorts the columns of the Item model by order.
--Lowest price --In descending order of price --New order of listing --Oldest order of listing
The view and controller are as follows. Simply put, it fires an event when the pull-down is changed in js and passes the selected value to the controller as a sort parameter.
ruby:search.html.haml
.sc-side__sort
%select{name: :sort_order, class: 'sort-order .sc-side__sort__select'}
%option{value: "location.pathname", name: "location.pathname"}
sort
%option{value: "price-asc"}
Price in ascending order
%option{value: "price-desc"}
Highest price
%option{value: "created_at-asc"}
Listings oldest
%option{value: "created_at-desc"}
Newest order of listing
%option{value: "likes-desc"}
favorite!In descending order
items_controller.rb
class ItemsController < ApplicationController
skip_before_action :authenticate_user!, except: [:new, :edit, :create, :update, :destroy]
before_action :set_item_search_query, expect: [:search]
~~
def search
sort = params[:sort] || "created_at DESC"
@q = Item.includes(:images).(search_params)
@items = @q.result(distinct: true).order(sort)
end
~~
end
item_search.js
//Sorting behavior
$(function() {
//Event occurs by selecting the pull-down menu
$('select[name=sort_order]').change(function() {
//Get the value attribute of the selected option tag
const sort_order = $(this).val();
//Branch of page transition destination depending on the value of value attribute
switch (sort_order) {
case 'price-asc': html = "&sort=price+asc"; break;
case 'price-desc': html = "&sort=price+desc"; break;
case 'created_at-asc': html = "&sort=created_at+asc"; break;
case 'created_at-desc': html = "&sort=created_at+desc"; break;
case 'likes-desc': html = "&sort=likes_count_desc"; break;
default: html = "&sort=created_at+desc";
}
//Current display page
let current_html = window.location.href;
//Duplicate sorting function
if (location['href'].match(/&sort=*.+/) != null) {
var remove = location['href'].match(/&sort=*.+/)[0]
current_html = current_html.replace(remove, '')
};
//Page transition
window.location.href = current_html + html
});
//Behavior after page transition
$(function () {
if (location['href'].match(/&sort=*.+/) != null) {
// option[selected: 'selected']Delete
if ($('select option[selected=selected]')) {
$('select option:first').prop('selected', false);
}
const selected_option = location['href'].match(/&sort=*.+/)[0].replace('&sort=', '');
switch (selected_option) {
case "price+asc": var sort = 1; break;
case "price+desc": var sort = 2; break;
case "created_at+asc": var sort = 3; break;
case "created_at+desc": var sort = 4; break;
case "likes_count_desc": var sort = 5; break;
default: var sort = 0
}
const add_selected = $('select[name=sort_order]').children()[sort]
$(add_selected).attr('selected', true)
}
});
});
I thought that I couldn't pass the code that expresses the order of the most child models to the parameters passed to the controller, so I decided to make a conditional branch on the controller side.
items_controller.rb
class ItemsController < ApplicationController
~~
def search
@q = Item.includes(:images).search(search_params)
sort = params[:sort] || "created_at DESC"
#The parameter that flew from js"likes_count_desc"In the case of, the description to sort in descending order of child models
if sort == "likes_count_desc"
@items = @q.result(distinct: true).select('items.*', 'count(likes.id) AS likes')
.left_joins(:likes)
.group('items.id')
.order('likes DESC').order('created_at DESC')
else
@items = @q.result(distinct: true).order(sort)
end
end
~~
end
There seems to be a better description ... I would like to know who understands it.
↑ Like this, I want to realize the function that the lower limit and the upper limit enter the columns respectively when the price range is selected.
However, the property that Item has is price (: integer type)
, so it seems that it cannot be used.
Create something that can handle hash data in the same way as a model (ActiveRecord) with ʻactive_hash`.
I didn't think "use active_hash!" From the beginning,
-Require an array that can be passed to the helper method of ʻoptions_from_collection_for_select` --Moreover, it is necessary to change the values passed to min and max according to the selected content. --Troublesome to write this in js ――Can you use the model? But it's a hassle to make a model and even a table
I thought that I used active_hash in the elimination method.
Click here for articles that refer to active_hash ⇒ active_hash summary
Specifically, I created a model called price_range
(which can be treated as) and displayed it in the view.
** ① Create a file manually in app / models and fill in the following **
price_range.rb
class PriceRange < ActiveHash::Base
self.data = [
{id: 1, name: '¥300〜¥1,000', min: 300, max: 1000},
{id: 2, name: '¥1,000〜¥5,000', min: 1000, max: 5000},
{id: 3, name: '¥5,000〜¥10,000', min: 5000, max: 10000},
{id: 4, name: '¥10,000〜¥30,000', min: 10000, max: 30000},
{id: 5, name: '¥30,000〜¥50,000', min: 30000, max: 50000},
]
end
belongs_to_active_hash
is omitted.** ② Create array data with view file and display with ʻoptions_from_collection_for_select` **
ruby:search.html.haml
~~
#Price search criteria
.sc-side__detail__field
%h5.sc-side__detail__field__label
%i.fas.fa-search-dollar
= f.label :price, 'price'
.sc-side__detail__field__form
# PriceRange.all, active_Data defined by hash(hash)As an array
#It, options_from_collection_for_Expand with select helper method and pull down
= select_tag :price_range, options_from_collection_for_select(PriceRange.all, :id, :name), { prompt: "Please select"}
.sc-side__detail__field__form.price-range
= f.number_field :price_gteq, placeholder: "¥ Min", class: 'price-range__input'
.price-range__swang 〜
= f.number_field :price_lteq, placeholder: "¥ Max", class: 'price-range__input'
~~
I feel that it is not the original usage of active_hash. .. ..
Among the properties of the Item model (product), the options are as follows. Most of the options are active_hash to create a model (what can be treated as).
① Product status (status_id) → Model with active_hash? Create ② Burden of shipping charges (delivery_charge_flag) → Describe options directly in the view ③ Sales status (trading_status_id) → Model with active_hash? Create ④Category (category_id) → categories There is a table
Everything except ① was in trouble.
status.rb
# active_is hash
class Status < ActiveHash::Base
self.data = [
{id: 1, name: 'New, unused'},
{id: 2, name: 'Nearly unused'},
{id: 3, name: 'No noticeable scratches or stains'},
{id: 4, name: 'Slightly scratched and dirty'},
{id: 5, name: 'There are scratches and dirt'},
{id: 6, name: 'Overall poor condition'},
]
end
item.rb
class Item < ApplicationRecord
~~
belongs_to_active_hash :status
~~
ruby:search.html.haml
~~
#Search conditions for product status
.sc-side__detail__field
%h5.sc-side__detail__field__label
%i.fas.fa-star
= f.label :status_id_eq, 'Product condition'
.sc-side__detail__field__form.checkbox-list
.sc-side__detail__field__form--checkbox.js_search_checkbox-all
.sc-side__detail__field__form--checkbox__btn
%input{type: 'checkbox', id: 'status_all', class: 'js-checkbox-all'}
.sc-side__detail__field__form--checkbox__label
= label_tag :status_all, 'all'
= f.collection_check_boxes :status_id_in, Status.all, :id, :name, include_hidden: false do |b|
.sc-side__detail__field__form--checkbox
.sc-side__detail__field__form--checkbox__btn.js_search_checkbox
= b.check_box
.sc-side__detail__field__form--checkbox__label
= b.label { b.text}
~~
With this description, you can create a group of check boxes including "all".
With this description, the check boxes are displayed as they are, and the check boxes are displayed on the screen after the selection and search.
(Because the search condition is held as a parameter in the @ q
instance and jumps to the controller, and the @ q
returned after the search contains that parameter)
The troublesome factor is that in the form at the time of product listing (item / new), the burden of shipping fee (whether it is the seller's burden or the purchaser's burden) is simply writing the option in the view.
ruby:views/items/_form.html.haml
~~
.field__form.item-area__field__form
= f.select :delivery_charge_flag, [['postage included(Exhibitor burden)', '1'], ['Cash on delivery(Buyer burden)', '2']], include_blank: 'Please select', required: :true
~~
Moreover, the js that changes the delivery method is described separately according to the flag (1 or 2), so if you change it significantly, the range of influence will be large and an unexpected error will occur.
In other words, it seems that you cannot use active_hash in the same way as ** ① **.
This was unexpectedly troublesome.
Initially, trading_status was designed below.
trading_status.rb
class TradingStatus < ActiveHash::Base
self.data = [
{id: 1, name: 'Sale'},
{id: 2, name: 'During trading'},
{id: 3, name: 'Sold'},
{id: 4, name: 'draft'}
]
end
As far as I can see, it seems simple that "on sale" has an id of 1 and "sold out" has an id of 3, but that is not the case. "Trading (id: 2)" is also included in ** "Sold out" **. (Not for sale)
In other words, ** the array obtained by active_hash cannot be used as a search condition as it is **.
When I tried to change this status, I didn't have time to fix it without leaking it because the influence was wide here as well. (The first design should have been better)
This was surprisingly simple.
When defining data with active_hash, if you use flag
instead of ʻid`, you can use active_hash in the same way as ①.
delivery_charge.rb
class DeliveryCharge < ActiveHash::Base
self.data = [
{flag: 1, name: 'postage included(Exhibitor burden)'},
{flag: 2, name: 'Cash on delivery(Buyer burden)'}
]
end
This is the feeling of pushing with the controller. So it's really hard to understand.
If you itemize what you did
--On the search condition screen, create an array (@trading_status) to display the choices as check boxes → This is just an instance of ** to display the search condition
--Once you perform a normal search for ransack (at this time, "sales status" is not a search condition) ... ʻA --Branch the processing to be performed when "Sales status" is specified as a search condition (when
params.require (: q) [: trading_status_id_in]is true) --Redefine
@ qbecause it is necessary to return the" sales status "specified as the search condition on the screen after the search. --Define and use private methods for redefinition --Processing is branched when the number of "sales status" specified as the search condition is one → When there are two, both "on sale" and "sold out" are specified = "sales status" is the search condition Will not be --When "Sold out" is specified, buyer_id gets items of not nil ...
B --Extract only the items that are common to ʻA
and B
and define them in @ items
--In other words, the @items
searched by the search conditions other than" sales status "is compared with the sold_items
which is "sold out", and only the matching items are acquired.
――Do the same thing for "on sale" (= buyer_id is nil)
items_controller.rb
~~
def search
#Define an instance to be an array to display the search condition of "Sales Status"
#id 1 and 3=On sale, sold
@trading_status = TradingStatus.find [1,3]
#Once, perform a normal search for ransack (at this time, "sales status" is not a search condition)
sort = params[:sort] || "created_at DESC"
@q = Item.not_draft.search(search_params)
@items = @q.result(distinct: true).order(sort)
~~
#When the sales status is in the search conditions
if trading_status_key = params.require(:q)[:trading_status_id_in]
#Return to the screen after searching@Redefinition of q
@q = Item.includes(:images).search(search_params_for_trading_status)
#One specified sales condition (If two are specified, both on sale and sold out are specified = not specified)
#And processing when the specified key is 3 = "sold out"
if trading_status_key.count == 1 && trading_status_key == ["3"]
#Get sold out items → In my team, buyer in the items table_It was defined that id has a value = sold out
sold_items = Item.where.not(buyer_id: nil)
#upper@items were searched by the search conditions received by ransack@items
#And sold_Extracting matching items with items → That is&
@items = @items & sold_items
elsif trading_status_key.count == 1 && trading_status_key == ["1"]
#Get Items for sale → In my team, buyer in the items table_It was defined that id is nil = on sale
selling_items = Item.where(buyer_id: nil)
@items = @items & selling_items
end
end
private
#When "Sales Status" is not specified as a search condition@q → ransack is searching with this search condition
def search_params
params.require(:q).permit(
:name_or_explanation_cont,
:category_id,
:price_gteq,
:price_lteq,
category_id_in: [],
status_id_in: [],
delivery_charge_flag_in: [],
)
end
#When "Sales Status" is specified as a search condition@q
def search_params_for_trading_status
params.require(:q).permit(
:name_or_explanation_cont,
:category_id,
:price_gteq,
:price_lteq,
category_id_in: [],
status_id_in: [],
delivery_charge_flag_in: [],
trading_status_id_in: [],
)
end
end
~~
The part that has become quite complicated. It was a scene that made me realize that the first design was important. I want to find a better way than this!
Serious? The remaining issues are
--@items
is not[]
--@items = @items & selling_items
or @items = @items & sold_items
returns []
(= that is, there is no corresponding item)
Under the condition, the bullet reacts.
ʻAVOID (Avoid!), So it seems that the N + 1 problem has not actually occurred, but ... When I chewed the English sentence, I wondered if it was "avoid it because eager loading was detected where it was not needed". However, if you remove
.includes (: images) as specified, you will get an alert of ʻUSE (use!)
For other patterns as you might expect.
I gave up once because the conditional branch did not work here.
The categories are divided into parents, children and grandchildren. The items table has only category_id, and the category_id selected here is the id of the grandchild category.
On the other hand, as a search method, it is overwhelmingly convenient to be able to search by "when only the parent category is selected" or "when even the child category is selected". How to achieve this? Is an issue.
If you subdivide the issues further
① I want to make the grandchild category a check box instead of pull-down
(2) The conditions of "when only the parent category is selected" and "when the child category is selected" cannot be returned properly in the search condition field after the search.
③ If you use a form that receives only category_id_in
, you cannot search by parent category / child category.
④ If you submit the form with only the parent category selected, the parameters will be skipped with category_id
blank.
Many ...
Only one grandchild category can be selected (pull-down), not ↓
I want to be able to select multiple grandchild categories (check box) ↓
For example, if you specify and search like this, ↓ After the search, the condition field will be in a state where nothing is specified (the same applies when even child categories are selected).
category_id_in
, you cannot search by parent category / child category.The description of the first view was like this, but in this case the category did not work as a search condition unless the grandchild category was selected.
ruby:search.html.haml
%li
= f.select :category_id_in ,options_for_select(@parents, @item.category.root.name),{include_blank: "Please select"}, {id: 'parent_category', required: :true}
%li
= f.select :category_id_in ,options_for_select(@category_child_array.map{|c|[c[:name], c[:id]]}, @item.category.parent.id),{include_blank: "Please select"}, {id: 'children_category', required: :true}
%li
= f.select :category_id_in ,options_for_select(@category_grandchild_array.map{|c|[c[:name], c[:id]]}, @item.category.id),{include_blank: "Please select"}, {id: 'grandchildren_category', required: :true}
Only the grandchild category can be searched by the category_id_in
received by the parameter, so even if only the parent category is selected in this state, there is no corresponding product. (Even if you select only "Men's", you cannot search for products whose parent category is Men's.)
category_id
blank.As a countermeasure for (3), the properties of the parent category and child category were set to category_id
(the reason will be described later), but if the search is executed with the child category not selected, category_id
will be skipped in a blank state.
It is a push with js and the controller.
Resolve the view and js as follows.
ruby:search.html.haml
.sc-side__detail__field
%h5.sc-side__detail__field__label
%i.fas.fa-list-ul
= f.label :category_id, 'Select a category'
.sc-side__detail__field__form
%ul.field__input--category_search
- if @search_category.present?
%li
= f.select :category_id, options_for_select(@search_parents, @search_category.root.name),{include_blank: "Please select"}, {id: 'parent_category_search'}
%li
- if @category_child.present?
= f.select :category_id, options_for_select(@category_child_array, @category_child.id),{include_blank: "Please select"}, {id: 'children_category_search'}
- else
= f.select :category_id, @category_child_array, {include_blank: "Please select"}, {id: 'children_category_search'}
- if @category_grandchild_array.present?
%li#grandchildren_category_checkboxes.checkbox-list
.sc-side__detail__field__form--checkbox.js_search_checkbox-all
.sc-side__detail__field__form--checkbox__btn
%input{type: 'checkbox', id: 'grandchildren_category_all', class: 'js-checkbox-all'}
.sc-side__detail__field__form--checkbox__label
= label_tag :grandchildren_category_all, 'all'
= f.collection_check_boxes :category_id_in, @category_grandchild_array, :id, :name, include_hidden: false do |b|
.sc-side__detail__field__form--checkbox
.sc-side__detail__field__form--checkbox__btn.js_search_checkbox
= b.check_box
.sc-side__detail__field__form--checkbox__label
= b.label { b.text}
- else
%li
= f.select :category_id, @search_parents, {include_blank: "Please select"}, {id: 'parent_category_search'}
item_search.js
//Category behavior in search form
function appendOption(category){
let html = `<option value="${category.id}" >${category.name}</option>`;
return html;
}
function appendCheckbox(category){
let html =`
<div class="sc-side__detail__field__form--checkbox">
<div class="sc-side__detail__field__form--checkbox__btn js_search_checkbox">
<input type="checkbox" value="${category.id}" name="q[category_id_in][]" id="q_category_id_in_${category.id}" >
</div>
<div class="sc-side__detail__field__form--checkbox__label">
<label for="q_category_id_in_${category.id}">${category.name}</label>
</div>
</div>
`
return html;
}
//Create display of child categories
function appendChildrenBox(insertHTML){
const childSelectHtml = `<li>
<select id="children_category_search" name="q[category_id]">
<option value="">Please select</option>
${insertHTML}
</select>
</li>`;
$('.field__input--category_search').append(childSelectHtml);
}
//Create display of grandchild category
function appendGrandchildrenBox(insertHTML){
const grandchildSelectHtml =`
<li id="grandchildren_category_checkboxes" class="checkbox-list">
<div class="sc-side__detail__field__form--checkbox js_search_checkbox-all">
<div class="sc-side__detail__field__form--checkbox__btn">
<input class="js-checkbox-all" id="grandchildren_category_all" type="checkbox">
</div>
<div class="sc-side__detail__field__form--checkbox__label">
<label for="grandchildren_category_all">all</label>
</div>
</div>
${insertHTML}
</li>`;
$('.field__input--category_search').append(grandchildSelectHtml);
}
//Events after selecting the parent category
$('#parent_category_search').on('change', function(){
//Get the name of the selected parent category
const parentName =$(this).val();
if (parentName != ""){
//Make sure the parent category is not the default
$.ajax({
url: '/items/category_children',
type: 'GET',
data: { parent_name: parentName },
dataType: 'json'
})
.done(function(children){
//When the parent changes, delete the child and below
$('#children_category_search').remove();
$('#grandchildren_category_checkboxes').remove();
let insertHTML = '';
children.forEach(function(child){
insertHTML += appendOption(child);
});
appendChildrenBox(insertHTML);
})
.fail(function(){
alert('Failed to get the category');
})
}else{
//When the parent category becomes the initial value, delete the child and below
$('#children_category_search').remove();
$('#grandchildren_category_checkboxes').remove();
}
});
//Event after selecting child category
$('.field__input--category_search').on('change', '#children_category_search', function(){
const childId = $(this).val();
//Get id of selected child category
if (childId != ""){
//Make sure the child category is not the default
$.ajax({
url: '/items/category_grandchildren',
type: 'GET',
data: { child_id: childId},
dataType: 'json'
})
.done(function(grandchildren){
if (grandchildren.length != 0) {
//When a child changes, delete the grandchildren and below
$('#grandchildren_category_checkboxes').remove();
let insertHTML = '';
grandchildren.forEach(function(grandchild){
insertHTML += appendCheckbox(grandchild);
});
appendGrandchildrenBox(insertHTML);
}
})
.fail(function(){
alert('Failed to get the category');
})
}else{
$('#grandchildren_category_checkboxes').remove();
}
});
Set the property name of the form of the parent category and child category to category_id
and process it with the controller.
The view is above.
items_controller.rb
def search
@trading_status = TradingStatus.find [1,3]
@keyword = params.require(:q)[:name_or_explanation_cont]
@search_parents = Category.where(ancestry: nil).where.not(name: "Category list").pluck(:name)
sort = params[:sort] || "created_at DESC"
@q = Item.not_draft.search(search_params)
if sort == "likes_count_desc"
@items = @q.result(distinct: true).select('items.*', 'count(likes.id) AS likes')
.left_joins(:likes)
.group('items.id')
.order('likes DESC')
.desc
else
@items = @q.result(distinct: true).order(sort)
end
#When the sales status is in the search conditions
if trading_status_key = params.require(:q)[:trading_status_id_in]
@q = Item.including.search(search_params_for_trading_status)
if trading_status_key.count == 1 && trading_status_key == ["3"]
sold_items = Item.where.not(buyer_id: nil)
@items = @items & sold_items
elsif trading_status_key.count == 1 && trading_status_key == ["1"]
selling_items = Item.where(buyer_id: nil)
@items = @items & selling_items
end
end
#When the category is in the search criteria
if category_key = params.require(:q)[:category_id]
if category_key.to_i == 0
@search_category = Category.find_by(name: category_key, ancestry: nil)
else
@search_category = Category.find(category_key)
end
if @search_category.present?
if @search_category.ancestry.nil?
#Parent category
@category_child_array = Category.where(ancestry: @search_category.id).pluck(:name, :id)
grandchildren_id = @search_category.indirect_ids.sort
find_category_item(grandchildren_id)
elsif @search_category.ancestry.exclude?("/")
#Child category
@category_child = @search_category
@category_child_array = @search_category.siblings.pluck(:name, :id)
@category_grandchild_array = @search_category.children
grandchildren_id = @search_category.child_ids
find_category_item(grandchildren_id)
end
#Pick up grandchild categories with ransack → category_id_in
end
end
@items = Kaminari.paginate_array(@items).page(params[:page]).per(20)
end
category_id
is blank.In js, when the child category is blank (unselected) when executing the search, it corresponds to the process of removing the pull-down of the child category
item_search.js
$('#detail_search_btn').click(function(e) {
if ($('#children_category_search').val() == "") {
$('#children_category_search').remove();
}
});
Like 4-3, the bullet sometimes reacts ...
Also, if you execute a search (= submit a form) with only the parent category selected (the child category is not selected), the category_id parameter will be passed to the controller blank. This time I took the method of deleting the child category f.select with js, but there seems to be a better way.
--When you press "All", select all the corresponding options. ――If even one of the applicable options is unchecked, remove "All" as well. --Check "All" when all options other than "All" are selected. --On the screen after searching, if all options other than "All" are selected, check "All" as well.
I was addicted to how to realize the unexpectedly complicated requirement. ↓ Behavior like this
Implemented with js.
There are three main types of processing. ** ① Behavior when "All" is clicked ** ** ② Behavior when clicking other than "All" ** ** ③ Function to judge whether to check the check box "All" when loading the page **
If you do not write the process of ③, execute the search with all the options selected in the search conditions → The "All" check box will be unchecked from the condition column after the search, which is awkward.
Click here for the I feel that the description is redundant ... I want to refactor it a little more, so I'm looking for a good way to write it. At first, I made a button to initialize (= reset) the form with Rails helper method.
Initialize all values in [Rails] form with one click (helper method definition + JavaScript) However, with this method, the intended movement cannot be performed on the screen after the search conditions have been specified and the search has been executed. What this means is that if you perform a search with the keyword "margiela", for example, this screen will appear when you perform the search.
It is easy to understand that the initial value is the value at the timing when the screen is loaded, so the initial value of the keyword in this state is "margiela".
In other words, even if you reset (= initialize the form) in this state, "margiela" in the keyword will not disappear. Since the initial value is "margiela" (rewrite the keyword to "yohji yamamoto" and perform "initialization" to return to "margiela"). That's not what we want to achieve, but the move to return all values to blanks ("select" for pull-downs). Implemented with js.
Since it would be redundant if the description runs processing for each item, I decided to search for child elements of the form. I feel like there is a better way. .. I will seek. I don't understand Rails well, so I think there is a more royal way to do it.
If there is something like "This way of writing is more beautiful!", I would be grateful if you could let me know.
Recommended Posts
code. </ summary>
item_search.js
$(function(){
const min_price = $('#q_price_gteq');
const max_price = $('#q_price_lteq');
let grandchild_category_all_checkbox = $('#grandchildren_category_all');
let grandchild_category_checkboxes = $('input[name="q[category_id_in][]"]');
const status_all_checkbox = $('#status_all');
const status_checkboxes = $('input[name="q[status_id_in][]"]')
const delivery_charge_all_checkbox = $('#delivery_charge_flag_all')
const delivery_charge_checkboxes = $('input[name="q[delivery_charge_flag_in][]"]')
const trading_status_all_checkbox = $('#trading_status_all')
const trading_status_checkboxes = $('input[name="q[trading_status_id_in][]"]')
//① Behavior when clicking "All"
$(document).on('change', '.js-checkbox-all', function() {
function targetCheckboxesChage(target, trigger) {
if (trigger.prop("checked") == true) {
target.prop("checked", true);
} else {
target.prop("checked", false);
}
}
let target_checkboxes;
switch ($(this).prop('id')) {
case $('#grandchildren_category_all').prop('id'):
target_checkboxes = $('input[name="q[category_id_in][]"]');
break;
case status_all_checkbox.prop('id'):
target_checkboxes = status_checkboxes;
break;
case delivery_charge_all_checkbox.prop('id'):
target_checkboxes = delivery_charge_checkboxes;
break;
case trading_status_all_checkbox.prop('id'):
target_checkboxes = trading_status_checkboxes;
break;
default: ;
}
targetCheckboxesChage(target_checkboxes, $(this));
});
//② Behavior when clicking other than "All"
$(document).on('change', '.js_search_checkbox > input:checkbox', function() {
function allCheckboxChange(target, all_checkbox, trigger) {
if (trigger.prop("checked") == false) {
all_checkbox.prop("checked", false);
} else {
let flag = true
target.each(function(e) {
if (target.eq(e).prop("checked") == false) {
flag = false;
}
});
if (flag) {
all_checkbox.prop("checked", true);
}
}
}
let all_checkbox;
grandchild_category_all_checkbox = $('#grandchildren_category_all');
grandchild_category_checkboxes = $('input[name="q[category_id_in][]"]');
switch ($(this).prop('name')) {
case grandchild_category_checkboxes.prop('name'):
target_checkboxes = grandchild_category_checkboxes;
all_checkbox = grandchild_category_all_checkbox;
break;
case status_checkboxes.prop('name'):
target_checkboxes = status_checkboxes;
all_checkbox = status_all_checkbox;
break;
case delivery_charge_checkboxes.prop('name'):
target_checkboxes = delivery_charge_checkboxes;
all_checkbox = delivery_charge_all_checkbox;
break;
case trading_status_checkboxes.prop('name'):
target_checkboxes = trading_status_checkboxes;
all_checkbox = trading_status_all_checkbox;
break;
default: ;
}
allCheckboxChange(target_checkboxes, all_checkbox, $(this));
});
//Function that determines whether to check the check box "All" when loading a page
function loadCheckboxSlection(target, all_checkbox) {
let flag = true;
target.each(function(e) {
if (target.eq(e).prop("checked") == false) {
flag = false;
}
});
if (flag) {
all_checkbox.prop("checked", true);
}
}
//③ When the page is loaded, run a function that determines whether to check the check box "All".
if ($('#item_search_form').length) {
loadCheckboxSlection(grandchild_category_checkboxes ,grandchild_category_all_checkbox)
loadCheckboxSlection(status_checkboxes, status_all_checkbox)
loadCheckboxSlection(delivery_charge_checkboxes, delivery_charge_all_checkbox)
loadCheckboxSlection(trading_status_checkboxes, trading_status_all_checkbox)
if (min_price.val() != "" && max_price.val() != "") {
$.ajax({
url: '/items/price_range',
type: 'GET',
data: { min: min_price.val(), max: max_price.val()},
dataType: 'json'
})
.done(function(range) {
if (range) {
$('#price_range').val(range.id);
}
})
.fail(function() {
alert('Failed to get the price range')
})
}
}
});
6-3. Remaining tasks
7. Clear button for search conditions
7-1. Challenge: Is there no choice but to implement "reset" with js? The description is likely to be redundant
7-2. Countermeasures
item_search.js
//Operation when the clear button is pressed
$(function () {
$("#js_conditions_clear").on("click", function () {
clearForm(this.form);
});
function clearForm (form) {
$(form)
.find("input, select, textarea")
.not(":button, :submit, :reset, :hidden")
.val("")
.prop("checked", false)
.prop("selected", false)
;
$('select[name=sort_order]').children().first().attr('selected', true);
$('#children_category_search').remove();
$('#grandchildren_category_checkboxes').remove();
}
});
7-3. Remaining tasks
in conclusion