[RUBY] [Rails] Beachten Sie, dass beim Schreiben ein erweitertes Suchformular mit Ransack erstellt wurde

Einführung

Dies ist eine Beschreibung zum Erstellen eines erweiterten Suchformulars wie das folgende. Es ist keine Zusammenfassung, sondern ein detaillierter Hinweis.

Ich hoffe, es wird denjenigen helfen, die auf ähnliche Probleme gestoßen sind.

56b5aab1bc98bb048821fc50814e8eeb.gif

Vollständiges Formular

▼ Sortieren 201418e4d94c98c62f249943c2713b93.gif

▼ Kategorie f509c4b3dbb2abf3fad585bebb2963c0.gif

▼ Preisspanne, Kontrollkästchen d9de65f9cc351577645bcb4aee008bf9.gif

▼ Schaltfläche Löschen d6353bb4e2e151c601fe06ab613e79ae.gif

Teil verstopft

  1. Durchsuchen Sie das Abfrageproblem
  2. Implementierung der Sortierung der Suchergebnisse
  3. Darstellung in der Auswahlfeldansicht
  4. Darstellung der angezeigten Kontrollkästchen
  5. Beziehung zwischen Elementen, die Abstammung und Durchsuchung verwenden
  6. Implementierung der Schaltfläche "Alle" in JavaScript
  7. Implementierung einer Schaltfläche zum Löschen für Suchbedingungen

Vorwort

Weitere Informationen zur Verwendung und Verwendung von Ransack finden Sie in Github und den folgenden Artikeln.

Github - ransack Zusammenfassung verschiedener Methoden zur Erstellung von Suchformularen mit [Rails] Ransack

1. Durchsuchen Sie das Abfrageproblem

1-1. Herausforderung: Die Suchabfrage (@q) muss auf jedem Controller platziert werden

Wie in der obigen Abbildung gezeigt, ist es auf den meisten Controllern erforderlich, die Abfrage (@q) in before_action festzulegen, da das Suchformular in der Kopfzeile platziert ist.

Andernfalls tritt der folgende Fehler auf (Es wurde kein Ransack :: Search-Objekt für search_form_for bereitgestellt!). スクリーンショット 2020-06-09 18.12.03.png

Es ist notwendig, die oben erwähnte Indexaktion in Qiita zu beschreiben.

items_controller.rb


class ItemsController < ApplicationController
  def index
    @q = Item.ransack(params[:q])
    #Hier definiert@Ohne q suchen_form_Der obige Fehler tritt in für auf
    @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

Zu Beginn definierte jeder Controller eine private Methode und führte before_action aus.

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

Bei dieser Methode ist es jedoch erforderlich, eine private Methode von "set_item_search_query" für alle Controller zu definieren, die ** Seiten mit einem Suchformular (= Kopfzeilen anzeigen) ** aufrufen, was gegen das DRY-Prinzip ** verstößt. (Die Beschreibung ist vor allem problematisch).

1-2. Gegenmaßnahmen

Abschließend habe ich eine Methode in application_controller.rb definiert und sie auf jedem erforderlichen Controller aufgerufen.

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  #Fügen Sie diese Beschreibung hinzu

  def index  
    @items = Item.including.limit(5).desc.trading_not
  end
  
  #Die folgende Beschreibung wird gelöscht
  # private
  # def set_item_search_query
  #   @q = Student.ransack(params[:q])
  #   @items = @q.result(distinct: true)
  # end

end

1-3. Verbleibende Aufgaben (eher Unverständnis)

Die Methode "set_item_search_query" kann von einem anderen Controller aufgerufen werden, unabhängig davon, ob sie in "application_controller.rb" über oder unter "private" definiert wurde.

Ich frage mich, was dies bedeutet, da private ursprünglich Methoden definieren soll, die nicht von anderen Controllern aufgerufen werden sollen.

2. Implementierung der Sortierung der Suchergebnisse

2-1. Herausforderung: So realisieren Sie die Sortierung in absteigender Reihenfolge verwandter Kindermodelle

Es war schwierig, in der Reihenfolge der "Favoriten" zu sortieren, die mit Elementen verbunden sind (die Reihenfolge der verwandtesten Kindermodelle). Abgesehen davon war die folgende Sortierung relativ einfach zu implementieren, da die Spalten des Artikelmodells einfach nach Reihenfolge sortiert werden.

--Geringster Preis

Die Ansicht und der Controller sind wie folgt. Einfach ausgedrückt, es löst ein Ereignis aus, wenn das Pulldown in js geändert wird, und übergibt den ausgewählten Wert als Sortierparameter an die Steuerung.

Jeder Code (zum Öffnen klicken)

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"}
Sortieren
    %option{value: "price-asc"}
Günstigste Bestellung
    %option{value: "price-desc"}
Höchster Preis
    %option{value: "created_at-asc"}
Listings älteste
    %option{value: "created_at-desc"}
Neueste Reihenfolge der Auflistung
    %option{value: "likes-desc"}
Favorit!In absteigender Reihenfolge

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


//Sortierverhalten
$(function() {
  //Das Ereignis tritt durch Auswahl des Pulldown-Menüs auf
  $('select[name=sort_order]').change(function() {
    //Ruft das Wertattribut des ausgewählten Options-Tags ab
    const sort_order = $(this).val();
    //Verzweigung des Seitenübergangsziels abhängig vom Wert des Wertattributs

    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"; 
    }
    //Aktuelle Anzeigeseite
    let current_html = window.location.href;
    //Doppelte Sortierfunktion
    if (location['href'].match(/&sort=*.+/) != null) {
      var remove = location['href'].match(/&sort=*.+/)[0]
      current_html = current_html.replace(remove, '')
    };
    //Seitenübergang
    window.location.href = current_html + html
  });
  //Verhalten nach Seitenübergang
  $(function () {
    if (location['href'].match(/&sort=*.+/) != null) {
      // option[selected: 'selected']Löschen
      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. Gegenmaßnahmen

Ich dachte, ich könnte den Code, der die Reihenfolge der untergeordneten Modelle ausdrückt, nicht an die an den Controller übergebenen Parameter übergeben, und entschied mich daher, einen bedingten Zweig auf der Controllerseite zu erstellen.

items_controller.rb


class ItemsController < ApplicationController
  ~~
  def search
    @q = Item.includes(:images).search(search_params)
    sort = params[:sort] || "created_at DESC"    
    #Der Parameter, der von js geflogen ist"likes_count_desc"Im Fall von die Beschreibung in absteigender Reihenfolge der untergeordneten Modelle zu sortieren
    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. Verbleibende Aufgaben

Es scheint eine bessere Beschreibung zu geben ... Ich würde gerne wissen, wer es versteht.

3. Darstellung in der Auswahlfeldansicht

3-1. Herausforderung: Wie werden Optionen realisiert, die eine andere Achse als die Modelleigenschaften haben, z. B. "Preisspanne"?

53b473176e29ae82957ead2f8d843b37.gif

↑ So möchte ich die Funktion realisieren, dass die Untergrenze und die Obergrenze in die Spalten eingegeben werden, wenn die Preisspanne ausgewählt wird. Die Eigenschaft von Item ist jedoch "price (: integer type)", sodass sie anscheinend nicht verwendet werden kann.

3-2. Gegenmaßnahmen

Erstellen Sie mit active_hash etwas, das Hash-Daten wie ein Modell (ActiveRecord) verarbeiten kann.

Ich dachte nicht "benutze active_hash!" Von Anfang an,

  • Erfordert ein Array, das an die Hilfsmethode "options_from_collection_for_select" übergeben werden kann
  • Darüber hinaus müssen die an min und max übergebenen Werte entsprechend dem ausgewählten Inhalt geändert werden.
  • Schwierig, dies in js zu schreiben »Kannst du das Modell benutzen? Aber es ist mühsam, ein Modell und sogar einen Tisch zu bauen

Ich dachte, ich hätte active_hash in der Eliminierungsmethode verwendet.

Klicken Sie hier für Artikel, die sich auf active_hash ⇒ [active_hash summary] beziehen (https://qiita.com/Toman1223/items/8633142312bfa886d50b)

Insbesondere habe ich ein Modell namens "price_range" erstellt (das als behandelt werden kann) und es in der Ansicht angezeigt.

** ① Erstellen Sie eine Datei manuell in App / Models und geben Sie Folgendes ein **

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
  • Der ursprüngliche active_hash ist dem Item-Modell zugeordnet, aber dieses Mal muss price_range nicht mit dem Item-Modell verknüpft werden, sodass die Beschreibung von "includes_to_active_hash" weggelassen wird.

** ② Array-Daten mit Ansichtsdatei erstellen und mit options_from_collection_for_select anzeigen **

ruby:search.html.haml


~~
#Preissuchkriterien
.sc-side__detail__field
  %h5.sc-side__detail__field__label
    %i.fas.fa-search-dollar
    = f.label :price, 'Preis'
  .sc-side__detail__field__form
    # PriceRange.alle aktiv_Durch Hash definierte Daten(Hash)Als Array
    #Es Optionen_from_collection_for_Erweitern Sie mit der Hilfsmethode Auswählen und Herunterziehen
    = select_tag :price_range, options_from_collection_for_select(PriceRange.all, :id, :name), { prompt: "Bitte auswählen"}
  .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. Verbleibende Aufgaben

Ich denke, dass es nicht die ursprüngliche Verwendung von active_hash ist. .. ..

4. Darstellung der angezeigten Kontrollkästchen

4-1. Herausforderung: Wie man Optionen ohne Modell ausdrückt

Unter den Eigenschaften des Artikelmodells (Produkts) stehen folgende Optionen zur Verfügung. Die meisten Optionen sind active_hash, um ein Modell zu erstellen (was als behandelt werden kann).

① Produktstatus (status_id) → Modell mit active_hash? Erstellen ② Belastung der Versandkosten (Delivery_charge_flag) → Beschreiben Sie die Optionen direkt in der Ansicht ③ Verkaufsstatus (trade_status_id) → Modell mit active_hash? Erstellen ④ Kategorie (category_id) → Kategorien Es gibt eine Tabelle

Alles außer ① war in Schwierigkeiten.

4-1-1. ① Produktstatus (status_id) → Modell mit active_hash? Erstellen

Vor den Details der Aufgabe folgt zunächst die Beschreibung von ①. (Zum Öffnen klicken)

status.rb


# active_ist Hash
class Status < ActiveHash::Base
  self.data = [
      {id: 1, name: 'Neu, unbenutzt'},
      {id: 2, name: 'Fast unbenutzt'},
      {id: 3, name: 'Keine erkennbaren Kratzer oder Flecken'},
      {id: 4, name: 'Leicht zerkratzt und schmutzig'},
      {id: 5, name: 'Es gibt Kratzer und Schmutz'},
      {id: 6, name: 'Insgesamt schlechter Zustand'},
  ]
end

item.rb


class Item < ApplicationRecord
~~
  belongs_to_active_hash :status
~~

ruby:search.html.haml


~~
#Suchbedingungen für den Produktstatus
.sc-side__detail__field
  %h5.sc-side__detail__field__label
    %i.fas.fa-star
    = f.label :status_id_eq, 'Produktzustand'
  .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, 'alles'
    = 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}
~~

Mit dieser Beschreibung können Sie eine Gruppe von Kontrollkästchen erstellen, einschließlich "alle". スクリーンショット 2020-06-09 21.25.33.png

Mit dieser Beschreibung werden die Auswahlmöglichkeiten als Kontrollkästchen angezeigt, und die Kontrollkästchen werden nach Auswahl und Suche so angezeigt, wie sie auf dem Bildschirm angezeigt werden. (Da die Suchbedingung als Parameter in der Instanz @ q gehalten wird und zum Controller springt und das nach der Suche zurückgegebene @ q diesen Parameter enthält)

4-1-2. ② Belastung der Versandkosten (Delivery_charge_flag) → Beschreiben Sie die Optionen direkt in der Ansicht

Der störende Faktor ist, dass in dem Formular zum Zeitpunkt der Artikelauflistung (Artikel / Neu) die Belastung durch die Versandkosten (ob es sich um die Belastung des Verkäufers oder des Käufers handelt) einfach in der Ansicht angegeben wird.

ruby:views/items/_form.html.haml


~~
.field__form.item-area__field__form
  = f.select :delivery_charge_flag, [['Porto enthalten(Ausstellerbelastung)', '1'], ['Zahlung(Käuferlast)', '2']], include_blank: 'Bitte auswählen', required: :true
~~

Da das js, das die Übermittlungsmethode ändert, separat gemäß dem Flag (1 oder 2) beschrieben wird, ist der Einflussbereich groß, wenn Sie es erheblich ändern, und es tritt ein unerwarteter Fehler auf.

Mit anderen Worten, es scheint, dass Sie active_hash nicht auf die gleiche Weise wie ** ① ** verwenden können.

4-1-3. ③ Verkaufsstatus (trade_status_id) → Modell mit active_hash? Erstellen

Dies war unerwartet problematisch.

Ursprünglich wurde Trading_status unten entworfen.

trading_status.rb


class TradingStatus < ActiveHash::Base
  self.data = [
      {id: 1, name: 'Verkauf'},
      {id: 2, name: 'Während des Handels'},
      {id: 3, name: 'Verkauft'},
      {id: 4, name: 'Entwurf'}
  ]
end

Soweit ich sehen kann, scheint es einfach, dass "im Verkauf" eine ID von 1 und "Ausverkauft" eine ID von 3 hat, aber das ist nicht der Fall. "Trading (id: 2)" ist auch in ** "Ausverkauft" ** enthalten. (Nicht zu verkaufen)

Mit anderen Worten, ** das von active_hash erhaltene Array kann nicht als Suchbedingung verwendet werden, wie es ist **.

Als ich versuchte, diesen Status zu ändern, hatte ich keine Zeit, ihn zu beheben, ohne ihn zu verlieren, da der Einfluss auch hier groß war. (Das erste Design hätte besser sein sollen)

  • ④ Kategorie wird später beschrieben

4-2. Gegenmaßnahmen

Korrespondenz mit ②

Das war überraschend einfach. Wenn Sie beim Definieren von Daten mit active_hash flag anstelle von id verwenden, können Sie active_hash auf die gleiche Weise wie ① verwenden.

delivery_charge.rb


class DeliveryCharge < ActiveHash::Base
  self.data = [
      {flag: 1, name: 'Porto enthalten(Ausstellerbelastung)'},
      {flag: 2, name: 'Zahlung(Käuferlast)'}
  ]
end

Korrespondenz mit ③

Dies ist das Gefühl, mit dem Controller zu drücken. Es ist also wirklich schwer zu verstehen.

Wenn Sie auflisten, was Sie getan haben

  • Erstellen Sie im Suchbedingungsbildschirm ein Array (@trading_status), um die Auswahlmöglichkeiten als Kontrollkästchen anzuzeigen. → Dies ist nur eine Instanz von **, nur um die Suchbedingung anzuzeigen. **
  • Sobald Sie eine normale Suche nach Ransack durchführen (zu diesem Zeitpunkt ist "Verkaufsstatus" keine Suchbedingung) ... A
  • Verzweigen Sie die Verarbeitung, die ausgeführt werden soll, wenn "Verkaufsstatus" als Suchbedingung angegeben ist (wenn "params.require (: q) [: trade_status_id_in]" true ist).
  • Definieren Sie "@ q" neu, da nach der Suche der als Suchbedingung angegebene "Verkaufsstatus" auf dem Bildschirm zurückgegeben werden muss.
  • Definieren und verwenden Sie private Methoden zur Neudefinition
  • Die Verarbeitung wird verzweigt, wenn die Anzahl der als Suchbedingung angegebenen "Verkaufsstatus" eins ist. → Wenn zwei vorhanden sind, werden sowohl "im Verkauf" als auch "ausverkauft" angegeben = "Verkaufsstatus" ist eine Suchbedingung Wird nicht sein --Wenn "Ausverkauft" angegeben ist, erhält Käufer_ID Artikel mit nicht Null ... B
  • Extrahieren Sie nur die Elemente, die "A" und "B" gemeinsam haben, und definieren Sie sie in "@ Elemente"
  • Mit anderen Worten, "@ Artikel", die nach anderen Suchbedingungen als "Verkaufsstatus" gesucht werden, werden mit "verkaufte_Elemente" verglichen, die "ausverkauft" sind, und es werden nur übereinstimmende Artikel erhalten. ――Das Gleiche für "zum Verkauf" tun (= Käufer_ID ist gleich Null)

items_controller.rb


~~
def search
  #Definieren Sie eine Array-Instanz, um die Suchbedingungen für den "Verkaufsstatus" anzuzeigen
  #ID 1 und 3=Im Verkauf verkauft
  @trading_status = TradingStatus.find [1,3]

  #Führen Sie einmal eine normale Suche nach Durchsuchung durch (zu diesem Zeitpunkt ist "Verkaufsstatus" keine Suchbedingung).
  sort = params[:sort] || "created_at DESC"      
  @q = Item.not_draft.search(search_params)
  @items = @q.result(distinct: true).order(sort)
~~
  #Wenn sich der Verkaufsstatus in den Suchbedingungen befindet
  if trading_status_key = params.require(:q)[:trading_status_id_in]
    #Kehren Sie nach der Suche zum Bildschirm zurück@Neudefinition von q
    @q = Item.includes(:images).search(search_params_for_trading_status)
    #Eine angegebene Verkaufsbedingung (Wenn zwei angegeben sind, werden sowohl zum Verkauf als auch zum Ausverkauf angegeben = nicht angegeben)
    #Und Verarbeitung, wenn der angegebene Schlüssel 3 = "ausverkauft" ist
    if trading_status_key.count == 1 && trading_status_key == ["3"]
      #Ausverkaufte Artikel erhalten → In meinem Team Käufer in der Artikeltabelle_Es wurde definiert, dass id einen Wert hat = ausverkauft
      sold_items = Item.where.not(buyer_id: nil)
      #Oberer, höher@Die Artikel wurden anhand der Suchbedingungen durchsucht, die bei der Durchsuchung eingegangen sind@items
      #Und verkauft_Übereinstimmende Elemente mit Elementen extrahieren → Das heißt&
      @items = @items & sold_items
    elsif trading_status_key.count == 1 && trading_status_key == ["1"]
      #Artikel zum Verkauf anbieten → In meinem Team Käufer in der Artikeltabelle_Es wurde definiert, dass id nil = on sale ist
      selling_items = Item.where(buyer_id: nil)
      @items = @items & selling_items
    end
  end

  private
  #Wenn "Verkaufsstatus" nicht als Suchbedingung angegeben ist@q → ransack sucht mit dieser Suchbedingung
  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

  #Wenn "Verkaufsstatus" als Suchbedingung angegeben ist@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. Verbleibende Aufgaben

Der Teil, der ziemlich kompliziert geworden ist. Es war eine Szene, die mir klar machte, dass das erste Design wichtig war. Ich möchte einen besseren Weg als diesen finden!

Ernst? Verbleibende Probleme

-- @ items ist nicht[] -- @ items = @items & sell_items oder @items = @items & sold_items gibt [] zurück (= das heißt, es gibt keinen entsprechenden Artikel)

Unter dieser Bedingung reagiert die Kugel.

スクリーンショット 2020-06-09 23.30.11.png

Da es "VERMEIDEN (vermeiden!)" Ist, scheint es, dass das N + 1-Problem nicht tatsächlich aufgetreten ist, aber ... Als ich den englischen Satz kaute, fragte ich mich, ob er "es vermeiden sollte, weil eifriges Laden dort festgestellt wurde, wo es nicht benötigt wurde". Wenn Sie jedoch ".includes (: images)" wie angegeben entfernen, erhalten Sie erwartungsgemäß einen "USE (use!)" - Alarm für andere Muster.

Ich habe einmal aufgegeben, weil der bedingte Zweig hier nicht funktioniert hat.

5. Beziehung zwischen Elementen, die Abstammung und Durchsuchung verwenden

5-1. Herausforderung: So senden Sie eine Kategorie als Suchbedingung an den Controller

Die Kategorien sind in Eltern, Kinder und Enkelkinder unterteilt. Die Artikeltabelle enthält nur category_id, und die hier ausgewählte category_id ist die ID der Enkelkategorie.

Andererseits ist es als Suchmethode äußerst praktisch, nach "wenn nur die übergeordnete Kategorie ausgewählt ist" oder "wenn nur die untergeordnete Kategorie ausgewählt ist" suchen zu können. Wie erreicht man das? Ist ein Problem.

Wenn Sie die Themen weiter unterteilen

① Ich möchte die Enkelkategorie zu einem Kontrollkästchen machen, anstatt sie herunterzuziehen (2) Die Bedingungen "wenn nur die übergeordnete Kategorie ausgewählt ist" und "wenn die untergeordnete Kategorie ausgewählt ist" können nach der Suche im Suchbedingungsfeld nicht ordnungsgemäß zurückgegeben werden. ③ Wenn Sie ein Formular erstellen, das nur "category_id_in" empfängt, können Sie nicht nach übergeordneter Kategorie / untergeordneter Kategorie suchen. ④ Wenn Sie das Formular nur mit der ausgewählten übergeordneten Kategorie senden, werden die Parameter mit dem Leerzeichen "category_id" übersprungen.

Viele ...

5-1-1. ① Ich möchte die Enkelkategorie zu einem Kontrollkästchen machen, anstatt sie herunterzuziehen

Es kann nur eine Enkelkategorie ausgewählt werden (Pulldown), nicht ↓ c2a4191736e7b580941b142cb934587d.gif

Ich möchte mehrere Enkelkategorien auswählen können (Kontrollkästchen) ↓ d97b6e77fc3888c783c49f51b73c1304.gif

5-1-2. (2) Die Bedingungen "wenn nur die übergeordnete Kategorie ausgewählt ist" und "wenn die untergeordnete Kategorie ausgewählt ist" können nach der Suche nicht in das Suchbedingungsfeld zurückgegeben werden.

Wenn Sie beispielsweise Folgendes angeben und suchen, スクリーンショット 2020-06-09 23.54.22.png ↓ Nach der Suche befindet sich das Bedingungsfeld in einem Zustand, in dem nichts angegeben ist (dasselbe gilt, wenn auch untergeordnete Kategorien ausgewählt sind). スクリーンショット 2020-06-09 23.55.18.png

5-1-3. ③ Wenn Sie ein Formular erstellen, das nur "category_id_in" empfängt, können Sie nicht nach übergeordneter Kategorie / untergeordneter Kategorie suchen.

Die Beschreibung der ersten Ansicht war wie folgt, aber in diesem Fall funktionierte die Kategorie nicht als Suchbedingung, es sei denn, die Enkelkategorie wurde ausgewählt.

ruby:search.html.haml


%li
  = f.select :category_id_in ,options_for_select(@parents, @item.category.root.name),{include_blank: "Bitte auswählen"}, {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: "Bitte auswählen"}, {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: "Bitte auswählen"}, {id: 'grandchildren_category', required: :true}

Nur die Enkelkategorie kann nach der vom Parameter empfangenen "category_id_in" durchsucht werden. Selbst wenn in diesem Status nur die übergeordnete Kategorie ausgewählt ist, gibt es kein entsprechendes Produkt. (Auch wenn Sie nur "Männer" auswählen, können Sie nicht nach Produkten suchen, deren übergeordnete Kategorie "Männer" ist.)

5-1-4. ④ Wenn Sie das Formular nur mit der ausgewählten übergeordneten Kategorie senden, werden die Parameter mit dem Leerzeichen "category_id" übersprungen.

Als Gegenmaßnahme für (3) wurden die Eigenschaften der übergeordneten Kategorie und der untergeordneten Kategorie auf "category_id" gesetzt (der Grund wird später beschrieben). Wenn die Suche jedoch mit nicht ausgewählter untergeordneter Kategorie ausgeführt wird, wird "category_id" in einem leeren Zustand übersprungen.

5-2. Gegenmaßnahmen

Drücken Sie mit js und Controller.

5-2-1. (1) Korrespondenz, um die Enkelkategorie zu einem Kontrollkästchen zu machen, anstatt sie herunterzuziehen

Lösen Sie die Ansicht und js wie folgt auf.

Klicken Sie hier, um den Code
anzuzeigen

ruby:search.html.haml


.sc-side__detail__field
  %h5.sc-side__detail__field__label
    %i.fas.fa-list-ul
    = f.label :category_id, 'Wählen Sie eine Kategorie'
  .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: "Bitte auswählen"}, {id: 'parent_category_search'}
        %li
          - if @category_child.present?
            = f.select :category_id, options_for_select(@category_child_array, @category_child.id),{include_blank: "Bitte auswählen"}, {id: 'children_category_search'}
          - else
            = f.select :category_id, @category_child_array, {include_blank: "Bitte auswählen"}, {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, 'alles'
            = 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: "Bitte auswählen"}, {id: 'parent_category_search'}

item_search.js


  //Kategorieverhalten im Suchformular
  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;
  }

  //Erstellen Sie eine Anzeige der untergeordneten Kategorien
  function appendChildrenBox(insertHTML){
    const childSelectHtml = `<li>
                              <select id="children_category_search" name="q[category_id]">
                                <option value="">Bitte auswählen</option>
                                ${insertHTML}
                              </select>
                            </li>`;
    $('.field__input--category_search').append(childSelectHtml);
  }
  //Anzeige der Enkelkindkategorie erstellen
  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">alles</label>
                                    </div>
                                  </div>
                                  ${insertHTML}
                                </li>`;
    $('.field__input--category_search').append(grandchildSelectHtml);
  }
  //Ereignisse nach Auswahl der übergeordneten Kategorie
  $('#parent_category_search').on('change', function(){
    //Rufen Sie den Namen der ausgewählten übergeordneten Kategorie ab
    const parentName =$(this).val(); 
    if (parentName != ""){ 
      //Stellen Sie sicher, dass die übergeordnete Kategorie nicht die Standardeinstellung ist
      $.ajax({
        url: '/items/category_children',
        type: 'GET',
        data: { parent_name: parentName },
        dataType: 'json'
      })
      .done(function(children){
         //Wenn sich das übergeordnete Element ändert, löschen Sie die untergeordneten Elemente und darunter
        $('#children_category_search').remove();
        $('#grandchildren_category_checkboxes').remove();
        let insertHTML = '';
        children.forEach(function(child){
          insertHTML += appendOption(child);
        });
        appendChildrenBox(insertHTML);
      })
      .fail(function(){
        alert('Die Kategorie konnte nicht abgerufen werden');
      })
    }else{
      //Wenn die übergeordnete Kategorie zum Anfangswert wird, löschen Sie die untergeordneten Kategorien und darunter
      $('#children_category_search').remove();
      $('#grandchildren_category_checkboxes').remove();
    }
  });
  //Ereignis nach Auswahl der untergeordneten Kategorie
  $('.field__input--category_search').on('change', '#children_category_search', function(){
    const childId = $(this).val();
    //ID der ausgewählten untergeordneten Kategorie abrufen
    if (childId != ""){ 
      //Stellen Sie sicher, dass die untergeordnete Kategorie nicht die Standardeinstellung ist
      $.ajax({
        url: '/items/category_grandchildren',
        type: 'GET',
        data: { child_id:  childId},
        dataType: 'json'
      })
      .done(function(grandchildren){
        if (grandchildren.length != 0) {
          //Wenn ein Kind geändert wird, löschen Sie das Enkelkind und darunter
          $('#grandchildren_category_checkboxes').remove();
          let insertHTML = '';
          grandchildren.forEach(function(grandchild){
            insertHTML += appendCheckbox(grandchild);
          });
          appendGrandchildrenBox(insertHTML);
        }
      })
      .fail(function(){
        alert('Die Kategorie konnte nicht abgerufen werden');
      })
    }else{
      $('#grandchildren_category_checkboxes').remove();
    }
  });

5-2-2 Korrespondenz mit ②③

Setzen Sie den Eigenschaftsnamen des Formulars der übergeordneten Kategorie und der untergeordneten Kategorie auf "category_id" und verarbeiten Sie ihn mit dem Controller. Die Ansicht ist oben.

Klicken Sie hier für den Controller

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: "Kategorieliste").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
    #Wenn sich der Verkaufsstatus in den Suchbedingungen befindet
    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

    #Wenn sich die Kategorie in den Suchkriterien befindet
    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?
          #Eltern-Kategorie
          @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?("/")
          #Untergeordnete Kategorie
          @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
          #Nehmen Sie die Enkelkindkategorie mit Durchsuchen → Kategorie auf_id_in
      end
    end
    @items = Kaminari.paginate_array(@items).page(params[:page]).per(20)
  end

5-2-3. ④ Wenn Sie ein Formular senden, bei dem nur die übergeordnete Kategorie ausgewählt ist, wird die Antwort auf den Parameter übersprungen, wenn category_id leer ist

Wenn in js die untergeordnete Kategorie beim Ausführen der Suche leer (nicht ausgewählt) ist, entspricht dies dem Vorgang des Entfernens des Pulldowns der untergeordneten Kategorie

item_search.js


  $('#detail_search_btn').click(function(e) {
    if ($('#children_category_search').val() == "") {
      $('#children_category_search').remove();
    }
  });

5-3. Verbleibende Aufgaben

Wie 4-3 reagiert Kugel manchmal ...

Wenn Sie eine Suche (= Formular senden) nur mit der ausgewählten übergeordneten Kategorie ausführen (wenn die untergeordnete Kategorie nicht ausgewählt ist), wird der Parameter category_id an das Controller-Leerzeichen übergeben. Diesmal habe ich die Methode zum Löschen der untergeordneten Kategorie f.select mit js gewählt, aber es scheint einen besseren Weg zu geben.

6. Implementierung der Schaltfläche "Alle" in JavaScript

6-1. Herausforderung: Realisierung von unerwartet kompliziertem "Alles"

--Wenn Sie "Alle" drücken, wählen Sie alle entsprechenden Optionen aus. ――Wenn auch nur eine der zutreffenden Optionen deaktiviert ist, entfernen Sie auch "Alle". --Überprüfen Sie "Alle", wenn alle Optionen außer "Alle" ausgewählt sind.

  • Wenn auf dem Bildschirm nach der Suche alle anderen Optionen als "Alle" ausgewählt sind, aktivieren Sie auch "Alle".

Ich war süchtig danach, die unerwartet komplizierte Anforderung zu realisieren. ↓ Verhalten wie dieses 836dea5b6925b9d31ed861810c3352d6.gif

6-2. Gegenmaßnahmen

Implementiert mit js.

Es gibt drei Hauptarten der Verarbeitung. ** ① Verhalten beim Klicken auf "Alle" ** ** ② Verhalten beim Klicken auf "Alle" ** ** ③ Funktion zur Beurteilung, ob beim Laden der Seite das Kontrollkästchen "Alle" aktiviert werden soll **

Wenn Sie den Vorgang von ③ nicht schreiben, führen Sie die Suche mit allen in den Suchbedingungen ausgewählten Optionen aus. → Das Kontrollkästchen "Alle" wird nach der Suche in der Bedingungsspalte deaktiviert, was umständlich ist.

Klicken Sie hier für den 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][]"]')

  //① Verhalten beim Klicken auf "Alle"
  $(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));
  });

  //② Verhalten beim Klicken auf andere als "Alle"
  $(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));
  });


  //Eine Funktion, die festlegt, ob beim Laden einer Seite das Kontrollkästchen "Alle" aktiviert werden soll
  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);
    }
  }

  //③ Führen Sie beim Laden der Seite eine Funktion aus, die festlegt, ob das Kontrollkästchen "Alle" aktiviert werden soll.
  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('Die Preisspanne konnte nicht ermittelt werden')
      })
    }
  }
});

6-3. Verbleibende Aufgaben

Ich habe das Gefühl, dass die Beschreibung überflüssig ist ... Ich möchte sie etwas weiter überarbeiten, deshalb suche ich nach einer guten Möglichkeit, sie zu schreiben.

7. Löschen Sie die Schaltfläche für die Suchbedingungen

7-1. Herausforderung: Gibt es keine andere Wahl, als "Zurücksetzen" in js zu implementieren? Die Beschreibung ist wahrscheinlich redundant

Zuerst habe ich eine Schaltfläche erstellt, um das Formular mit der Hilfsmethode von Rails zu initialisieren (= zurückzusetzen). Initialisieren Sie alle Werte in [Rails] -Formular mit einem Klick (Definition der Hilfsmethode + JavaScript)

Bei dieser Methode kann die beabsichtigte Bewegung jedoch nicht auf dem Bildschirm ausgeführt werden, nachdem die Suchbedingungen angegeben und die Suche ausgeführt wurden.

  • Zurücksetzen ist eine Bewegung, um zum Wert (= Anfangswert) zurückzukehren, als der Bildschirm gelesen wurde.

Dies bedeutet, dass, wenn Sie beispielsweise eine Suche mit dem Schlüsselwort "margiela" durchführen, dieser Bildschirm angezeigt wird, wenn Sie die Suche ausführen. スクリーンショット 2020-06-10 11.49.49.png

Es ist leicht zu verstehen, dass der Anfangswert der Wert zum Zeitpunkt des Ladens des Bildschirms ist, sodass der Anfangswert des Schlüsselworts in diesem Zustand "margiela" ist. Mit anderen Worten, selbst wenn Sie in diesem Zustand das Formular zurücksetzen (= das Formular initialisieren), verschwindet "margiela" im Schlüsselwort nicht. Da der Anfangswert "margiela" ist (schreiben Sie das Schlüsselwort in "yohji yamamoto" um und führen Sie "initialization" durch, um zu "margiela" zurückzukehren).

Das ist nicht das, was wir erreichen wollen, sondern die Bewegung, alle Werte in Leerzeichen zurückzugeben ("Auswahl" für Pulldowns).

7-2. Gegenmaßnahmen

Implementiert mit js. Da es überflüssig wäre, wenn die Beschreibung für jedes Element verarbeitet wird, habe ich mich entschlossen, nach untergeordneten Elementen des Formulars zu suchen.

item_search.js


//Bedienung bei gedrückter Löschtaste
$(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. Verbleibende Aufgaben

Ich habe das Gefühl, es gibt einen besseren Weg. .. Ich werde suchen.

abschließend

Ich verstehe Rails nicht gut, daher denke ich, dass es einen königlicheren Weg gibt, dies zu tun. Wenn es so etwas wie "Diese Art zu schreiben ist schöner!" Gibt, wäre ich dankbar, wenn Sie mich wissen lassen könnten.

Recommended Posts