DBから取り出したINT型フィールドを、星の数として表示する機能を実装する


はじめに

AmazonなどのECサイトで、商品に対して星をつけて商品の評価をしているのを、
見たことありませんか?

「この商品は5つ星!これは3つ星!」のような。
アラビア数字で「これは何点」と表記されているよりも、すごくすごくわかりやすいですよね。

背景

筆者はWebフレームワークの勉強の課題として、
「読んだ技術書を評価してリストアップする」Webアプリケーションを製作中なのですが、
データベースに登録した評価カラムの数字を取り出して、その内容を星の個数として反映することを目指しました。

注意

  • 問題解決からしばらく経ってからの投稿なので、データベースの中身などに矛盾が発生しています。
    (例えば、対応する技術書の評価の数がスクリーンショットごとに異なるとか。)

  • 上の例のような、星3.5個というような小数は想定していません。
     あくまで星1, 2, 3, 4, 5個としての実装です。

開発環境

  • Node.js 12.16.3
  • Express 4.17.1
  • MySQL 8.0.19

実装したいもの

  • 星の数は常に5つ表示する
  • 評価数に応じて星の色をオレンジにすることで、評価点を確認できるようにする
  • 評価数に満たない部分は灰色の星として表示する
  • これらによって、「評価点の最大値がいくらか」「この本の評価点がいくつか」の可読性を一気に向上させる

解決方法

まずヴァニラなHTML・CSSを用意する

まず、星を表示するソースコードを用意する必要があります。

こちらを参考にしました。

改変したところ:uncheckedというクラスも用意した(文字色をgreyに変更する)

ソースコード


<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">

<span class="fa fa-star checked"></span>
<span class="fa fa-star checked"></span>
<span class="fa fa-star checked"></span>
<span class="fa fa-star unchecked"></span>
<span class="fa fa-star unchecked"></span>



.checked {
    color: orange;
}
.unchecked {
    color: grey;
}

表示結果

実装してみる

データベースの中身

このテーブルの"good"というカラムが評価(星の数)に対応します。

実装前のソースコードとスクリーンショット

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 });
  });
});

inex.ejs

      <table>
        <tr>
          <th>タイトル</th>
          <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><%= value.good %></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>

実装後のソースコードとスクリーンショット(そして補足コメント)

app.js

const maxStar = 5;

app.get("/", (req, res) => {
  const sql = "select * from book";
  connection.query(sql, function (err, result, fields) {
    if (err) throw err;
    books = [];
    for (book of result) {
      book.colored = book.good;  // 評価の数(オレンジ色の星の数)
      book.uncolored = maxStar - book.good;  // 灰色の星の数
      books.push(book);
    }
    res.render("index", { book: books }); 
  });
});
index.ejs

      <table>
        <tr>
          <th>タイトル</th>
          <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>
            <% for (let i = 0; i < value.colored; i++){ %>
            <span class="fa fa-star checked"></span>
            <% } %> <% for (let i = 0; i < value.uncolored; i++){ %>
            <span class="fa fa-star unchecked"></span>
            <% } %>
          </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>

app.jsについて

実装前では「評価の数」だけあればよかったのですが、
今回は「オレンジ色に染まった星の数」「色がついていない(ように見える灰色の)星の数」という2つの変数が必要になります。
データベースからレコードを取り出したあと、
評価数の最大値(5)から、goodカラムの数を引くことで、灰色の星の数を取得し、
それらをまとめて初期化した配列 books に挿入、index.ejsへレンダリングすることで解決を図っています。

index.ejsについて

ejsファイルは通常HTMLで用いるJavaScriptだけでなく、
app.jsから渡されたデータを利用したJavaScriptを埋め込むことができます。

星の数について使った部分は以下です。
colored変数に格納された数だけforループしオレンジ色の星を表示、
uncolored変数に格納された数だけforループし、灰色の星を表示させます。

            <% for (let i = 0; i < value.colored; i++){ %>
            <span class="fa fa-star checked"></span>
            <% } %> <% for (let i = 0; i < value.uncolored; i++){ %>
            <span class="fa fa-star unchecked"></span>
            <% } %>