『自分用』テンプレートエンジンを使って簡単なアンケートをつくってみた

##免責事項
この記事は初心者視点でザックリとした説明をしています。正確性に欠ける可能性がございますが、ご了承ください。「明らかに違うよ」ということがありましたら、ご指摘くださると幸いです。

##環境
OS：最新版ではないMacOS
VirtualBox：5.2.26
Vagrant：2.2.6
Ubuntu：ubuntu/bionic64 v20181129.0.0


##目次
1. テンプレートエンジンとは
2. アンケートをつくる

#1. テンプレートエンジンとは
テンプレートエンジンはテンプレートと呼ばれる雛形とデータを合成し、HTML等の成果ドキュメントを出力するライブラリです。

Wikipediaにわかりやすい画像があったのでお借りしました。
![320px-TempEngGen015.svg.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/374924/c8faf325-d922-4137-a816-c712853e4084.png)
（画像 by Dreftymac at English Wikipedia）

ようするに、HTMLなどのドキュメントを作るのを簡単にしてくれるらしいです。

今回はPugというNode.jsのテンプレートを使って簡単なアンケートを作ります。
Pugは以下のように書きます。

```:Pug
doctype html
html(lang="ja")
  head
    meta(charset="UTF-8")
    title 会員登録ページ
  body
    h1 会員情報
    form(method="post" action="/member/registry")
　　　 メールアドレス: <input type="text" name="mail">
      名前: <input type="text" name="name">
      input(type="radio" name="性別" value="男")
      span 男
      input(type="radio" name="性別" value="女")
      span 女
      button(type="submit") 登録
```
以下はHTMLです。

```html:HTML
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>会員登録ページ</title>
</head>

<body>
<h1>会員情報</h1>
<form method="post" action="/member/registry">
  メールアドレス: <input type="text" name="mail">
  名前: <input type="text" name="name">
  <input type="radio" name="性別" value="男" /> 男
  <input type="radio" name="性別" value="女" /> 女
  <button type="submit">登録</button>
</form>
</body>

</html>
```
見比べてみると、Pugは閉じタグが不要なため簡潔に表現することができます。
閉じタグがないため、改行やインデントなどで全体を構成していきます。

慣れるまで違和感がありますが、慣れてくると以下のようなメリットがあります。
・CSSと書き方の統一（classは. 、idは#）
・ファイル分割ができるため、管理がしやすい。
・繰り返しが楽につくれる

この他にもたくさんのメリットがあるので、Node.jsを使う方にはおすすめのテンプレートエンジンです。

#2. アンケートをつくる
Pugは先ほど見た通り、
・要素名（h1やbuttonなど）の後に要素の値を記述します。
・属性（methodやtype）を書く場合は要素名のあとに () を書き、その中に属性を記述していきます。
・属性が2つ以上続く場合には半角スペースで区切ります。
・入れ子構造にする場合はインデントを使います。
というルールがあります。

```pug:form.pug
doctype html
html(lang="ja")
  head
    meta(charset="UTF-8")
    title アンケート
  body
    h1 どのスポーツがやりたいですか？
    form(method="post" action=path)
      span 名前:
      input(type="text" name="name")
      span 年齢:
      input(type="text" name="age")
      span 血液型:
      select(name="blood")
        option(value="A") A型
        option(value="B") B型
        option(value="O") C型
        option(value="AB") D型
      input(type="radio" name="favoriteSport" value=firstItem)
      span #{firstItem}
      input(type="radio" name="favoriteSport" value=secondItem)
      span #{secondItem}
      button(type="submit") 投稿
```
以上の.pugファイルをつくり、ファイルが保存されているディレクトリに移動し、
コンソールで以下のようにコマンドを打ちコンパイルをしてみます。
今回は表示の確認のためにコンパイルするだけです。

```
$ pug form.pug --pretty
```
これでform.htmlファイルを作り出すことができます。
--prettyオプションは、htmlを綺麗な形で出力してくれます。
ディレクトリを見てみて、以下のようなhtmlファイルができていたら確認成功です。

```html:form.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title>アンケート</title>
  </head>
  <body>
    <h1>どのスポーツがやりたいですか？</h1>
    <form method="post"><span>名前:</span>
      <input type="text" name="name"><span>年齢:</span>
      <input type="text" name="age"><span>血液型:</span>
      <select name="blood">
        <option value="A">A型</option>
        <option value="B">B型</option>
        <option value="O">C型</option>
        <option value="AB">D型</option>
      </select>
      <input type="radio" name="favoriteSport"><span></span>
      <input type="radio" name="favoriteSport"><span></span>
      <button type="submit">投稿</button>
    </form>
  </body>
</html>
```

次にPugファイルを自動的にhtmlに変換してくれて、URLによって内容を変えることができるようにNode.js（JavaScript）ファイルを作っていきます。
以下のようなファイルを作ってください。

```JS:index.js
'use strict';
const http = require('http');
const pug = require('pug');
const server = http.createServer((req, res) => {
  console.info('[' + new Date() + '] Requested by ' + req.connection.remoteAddress);
  res.writeHead(200, {
    'Content-Type': 'text/html; charset=utf-8'
  });

  switch (req.method) {
    case 'GET':
     if (req.url === '/enquetes/base-soccer') {
      res.write(pug.renderFile('./form.pug', {
        path: req.url,
        firstItem: '野球',
        secondItem: 'サッカー'
     }));
      } else if (req.url === '/enquetes/tennis-basket') {
        res.write(pug.renderFile('./form.pug', {
          path: req.url,
          firstItem: 'テニス',
          secondItem: 'バスケ'
        }));
      }
      res.end();
      break;
    case 'POST':
      let data = '';
      req.on('data', (chunk) => {
        data = data + chunk;
      }).on('end', () => {
        const decodedData = decodeURIComponent(data);
        console.info('[' + new Date() + '] 投稿: ' + decodedData);
        res.write('<!DOCTYPE html><html lang="ja"><body><h1>' +
          decodedData + 'が投稿されました</h1></body></html>');
        res.end();
      });
      break;
    default:
      break;
  }
}).on('error', (e) => {
  console.error('[' + new Date() + '] Server Error', e);
}).on('clientError', (e) => {
  console.error('[' + new Date() + '] Client Error', e);
});
const port = 8000;
server.listen(port, () => {
  console.info('[' + new Date() + '] Listening on ' + port);
});
```
#####解説

```JS
const http = require('http');
const pug = require('pug');
const server = http.createServer((req, res) => {
  console.info('[' + new Date() + '] Requested by ' + req.connection.remoteAddress);
  res.writeHead(200, {
    'Content-Type': 'text/html; charset=utf-8'
  });
```
>1行目でhttpモジュールをインストールしています。
2行目でpugモジュールをインストールしています。
3行目でhttpモジュールを使い、サーバーを作っています。
(req, res) => { は、サーバーが返すリクエストとレスポンスのオブジェクトです。
6行目のres.writeHead(200, {
    'Content-Type': 'text/html; charset=utf-8'
  });
は、
レスポンスとしてヘッダに「ステータスコードが200（リクエスト成功）である」こと、「ファイルがhtmlである」こと、「文字コードがutf-8である」ことを書き込んでいます。

```JS
  switch (req.method) {
    case 'GET':
     if (req.url === '/enquetes/base-soccer') {
      res.write(pug.renderFile('./form.pug', {
        path: req.url,
        firstItem: '野球',
        secondItem: 'サッカー'
     }));
      } else if (req.url === '/enquetes/tennis-basket') {
        res.write(pug.renderFile('./form.pug', {
          path: req.url,
          firstItem: 'テニス',
          secondItem: 'バスケ'
        }));
      }
      res.end();
      break;
```
>1行目はswitch文の最初です。
2行目にはリクエストがGETである場合、
3行目にはurlが/enquetes/base-soccerである場合、
4行目のres.write（）は()内の処理を直接ブラウザに表示します。
pug.renderFile('./form.pug'...は
上で説明した pug form.pugと同じ意味です。
5-7行目はpath: req.urlがurlが/enquetes/base-soccerであることを意味しており、urlが/enquetes/base-soccerである場合、firstItem: '野球', secondItem: 'サッカー'が
form.pugの以下の部分に当てはまるようになるということです。

```
input(type="radio" name="favoriteSport" value=firstItem)
      span #{firstItem} <= '野球が代入される'
      input(type="radio" name="favoriteSport" value=secondItem)
      span #{secondItem}　<= 'サッカーが代入される'
```
GETメソッドでURLが/enquetes/base-soccer の場合、以下の画像のような表示になるかと思います。
![スクリーンショット 2019-11-08 21.05.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/374924/c5fe4b92-8bc4-59c8-e511-522c38e1f1d7.png)


続きの部分の説明になります。

```JS
    case 'POST':
      let data = '';
      req.on('data', (chunk) => {
        data = data + chunk;
      }).on('end', () => {
        const decodedData = decodeURIComponent(data);
        console.info('[' + new Date() + '] 投稿: ' + decodedData);
        res.write('<!DOCTYPE html><html lang="ja"><body><h1>' +
          decodedData + 'が投稿されました</h1></body></html>');
        res.end();
      });
      break;
    default:
      break;
```
>1行目はリクエストメソッドがPOSTである場合、
2行目は4行目で使用する変数dataを作成しています。
3-4行目は'data'イベントがあった場合の処理を表しており、
POSTで送られたデータがchunkにバイト文字として少しずつ代入され、data変数に代入されます。chunkはコンソールで表示すると以下のような文字になります。
![スクリーンショット 2019-11-08 21.00.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/374924/1292c657-6288-b0e2-97c6-84735738320c.png)
5行目からは'end'イベントがあった場合の処理が書かれています。
まず変数dataに代入されたバイト文字列はdecodeURIComponent(data);で、
デコードされて、バイト文字列が解除され、decodedData変数に代入されます。
その後、8行目で、res.writeでそのdecodedDataが表示されます。
10行目のres.end()で'end'イベントが終わります。
11行目以降のbreakやdefaultはswitch文の分岐処理です。

POSTメソッドの場合、ブラウザは以下のような表示になります。
POSTメソッドは、GETメソッドで取得したアンケートで、投稿ボタンを押すことを意味します。
![スクリーンショット 2019-11-08 21.06.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/374924/1003b34c-6185-030b-288a-6c611e518fa9.png)

```JS
}).on('error', (e) => {
  console.error('[' + new Date() + '] Server Error', e);
}).on('clientError', (e) => {
  console.error('[' + new Date() + '] Client Error', e);
});
const port = 8000;
server.listen(port, () => {
  console.info('[' + new Date() + '] Listening on ' + port);
});
```
>1行目から5行目はエラー処理です。
6行目から9行目はサーバーが待ち受ける設定になります。
ポート番号8000番で待ち受けることを意味します。

解説が終わったので、
ファイルを作ったディレクトリ上で、以下のコマンドを打ちサーバーを立ち上げ、ちゃんと表示されるか確認しましょう。

```
$ node index.js
> [Fri Nov 08 2019 20:26:25 GMT+0900 (GMT+09:00)] Listening on 8000
```
以上のような表示が出たら、ブラウザで
http://localhost:8000/enquetes/base-soccer
と
http://localhost:8000/enquetes/tennis-basket
にアクセスしてみましょう。
・アンケートが表示されたこと
・URLによってアンケートの表示が違うこと
が確認できたら成功です。

##参考
「N予備校 プログラミングコース」
https://www.nnn.ed.nico/
「HTMLタグリファレンス」
http://www.htmq.com/html/form.shtml
「PugでHTMLコーディングを効率化・メリットと使い方を知る」
https://tech.qookie.jp/posts/info-pug-feature/
「【Pug】ゴリラでもわかるJade改めPug入門」
https://blog.mismithportfolio.com/web/20160326pugbegin#d