はじめに
セレクトボックスの選択肢を選ぶと非同期的にコンテンツが変更されるサイトが結構あると思います。
例えばAmazonで商品の色を選んだ時とか。
これをRailsで実装する方法を記していきます。
注意
Rails学習中の初学者による記事です。
内容に誤りを含む可能性・さらに良い手法がある可能性が多分にありますので、参考にする際はその点ご留意ください。
また、間違いのご指摘やアドバイスは大歓迎です。
RubyとRailsのバージョン
下記のバージョンにて動作確認しています。
- Ruby 2.5.1
- Rails 5.2.1
処理の流れ
Ajaxで非同期的にコンテンツ内容を変更しますが、今回はRailsのViewでremote: true
を設定していくことにします。
AjaxはjQuery.ajax()を使用して実装する方法もありますが、そちらは検索すればたくさん記事が出てくると思うので、調べてください。
remote :true
を使用した際の流れは
1. セレクトボックスの選択肢を選択
2. セレクトボックスの変更をjQueryで読み取り、フォームをsubmitする
3. フォームからJS形式のリクエストが送信され、ルーティングによりコントローラー/アクションの判定
4. コントローラーのアクションが動作し、app/views/コントローラー名/アクション名.js.erb
を呼び出す
5. アクション名.js.erb
内でhtmlをレンダリング
となります。
3〜5の部分がAjax(非同期通信)の処理です。こちらの記事にAjax処理のステップが詳しく書いてありますので、参考にしてください。
モデル
モデルはProduct, Variation, Imageの3つです。
シンプルに、1つのProductが複数のVariations(今回は色違いとします)を持ち、Variationはそれぞれ1枚のImageを持ちます。
class Product < ActiveRecord::Base
has_many :images
end
class Variation < ActiveRecord::Base
belongs_to :product
has_one :Image
end
class Image < ActiveRecord::Base
belongs_to :variation
end
コントローラー
コントローラーはProductsのみです。
class ProductsController < ApplicationController
def show
@product = Product.find(params[:id])
@variation = @product.variations.first
end
def ajax
@selected_color = params[:color]
@product = Product.find(params[:id])
@variation = @product.variations.find_by(color: @selected_color)
end
end
showアクションでproductの詳細ページが表示されますが、最初に示されるvariationはとりあえずproduct.variations.first
としておきます。
ajaxアクションはJS形式のリクエストを受け取り、選択されたcolorのvariationを呼び出します。
ルーティング
routes.rbは下記の通りです。
Rails.application.routes.draw do
get 'products/:id', to: 'products#show'
get 'ajax', to: 'products#ajax'
end
View
Viewはproduct情報、variation画像、セレクトボックスから構成されています。variation画像とセレクトボックスは_variant.html.erb
として、パーシャル化しています。
<div class="product_info">
<%= @product.name %>
<%= @product.description %>
</div>
<div class="variation_contents">
<%= render 'variation', { product: @product, variation: @variation, selected_color: @selected_color } %>
</div>
<div class="variation_image">
<%= image_tag variation.image.url %> #carrierwaveの場合
</div>
<span class="quick-drop">
<%= form_tag ajax_path, method: :get, remote: true do %>
<%= hidden_field_tag :id, product.id %>
<%= select_tag :color, options_for_select(["Red", "Green", "Blue"], selected_color), class: "select-drop" %>
<% end %>
</span>
このパーシャル部分をAjaxで更新することになります。
- remote: trueとすることでフォームのリクエストがJS形式で送られることになります。
- variationを特定するためにまずproductを特定しなければなりません。hidden_field_tagでproductのidを埋め込んでおきましょう。
- options_for_selectでオプションタグを生成します。なお、第二引数にデフォルト値を入れられるので、Ajax更新後も選択されたcolorを表示できるように、@selected_colorに格納したcolorをデフォルト値としています。詳しくはRails Guideを参照してください
Ajaxの実装
JS形式で発行されたリクエストを受け取ったコントローラーはアクション名.js.erb
を呼び出しますので、以下のファイルを作成します。
$('.variation_contents').html('<%= escape_javascript(render 'variation', { product: @product, variation: @variation, selected_color: @selected_color } ) %>')
variation_contentクラスに対して、variationパーシャルをレンダリングするという処理になります。
この記述により、showテンプレート内部のvariation_contentが非同期的に置換されます。ただし、このままではフォームの自動送信が行われないため、colorを選択してもAjaxは起こりません。
セレクトボックスの変更を読み取り自動送信する動作を実装していきましょう。
jQueryによる自動送信の実装
jQuery(document).bind('ready ajaxComplete', function() {
$('.select-drop').change(function() {
$(this).parent().submit();
});
});
- select-dropクラスが変更された時、その親要素(今回はform)をsubmitします。
-
bind('ready ajaxComplete', function()
について、jQuery(document).ready()
とするとページ読み込みを起点に動作することになり、Ajax後(つまりリロードされず、一部のコンテンツのみ更新される)には動作しないことになってしまいます。そこで、ajaxComplete
も付加することでリダイレクト時、Ajax後どちらでも動作するようにしています。
完成
これで完成です。サイト上に表示されたセレクトボックスからcolorを選択すると、そのcolorを持つvariationのimageに切り替わるはずです。
まとめ
セレクトボックスの変更によって動的にコンテンツが変わるサンプルコードを紹介しました。
今回は選択肢がcolorのみ、画像も各variationに1枚ずつというシンプルなものでしたが、紹介したコードを応用すればもうちょっと複雑な組み合わせも可能になると思います。
自分で実装する上でかなり苦戦した内容でしたので、同じように困っている人の助けになれば幸いです。
間違いの指摘、より良い方法等ありましたらご指摘いただけると嬉しいです!