ajaxを用いて自動更新機能を実装する。
今回は前回、作成した非同期通信のコードに書き加える形で自動更新を実装する。
##環境
ruby:2.5.1
rails:2.5.3
DB:mysql(Sequel Pro)
ブラウザ:Google
OS:Mac(10.14.6)
##Github
https://github.com/tana1818/auto_update
##作成する前に
####なぜ必要か
日々使ってるLINEを考えてみましょう。
LINEのように、相手側が入力すると自分側が、LINEを開いたままでも、
リアルタイムで更新されます。あれも自動更新機能が実装されています。
自動更新がないままLINEのチャット機能を使用するとなると
表示されている画面をいちいちリロードしないと画面が更新されません。
これを一人ならまだしも、グループでチャットをするとなると面倒です。
自動更新の大きなメリットはリロードしなくてもリアルタイムでデータが更新されるという点です。
####通信の流れ
実装に移る前に通信の流れを把握しておきましょう。
①ブラウザからindexアクションのリクエストが来たのでサーバーはレスポンスを返す
②ブラウザはサーバーからのレスポンスを解析
③解析して必要な全てのアセット (JavaScriptファイル、スタイルシート、画像) をサーバーから取得する
④JavaScriptを読み込む(指定の画面に移ったら、最新のデータがあったら取得、なかったら取得しない)
⑤json形式で返ってきた値をcontrollerで判別
⑥controllerからjson形式でリクエストが読まれjbuilderのファイルを読み込む
⑦jbuilderのハッシュの中にキーとバリューで値が保存されているのでJavaScriptに値(data)を渡す
⑧JavaScriptのファイルのdone、fail以下の記述が読まれ実行される
⑨ブラウザに表示される
同じ画面に居続けたら③から⑨を繰り返し自動更新が行われる
##作成手順
ここでは自動更新機能を実装するだけの人用で書いていくが
一応データベースの情報はいじってないので自分の非同期通信を実装した方がいれば
「コード編集(ビュー)」の手順からファイルに追記していけば同じデータベースで自動更新機能が実装できる。
結論、データベースの名前が一律"ajax"になる。
もし「非同期通信」と「自動更新」のファイルを比較化をしたいって人は
データベースの名前を決めるファイル(database.yml)を編集してrails db:createしてください
####Githubからコードをダウンロード
下記のURLからコードをダウンロード
https://github.com/tana1818/ajax
####ダウンロードが完了したらファイル名を変更(なんでも良い)
任意のディレクトリにファイルうを配置したらファイル名変更
自分は自動更新なので’auto_update’って名前にしました。
####gemのインストール、DBの作成、マイグレーション実行、サーバー起動
bundle install #gemのインストール
rake db:create #DB作成
rake db:migrate #マイグレーションファイルを実行する
rails s #サーバーの起動
####コード編集(ビュー)
①タイトルバーの名前変更
!!!
%html
%head
%meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/
//下記記述変更
%title Auto_update
= csrf_meta_tags
= csp_meta_tag
= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload'
= javascript_include_tag 'application', 'data-turbolinks-track': 'reload'
%body
= yield
②indexにdata-id付与
.container
.row
%h1 Listing fruit
- if flash[:notice]
%p= flash[:notice]
.row
%table.table.table-bordered.table-sortable
%thead
%tr
%th Name
%th
%th
%th
%tbody.fruit_item
- @fruits.each do |fruit|
//下記追加('item'クラスにデータのid情報追加)
%tr.item{"data-id": "#{fruit.id}"}
%td
= fruit.name
%td
= link_to 'Show', fruit
%td
= link_to 'Edit', edit_fruit_path(fruit)
%td
= link_to 'Destroy', fruit, data: {confirm: 'Are you sure?'}, method: :delete
.row
%h1 New fruit
= render 'form'
最新のfruitのidを取得するためにfruitテーブルのidを、HTMLの中に埋め込む必要がある。
ここで登場するのが「カスタムデータ属性」
カスタムデータ属性を使うときは、属性名を「data-」で始まる名称にする。
上記のように記述すれば、「data-id」という名前のカスタムデータ属性を設定できたことになる
このように「data-任意の名前」でカスタムデータ属性を設定しておくと
JavaScriptから簡単に値を取得できる
この記事がわかりやすいかも
https://wp.tech-style.info/archives/102
####jsファイル編集
$(function(){
//非同期通信
function buildHTML(fruit){ //通信が成功するとdoneメソッドの引数にデータが入るようになっているため、これを利用してHTMLを組み立てる
//5行目にdata-id = ${fruit.id}を追加
var html = `<tr class = "item ui-sortable-handle" data-id = ${fruit.id}>
<td>
${fruit.name}
</td>
<td>
<a href = /fruits/${fruit.id}>Show</a>
</td>
<td>
<a href = /fruits/${fruit.id}/edit>Edit</a>
</td>
<td>
<a data-confirm = "Are you sure?", rel = "nofollow", href = /fruits/${fruit.id}, data-method = "DELETE">Destroy</a>
</td>
</tr>`
return html;
}
$('#new_fruit').on('submit', function(e){ //'#new_fruit'の'submit'が押された時に発火
e.preventDefault(); //これを書いてるせいで'submit'を押した際に要素にdisabledが付与される→最後の.alwasが書いてある行を足すことでそのdisabled要素を削除してる
var formData = new FormData(this); //FormDataオブジェクトの引数はthisとなってる。イベントで設定したfunction内でthisを利用した場合はイベントが発生したDOM要素を指す。今回であればnew_commentというIDがついたフォームの情報を取得している
$.ajax({
url: "/fruits/", //ここはアクションのURLなのでrails routesで確認
type: "POST", //ここはアクション名
data: formData,
dataType: 'json',
processData: false,
contentType: false
})
.done(function(data){ //非同期通信の結果として返ってくるデータは、done(function(data) { 処理 })の関数の引数で受け取る
var html = buildHTML(data);
$('.fruit_item').append(html) //htmlに追加
// $('.b').val('') //なんかエラー出るなーと思ったらvarと書き間違えていた(ここでは'submit'を押した後テキストボックスに空の要素を付与してる)
$('.new_fruit')[0].reset() //初期値があれば初期値にリセットされる
})
.fail(function(){ //エラーが置きた際
alert('投稿できませんでした')
});
// .always(function(){ //この記述を書いてないと連続で投稿できない
// $(".c").removeAttr("disabled")
// })
return false;
//要素の効果を無効化する、ちょっとわかんないけどとりま親要素のクリックをなかったことにするっぽいw
//上記のコメント化記述は'disabled'を消しにいってる
//こっちの方が記述量が少なくて良い
})
//自動更新
if (location.pathname.match()){ //もし現在のURLパスがindexアクションだったら(http://localhost:3000/fruitsもしくはhttp://localhost:3000)
setInterval(update, 5000);//5000ミリ秒ごとにupdateという関数を実行する
}
function update(){
if($('.item')[0]){ //もし'.item'というクラスがあったら
var fruit_id = $('.item:last').data('id'); //一番最後にある'.item'クラスの'id'というデータ属性を取得し、'fruit_id'という変数に代入
$.ajax({
url: location.href, //urlは現在のページを指定
type: 'GET', //アクション名指定(データを表示させる)
data: { id: fruit_id }, //rails に引き渡すデータ(これがparams[:id]になる)
dataType: 'json'
})
.done(function(data){
if (data.length){ //もしdataに値があったら
$.each(data, function(i, data){ //'data'を'data'に代入してeachで回す
var html = buildHTML(data);
$('.fruit_item').append(html);
})
}
})
.fail(function(){ //そんなにこの記述はいらない気がするけど、異常系のエラー(途中で通信が中断されたり)が起きた時用
alert('自動更新に失敗しました')
});
}
else {
clearInterval(); //値がなかったらsetIntervalを止める(この記述はなくても動く、別画面に遷移した際は、ブラウザを閉じた時同様、遷移した時点で遷移前のJavaScript実行は勝手に打ち切られる模様)
}
}
});
####コントローラー編集(indexのみ)
def index
@fruits = Fruit.all
@fruit = Fruit.new
respond_to do |format| #記述追加
format.html
format.json { @new_fruit = Fruit.where('id > ?', params[:id]) } #jsのdataの情報がparams[:id]に入る
end
end
####index.json.jbuilderのファイル編集
json.array! @new_fruit.each do |fruit|
json.name fruit.name
json.id fruit.id # 配列かつjson形式で@new_fruitを返す
end
以上で実装は終了
再度サーバーを立ち上げ直して
rails s
うまく実装してできていれば上記のGIFのように自動で最新のデータが更新される
うまく実装できました?
実装できて自分の理解ができたら無駄なコメントは削除して読みやすいコードにしましょう。
(なんだか自分のコードにも_form.html.hamlに無駄なコメントアウトがあったりしたので、、)
今回でqiita2記事目なんですが、めちゃしんどいっすw
書いてみるとやってる事全然簡単に見えるのに、これあげるのにどれだけかかったか、、、
qiita人民の凄味を改めて実感しました。
ちょいまだまだ補足が書けていないのですが、一旦あげようと思います。
指摘事項あれば教えて頂けると嬉しいです。
それでは
はてなブログもやっているので是非!
https://tanagram18.hatenablog.com/