1. はじめに
RailsでAjaxを実装するには、jQueryを使うのが便利です。
しかし、ブラックボックスが多すぎて、何をやっているのか、いまいちよく分かりません。
そこで、データの受け渡しの状況を確認するため、jQueryで記述したAjax処理を、改めてJavaScriptのみを用いて記述し直してみました。
以下、jQuery及びJavaScriptのコードを比較しつつ、使用したメソッドなどについて記録しておきます。
なお、あくまで、学習のための記事なので、基本的な内容しか書いていません。
お気付きのことがあれば、ご指摘などをいただけると幸いです。
この記事で対象とするHTTPメソッドは、POSTメソッドとDELETEメソッドです。
turbolinksは無効にしています。
実行した環境
- Rails 5.2.4.1
- Ruby 2.5.1
- jQuery 1.12.4
- jquery-rails 4.3.5
- (※ turbolinksは無効にしています)
コードの記載にあたっては、次のような簡易ツールを作って、挙動を確認しています。
ビューファイルは、次のように記載しています。
<h4>メモアプリ(サンプル)</h4>
<div class="form">
<% if user_signed_in? %>
<%= form_with(model: @note, class: "note_form", id: "note_input", local: true) do |form| %>
<%= form.text_area :body, class: "note_form-text" %>
<%= form.submit "メモを登録", class: "note_form-btn" %>
<% end %>
<% end %>
</div>
<div class="notes">
<% @notes.each do |note| %>
<div class="note" id="note<%= note.id %>">
<span class="note_name">
投稿者:<%= note.user.name %>
</span>
<% if user_signed_in? && note.user_id == current_user.id %>
<%= link_to "削除", note_path(note.id) ,class: "note_delete", method: :delete %>
<% end %>
<%= simple_format note.body, class: "note_body"%>
</div>
<% end %>
</div>
2. 投稿(POSTメソッド)についてのAjaxのコード
まずは、POSTメソッド(投稿)についてのAjax処理です。
2-1. jQueryでAjaxを記載(POSTメソッド)
まずは、一般的な、jQueryを使用したサンプルコードです。
データの受け渡しの状況を確認するため、FormDataは使用していません。
$(function() {
// 追加するDOMノード(HTMLデータ)を生成する関数
function createHTML(note) {
let html = `<div class="note" id="note${note.id}">
<span class="note_name">投稿者:${note.user_name}</span>
<a class="note_delete" rel="nofollow" data-method="delete" href="/notes/${note.id}">削除</a>
<p class="note_body">${note.body}</p>
</div>`
return html;
}
// メモ投稿(POSTメソッド)の処理
$("#note_input").on("submit", function(e) {
e.preventDefault(); // デフォルトのイベント(HTMLデータ送信など)を無効にする
let inputText = $(".note_form-text").val(); // textareaの入力値を取得
let url = $(this).attr("action"); // action属性のurlを抽出
$.ajax({
url: url, // リクエストを送信するURLを指定
type: "POST", // HTTPメソッドを指定(デフォルトはGET)
data: { // 送信するデータをハッシュ形式で指定
note: {body: inputText}
},
dataType: "json" // レスポンスデータをjson形式と指定する
})
.done(function(data) {
let html = createHTML(data); // 受信したデータ(data)を元に追加するURLを生成(createHTML関数は冒頭で定義)
$(".notes").append(html); // 生成したHTMLをappendメソッドでドキュメントに追加
$(".note_form-text").val(""); // textareaを空にする
})
.fail(function() {
alert("error!"); // 通信に失敗した場合はアラートを表示
})
.always(function() {
$(".note_form-btn").prop("disabled", false); // submitボタンのdisableを解除
$(".note_form-btn").removeAttr("data-disable-with"); // submitボタンのdisableを解除(Rails5.0以降はこちらも必要)
});
});
});
コントローラーは、次のように記載しています。
class NotesController < ApplicationController
def index
@note = Note.new
@notes = Note.includes(:user)
end
def create
@note = Note.new(note_params)
if @note.save
respond_to do |format|
format.html { redirect_to root_path }
format.json { render json: { body: @note.body, user_name: @note.user.name, user_id: @note.user_id, id: @note.id } }
end
end
end
def destroy
@note = Note.find(params[:id])
if @note.destroy
respond_to do |format|
format.html { redirect_to root_path }
format.json { render json: { id: params[:id] } }
end
end
end
private
def note_params
params.require(:note).permit(:body).merge(user_id: current_user.id)
end
end
2-1-1. jQueryにおけるAjax通信の基本的な記載項目
備忘としてjQueryのAjaxについても、ざっと概要のみ紹介しておきます。
2-1-1-1. $.ajax({ })
jQueryでAjax通信をする場合に、送信データとして記載する主な内容は次のとおりです。
項目 | 内容 |
---|---|
url | リクエストを送信するURLを指定(formタグのaction属性にあるURLを指定します) |
type | HTTPメソッドを指定(デフォルトはGET) |
data | 送信するデータをハッシュ形式で指定 |
dataType | サーバから返信されるデータの形式を指定 |
●参考サイト(本記事の全般で参考にさせていただいています)
jQuery逆引きリファレンス:一般的なAjax通信を実装するには?
JavaScript 日本語リファレンス jQuery $.ajax()
2-1-1-2. .done(function(data){ })
.doneの部分には、通信が成功した場合の処理を記載します。
第一引数のdataには、受信したデータが格納されています。
2-1-1-3. .fail(function(){ })
.failの部分には、通信が失敗した場合の処理を記載します。
2-1-1-4. .always(function(){ })
.alwaysの部分には、通信の成功、失敗に関わらず行う処理を記載します。
なお、この.always(function(){ })
には「submitボタンが2回目以降押せなくなる」という元々の仕様を解除するためのコードを記載しています。
.always(function() {
$(".note_form-btn").prop("disabled", false); // submitボタンのdisableを解除
$(".note_form-btn").removeAttr("data-disable-with"); // submitボタンのdisableを解除(Rails5.0以降はこちらも必要)
})
submitボタンのdisableを解除するためには、上記の1つ目の処理のように$(セレクタ名).prop("disabled", false)
と記載します。
ただし、Railsの新しいバージョン(Rails5.0以降)では、上記のコードを記載しただけでは、submitボタンの無効が解除されません。
その理由は、ボタンの2度押しを回避するために、次のようにdata-disable-with
という属性が自動で追加される仕様となっているためです。
<input type="submit" name="commit" value="メモを登録" class="note_form-btn" data-disable-with="メモを登録" disabled>
これについては、$(セレクタ名).removeAttr("data-disable-with")
というように、data-disable-with
属性自体を消去するコードを追加することでsubmitボタンがロックされなくなります(参照記事:[Rails5] submitタグでAjaxを使うと2回目以降に無効になる)。
●参考サイト
Rails で JavaScript を使用する / 3.4 入力を自動で無効にする
2-1-2. セキュリティトークンについて
jQueryでAjaxを記述する場合は、本来、セキュリティトークンは意識しなくとも良いのですが、JavaScriptでコードを記載する場合に必要になりますので先に触れておきます。
Railsにおいて、POSTメソッド等でサーバにリクエストを送信する場合は、サーバからクライアントあてに発行されているセキュリティトークン(ワンタイムパスワードのようなもの)を送信データと共に送らなければ、リクエストは受け付けられない仕様になっています。
しかし、今回のサンプルコードでは、特にセキュリティトークンの送信について記述していません。
下のようにログを確認しても、パラメータとしては、Parameters: {"note"=>{"body"=>"こんにちは!"}}
というように、1つのみのデータしか送られていません。
Started POST "/notes" for ::1 at 2020-01-19 12:30:52 +0900
Processing by NotesController#create as JSON
Parameters: {"note"=>{"body"=>"こんにちは!"}}
このように、明示的にセキュリティートークンの送信を指定しなくても、リクエストは正常に処理されています。
なぜこのようにリクエストが正常に成立するかと言うと、jQueryで処理する場合は、サーバにリクエストを送信する際のリクエストヘッダに、自動的にセキュリティトークンが付加されているからです(JavaScriptでは自動的に付加されません)。
実際のリクエストヘッダは、次のようになっています。上から7行目のX-CSRF-Token:
のところがセキュリティトークンに当たります。
POST /notes HTTP/1.1
Host: localhost:3000
Connection: keep-alive
Content-Length: 69
Accept: application/json, text/javascript, */*; q=0.01
Origin: http://localhost:3000
X-CSRF-Token: gHVpLnJtIFjVzl5VsSESArnw7+sNU67AenEoa29eALi3s9EPl+O5VbM8TnE1QgrA1PbS4Avhdg9atdz2rDcJhg==
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.117 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
(以下略)
リクエストヘッダの確認方法は、「HTTPレスポンスヘッダ・リクエストヘッダ情報をウェブブラウザで表示・確認する方法」でご確認ください。
●参考サイト
Rails で JavaScript を使用する / 6 AjaxのCSRF(Cross-Site Request Forgery)トークン
2-1-3. (参考)FormDataを使用した場合のコード
参考のため、FormDataを使用した場合の記載も掲載しておきます。
省略した部分は、先に記載している「FormDataを使用しない場合」と同様です。
$(function() {
// (省略)
$("#note_input").on("submit", function(e) {
e.preventDefault();
let formData = new FormData(this); // FormDataを作成
let url = $(this).attr("action");
$.ajax({
url: url,
type: "POST",
data: formData, //FormDataをそのまま渡せば良い(必要な"note[body]"と"authenticity_token"を含む)
dataType: "json",
processData: false, //FormDataを使用した場合に必要となる
contentType: false //FormDataを使用した場合に必要となる
})
.done(function(data) {
// (以下省略)
});
});
});
FormDataを使用した場合に、どのようなデータが送信されているかを確認しておきます。
下記のログに表示されているパラメータ(3行目のParameters:
のところ)には、3つのデータが含まれています。
これは、formタグの中に記載された内容を、そのまま送信しているようです。
2つ目にある"authenticity_token"
は、formタグ内に含まれているセキュリティトークンです。
中身の文字列はこの次に掲載しているリクエストヘッダと全く同じです。
Started POST "/notes" for ::1 at 2020-01-19 13:27:54 +0900
Processing by NotesController#create as JSON
Parameters: {"utf8"=>"✓", "authenticity_token"=>"FcqVGUaR69OzydUk9hwJRleLmX0z6auwkgd+NSy1RXwiDC04ox9y3tU7xQByfxGEOo2kdjVbc3+yw4qo79xMQg==", "note"=>{"body"=>"こんばんは!"}}
リクエストヘッダでも、7行目のところにX-CSRF-Token:
とあり、セキュリティトークンが付加されています。
POST /notes HTTP/1.1
Host: localhost:3000
Connection: keep-alive
Content-Length: 446
Accept: application/json, text/javascript, */*; q=0.01
Origin: http://localhost:3000
X-CSRF-Token: FcqVGUaR69OzydUk9hwJRleLmX0z6auwkgd+NSy1RXwiDC04ox9y3tU7xQByfxGEOo2kdjVbc3+yw4qo79xMQg==
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.117 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryPiYVoZWTHjJdIdbK
(以下略)
FormDataでは、以上のようにパラメータ及びリクエストヘッダの両方にセキュリティトークンが付加されています。
実際の挙動を確認しましたところ、どちらか一方にセキュリティトークンがあれば、リクエストは成功するようです。
2-2. JavaScriptでAjaxを記載(POSTメソッド)
前置きが長すぎましたが、ここからが本題です。
JavaScriptでPOSTメソッドを実装すると次のようなコードとなります。
window.addEventListener("load", function() {
let token = document.getElementsByName("csrf-token")[0].content; //セキュリティトークンの取得
// 追加するDOMノード(HTMLデータ)を生成する関数
function createHTML(note) {
let strHtml = '<div class="note" id="note' + note.id + '">' +
'<span class="note_name">投稿者:' + note.user_name + '</span>' +
'<a class="note_delete" rel="nofollow" data-method="delete" href="/notes/' + note.id + '">削除</a>' +
'<p class="note_body">' + note.body + '</p>' +
'</div>';
let divElm = document.createElement("div"); // divタグを持つノードを作成
divElm.innerHTML = strHtml; // 文字列(strHtml)をinnerHTMLとして代入する際にDOMノードに変換される
return divElm.children[0]; // 子要素を返す(必要なDOMノードが戻り値となる)
};
// メモ投稿(POSTメソッド)の処理
document.getElementById("note_input").addEventListener("submit", function(e) {
e.preventDefault(); // デフォルトのイベント(HTMLデータ送信など)を無効にする
//送信データの生成
let inputText = document.getElementsByClassName("note_form-text")[0].value; // textareaの入力値を取得
let url = document.getElementById("note_input").getAttribute("action") + ".json"; // 末尾に[.json]を追加してレスポンスデータのフォーマットをjson形式に指定
let hashData = { // 送信するデータをハッシュ形式で指定
note: {body: inputText} // 入力テキストを送信
// authenticity_token: token // セキュリティトークンの送信(ここから送信することも可能)
};
let data = JSON.stringify(hashData); // 送信用のjson形式に変換
// Ajax通信を実行
let xmlHR = new XMLHttpRequest(); // XMLHttpRequestオブジェクトの作成
xmlHR.open("POST", url, true); // open(HTTPメソッド, URL, 非同期通信[true:default]か同期通信[false]か)
xmlHR.responseType = "json"; // レスポンスデータをjson形式と指定
xmlHR.setRequestHeader("Content-Type", "application/json"); // リクエストヘッダーを追加(HTTP通信でJSONを送る際のルール)
xmlHR.setRequestHeader("X-CSRF-Token", token); // リクエストヘッダーを追加(セキュリティトークンの追加)
xmlHR.send(data); // sendメソッドでサーバに送信
// 受信したデータの処理
xmlHR.onreadystatechange = function() {
if (xmlHR.readyState === 4) { // readyStateが4になればデータの読込み完了
if (xmlHR.status === 200) { // statusが200の場合はリクエストが成功
let note = xmlHR.response; // 受信したjsonデータを変数noteに格納
let html = createHTML(note); // 受信データを元にHTMLを作成
document.getElementsByClassName("notes")[0].appendChild(html); // 作成したHTMLをドキュメントに追加
document.getElementsByClassName("note_form-text")[0].value = ""; // テキストエリアを空白に戻す
} else { // statusが200以外の場合はリクエストが適切でなかったとしてエラー表示
alert("error");
}
document.getElementsByClassName("note_form-btn")[0].disabled = false; // submitボタンのdisableを解除
document.getElementsByClassName("note_form-btn")[0].removeAttribute("data-disable-with"); // submitボタンのdisableを解除(Rails5.0以降はこちらも必要)
}
};
}, false);
});
●参考サイト
基本的なことは、全般的に次の記事を参考にさせていただきました。
JavascriptのAjaxについての基本まとめ
2-2-1. ノード(Elementオブジェクト)の取得等
最初に、jQueryとJavaScriptの記述の違いとして、基本的なところを記録しておきます。
上記の、JavaScriptのサンプルコードでは、ノード(Elementオブジェクト)の取得等において、次のようなメソッドを使用しています。
対比が分かりやすいように、表の右側には、対応するjQueryの記述を入れました。
項番 | 内容 | JavaScript | jQueryt |
---|---|---|---|
1 | ID名から取得 | document.getElementById("note-id") | $("#note-id") |
2 | class名から取得 | document.getElementsByClassName("note_form-text")[0] | $(".note_form-text") |
3 | name名から取得 | document.getElementsByName("csrf-token")[0] | $("*[name=csrf-token]") |
4 | 属性値を取得(getAttributeメソッド) | document.getElementById("note_input").getAttribute("action") | $("#note_input").attr("action") |
5 | ページ全体の読込みができたことの確認 | window.addEventListener("load", function() {}) | $(function() {}) |
class名、name名を指定してElementオブジェクトを取得する場合、jQueryでは最初に見つかったElementオブジェクトを返しますが、JavaScriptでは該当する全ての要素を返すため、要素を指定する番号[0]を付すことが必要です。
一方、id名を指定してElementオブジェクトを取得する場合は、結果は1つのみ(idは原則として1つしか使用されない)ですので、要素の番号指定は不要です。
idによる指定では、getElementByIdというように、[Element]と単数形になっていることからも、取得するノード数が1つであることが分かります。
2-2-2. DOMノード(HTMLデータ)の生成
ページに追加するDOMノード(HTMLデータ)作成に使用しているメソッドについて、jQueryと比較しつつ簡単にまとめておきます。
2-2-2-1 jQueryの場合
まず、jQueryでは、次のように、バッククオートでHTML文を囲むことで(テンプレートリテラル)、簡単にDOMノード(JavaScriptで操作できるAPI)の生成ができます(シングルクオートではないことに注意)。
文字列中に変数を挿入する場合は、${ }
の中に変数を入れれば反映されます。
function createHTML(note) {
let html = `<div class="note" id="note${note.id}">
<span class="note_name">投稿者:${note.user_name}</span>
<a class="note_delete" rel="nofollow" data-method="delete" href="/notes/${note.id}">削除</a>
<p class="note_body">${note.body}</p>
</div>`
return html;
}
2-2-2-2 JavaScriptの場合(サンプルコードの方法)
JavaScriptの場合はHTML文を一般的な文字列として扱い、次のように、シングルクオート(又はダブルクオート)で囲んで、変数は+
を用いて結合します(※下記に追記あり)。
function createHTML(note) {
let strHtml = '<div class="note" id="note' + note.id + '">' +
'<span class="note_name">投稿者:' + note.user_name + '</span>' +
'<a class="note_delete" rel="nofollow" data-method="delete" href="/notes/' + note.id + '">削除</a>' +
'<p class="note_body">' + note.body + '</p>' +
'</div>';
let divElm = document.createElement("div"); // divタグを持つノードを作成
divElm.innerHTML = strHtml; // 文字列(strHtml)をinnerHTMLとして代入する際にDOMノードに変換される
return divElm.children[0]; // 子要素を返す(必要なDOMノードが戻り値となる)
};
そして、データが文字列のままではJavaScript等による操作ができないため、これをDOMノードに変換する必要があります。
これは、innerHTMLに文字列を代入すると自動でDOMに変更されることを利用して、DOMノードに変換を行っています(divElm.innerHTML = strHtml
のところです)。
<追記>2021/3/27
JavaScriptでも、HTML文の記述にテンプレートリテラルを使用することができます(koffee7さんのコメントを受けて追加)。
コードは次のようになり、jQueryとの違いはDOMノードの変換のところのみとなります。
let strHtml = `<div class="note" id="note${note.id}">
<span class="note_name">投稿者:${note.user_name}</span>
<a class="note_delete" rel="nofollow" data-method="delete" href="/notes/${note.id}"> 削除</a>
<p class="note_body">${note.body}</p>
</div>`
let divElm = document.createElement("div"); // divタグを持つノードを作成
divElm.innerHTML = strHtml; // 文字列(strHtml)をinnerHTMLとして代入する際にDOMノードに変換される
return divElm.children[0];
2-2-2-3 JavaScriptで1つずつHTML要素を作成する場合
参考ですが、JavaScriptのメソッドを使用して、1つずつHTML要素を作成していくと次のようになります。
私は、最初はこのように作成していました(知識不足であったためです)。
このように記載した場合は、コードが無駄に長くなりますが、innerHTMLで変換するよりも、若干処理が早いというメリットがあります。
function createHTML(note) {
// 必要となるタグ及びテキストノードを生成
let divElm = document.createElement("div");
let spanElm = document.createElement("span");
let aElm = document.createElement("a");
let pElm = document.createElement("p");
let nameText = document.createTextNode("投稿者:" + note.user_name);
let deleteText = document.createTextNode(" 削除");
let bodyText = document.createTextNode(note.body);
// 各タグに属性・属性値を付加
divElm.setAttribute("class", "note");
divElm.setAttribute("id", "note" + note.id);
spanElm.setAttribute("class", "note_name");
aElm.setAttribute("class", "note_delete");
aElm.setAttribute("rel", "nofollow");
aElm.setAttribute("data-method", "delete");
aElm.setAttribute("href", "/notes/" + note.id);
// aElm.addEventListener("click", function(e) { deleteHTMLEvent(e, aElm) }, false); //ここは削除メソッド実装時に使用
pElm.setAttribute("class", "note_body");
// ノードの結合(各子要素にテキストを追加)
spanElm.appendChild(nameText);
aElm.appendChild(deleteText);
pElm.appendChild(bodyText);
// ノードの結合(親要素に子要素を追加)
divElm.appendChild(spanElm);
divElm.appendChild(aElm);
divElm.appendChild(pElm);
return divElm;
};
ここで使用しているDOMノード(DOM element)を生成するメソッドをまとめると、次のとおりです。
項番 | 項目 | 構文 | 具体例 |
---|---|---|---|
1 | 要素(タグ)の生成 | document.createElement(タグ名) | document.createElement("div") |
2 | テキストノードの生成 | document.createTextNode(テキスト文) | document.createTextNode("こんにちは!") |
3 | 属性の追加 | 要素ノード.setAttribute(属性名, 属性値) | divElm.setAttribute("class", "comment") |
4 | 子要素として追加 | 親ノード.appendChild(子ノード) | divElm.appendChild(pElm) |
●参考サイト
【JavaScript入門】appendと何が違う?appendChild徹底解説
2-2-3. サーバへのリクエスト送信
let xmlHR = new XMLHttpRequest(); // XMLHttpRequestオブジェクトの作成
xmlHR.open("POST", url, true); // open(HTTPメソッド, URL, 非同期通信[true:default]か同期通信[false]か)
xmlHR.responseType = "json"; // レスポンスデータをjson形式と指定
xmlHR.setRequestHeader("Content-Type", "application/json"); // リクエストヘッダーを追加(HTTP通信でJSONを送る際のルール)
xmlHR.setRequestHeader("X-CSRF-Token", token); // リクエストヘッダーを追加(セキュリティトークンの追加)
xmlHR.send(data); // sendメソッドでサーバに送信
サーバにリクエストを送信するために使用しているオブジェクト及びメソッドは、次のとおりです。
項番 | 項目 | 内容 |
---|---|---|
1 | XMLHttpRequestオブジェクト | サーバとの通信を行うためのオブジェクト(API)。このオブジェクトにより、ページ全体の更新をすることなくサーバとの送受信を行うことができる。 |
2 | XMLHttpRequest.open()メソッド | XMLHttpRequestオブジェクトのメソッド。リクエストの作成に使用する。 |
3 | XMLHttpRequest.responseTypeメソッド | XMLHttpRequestオブジェクトのメソッド。サーバから返信されるデータの形式を指定。 |
4 | XMLHttpRequest.setRequestHeaderメソッド | XMLHttpRequestオブジェクトのメソッド。リクエストヘッダーを追加する。 |
5 | XMLHttpRequest.send()メソッド | XMLHttpRequestオブジェクトのメソッド。引数に指定したデータをサーバに送信する。 |
2-2-3-1. XMLHttpRequestオブジェクト
まず、1つめのXMLHttpRequestオブジェクトですが、これについては、次の説明が直感的に分かりやすいです。
XMLHttpRequestはブラウザ上でサーバーとHTTP通信を行うためのAPIです。名前にXMLが付いていますがXMLに限ったものではなく,HTTPリクエストを投げてテキスト形式かDOMノードでレスポンスを受け取る機能を持っています。(これでできる! クロスブラウザJavaScript入門)
このXMLHttpRequestオブジェクトを介して、サーバへのリクエストを作成してデータのやり取りを行うことになります。
●参考サイト
MDN Web Docs / XMLHttpRequest
2-2-3-2. XMLHttpRequest.open()メソッド
構文:XMLHttpRequest.open(HTTPメソッド, URL, 非同期通信か同期通信か)
このopen()メソッドは、リクエストを作成する場合に使用します。
第1引数でHTTPメソッド、第2引数でURL、第3引数で非同期通信[true]か同期通信[false]かを指定します。第3引数はデフォルトがtrueなので、一般的には省略されているようです。
更に、第4引数(ユーザ名)、第5引数(パスワード)もありますので、詳しくは「MDN Web Docs / XMLHttpRequest.open()」を参照してください。
なお、JSON形式のフォーマットでレスポンスを得たい場合、URLの指定において、末尾に.JSON
を付すことが必要です(他にリクエストヘッダのAccsptで指定する方法もあります。)。
これにより、json形式のフォーマットでレスポンスデータを受け取ることができます(つまり、コントローラのrespond_to
でjsonフォーマットが選択されることになります)。
2-2-3-3. XMLHttpRequest.responseTypeメソッド
responseTypeメソッドでは、サーバから返信されるデータの形式を指定します。
ここでは、返信されるデータ形式として"json"
を指定していますが、そのほか"text"
などの形式の指定ができます(詳細は「MDN Web Docs - XMLHttpRequest.responseType」を参照してください)。
2-2-3-4. XMLHttpRequest.setRequestHeaderメソッド
これは、HTTP通信におけるリクエストヘッダを追加するメソッドです(参考「MDN Web Docs / XMLHttpRequest.setRequestHeader()」)。
xmlHR.setRequestHeader("Content-Type", "application/json"); // リクエストヘッダーを追加(HTTP通信でJSONを送る際のルール)
xmlHR.setRequestHeader("X-CSRF-Token", token); // リクエストヘッダーを追加(セキュリティトークンの追加)
JSON形式のリクエストを送信する場合には、上記1行目のように、Content-Typeヘッダに"application/json"(又は"text/json")というMIMEタイプの指定が必要となります(引用サイト:JSON リクエストとレスポンス)。
2行目のコードは、セキュリティトークンをリクエストヘッダに追加する処理です。
このセキュリティトークンの詳細は、次の項「2-2-4. セキュリティトークンの送信」を参照してください。
●参考サイト
XMLHttpRequestでJSONをPOST
ajaxでpostするときに必要なリクエストヘッダ
2-2-3-5. XMLHttpRequest.send()メソッド
send()メソッドは、リクエストをサーバに送信するメソッドです。送信するデータを引数に指定することができます(参考「MDN Web Docs / XMLHttpRequest.send()」)。
関係部分を抜粋すると、次のようになっています。
let hashData = { // 送信するデータをハッシュ形式で指定
note: {body: inputText} // 入力テキストを送信
};
let data = JSON.stringify(hashData); // 送信用のjson形式に変換
xmlHR.send(data); // sendメソッドでサーバにリクエストを送信
上記のコードでは、まず、ハッシュオブジェクトとしてデータを作成し、これを、JSON.stringifyメソッドを使用して、JSON文字列に変換しています(参考「MDN Web Docs / JSON.stringify()」)。
これにより、引数に指定したデータをJSON形式として送信することができます。
なお、jQueryでは、HTML形式で送信されています。形式はやり易い方を選べば良いと思います。
2-2-4. セキュリティトークンの送信
JavaScriptでAjaxを実装する場合には、セキュリティトークン(サーバから発行されるワンタイムパスワードのようなもの)を意識する必要があります。
自動的にセキュリティトークンが送信されるjQueryの場合とは異なり(前出の「2-1-2. セキュリティトークンについて」を参照)、JavaScriptでAjaxを記述する場合には、サーバへのHTTPリクエストにセキュリティトークンを付加する処理が必要となります。
この処理を書かなければ、POSTメソッドやDELETEメソッドによるリクエストを、サーバ側で受け付けることができません。
サンプルコードから、セキュリティトークンに関する部分を抜粋すると、次のようになっています。
let token = document.getElementsByName("csrf-token")[0].content; //セキュリティトークンの取得
// (中略)
let xmlHR = new XMLHttpRequest(); // XMLHttpRequestオブジェクトの作成
// (中略)
xmlHR.setRequestHeader("X-CSRF-Token", token); // リクエストヘッダーを追加(セキュリティトークンの追加)
上記の最初の行の処理で、ページのHTMLのヘッダ部分からセキュリティトークンの情報を抽出し、最後の行で、リクエストヘッダにセキュリティトークンを追加する処理を行っています。
なお、セキュリティトークンは、ページのHTMLを見れば確認できます。
(HTMLのヘッダ部分に表示されているセキュリティトークン)
<head>
<title>DemoApp</title>
<meta name="csrf-param" content="authenticity_token">
<meta name="csrf-token" content="RqHWJuRG/kobdPDTVdQEaJAl7hwpmfptmuJbamK/+5JxZ24HAchnR32G4PfRtxyq/SPTFy8rIqK6Jq/3odbyrA==">
<!-- 略 -->
</head>
ヘッダ部分の3行目にある<meta name="csrf-token" content="RqHWJuRG/kobdPDTVdQEaJAl7hwpmfptmuJbamK/+5JxZ24HAchnR32G4PfRtxyq/SPTFy8rIqK6Jq/3odbyrA==">
がセキュリティトークンです。
この、content
の属性値を拾ってサーバに送信していると言うことになります。
また、Formタグ(下記)の中にも、name="authenticity_token"
の属性value
に、セキュリティートークンが格納されています。こちらからデータを拾って送信しても同様の結果を得ることができます。
<form id="note_input" class="note_form" action="/notes" accept-charset="UTF-8" method="post">
<input name="utf8" type="hidden" value="✓">
<input type="hidden" name="authenticity_token" value="RqHWJuRG/kobdPDTVdQEaJAl7hwpmfptmuJbamK/+5JxZ24HAchnR32G4PfRtxyq/SPTFy8rIqK6Jq/3odbyrA==">
<textarea class="note_form-text" name="note[body]" id="note_body"></textarea>
<input type="submit" name="commit" value="メモを登録" class="note_form-btn" data-disable-with="メモを登録">
</form>
詳細は、Rails で JavaScript を使用する / 6 AjaxのCSRF(Cross-Site Request Forgery)トークンを参照してください。
2-2-5. サーバからのレスポンスの受信
2-2-5-1. レスポンス処理の構造
JavaScriptにおいては、次のように記述することで、リクエストの成否に合わせて処理が実行されます。
(JavaScriptにおけるレスポンスの処理)
xmlHR.onreadystatechange = function() {
if (xmlHR.readyState === 4) { // readyStateが4になればデータの読込み完了
if (xmlHR.status === 200) { // statusが200の場合はリクエストが成功
// (1) リクエストが成功した場合に行う処理
} else { // statusが200以外の場合はリクエストが適切でなかったとしてエラー表示
// (2) リクエストが成功しなかった場合に行う処理
}
// (3) リクエストの成功・失敗に関わらず行う処理
}
};
下記は、jQueryでのコード記載です。JavaScriptにおける(1)から(3)の処理とほぼ同じ形で対応しています(細かい違いはあるかもしれませんが)。
(jQueryにおけるレスポンスの処理)
.done(function(data) {
// (1) リクエストが成功した場合に行う処理
})
.fail(function() {
// (2) リクエストが成功しなかった場合に行う処理
})
.always(function() {
// (3) リクエストの成功・失敗に関わらず行う処理
});
2-2-5-2. レスポンスデータの取得について
JavaScriptのレスポンスデータは、次のように取得することができます。
(JavaScriptにおけるレスポンスデータの取得)
if (xmlHR.status === 200) { // statusが200の場合はリクエストが成功
let note = xmlHR.response; // XMLHttpRequest.responseメソッドからレスポンスデータを取得できる
// (略)
}
対比として、jQueryにおけるレスポンスデータの取得も以下に記載しておきます。
(jQueryにおけるレスポンスデータの取得)
.done(function(data) {
let note = data; // 第一引数のdataからレスポンスデータを取得できる
// (略)
})
2-2-5-3. readyStateプロパティとstatusプロパティについて
XMLHttpRequest.readyStateプロパティ
これは、XMLHttpRequestオブジェクトのプロパティとなります。
readyStateプロパティは、XMLHttpRequestのインスタンスの状態を0から4の数値で返します。
数値と状態の対比は次のとおりです(引用サイト「MDN Web Docs / XMLHttpRequest.readyState」→こちらを見ていただいた方が正確です)。
戻り値 | 状態 | 内容 |
---|---|---|
0 | UNSENT | XMLHttpRequestのインスタンス作成済み |
1 | OPENED | open()メソッド呼び出し済み |
2 | HEADERS_RECEIVED | send()メソッド呼び出し済み |
3 | LOADING | レスポンスデータの読み込み中 |
4 | DONE | 読み込み完了 |
サンプルコードでは、if (xmlHR.readyState === 4)
という条件を満たした場合に、レスポンスデータの処理等を行うという構造になっています。
XMLHttpRequest.statusプロパティ
このXMLHttpRequest.statusプロパティには、サーバから送信される「HTTPステータスコード」が格納されています(引用サイト「MDN Web Docs / XMLHttpRequest.status」)。
このHTTPステータスコードにより、HTTPリクエストが正常に終了したかどうかが分かります。
下記は今回の記事作成中に見かけたHTTPステータスコードです(ほんの一例です)。
コード | 意味 | 説明 |
---|---|---|
200 | OK | リクエストが成功した |
400 | Bad Request | 無効なリクエストが送信されたなど |
404 | Not Found | リクエストされたリソースが見つからなかった |
422 | Unprocessable Entity | リクエストは正しいがサーバで処理できない |
例えば、セキュリティトークンが送信されていない場合、422のエラーが生じます。
その他、コードの一覧は「MDN Web Docs / HTTP レスポンスステータスコード」などで確認できます。
サンプルコードでは、if (xmlHR.status === 200)
という条件を満たした場合にはリクエスト成功時の処理を記述し、条件を満たさなかった場合にはリクエスト失敗時の処理を記述するという構造になっています。
3. 削除(DELETEメソッド)についてのAjaxのコード
次に、投稿内容を削除するDELETEメソッドについてのAjax通信です。
3-1. jQueryでAjaxを記載(DELETEメソッド)
まず、jQueryでの実装例です。
新たに削除(DELETE)メソッドを追加した部分は、後半の20行分となります。
$(function() {
// 追加するHTMLデータを生成する関数
function createHTML(note) {
let html = `<div class="note" id="note${note.id}">
<span class="note_name">投稿者:${note.user_name}</span>
<a class="note_delete" rel="nofollow" data-method="delete" href="/notes/${note.id}">削除</a>
<p class="note_body">${note.body}</p>
</div>`
return html;
}
// メモ投稿(POSTメソッド)の処理
$("#note_input").on("submit", function(e) {
e.preventDefault(); // デフォルトのイベント(HTMLデータ送信など)を無効にする
let inputText = $(".note_form-text").val(); // textareaの入力値を取得
let url = $(this).attr("action"); // action属性のurlを抽出
$.ajax({
url: url, // リクエストを送信するURLを指定
type: "POST", // HTTPメソッドを指定(デフォルトはGET)
data: { // 送信するデータをハッシュ形式で指定
note: {body: inputText}
},
dataType: "json" // レスポンスデータをjson形式と指定する
})
.done(function(data) {
let html = createHTML(data); // 受信したデータ(data)を元に追加するURLを生成(createHTML関数は冒頭で定義)
$(".notes").append(html); // 生成したHTMLをappendメソッドでドキュメントに追加
$(".note_form-text").val(""); // textareaを空にする
})
.fail(function() {
alert("error!"); // 通信に失敗した場合はアラートを表示
})
.always(function() {
$(".note_form-btn").prop("disabled", false); // submitボタンのdisableを解除
$(".note_form-btn").removeAttr("data-disable-with"); // submitボタンのdisableを解除(Rails5.0以降はこちらも必要)
});
});
// メモ削除(DELETEメソッド)の処理
$(".notes").on("click", ".note_delete", function(e) {
e.preventDefault(); // デフォルトのイベント(リンクURLへの遷移処理など)を無効にする
e.stopPropagation(); // 現在のイベントのさらなる伝播(DELETEメソッドの実行)を止める
let url = $(this).attr("href");
$.ajax({
url: url,
type: "POST", // 原則に従って"DELETE"メソッドを使用しない
data: {
_method: "delete", // ここで"DELETE"メソッドを使用することを指定
},
dataType: "json"
})
.done(function(data) {
$("#note" + data.id).remove(); // レスポンスデータのIDを元に投稿を削除
})
.fail(function(XMLHttpRequest) {
alert(XMLHttpRequest.status);
});
});
});
基本的な形式は、POSTメソッドと変わりません。
異なる部分を中心に、以下、説明を書いていきます。
3-1-1. 動的に追加した要素をクリックする(jQuery)
前提として、イベントリスナーの登録のタイミングについて確認しておきます。
JavaScript(jQuery)において、イベントリスナーの登録は、最初のページ読み込み時に行われます。
それをブラウザ内に登録しておき、イベントの都度、呼び出して実行しているということになります。
そのため、ページを読み込んだ後に、動的に新たに追加された要素には、イベントリスナーとして登録されないことになるため、クリックしても反応しないということになってしまいます。
jQueryでは、この問題を、簡単に解決できるようになっています。
サンプルコード上で、その処理を実現している部分は、次の部分となります。
$(".notes").on("click", ".note_delete", function(e) {
// イベント発火時の処理内容を記載
});
これを構文として書き表すと次のようになります。
$(親要素のセレクタ).on(イベントの種類, 子要素のセレクタ, 関数等のオブジェクト)
この構文において、クリック(他のイベントも同じ)によるイベント発火は、見かけ上、子要素へのクリックに基づき実行されます。
ところが、実際は、親要素へのクリックにより、イベント発火が行われているという仕組みになっています。
そのため、イベント発火の元となる親要素は、最初(ページ読み込み時)に存在している必要がありますが、子要素は、後から追加された動的な要素であっても構わないという仕組みになっています(と理解しています)。
具体的にこのjQueryのメソッドどのような構造なのかについては、下記のサイトを参考にしていただければと思います。
●参考サイト
[jQuery] on() で後から追加した要素にもイベントを定義したい
jQuery write less, do more. / .on()
3-1-2. preventDefault()メソッド及びstopPropagation()メソッド
次に、デフォルトの処理等をキャンセルするpreventDefault()メソッド及びstopPropagation()メソッドについてです。
サンプルコードでは、次のように記載しています。
$(".notes").on("click", ".note_delete", function(e) {
e.preventDefault(); // デフォルトのイベント(リンクURLへの遷移処理)を無効にする
e.stopPropagation(); // 現在のイベントのさらなる伝播(DELETEメソッドの実行)を止める
// (略)
});
これらの記述により、デフォルトのイベント処理をキャンセルすることで、無用の処理を発生させず、Ajaxでの処理との重複などを避けることができます。
以下、個別に見てみます。
3-1-2-1. Event.preventDefault()メソッド
preventDefault()メソッドは、デフォルトのイベント処理をキャンセルして実行しないようにするメソッドです(参照:MDN Web Docs / Event.preventDefault())。
サンプルコードでは、次のaタグにおけるリンク機能が、preventDefault()メソッドによりキャンセルされています。
<a class="note_delete" rel="nofollow" data-method="delete" href="/notes/5">削除</a>
3-1-2-2. event.stopPropagation()メソッド
stopPropagation()メソッドは、現在のイベントの更なる伝播をキャンセルするメソッドです(参照:MDN Web Docs / event.stopPropagation)。
サンプルコードでは、上記aタグの属性data-method="delete"
によるdeleteメソッドの実行が、stopPropagation()メソッドによりキャンセルされています。
なお、全ての削除処理をAjax通信のみで行うのであれば、data-method="delete"
の部分を消してしまうことでも同様の結果が得られると思います(stopPropagation()メソッドがなくてもエラーなく処理が実行できることになります)。
3-1-3. HTTPにおけるDELETEメソッドについて
3-1-3-1. POSTメソッドによる削除
サンプルコードでは、削除(DELETEメソッド)の処理であるにも関わらず、次のように、type
としてPOSTメソッドを指定しています。
$.ajax({
url: url,
type: "POST", // 原則に従って"DELETE"メソッドを使用しない
data: {
_method: "delete", // ここで"DELETE"メソッドを使用することを指定
},
dataType: "json"
})
POSTメソッドを使用して、削除機能を実装する理由は、「jQueryの日本語リファレンス / $.ajax()」にある次の説明のとおりで、DELETEメソッドが全てのブラウザでサポートされている保証がないからです。
キー:type
型:String 初期値:'GET'
リクエストのタイプ("POST"または"GET")を指定します。
注意: PUTやDELETEのような、他のHTTPリクエストメソッドも、ここで指定することが可能ですが、 全てのブラウザでサポートされている保証がありません。
具体的な、コードの記載については、次の記事などを参考とさせていただきました。
[Laravel / jQuery] 非同期(Ajax)でレコードを削除したい
3-1-3-2. DELETEメソッドによる削除
どこまでの環境で動作するかは確認できていませんが、次のようにDELETEメソッドによる記載をしても、Ajaxによる削除を行うことが可能です。
$.ajax({
url: url,
type: "DELETE", // 原則に従って"DELETE"メソッドを使用しない
dataType: "json"
})
3-2. JavaScriptでAjaxを記載(DELETEメソッド)
次に、JavaScriptでの削除(DELETEメソッド)機能の実装例です。
window.addEventListener("load", function() {
let token = document.getElementsByName("csrf-token")[0].content; //セキュリティトークンの取得
// 追加するDOMノード(HTMLデータ)を生成する関数
function createHTML(note) {
let strHtml = '<div class="note" id="note' + note.id + '">' +
'<span class="note_name">投稿者:' + note.user_name + '</span>' +
'<a class="note_delete" rel="nofollow" data-method="delete" href="/notes/' + note.id + '">削除</a>' +
'<p class="note_body">' + note.body + '</p>' +
'</div>';
let divElm = document.createElement("div"); // divタグを持つノードを作成
divElm.innerHTML = strHtml; // 文字列(strHtml)をinnerHTMLとして代入する際にDOMノードに変換される
let deleteElm = divElm.getElementsByClassName("note_delete")[0]; // イベントリスナーを登録する要素を特定する
deleteElm.addEventListener("click", function(e) { deleteHTMLEvent(e, deleteElm) }, false); // イベントリスナーの追加(動的追加)
return divElm.children[0]; // 子要素を返す(必要なDOMノードが戻り値となる)
};
// メモ投稿(POSTメソッド)の処理
let addHTMLEvent = function(e) {
e.preventDefault(); // デフォルトのイベント(HTMLデータ送信など)を無効にする
//送信データの生成
let inputText = document.getElementsByClassName("note_form-text")[0].value; // textareaの入力値を取得
let url = document.getElementById("note_input").getAttribute("action") + ".json"; // 末尾に[.json]を追加して受信データのフォーマットをjson形式として指定
let hashData = { // 送信するデータをハッシュ形式で指定
note: {body: inputText} // 入力テキストを送信
// authenticity_token: token // セキュリティトークンの送信(ここから送信することも可能)
};
let data = JSON.stringify(hashData); // 送信用のjson形式に変換
// Ajax通信を実行
let xmlHR = new XMLHttpRequest(); // XMLHttpRequestオブジェクトの作成
xmlHR.open("POST", url, true); // open(HTTPメソッド, URL, 非同期通信[true:default]か同期通信[false]か)
xmlHR.responseType = "json"; // レスポンスデータをjson形式と指定
xmlHR.setRequestHeader("Content-Type", "application/json"); // リクエストヘッダーを追加(HTTP通信でJSONを送る際のルール)
xmlHR.setRequestHeader("X-CSRF-Token", token); // リクエストヘッダーを追加(セキュリティトークンの追加)
xmlHR.send(data); // sendメソッドでサーバに送信
// 受信したデータの処理
xmlHR.onreadystatechange = function() {
if (xmlHR.readyState === 4) { // readyStateが4になればデータの読込み完了
if (xmlHR.status === 200) { // statusが200の場合はリクエストが成功
let note = xmlHR.response; // 受信したjsonデータを変数noteに格納
let html = createHTML(note); // 受信データを元にHTMLを作成
document.getElementsByClassName("notes")[0].appendChild(html); // 作成したHTMLをドキュメントに追加
document.getElementsByClassName("note_form-text")[0].value = ""; // テキストエリアを空白に戻す
} else { // statusが200以外の場合はリクエストが適切でなかったとしてエラー表示
alert("error");
}
document.getElementsByClassName("note_form-btn")[0].disabled = false; // submitボタンのdisableを解除
document.getElementsByClassName("note_form-btn")[0].removeAttribute("data-disable-with"); // submitボタンのdisableを解除(Rails5.0以降はこちらも必要)
}
};
};
document.getElementById("note_input").addEventListener("submit", addHTMLEvent, false);
// 削除イベントの処理
let deleteHTMLEvent = function(e, noteDelete) {
e.preventDefault();
e.stopPropagation();
let url = noteDelete.getAttribute("href") + ".json"; //末尾に .json を追加することで受信データのフォーマットをjson形式と指定する
// Ajax通信を実行
let xmlHR = new XMLHttpRequest(); //XMLHttpRequestの作成
xmlHR.open("DELETE", url, true); //open(HTTPメソッド, URL, 非同期通信[true:default]か同期通信[false]か)
xmlHR.responseType = "json"; //レスポンスデータを json形式と指定する
xmlHR.setRequestHeader("Content-Type", "application/json"); // リクエストヘッダーを追加(HTTP通信でJSONを送る際のルール)
xmlHR.setRequestHeader("X-CSRF-Token", token); // リクエストヘッダーを追加(セキュリティトークンの追加)
xmlHR.send(); //サーバに送信
// 受信したデータの処理
xmlHR.onreadystatechange = function() {
if (xmlHR.readyState === 4) {
if (xmlHR.status === 200) {
let note = xmlHR.response;
document.getElementById("note" + note.id).remove();
} else {
alert(xmlHR.status);
}
}
};
};
let noteDeletes = document.getElementsByClassName("note_delete")
Array.prototype.forEach.call(noteDeletes, function(noteDelete) {
noteDelete.addEventListener("click", function(e) { deleteHTMLEvent(e, noteDelete) }, false);
});
});
POSTメソッドのサンプルコードに、末尾の27行を追加した上で、その他に若干の修正を行っています。
3-2-1. AjaxによるDELETEメソッドの送信(JavaScript)
Ajax通信を行うために、XMLHttpRequestオブジェクトを使ってサーバと通信するという点は、基本的にPOSTメソッドの場合と変わりません。
該当部分のコードは次のとおりです。
let xmlHR = new XMLHttpRequest(); //XMLHttpRequestの作成
xmlHR.open("DELETE", url, true); //open(HTTPメソッド, URL, 非同期通信[true:default]か同期通信[false]か)
xmlHR.responseType = "json"; //レスポンスデータを json形式と指定する
xmlHR.setRequestHeader("Content-Type", "application/json"); // リクエストヘッダーを追加(HTTP通信でJSONを送る際のルール)
xmlHR.setRequestHeader("X-CSRF-Token", token); // リクエストヘッダーを追加(セキュリティトークンの追加)
xmlHR.send(); //サーバに送信
異なると言えば、send()メソッドの送信時に、引数としてデータを指定していないということくらいでしょうか。
なお、このJavaScriptにおいても、POSTメソッドを使用した削除機能(DELETEメソッド)の実装を試みましたが、設定が上手く行かず、現時点では実装できていません。
3-2-2. 複数の要素にイベントリスナーを適用する方法(JavaScript)
POSTメソッドの実装時に、イベントリスナーを適用するノード(要素、タグ)は、formタグの中のsubmitボタンのクリックイベントの1箇所だけで済みました。
しかし、DELETEメソッドでは、表示されている全ての投稿の削除ボタンにイベントリスナーを設定する必要があります。
複数の投稿の全てに、イベントリスナーを設定するには、次のように、Array.prototype.forEach.callメソッドなどを用いて該当する全ての要素にイベントリスナーを登録することになります。
この方法は、「【JavaScript】イベントリスナを複数要素にまとめて登録する方法」で解説されている内容を使用させていただきました。
let noteDeletes = document.getElementsByClassName("note_delete")
Array.prototype.forEach.call(noteDeletes, function(noteDelete) {
noteDelete.addEventListener("click", function(e) { deleteHTMLEvent(e, noteDelete) }, false);
});
これは、for文で単純にループさせることでも可能です。
なお、コールバック関数(イベント発火により実行される関数)に引数があるため、function(e) { deleteHTMLEvent(e, noteDelete) }
という特殊な書き方になっています。
これについては、「Javascriptでイベントハンドラのコールバック関数に引数を渡す」の記事等を参考にさせていただきました。詳細はリンク先を参照してください。
●参考サイト
配列ライクなオブジェクトをforEachするときのイディオム
JavaScriptでコールバック関数にあらかじめ引数を渡したい!
3-2-3. 動的に追加された要素へのイベントリスナーの適用(JavaScript)
動的に追加された要素(本サンプルではメモ投稿を表示するノード)に、後からイベントリスナーを登録するには、多少の工夫が必要となります。
大事なところは、createHTML関数の中に追加した次の2行です(上から12-13行目)。
let deleteElm = divElm.getElementsByClassName("note_delete")[0]; // イベントリスナーを登録する要素を特定する
deleteElm.addEventListener("click", function(e) { deleteHTMLEvent(e, deleteElm) }, false); // イベントリスナーの追加(動的追加)
これにより、新規投稿のDOMノードを作成するたびに、対象となる要素(削除のリンクを設定したaタグ)に新しいイベントリスナーを動的に追加することができます。
このイベントリスナーで呼び出される関数はfunction(e) { deleteHTMLEvent(e, deleteElm) }
の部分で、これは、ページのロード時に読み込ませる関数と同じものを使用しています。
●参考サイト
[JavaScript] イベント処理を動的に追加する
動的に追加した要素にaddEventListnerを設定する方法
4. おわりに
まとめておきたかった内容は、以上のところです。
挙動は確認しているので、動作はするはずですが、もっと良い書き方や、正しい書き方があるのだろうと思います。
お気付きのことがあれば、ご指摘等をいただけると幸いです。