Edited at

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


免責事項

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


環境

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

(画像 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

<!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つ以上続く場合には半角スペースで区切ります。

・入れ子構造にする場合はインデントを使います。

というルールがあります。


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ファイルができていたら確認成功です。


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)ファイルを作っていきます。

以下のようなファイルを作ってください。


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



解説

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である」ことを書き込んでいます。


  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

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

    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

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

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