Ceci est une description de la création d'un formulaire de recherche avancée comme celui ci-dessous. Ce n'est pas un résumé, mais un avis détaillé.
J'espère que cela aidera ceux qui ont rencontré des problèmes similaires.
▼ Trier
▼ Catégorie
▼ Gamme de prix, case à cocher
▼ Bouton Effacer
Veuillez vous référer à Github et aux articles suivants pour obtenir des explications et l'utilisation de ransack.
Github - ransack Résumé des diverses méthodes de création de formulaires de recherche à l'aide de [Rails] ransack
Comme le montre la figure ci-dessus, puisque le formulaire de recherche est placé dans l'en-tête, il est nécessaire de définir la requête (@q) dans before_action sur la plupart des contrôleurs.
Sinon, l'erreur suivante (Aucun objet Ransack :: Search n'a été fourni à search_form_for!
) Se produira.
Il est nécessaire de décrire l'action d'index dans Qiita mentionnée ci-dessus.
items_controller.rb
class ItemsController < ApplicationController
def index
@q = Item.ransack(params[:q])
#Défini ici@Sans q, recherchez_form_L'erreur ci-dessus se produit dans pour
@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
Au départ, chaque contrôleur a défini une méthode privée et a fait 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
Cependant, avec cette méthode, il est nécessaire de définir une méthode privée de set_item_search_query
dans ** tous les contrôleurs qui appellent la page avec le formulaire de recherche (= afficher l'en-tête) **, ce qui viole le principe DRY **. (La description est avant tout gênante).
En conclusion, j'ai défini une méthode dans ʻapplication_controller.rb` et l'ai appelée sur chaque contrôleur requis.
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 #Ajoutez cette description
def index
@items = Item.including.limit(5).desc.trading_not
end
#La description suivante est supprimée
# private
# def set_item_search_query
# @q = Student.ransack(params[:q])
# @items = @q.result(distinct: true)
# end
end
La méthode set_item_search_query
peut être appelée depuis un autre contrôleur indépendamment du fait qu'elle ait été définie au-dessus ou en dessous de private dans ʻapplication_controller.rb`.
Je me demande ce que cela signifie parce que privé sert essentiellement à définir des méthodes que je ne veux pas être appelées par d'autres contrôleurs.
J'ai eu du mal à trier dans l'ordre des plus "favoris!" Associés aux éléments (l'ordre des modèles enfants les plus liés). En dehors de cela, le tri suivant était relativement facile à mettre en œuvre car il trie simplement les colonnes du modèle Item par ordre.
--Prix le plus bas
La vue et le contrôleur sont les suivants. En termes simples, il déclenche un événement lorsque le menu déroulant est modifié en js et transmet la valeur sélectionnée au contrôleur en tant que paramètre de tri.
<détails> Je pensais que je ne pouvais pas passer le code qui exprime l'ordre du plus grand nombre de modèles enfants aux paramètres passés au contrôleur, alors j'ai décidé de créer une branche conditionnelle côté contrôleur. Il semble y avoir une meilleure description ... J'aimerais savoir qui la comprend. ↑ Comme ça, je veux réaliser la fonction que la limite inférieure et la limite supérieure entrent respectivement dans les colonnes lorsque la gamme de prix est sélectionnée.
Cependant, la propriété de Item est Créez quelque chose qui peut gérer les données de hachage de la même manière qu'un modèle (ActiveRecord) avec ʻactive_hash`. Je ne pensais pas "utiliser active_hash!" Depuis le début, --Require un tableau qui peut être passé à la méthode d'assistance de ʻoptions_from_collection_for_select` Je pensais avoir utilisé active_hash dans la méthode d'élimination. Cliquez ici pour les articles qui font référence à active_hash ⇒ active_hash summary Plus précisément, j'ai créé un modèle appelé «price_range» (qui peut être traité comme) et je l'ai affiché dans la vue. ** ① Créez un fichier manuellement dans app / models et remplissez ce qui suit ** ** ② Créez des données de tableau avec le fichier de vue et affichez-les avec ʻoptions_from_collection_for_select` ** Je pense que ce n'est pas l'usage original de active_hash. .. .. Parmi les propriétés du modèle Item (produit), les options sont les suivantes.
La plupart des options sont active_hash pour créer un modèle (ce qui peut être traité comme). ① Statut du produit (status_id) → modèle avec active_hash? Créer
② Charge des frais d'expédition (delivery_charge_flag) → Décrivez les options directement dans la vue
③ Statut des ventes (trading_status_id) → Modèle avec active_hash? Créer
④ Category (category_id) → categories Il y a un tableau Tout sauf ① était en difficulté. Avec cette description, vous pouvez créer un groupe de cases à cocher comprenant "tous".
Avec cette description, les choix sont affichés sous forme de cases à cocher et les cases à cocher s'affichent telles qu'elles sont à l'écran après la sélection et la recherche.
(Parce que la condition de recherche est conservée en tant que paramètre dans l'instance Le facteur gênant est que dans la forme au moment de la mise en vente de l'article (article / nouveau), la charge des frais d'expédition (que ce soit la charge du vendeur ou celle de l'acheteur) est simplement inscrite dans la vue. De plus, puisque le js qui modifie la méthode de livraison est décrit séparément en fonction du drapeau (1 ou 2), si vous le changez de manière significative, la plage d'influence sera grande et une erreur inattendue se produira. En d'autres termes, il semble que vous ne pouvez pas utiliser active_hash de la même manière que ** ① **. C'était étonnamment gênant. Initialement, trading_status a été conçu ci-dessous. Pour autant que je sache, il semble simple que "en vente" a un identifiant de 1 et "épuisé" a un identifiant de 3, mais ce n'est pas le cas.
"Trading (id: 2)" est également inclus dans ** "Epuisé" **. (Pas à vendre) En d'autres termes, ** le tableau obtenu par active_hash ne peut pas être utilisé comme condition de recherche en l'état **. Quand j'ai essayé de changer ce statut, je n'ai pas eu le temps de le réparer sans le divulguer car l'influence était large ici aussi.
(Le premier design aurait dû être meilleur) C'était étonnamment simple.
Lorsque vous définissez des données avec active_hash, si vous utilisez C'est la sensation de pousser avec le contrôleur.
C'est donc vraiment difficile à comprendre. Si vous listez ce que vous avez fait --Sur l'écran des conditions de recherche, créez un tableau (@trading_status) pour afficher les choix sous forme de case à cocher → Ceci est juste une instance de ** juste pour afficher la condition de recherche **
--Une fois que vous effectuez une recherche normale de ransack (pour le moment, "l'état des ventes" n'est pas une condition de recherche) ... ʻA La partie qui est devenue assez compliquée.
C'était une scène qui m'a fait réaliser que le premier design était important.
Je veux trouver un meilleur moyen que ça! Sérieux? Problèmes restants -- Dans ces conditions, la balle réagit. ʻAVOID (Eviter!) J'ai abandonné une fois parce que la branche conditionnelle ne fonctionnait pas ici. Les catégories sont divisées en parents, enfants et petits-enfants.
La table items n'a que category_id, et le category_id sélectionné ici est l'id de la catégorie petit-enfant. D'autre part, en tant que méthode de recherche, il est extrêmement pratique de pouvoir effectuer une recherche par "lorsque seule la catégorie parente est sélectionnée" ou "lorsque seule la catégorie enfant est sélectionnée". Comment y parvenir? C'est un problème. Si vous subdivisez davantage les problèmes ① Je veux faire de la catégorie petit-enfant une case à cocher au lieu d'un menu déroulant
(2) Les conditions "lorsque seule la catégorie parente est sélectionnée" et "lorsque la catégorie enfant est sélectionnée" ne peuvent pas être renvoyées correctement dans le champ des conditions de recherche après la recherche.
③ Si vous créez un formulaire qui ne reçoit que «category_id_in», vous ne pouvez pas effectuer de recherche par catégorie parent / catégorie enfant.
④ Si vous soumettez le formulaire avec uniquement la catégorie parent sélectionnée, les paramètres seront ignorés avec Beaucoup ... Une seule catégorie de petits-enfants peut être sélectionnée (menu déroulant), pas ↓
Je veux pouvoir sélectionner plusieurs catégories de petits-enfants (case à cocher) ↓
Par exemple, si vous spécifiez et recherchez comme ceci,
↓
Après la recherche, le champ de condition sera dans un état où rien n'est spécifié (la même chose s'applique lorsque même des catégories enfants sont sélectionnées).
La description de la première vue était comme celle-ci, mais dans ce cas, la catégorie ne fonctionnait pas comme condition de recherche à moins que la catégorie de petit-enfant ne soit sélectionnée. Seule la catégorie petit-enfant peut être recherchée par le En guise de contre-mesure pour (3), les propriétés de la catégorie parent et de la catégorie enfant ont été définies sur Poussez avec js et contrôleur. Résolvez la vue et js comme suit. Définissez le nom de propriété du formulaire de la catégorie parent et de la catégorie enfant sur Dans js, lorsque la catégorie enfant est vide (non sélectionnée) lors de l'exécution de la recherche, cela correspond au processus de suppression du menu déroulant de la catégorie enfant Comme 4-3, la balle réagit parfois ... De plus, si vous exécutez une recherche (= soumettre un formulaire) avec uniquement la catégorie parent sélectionnée (lorsque la catégorie enfant n'est pas sélectionnée), le paramètre category_id sera passé au contrôleur vide.
Cette fois, j'ai pris la méthode de suppression de la catégorie enfant f.select avec js, mais il semble y avoir un meilleur moyen. --Lorsque vous appuyez sur "Tout", sélectionnez toutes les options correspondantes.
――Si même l'une des options applicables n'est pas cochée, supprimez également «Tout». J'étais accro à la façon de réaliser l'exigence d'une complexité inattendue.
↓ Comportement comme celui-ci
Implémenté avec js. Il existe trois principaux types de traitement.
** ① Comportement lorsque vous cliquez sur "Tout" **
** ② Comportement lorsque vous cliquez sur autre que "Tous" **
** ③ Fonction pour juger s'il faut cocher la case "Tout" lors du chargement de la page ** Si vous n'écrivez pas le processus de ③, exécutez la recherche avec toutes les options sélectionnées dans les conditions de recherche → La case à cocher "Tous" sera décochée dans la colonne des conditions après la recherche, ce qui est gênant. Cliquez ici pour le code J'ai l'impression que la description est redondante ... Je veux la refactoriser un peu plus, donc je cherche une bonne façon de l'écrire. Au début, j'ai créé un bouton pour initialiser (= réinitialiser) le formulaire avec la méthode d'assistance de Rails.
Initialisez toutes les valeurs sous la forme [Rails] en un seul clic (définition de méthode d'aide + JavaScript) Cependant, avec cette méthode, le mouvement prévu ne peut pas être effectué sur l'écran une fois que les conditions de recherche ont été spécifiées et que la recherche a été exécutée. Cela signifie que si vous effectuez une recherche avec le mot-clé "margiela", par exemple, cet écran apparaîtra lorsque vous exécuterez la recherche.
Il est facile de comprendre que la valeur initiale est la valeur au moment où l'écran est chargé, donc la valeur initiale du mot-clé dans cet état est "margiela".
En d'autres termes, même si vous réinitialisez (= initialisez le formulaire) dans cet état, "margiela" dans le mot-clé ne disparaîtra pas. Puisque la valeur initiale est "margiela" (réécrivez le mot-clé en "yohji yamamoto" et effectuez une "initialisation" pour revenir à "margiela"). Ce n'est pas ce que nous voulons réaliser, mais le mouvement de renvoyer toutes les valeurs aux blancs ("sélectionner" pour les menus déroulants). Implémenté avec js.
Puisqu'il serait redondant si la description exécute le traitement pour chaque élément, j'ai décidé de rechercher des éléments enfants du formulaire. J'ai l'impression qu'il y a une meilleure façon. .. Je chercherai. Je ne comprends pas bien Rails, donc je pense qu'il y a une façon plus royale de le faire.
S'il y a quelque chose comme "Cette façon d'écrire est plus belle!", Je vous serais reconnaissant de bien vouloir me le faire savoir.
Recommended Posts
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"}
Trier
%option{value: "price-asc"}
Commande la moins chère
%option{value: "price-desc"}
Le prix le plus élevé
%option{value: "created_at-asc"}
Annonces les plus anciennes
%option{value: "created_at-desc"}
Dernier ordre d'inscription
%option{value: "likes-desc"}
préféré!Par ordre décroissant
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
//Comportement de tri
$(function() {
//L'événement se produit en sélectionnant le menu déroulant
$('select[name=sort_order]').change(function() {
//Récupère l'attribut value de la balise d'option sélectionnée
const sort_order = $(this).val();
//Branche de destination de transition de page en fonction de la valeur de l'attribut value
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";
}
//Page d'affichage actuelle
let current_html = window.location.href;
//Fonction de tri en double
if (location['href'].match(/&sort=*.+/) != null) {
var remove = location['href'].match(/&sort=*.+/)[0]
current_html = current_html.replace(remove, '')
};
//Transition de page
window.location.href = current_html + html
});
//Comportement après la transition de page
$(function () {
if (location['href'].match(/&sort=*.+/) != null) {
// option[selected: 'selected']Effacer
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)
}
});
});
2-2. Contre-mesures
items_controller.rb
class ItemsController < ApplicationController
~~
def search
@q = Item.includes(:images).search(search_params)
sort = params[:sort] || "created_at DESC"
#Le paramètre qui a volé de js"likes_count_desc"Dans le cas de, la description à trier par ordre décroissant des modèles enfants
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
2-3. Tâches restantes
3. Représentation dans la boîte de sélection
3-1 Défi: Comment réaliser des options qui ont un axe différent des propriétés du modèle, comme la «fourchette de prix»?
price (: integer type)
, donc il semble qu'il ne puisse pas être utilisé.3-2. Contre-mesures
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
appartient_to_active_hash
est omise.ruby:search.html.haml
~~
#Critères de recherche de prix
.sc-side__detail__field
%h5.sc-side__detail__field__label
%i.fas.fa-search-dollar
= f.label :price, 'prix'
.sc-side__detail__field__form
# PriceRange.tout, actif_Données définies par hachage(hacher)En tant que tableau
#Il, options_from_collection_for_Développez avec la méthode d'assistance de sélection et de déroulement
= select_tag :price_range, options_from_collection_for_select(PriceRange.all, :id, :name), { prompt: "Veuillez sélectionner"}
.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'
~~
3-3. Tâches restantes
4. Représentation des cases à cocher en vue
4-1. Défi: comment exprimer des options sans modèle
4-1-1. ① État du produit (status_id) → Modèle avec active_hash? Créer
Avant les détails de la mission, voici tout d'abord la description de ①. (Cliquez pour ouvrir) summary>
status.rb
# active_est du hachage
class Status < ActiveHash::Base
self.data = [
{id: 1, name: 'Nouveau, inutilisé'},
{id: 2, name: 'Presque inutilisé'},
{id: 3, name: 'Pas de rayures ou de taches visibles'},
{id: 4, name: 'Légèrement rayé et sale'},
{id: 5, name: 'Il y a des rayures et de la saleté'},
{id: 6, name: 'Mauvais état général'},
]
end
item.rb
class Item < ApplicationRecord
~~
belongs_to_active_hash :status
~~
ruby:search.html.haml
~~
#Conditions de recherche pour l'état du produit
.sc-side__detail__field
%h5.sc-side__detail__field__label
%i.fas.fa-star
= f.label :status_id_eq, 'État du produit'
.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, 'tout'
= 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}
~~
@ q
et saute au contrôleur, et que @ q
retourné après la recherche contient ce paramètre)4-1-2. ② Charge des frais d'expédition (delivery_charge_flag) → Décrivez les options directement dans la vue
ruby:views/items/_form.html.haml
~~
.field__form.item-area__field__form
= f.select :delivery_charge_flag, [['frais de port inclus(Fardeau de l'exposant)', '1'], ['Paiement(Charge de l'acheteur)', '2']], include_blank: 'Veuillez sélectionner', required: :true
~~
4-1-3. ③ Statut des ventes (trading_status_id) → modèle avec active_hash? Créer
trading_status.rb
class TradingStatus < ActiveHash::Base
self.data = [
{id: 1, name: 'Vente'},
{id: 2, name: 'Pendant le trading'},
{id: 3, name: 'Vendu'},
{id: 4, name: 'Brouillon'}
]
end
4-2. Contre-mesures
Correspondance à ②
flag
au lieu de ʻid`, vous pouvez utiliser active_hash de la même manière que ①.delivery_charge.rb
class DeliveryCharge < ActiveHash::Base
self.data = [
{flag: 1, name: 'frais de port inclus(Fardeau de l'exposant)'},
{flag: 2, name: 'Paiement(Charge de l'acheteur)'}
]
end
Correspondance à ③
--Branch le traitement à effectuer lorsque "Statut des ventes" est spécifié comme condition de recherche (lorsque
params.require (: q) [: trading_status_id_in]est vrai) --Redéfinissez
@ q` car il est nécessaire de renvoyer le" statut des ventes "spécifié comme condition de recherche à l'écran après la recherche.
--Définir et utiliser des méthodes privées pour la redéfinition
B
et
Bet les définir dans
@ items`items_controller.rb
~~
def search
#Définir une instance de tableau pour afficher les conditions de recherche "état des ventes"
#id 1 et 3=En vente, vendu
@trading_status = TradingStatus.find [1,3]
#Une fois, effectuez une recherche normale de ransack (pour le moment, "l'état des ventes" n'est pas une condition de recherche)
sort = params[:sort] || "created_at DESC"
@q = Item.not_draft.search(search_params)
@items = @q.result(distinct: true).order(sort)
~~
#Lorsque le statut des ventes est dans les conditions de recherche
if trading_status_key = params.require(:q)[:trading_status_id_in]
#Revenir à l'écran après la recherche@Redéfinition de q
@q = Item.includes(:images).search(search_params_for_trading_status)
#Une condition de vente spécifiée (si deux sont spécifiées, à la fois en vente et épuisé sont spécifiées = non spécifiées)
#Et traitement lorsque la clé spécifiée est 3 = "épuisé"
if trading_status_key.count == 1 && trading_status_key == ["3"]
#Obtenir des articles épuisés → Dans mon équipe, acheteur dans le tableau des articles_Il a été défini que l'identifiant a une valeur = épuisé
sold_items = Item.where.not(buyer_id: nil)
#plus haut@les articles ont été recherchés selon les conditions de recherche reçues par ransack@items
#Et vendu_Extraire des éléments correspondants avec des éléments → C'est-à-dire&
@items = @items & sold_items
elsif trading_status_key.count == 1 && trading_status_key == ["1"]
#Obtenir des articles à vendre → Dans mon équipe, acheteur dans le tableau des articles_Il a été défini que l'identifiant est nul = en vente
selling_items = Item.where(buyer_id: nil)
@items = @items & selling_items
end
end
private
#Lorsque "Statut des ventes" n'est pas spécifié comme condition de recherche@q → ransack recherche avec cette condition de recherche
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
#Lorsque "Statut des ventes" est spécifié comme condition de recherche@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
~~
4-3. Tâches restantes
@ items
n'est pas[]
-- @ items = @items & selling_items
ou @items = @items & sold_items
renvoie []
(= c'est-à-dire qu'il n'y a pas d'article correspondant), Il semble donc que le problème N + 1 ne se soit pas réellement produit, mais ... Quand j'ai mâché la phrase en anglais, je me suis demandé si c'était "éviter car un chargement impatient a été détecté là où il n'était pas nécessaire". Cependant, si vous supprimez
.includes (: images)comme spécifié, vous obtiendrez une alerte de ʻUSE (use!)
Pour d'autres modèles comme vous pouvez vous y attendre.5. Relation entre les objets qui utilisent l'ascendance et le saccage
5-1. Défi: comment envoyer une catégorie au contrôleur comme condition de recherche
category_id
vide.5-1-1. ① Je veux faire de la catégorie petit-enfant une case à cocher au lieu d'un menu déroulant
5-1-2. (2) Les conditions "lorsque seule la catégorie parente est sélectionnée" et "lorsque la catégorie enfant est sélectionnée" ne peuvent pas être renvoyées dans le champ de condition de recherche après la recherche.
5-1-3. ③ Si vous créez un formulaire qui ne reçoit que
category_id_in
, vous ne pouvez pas effectuer de recherche par catégorie parente / catégorie enfant.ruby:search.html.haml
%li
= f.select :category_id_in ,options_for_select(@parents, @item.category.root.name),{include_blank: "Veuillez sélectionner"}, {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: "Veuillez sélectionner"}, {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: "Veuillez sélectionner"}, {id: 'grandchildren_category', required: :true}
category_id_in
reçu par le paramètre, donc même si seule la catégorie parent est sélectionnée dans cet état, il n'y a pas de produit correspondant. (Même si vous sélectionnez uniquement "Hommes", vous ne pouvez pas rechercher de produits dont la catégorie parente est Hommes.)5-1-4. ④ Si vous soumettez le formulaire avec uniquement la catégorie parent sélectionnée, les paramètres seront ignorés avec
category_id
vide.category_id
(la raison sera décrite plus tard), mais si la recherche est exécutée avec la catégorie enfant non sélectionnée, category_id
sera ignoré dans un état vide.5-2. Contre-mesures
5-2-1. (1) Correspondance pour vouloir faire de la catégorie petit-enfant une case à cocher au lieu de dérouler
Cliquez pour voir le code summary>
ruby:search.html.haml
.sc-side__detail__field
%h5.sc-side__detail__field__label
%i.fas.fa-list-ul
= f.label :category_id, 'Choisir une catégorie'
.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: "Veuillez sélectionner"}, {id: 'parent_category_search'}
%li
- if @category_child.present?
= f.select :category_id, options_for_select(@category_child_array, @category_child.id),{include_blank: "Veuillez sélectionner"}, {id: 'children_category_search'}
- else
= f.select :category_id, @category_child_array, {include_blank: "Veuillez sélectionner"}, {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, 'tout'
= 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: "Veuillez sélectionner"}, {id: 'parent_category_search'}
item_search.js
//Comportement des catégories dans le formulaire de recherche
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;
}
//Créer un affichage des catégories enfants
function appendChildrenBox(insertHTML){
const childSelectHtml = `<li>
<select id="children_category_search" name="q[category_id]">
<option value="">Veuillez sélectionner</option>
${insertHTML}
</select>
</li>`;
$('.field__input--category_search').append(childSelectHtml);
}
//Créer un affichage de la catégorie des petits-enfants
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">tout</label>
</div>
</div>
${insertHTML}
</li>`;
$('.field__input--category_search').append(grandchildSelectHtml);
}
//Événements après sélection de la catégorie parente
$('#parent_category_search').on('change', function(){
//Obtenez le nom de la catégorie parent sélectionnée
const parentName =$(this).val();
if (parentName != ""){
//Assurez-vous que la catégorie parente n'est pas la catégorie par défaut
$.ajax({
url: '/items/category_children',
type: 'GET',
data: { parent_name: parentName },
dataType: 'json'
})
.done(function(children){
//Lorsque le parent change, supprimez les enfants et ci-dessous
$('#children_category_search').remove();
$('#grandchildren_category_checkboxes').remove();
let insertHTML = '';
children.forEach(function(child){
insertHTML += appendOption(child);
});
appendChildrenBox(insertHTML);
})
.fail(function(){
alert('Échec de l'obtention de la catégorie');
})
}else{
//Lorsque la catégorie parent devient la valeur initiale, supprimez les enfants et ci-dessous
$('#children_category_search').remove();
$('#grandchildren_category_checkboxes').remove();
}
});
//Événement après sélection de la catégorie enfant
$('.field__input--category_search').on('change', '#children_category_search', function(){
const childId = $(this).val();
//Obtenir l'identifiant de la catégorie enfant sélectionnée
if (childId != ""){
//Assurez-vous que la catégorie enfant n'est pas la catégorie par défaut
$.ajax({
url: '/items/category_grandchildren',
type: 'GET',
data: { child_id: childId},
dataType: 'json'
})
.done(function(grandchildren){
if (grandchildren.length != 0) {
//Lorsqu'un enfant est changé, supprimez le petit-enfant et les dessous
$('#grandchildren_category_checkboxes').remove();
let insertHTML = '';
grandchildren.forEach(function(grandchild){
insertHTML += appendCheckbox(grandchild);
});
appendGrandchildrenBox(insertHTML);
}
})
.fail(function(){
alert('Échec de l'obtention de la catégorie');
})
}else{
$('#grandchildren_category_checkboxes').remove();
}
});
5-2-2. Correspondance avec ②③
category_id
et traitez-le avec le contrôleur.
La vue est au dessus. Cliquez ici pour le contrôleur summary>
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: "Liste des catégories").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
#Lorsque le statut des ventes est dans les conditions de recherche
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
#Lorsque la catégorie est dans les critères de recherche
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?
#Catégorie Parentale
@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?("/")
#Catégorie enfant
@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
#Ramassez la catégorie des petits-enfants avec saccage → catégorie_id_in
end
end
@items = Kaminari.paginate_array(@items).page(params[:page]).per(20)
end
5-2-3. ④ Lors de la soumission d'un formulaire avec uniquement la catégorie parent sélectionnée, la réponse au paramètre saute lorsque
category_id
est videitem_search.js
$('#detail_search_btn').click(function(e) {
if ($('#children_category_search').val() == "") {
$('#children_category_search').remove();
}
});
5-3. Tâches restantes
6. Implémentation du bouton "Tout" en JavaScript
6-1. Défi: réalisation de "tout" compliqué de manière inattendue
6-2. Contre-mesures
. </ résumé>
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][]"]')
//① Comportement en cliquant sur "Tout"
$(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));
});
//② Comportement lors d'un clic autre que "Tout"
$(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));
});
//Une fonction qui détermine s'il faut cocher la case "Tout" lors du chargement d'une 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);
}
}
//③ Lorsque la page est chargée, exécutez une fonction qui détermine s'il faut cocher la case "Tout".
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('Impossible d'obtenir la fourchette de prix')
})
}
}
});
6-3. Tâches restantes
7. Bouton Effacer pour les conditions de recherche
7-1 Défi: N'y a-t-il pas d'autre choix que d'implémenter "reset" dans js? La description est susceptible d'être redondante
7-2. Contre-mesures
item_search.js
//Fonctionnement lorsque le bouton d'effacement est enfoncé
$(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. Tâches restantes
en conclusion