Rails / Ajaxを使って画面遷移しない一時保存機能をつける
先日つけてみたソーシャルボタンですが、やっぱ数字が出るってモチベーションになっていいね。
意外とPocketも多いんだなぁ。
今回もまたRails。
投稿サイトとかでよくある、一時保存機能をやってみた。文章書いていると、何かの拍子で「あ!」てなることが多いから、あるとユーザーに優しい。
フォーム
erb
1
2
3
4
5
6
7
8
9
10
11
12
13
<div class="blog-form">
<%= form_for(@post) do |f| %>
<%= f.text_field :tilte %>
<%= f.text_area :body %>
<!-- for temporal saving -->
<div class="create-temp"></div>
<%= f.submit "送信する" %>
<% end %>
</div>
フォームは例えばこんな感じ。
一時保存するかどうかのフラグと、行われた後のメッセージ表示用に class="create-temp" がついた div を置いている。
コントローラー
ruby
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def create
@post = current_user.posts.build(strong_params)
if @post.save
redirect_to @post
else
render 'new'
end
end
def create_temp
@post = current_user.posts.build(strong_params)
@post.published = false
@post.save
end
createアクションの他に、一時保存用に、create_temp
というのを作った。current_user
とかstrong_params
とかは、人によっていろいろでしょうが、適宜読み替えてください。
一時保存だから、一般には公開したくないわけで、postsテーブルにpublished(boolean)を追加して、create_tempアクションでは @post.published=false としています。
config/routesはこんな感じ。
ruby
1
2
3
4
resources :posts
post '/posts/temp', to: 'posts#create_temp', as: :temp_post
Javascriptでフォーム送信
coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
ready = ->
# Temporal Saving
if $('.create-temp')
window.tempTimer = null
$('.blog-form form').keydown ->
# reset
window.clearTimeout(tempTimer)
window.tempTimer = window.setTimeout ->
tempSubmit()
, 5000
tempSubmit = ->
# create
if $('.create-temp span').data('result') != true
$(".blog-form").ajaxSubmit(
url: '/posts/temp',
type: 'post'
)
# update
else
$(".blog-form form").ajaxSubmit()
# For turbolinks
$(document).ready(ready)
$(document).on('page:load', ready)
色んなやり方があると思いますが、keydownされた後、5秒たったら一時保存のフォーム送信をするようにしました。
5秒以内にkeydownされると、キャンセルされます。
ユーザーのアクション関係なくデータを投げるのもありかもしれませんが、ユーザーが画面を開きっぱなしでどこかに行ってしまったら、ずーーっと送信されることになります…ので、なにか手は必要ですね。
ajax送信にはこのプラグインを使いました。
Turbolinksなので、page:loadを使わないとダメです。
Viewファイル
jsに対応するビューファイルを作ります。
javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
var result = <%= @post.valid? %>;
var msg = "";
var notice = "";
if (result == true) {
msg = "下書き保存されました";
// This is for Temporal saving
// forcing the form to look update method.
// Add id
$('.blog-form').prepend('<input name="post[id]" type="hidden" value="<%= @post.id %>">');
// Chnage REST method
$('.blog-form input[name=_method]').remove();
$('.blog-form').prepend('<input name="_method" type="hidden" value="patch">');
// Change URL
$('.blog-form').attr('action', '/posts/<%= @post.id %>');
} else {
msg = "下書き保存のための情報が足りません";
}
// Message
notice = "<span data-result=" + result + ">" + msg + "</span>";
$('.create-temp span').remove();
$('.create-temp').hide().append(notice).fadeIn();
一回目の一時保存は普通にcreate_tempアクションに飛んでもらって良いんだけど、次のタイミング(例えば1分に1回保存するとかする場合)では、もうそのレコードはあるんだから、updateアクションに飛んでもらわないと困る。
ということで、javascriptでフォームの内容を変更しています。
強引だけど、これ以外に思いつかなかった。
Railsのフォームは input name="_method" で、フォームの送信先を変える情報を持っています。
同じPOST送信でも、value="patch"
ならupdateメソッド、value="post"
ならcreateメソッドが呼び出されるので、updateしたいタイミングで変更してるのです。
updateメソッド
最後にupdateメソッド。
通常のupdateと、一時保存のupdateで、publishedの状態を変える必要があります。
通常update(送信ボタンを押した時)
published = true
一時保存update(下書きとして扱う)
published = false
あと、通常はHTML、一時保存はajaxなので、respond_toで分けています。
こんな感じになりました。
ruby
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def update
# 所有権のチェック
@post.attributes = strong_params
# publish if update botton was clicked
@post.published = true if params[:commit]
respond_to do |format|
format.js do
@post.save
end
format.html do
if @post.save
flash[:success] = "ブログが更新されました。"
redirect_to @post
else
render 'edit'
end
end
end
end
updateの前にこの記事が本当にユーザーの持ち物かどうかチェックする必要がありますが(所有権のチェック)、そのメソッドは省略します。
以上です。
初めてやってみたので、つっこみあればお願いします :)