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

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

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549712/98c4d7be-0648-0225-2212-e722cd00b25d.png)


# 背景

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549712/c7b6f730-059a-59a9-c64f-c9ec3463be9d.png)

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

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


# 開発環境

- Node.js 12.16.3
- Express 4.17.1
- MySQL 8.0.19

# 実装したいもの

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

# 解決方法

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

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

[こちら](https://www.w3schools.com/howto/howto_css_star_rating.asp)を参考にしました。

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

### ソースコード

```html

<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>

```

```css


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

### 表示結果

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549712/b7f46554-3092-d97a-b16c-54c7bf1aef1d.png)


## 実装してみる


### データベースの中身

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549712/df1298a9-bc19-0ced-1128-ac887a095cb0.png)



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


```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>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549712/e3767aea-40f6-9a8c-ece4-2898bebbd5f6.png)




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




```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>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549712/c7b6f730-059a-59a9-c64f-c9ec3463be9d.png)

### 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>
            <% } %>
```