ポートフォリにAjaxでいいね機能を実装した時のメモです。
Ajaxとは?
JavaScriptを使ってサーバーにリクエストを投げて、レスポンスに基づきHTMLに変更を加える技術のことらしい。
分かりやすくいうとページのリロードなしで表示が切り替わるってこと。
身近なのはやっぱりTwitterのいいねですかね。
多対多のクリップ機能は既に実装済みでとします。
ちなみに今回の場合は、以下のようにUserモデルとEventモデルの中間テーブルとしてClipモデルがあることとしてます。
完成イメージ
クリップしたときにリロードなしでクリップできていてクリップしたイベントとしてクリップイベント一覧に反映されている。
実装
① jQuery導入
jQueryを導入していきます。
yarn add jQuery
yarn
とはJavaScriptのライブラリを管理するもの。Rubyでいうgemのようなもの。
npm
というのもあるがRailsでは基本的にyarn
を使うらしい。
次にwebpackerのサーバーを立ち上げます。
bin/webpack-dev-server
webpacker
とはwebpack
のRails版みたいなもので複数のJavaScriptファイルを読み込んで1つのファイルに出力してくれます。
これをすることでJavaScriptの読み込みを早くすることができ開発スピードを上げることができます。
それでは動作確認をしていきます。
import $ from 'jquery'
document.addEventListener('DOMContentLoaded', () => {
$('.title').on('click', () => {
window.alert('CLICKED')
})
})
確認方法は何でもいいんですが、今回はtitle
というクラスのついた部分をクリックしたらアラートが出るようにして動作を確認していきます。
以下のようにタイトルをクリックしてアラートが出てればOKです。
② クリップの状態を非同期で取得する
まずはアクセスした時にイベントがクリップしてるのかしてないかの状態を判断するAPIを作っていきます。
クリップしているかどうかのメソッドを作って、
def has_clipped?(event)
clips.exists?(event_id: event.id)
end
現状のclip_controller.rb
にshow actionを追加します。
resource :clip, only: [:show, :create, :destroy]
class ClipsController < ApplicationController
before_action :authenticate_user!
# -------------------------追加-----------------------
def show
event = Event.find(params[:event_id])
clip_status = current_user.has_clipped?(event)
render json: { hasClipped: clip_status }
end
# ---------------------------------------------------
def create
event = Event.find(params[:event_id])
event.clips.create!(user_id: current_user.id)
redirect_to event_path(event)
end
def destroy
event = Event.find(params[:event_id])
clip = event.clips.find_by!(user_id: current_user.id)
clip.destroy!
redirect_to event_path(event)
end
end
やっていることはアクセスしたイベントがクリップされているかいないかを判断しています。
クリップしているイベントには{"hasCliped":true}
というデータがjsonで返されます。
このステータス状態によってHTMLを変更していきます。
axiosをインストールします。
yarn add axios
axiosでgetリクエストをしてステータス状態を取得します。
その前に、イベントのステータス状態を取得するのにはevent_id
が必要なのでクリップボタンを設置するHTML部分に以下を記述することでevent_id
の取得ができます。
.container#event-show{data: {event_id: @event.id}}
あとは取得したevent_id
を使ってgetリクエストしていきます。
import axios from 'axios'
document.addEventListener('DOMContentLoaded', () => {
const dataset = $('#event-show').data()
const eventId = dataset.eventId
axios.get(`/events/${eventId}/clip`)
.then((response) => {
console.log(response)
const hasClipped = response.data.hasClipped
})
})
axiosのレスポンスの結果次第でクリップボタンの表示をJavaScriptで切り替えできるようにしていきます。
.clip
- if user_signed_in?
.clip__active.hidden.active-clip クリップ中
.clip__icon.hidden.inactive-clip クリップする
.hidden {
display: none;
}
クリップボタンの部分のHTMLにhidden
クラスを両方につけdisplay: none;
のcssを当てます。
const handleClipDisplay = (hasClipped) => {
if (hasClipped) {
$('.active-clip').removeClass('hidden')
} else {
$('.inactive-clip').removeClass('hidden')
}
}
axiosのレスポンスのステータス状態に合わせてhidden
クラスをつけるか外すかを決めることでクリップボタンのHTMLが変化します。
そしてこのfunctionを先程のgetリクエスト部分に記述します。
import axios from 'axios'
document.addEventListener('DOMContentLoaded', () => {
const dataset = $('#event-show').data()
const eventId = dataset.eventId
axios.get(`/events/${eventId}/clip`)
.then((response) => {
console.log(response)
const hasClipped = response.data.hasClipped
//-------追加-------------------
handleClipDisplay(hasClipped)
//------------------------------
})
})
これで非同期でクリップしているかしていないかを取得することができます。
③非同期でクリップする
次は実際にクリップするpostリクエスト部分とクリップを外すdeleteリクエスト部分を作っていきます。
APIを作っていきます。
リダイレクトしていた部分をrenderできるように変更していきます。
class ClipsController < ApplicationController
before_action :authenticate_user!
def show
event = Event.find(params[:event_id])
clip_status = current_user.has_clipped?(event)
render json: { hasClipped: clip_status }
end
def create
event = Event.find(params[:event_id])
event.clips.create!(user_id: current_user.id)
render json: { status: 'ok' } #変更部分
end
def destroy
event = Event.find(params[:event_id])
clip = event.clips.find_by!(user_id: current_user.id)
clip.destroy!
render json: { status: 'ok' } #変更部分
end
end
axiosでpostリクエストを作る前にターミナルで以下をインストールしてapplication.js
に記述します。
yarn add rails-ujs
import { csrfToken } from 'rails-ujs'
axios.defaults.headers.common['X-CSRF-Token'] = csrfToken()
ここでやっているのはセキュリティ対策です。
postリクエストの場合はgetリクエストと違いセキュリィティ的に問題があるので上の記述がないとpostリクエストが通りません。
csrfToken
というのは鍵みたいなもので、その鍵をaxiosのときはデフォルトで持たせるように記述しています。
postリクエスト部分
$('.inactive-clip').on('click', () => {
axios.post(`/events/${eventId}/clip`)
.then((response) => {
if (response.data.status === 'ok') {
$('.active-clip').removeClass('hidden')
$('.inactive-clip').addClass('hidden')
}
console.log(response)
})
.catch((e) => {
window.alert('Error')
console.log(e)
})
})
.then((response) => {}
はリクエストがうまくいった時の処理。今回はリクエストがうまくいったらクリップした状態にボタンを変更させています。
.catch((e) => {}
の部分はリクエストが失敗した時の処理。今回はwindow.alert
でErrorという文字列を表示させます。
最後にクリップを外すdeleteリクエスト
$('.active-clip').on('click', () => {
axios.delete(`/events/${eventId}/clip`)
.then((response) => {
if (response.data.status === 'ok') {
$('.active-clip').addClass('hidden')
$('.inactive-clip').removeClass('hidden')
}
console.log(response)
})
.catch((e) => {
window.alert('Error')
console.log(e)
})
})
やっているのはpostの逆の処理です。
動作確認をして画面がリロードせずクリップできていたら完成です!