はじめに
ポートフォリオとして作っていたグループチャットアプリで、画像を複数投稿できるようにしました。
その投稿をajaxで表示する方法を本記事で書いていきます!
投稿機能はできていることを前提にしてますので、できていない方は
前回記事:【Rails】画像を複数投稿したい
を見ていただければと思います!
#目指すゴール
目指すの動作は前回同様です!これをAjaxでいきます。
#前提
- 投稿機能は実装済み(テキスト複数行&画像複数枚が投稿できる)
- jQueryを導入の上、turbolinksは今回使用しないので、コメントアウトしている。
- テーブル同士の関係は以下の通り。(実際のアプリからは簡略化しています)
##開発環境
ruby 2.5.1
Rails 5.2.4.2
Haml 5.1.2
jQuery
#いざ実装
jQueryを導入してください。▶︎(https://github.com/rails/jquery-rails)
最後に完成コードを載せていますので、結果だけ教えてって方は読み飛ばしてください。
##1. ajaxでリクエストを送信しよう
フォームの投稿ボタンがクリックされたら、イベントを発火しajaxでデータを送信します。
$(function () {
// formのidである'#new_post'が'submit'されたらイベント発火
$('#new_post').on('submit', function (e) {
// ブラウザが最初から持っているアクションをキャンセル
e.preventDefault()
// formのinput要素をJavaScriptのオブジェクトとしてキーバリュー形式で表示
var formData = new FormData(this);
// formのリクエスト送信先のパスをattrメソッドで取得
var url = $(this).attr('action');
// ajaxでデータを送信
$.ajax({
url: url,
type: "POST",
data: formData,
dataType: 'json',
// formDataを使用する場合は必要
processData: false,
contentType: false
})
});
});
formのid
やaction
はブラウザの要素検証から確認します。
##2. posts#createで投稿を保存し、JSON形式でレスポンスする
ajaxでJSON形式で送られてきたデータを保存します。
コントローラを以下のように編集します。
# 省略
def create
@post = @group.posts.new(post_params)
if @post.save
if params[:post_files].present?
params[:post_files][:file].each do |a|
@post_file = @post.post_files.create!(file: a, post_id: @post.id)
end
end
# json形式で来たリクエストに対してJSON形式のレスポンスを返す
respond_to do |format|
format.json
end
else
@posts = @group.posts.includes(:user).order(created_at: "DESC")
render :index
end
end
# 省略
続いて、jbuilderを使用して、保存された投稿をJSON形式で返します。
まずはcreate.json.jbuilder
を作成します。
$ touch app/views/posts/create.json.jbuilder
create.json.jbuilder
を以下のように編集します。
json.id @post.id
json.user_avater @post.user.avater_url
json.user_name @post.user.name
json.created_at @post.created_at.strftime("%Y/%m/%d %H:%M")
json.content @post.content
json.post_files @post.post_files
# コメントと"見ました"ボタンのリンク(不要の場合は消してOK)
json.post_link group_post_path(@group, @post) <---"posts#show"のパス
json.looks_link group_post_looks_path(@group, @post)
フォームの投稿ボタンをクリックしたとき、ターミナルで以下のようなログが出ていれば成功です!
##3. 返ってきたJSONを受取り、表示するHTMLを作っていく
テンプレートリテラル記法を使用し、表示するHTMLを作っていきます。
テンプレートリテラルって?って方はこちらの記事がとてもわかりやすいです。
▶︎ JavaScript の テンプレートリテラル を極める!
ここからが本番です。
なぜなら、画像が何枚投稿されているかによって、HTMLの表示って変わりますよね?
<div class="post-files">
<object class="post-files__file" data="画像1.jpg"></object>
<object class="post-files__file" data="画像2.jpg"></object>
...
</div>
このように、画像があるのか、何枚あるのかでobjectタグ
の数が変わるので厄介です。(個人的感想)
結論、画像を表示させるHTMLだけ切り出しておいて、画像の数だけ挿入するという方法を取りました。
以下解説コードです。
$(function () {
// appendHTMLメソッドで画像部分のHTMLを作成しています
function appendHTML(file) {
// 引数として受け取ったfileを${変数}として挿入しています
var fileHTML = `<object class="post-files__file" data="${file.file.url}"></object>`
return fileHTML;
}
function buildHTML(post, filesHTML) {
// 長くなるので重要なところだけ記述してます
var html =
`<div class="post">
// 省略(完成コード参照)
<div class="middle">
<div class="post-content">
${ post.content}
</div>
<div class="post_files">
${filesHTML}
</div>
// 省略(完成コード参照)
</div>`
return html;
}
$('#new_post').on('submit', function (e) {
e.preventDefault()
var formData = new FormData(this);
var url = $(this).attr('action');
$.ajax({
url: url,
type: "POST",
data: formData,
dataType: 'json',
processData: false,
contentType: false
})
.done(function (data) {
// 画像を表示するHTMLを定義しています
var filesHTML = '';
// != は"等しくない"ということなので、"dataのpost_filesが0でなければ"となります
if (data.post_files != 0) {
// post_filesのfileを一つずつ取り出して処理(HTMLと一緒ですね!)
data.post_files.forEach(function (file) {
// 定義しておいたfilesHTMLに、fileの数だけappendHTMLメソッドを挿入する
// fileを引数として渡している
filesHTML += appendHTML(file);
})
}
// 上述のbuildHTMLというメソッドで、htmlを組み立てる
// 引数に(data, filesHTML)を渡している
var html = buildHTML(data, filesHTML);
// postsクラスの一番上にhtmlを挿入する
$('.posts').prepend(html);
// formに入力された値をリセットする
$('form')[0].reset();
// 連続で投稿ボタンをクリックできるようにする
$('.submit-btn').prop('disabled', false);
})
.fail(function () {
alert("投稿に失敗しました\nリロードしてください");
$('.submit-btn').prop('disabled', false);
})
});
});
ポイントはdoneメソッド
直後のこの部分。
var filesHTML = '';
if (data.post_files != 0) {
data.post_files.forEach(function (file) {
filesHTML += appendHTML(file);
})
}
これで何をしているかというと、下図のように画像が何枚であろうと対応できるコードにしています。
appendHTMLメソッド
により、filesHTML
の中にfileHTML
がシャって入っていく感じです。
ちなみに、テンプレートリテラルで一行ずつHTMLのコードを書いていくのは時間がかかるし、スペルミスを誘発します。
そこで、ブラウザの要素検証からコピペすることをおすすめします。
コピペしたい要素を選択して右クリックし、「Edit as HTML」を選択します。
範囲を選択してコピペし、変数の部分だけ書き換えています。
##4. textareaで入力された改行の処理をプラス
コードはほぼ完成していますが、ここで長文入力に対応したフォームにしていきます。
textareaの大きさが固定になっていると、長文を入力する際、スクロールしないと前の文章が確認できません。
そこで、以下のコードをプラスします。
$(document).on('change keyup keydown paste cut',
'#textarea', function () {
if ($(this).outerHeight() > this.scrollHeight) {
$(this).height(1)
}
while ($(this).outerHeight() < this.scrollHeight) {
$(this).height($(this).height() + 1)
}
});
するとこのように文章をコピペして貼り付けたり、行数が変化したりすると、フォームの大きさが変わります。
ただ、今のままですと、ajaxでは改行が反映された形で表示されません!
せっかく改行して見やすくしているわけですから、そのまま表示させましょう!
$(function () {
// 省略
function buildHTML(post, filesHTML) {
// 正規表現を用いて、入力されたテキストを加工
var content = post.content.replace(/\n|\r\n|\r/g, '<br>');
var html =
`<div class="post">
// 省略(完成コード参照)
<div class="middle">
<div class="post-content">
${ content} <--------定義したcontentに変えます
</div>
<div class="post_files">
${filesHTML}
</div>
// 省略(完成コード参照)
</div>`
return html;
}
// 省略
});
これでajaxで表示するコードが完成しました!
##5. 完成コード
$(function () {
function appendHTML(file) {
var fileHTML = `<object class="post-files__file" data="${file.file.url}"></object>`
return fileHTML;
}
function buildHTML(post, filesHTML) {
var content = post.content.replace(/\n|\r\n|\r/g, '<br>');
var html =
`<div class="post">
<div class="top">
<img id="avater" src="${ post.user_avater}">
<div class="top__right">
<div class="top__userinfo">
<div class="top__userinfo--user-name">
${ post.user_name}
</div>
<div class="top__userinfo--datetime">
${ post.created_at}
</div>
</div>
</div>
</div>
<div class="middle">
<div class="post-content">
${ content}
</div>
<div class="post_files">
${filesHTML}
</div>
<div class="look" id="look_${post.id}">
<div class="looked-count">
<i class="fa fa-check"></i>
0 人
</div>
<a class="look-btn" data-remote="true" rel="nofollow" data-method="post" href="${ post.looks_link}">見ました</a>
</div>
</div>
<div class="bottom">
<a class="comment-link" href="${ post.post_link}"><i class="fa fa-comment-dots"></i>
コメントする
</a>
</div>
</div>`
return html;
}
$(document).on('change keyup keydown paste cut',
'#textarea', function () {
if ($(this).outerHeight() > this.scrollHeight) {
$(this).height(1)
}
while ($(this).outerHeight() < this.scrollHeight) {
$(this).height($(this).height() + 1)
}
});
$('#new_post').on('submit', function (e) {
e.preventDefault()
var formData = new FormData(this);
var url = $(this).attr('action');
$.ajax({
url: url,
type: "POST",
data: formData,
dataType: 'json',
processData: false,
contentType: false
})
.done(function (data) {
var filesHTML = '';
if (data.post_files != 0) {
data.post_files.forEach(function (file) {
filesHTML += appendHTML(file);
})
}
var html = buildHTML(data, filesHTML);
$('.posts').prepend(html);
$('form')[0].reset();
$('.submit-btn').prop('disabled', false);
})
.fail(function () {
alert("投稿に失敗しました\nリロードしてください");
$('.submit-btn').prop('disabled', false);
})
});
});
参考サイト
- jQueryで自動リサイズする textarea を数行で実装する方法
- JavaScript:HTMLテキストエリアに改行を追加する方法は?
- ancestryによる多階層構造データを用いて、動的カテゴリーセレクトボックスを実現する~Ajax~
なぜancestry?って思われた方もいると思います。
実は、プログラミングスクールでフリマアプリをチーム開発していた際に、他のメンバーがこのカテゴリー選択機能を実装していました。
そのコードを見て、appendOptionメソッド
の仕組みが画像の複数投稿のajaxにも使えるのではないか!?と思い、実装してみた次第です。
というわけなので、参考サイトにそのカテゴリー選択機能のサイトを入れておきました。
もっと良い実装の仕方をご存知の方、ぜひコメントください!!
最後までお読みいただきありがとうございました!