[Express]DBのデータを利用しつつイベントハンドラの実行、関数の定義・実行をする


はじめに

所有する技術書を登録(追加)、内容の更新、削除するWebアプリケーションを実装した。

そこから、削除機能について「本当に削除していいですか?」という確認を行うプロセスを追加したかった。

作ったのはどんなアプリなの

まだまだ拡張の余地はあるが、現時点での完成形を紹介する。
DBに登録されているデータを表示しているが、それを削除する様子。

テーブルの中身はだいたいこんな感じ

最終的なソースコードの一部

app.js
app.get("/", (req, res) => {
  const sql = "select * from book";
  connection.query(sql, function (err, result, fields) {
    if (err) throw err;
    res.render("index", { book: result }); 
  });
});
index.ejs
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>User list</title>
    <link rel="stylesheet" href="index.css" />
  </head>
  <body>
    <script>
      function disp(title) {
        if (window.confirm("削除しますか?")) {
          location.href = "/delete/" + title;
        } else {
          window.alert("キャンセルされました");
        }
      }
    </script>
    <h1>蔵書管理システム</h1>
    <h2>蔵書リスト</h2>
    <table>
      <tr>
        <th>タイトル</th>
        <th>著者</th>
        <th>出版社</th>
        <th>更新</th>
        <th>削除</th>
      </tr>
      <% book.forEach(function (value) { %>
      <tr>
        <td class="title"><%= value.title %></td>
        <td><%= value.author %></td>
        <td><%= value.publisher %></td>
        <td><a href="/edit/<%= value.title %>">更新</a></td>
        <td>
          <a
            href="/delete/<%= value.title %>"
            onClick="disp('<%= value.title %>'); return false;"
            >削除</a
          >
        </td>
      </tr>
      <% }); %>
    </table>

    <h2>蔵書を追加する</h2>

    <a href="/add"><h3>追加</h3></a>
  </body>
</html>

ここまでが、なんやかんや頑張った結果、
所属コミュニティのみなさまにたいへんお世話になった結果、正しく動くようになったソースコード。

ここから、間違えたソースコードやそこから改善していったプロセスを書いていく。

間違った(間違えていた)ソースコード

index.ejs

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>User list</title>
    <link rel="stylesheet" href="index.css" />
  </head>
  <body>
      <%= function disp() {
        if (window.confirm("削除しますか?")) {
          location.href = "/delete/" + title;
        } else {
          window.alert("キャンセルされました");
        }
      } %>
    <h1>蔵書管理システム</h1>
    <h2>蔵書リスト</h2>
    <table>
      <tr>
        <th>タイトル</th>
        <th>著者</th>
        <th>出版社</th>
        <th>更新</th>
        <th>削除</th>
      </tr>
      <% book.forEach(function (value) { %>
      <tr>
        <td class="title"><%= value.title %></td>
        <td><%= value.author %></td>
        <td><%= value.publisher %></td>
        <td><a href="/edit/<%= value.title %>">更新</a></td>
        <td>
          <a
            href="/delete/<%= value.title %>"
            onClick="disp(<%= value.title %>); return false;"
            >削除</a
          >
        </td>
      </tr>
      <% }); %>
    </table>

    <h2>蔵書を追加する</h2>

    <a href="/add"><h3>追加</h3></a>
  </body>
</html>

正しく動いたものと比較して、どこが異なっているか(間違っているか)確認するのは大変なので、簡単に説明する。
間違ったほうでは、ソースコード上部のfunctionの定義に関してこのように書いている。

      <%= function disp() {
        if (window.confirm("削除しますか?")) {
          location.href = "/delete/" + title;
        } else {
          window.alert("キャンセルされました");
        }
      } %>

そして、ソースコード下部の、削除機能をもつリンクタグ部分に関してはこのように。

          <a
            href="/delete/<%= value.title %>"
            onClick="disp(<%= value.title %>); return false;"
            >削除</a
          >

で、なにがアカンのや

前者のfunctuon disp()について、2つの問題点。

EJSファイルというのは、HTMLソースコードにJavaScriptを、
というよりはサーバーサイドとしてのNode.jsで定義した内容を流用し、組み込めるようにしたものだと認識している。
(HTMLファイルでもJS組み込めるからね)

まず1つ目。
<script></script>で囲うだけでよかった。
EJSファイル特有の<%= hoge %>というような表現で、JavaScriptの関数定義を表現する必要はない。
(サーバーサイドから内容を引っ張ってきているわけではないので、あとになってみればそりゃそうだと思う。)

次に、2つ目。
関数dispには引数が必要だった。
どの要素に対して効果を及ぼすのか、ということを設定してあげないと正しく機能するはずがない。
このあたりはイベントハンドラ周辺の知識が欠落していたため気づかなかった。
ここではDBから取ってきたデータを流用し、「この名前に一致するデータを、指定した要素として処理してよ」という風に命令を書いてやる必要がある。
ここでは関数の定義なので、渡す仮引数をtitleとする。

そういうわけで、正しいソースコードは以下。

    <script>
      function disp(title) {
        if (window.confirm("削除しますか?")) {
          location.href = "/delete/" + title;
        } else {
          window.alert("キャンセルされました");
        }
      }
    </script>

後者の削除機能について

ルーティングを構築のところだったり、
(つまりhref=...の部分)

どの要素に対してonClickというイベントハンドラを処理させるか、ということだったりについて、

これらを達成するにはDBからデータを取って、その内容を利用して(<%= %>という形で表現して)ソースコードを完成させる必要があるのだけれど、
どこでは変数名として扱って、どこでは文字列として扱うのかということを意識しないといけない。

ここではGoogleChromeのデベロッパーツールや、ソースコードの表示(Ctrl + U)が非常に役に立った。
これを使えば、DBから引っ張ってきたデータが、どういう形としてHTMLに出力されているかが一発で分かる。



 例

ここでは参照するURL(ルーティング)は文字列として処理され、
onClickのイベントハンドラ(の引数)は変数名として処理されていることが把握できる。

さて、ルーティング構築に関しては、<%= %>で囲うことで本のタイトルを変数として指定しないといけない。

逆に、onClickのイベントハンドラでは、文字列として本のタイトルを指定しないといけない。
「DB参照して、こういう名前のデータを探してきてくれや」という意味で引数を指定しているのだと思うので、そりゃ文字列とするべきだわなと思った。
イベントハンドラはまだよくわかっていないので、もしかするとこれに関しては盛大に勘違いした解釈かもしれない。
でもこうすると動いた。

そういうわけで、正しいソースコードは以下。

          <a
            href="/delete/<%= value.title %>"
            onClick="disp('<%= value.title %>'); return false;"
            >削除</a
          >

開発環境

  • Node.js 12.16.3
  • Express 4.17.1
  • MySQL 8.0.19

参考

(EJSでコンポーネントのインクルードや値の受け渡しなどを試してみる
JavaScript/ウィンドウ/確認ダイアログを表示する - TAG index
Express.js(node.js)からMySQLへの接続とCRUD操作