M.Hiroi's Home Page

JavaScript Programming

お気楽 Node.js 超入門

[ Home | Light | JavaScript | Node.js ]

簡易掲示板の作成

今回は簡単な例題として、名前とコメントを投稿する簡易掲示板を作ってみましょう。

●プログラムの構成

プログラムの構成は次のようになります。

bbs.js
public_html/
    bbs1.ejs
    bbs.sqlite

カレントディレクトリにサーバー本体のプログラム bbs.js とディレクトリ public_html を用意します。public_html の中にはテンプレートファイル bbs1.ejs とデータベース bbs.sqlite を格納します。bbs.sqlite はあらかじめ用意しておく必要はありません。あとは、node bbs.js でサーバーを起動し、ブラウザで URL localhost:1337 を入力します。

●テンプレートファイルの作成

テンプレートファイルは次のようになります。

リスト : 簡易掲示板 (bbs1.ejs)

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>簡易掲示板</title>
  <style>
    body {
      margin-left: 2em; margin-right: 2em;
    }
    .bbs {
      font-family: sans-serif;
      font-size: medium;
    }
  </style>
</head>
<body>
<% for (let data of posts) { %>
<%= data.id %>. <%= data.name %> - <%= data.date %><br>
<pre class="bbs">
<%= data.comment %>
</pre>
<hr>
<% } %>
<% if (msg) { %>
<p><b><%= msg %></b></p>
<% } %>
<form method="post">
<label>名前: <input type="text" name="name"></label><br>
<label>コメント:<br>
<textarea name="comment" rows="6" cols="60"></textarea></label><br>
<input type="submit" value="送信">
</form>
</body>
</html>

名前は input タグで、コメントは textarea タグで入力します。textarea はテキストを改行することができますが、表示するときに p タグを使うとテキストは改行されません。そこで、コメントを表示するときは pre タグで囲むことにします。これで改行されたテキストを表示することができます。

●プログラムの作成

サーバー側のプログラムは次のようになります。

//
// bbs.js : 簡易掲示板 (サーバー)
//
//          Copyright (C) 2017 Makoto Hiroi
//
const http = require('http'),
      fs = require('fs'),
      ejs = require('ejs'),
      qs = require('querystring'),
      sqlite = require('sqlite3'),
      template = fs.readFileSync(__dirname + '/public_html/bbs1.ejs', 'utf-8'),
      db = new sqlite.Database(__dirname + '/public_html/bbs.sqlite'),
      server = http.createServer();

function renderForm(db, res, msg = null) {
  db.all('select * from bbs', (err, posts) => {
      const data = ejs.render(template, {
      posts: posts,
      msg: msg
    });
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.write(data);
    res.end();
  });
}

function insertData(query, db, res) {
  if (query.name != '' && query.comment != '') { 
    db.run('insert into bbs (name, comment, date) values (?, ?, ?)',
           [query.name, query.comment, (new Date()).toLocaleString()]);
    renderForm(db, res);
  } else {
    renderForm(db, res, "名前とコメントを入力してください");
  }
}

db.serialize();
db.run('create table if not exists bbs (id integer primary key autoincrement, name text, comment text, date text)');

server.on('request', (req, res) => {
  if (req.method == 'POST') {
    let data = "";
    req.on('data', x => data += x);
    req.on('end', () => insertData(qs.parse(data), db, res));
  } else {
    renderForm(db, res);
  }
});

server.listen(1337, 'localhost');
console.log("server listening...");

サーバー側では POST のみを受け付けて、関数 insertDate() で名前とコメントが入力されていることをチェックします。どちらも空文字列であればデータベースには登録しません。そして、テンプレートファイル (bbs1.ejs) にメッセージ msg を渡して表示します。

あとは特に難しいところはないと思います。興味のある方は実際に動かしてみてください。

●フォームの入力チェック

フォームの入力チェックはクライアント側 (ブラウザ) で行うこともできます。データ入力の有無だけでよければ、HTML5 で導入された required 属性を使うと簡単です。

簡単な使用例を示しますので、実際に試してみてください。

リスト : required 属性の使用例

<form method="post" onsubmit="return false">
<label>本文 <input type="text" name="content" required></label>
<input type="submit" value="Submit">
</form>

form タグで onsubmit 属性にプログラムを登録すると、submit ボタンを押したときに登録されたプログラムを実行します。ここで false を返すとデータの送信は行われません。JavaScript (DOM) を使って入力データをチェックすることも可能です。これはあとで試してみましょう。

テンプレートファイルは次のようになります。

リスト : テンプレートファイル (bbs2.ejs)

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>簡易掲示板</title>
  <style>
    body {
      margin-left: 2em; margin-right: 2em;
    }
    .bbs {
      font-family: sans-serif;
      font-size: medium;
    }
  </style>
</head>
<body>
<% for (let data of posts) { %>
<%= data.id %>. <%= data.name %> - <%= data.date %><br>
<pre class="bbs">
<%= data.comment %>
</pre>
<hr>
<% } %>
<% if (msg) { %>
<p><b><%= msg %></b></p>
<% } %>
<form method="post">
<label>名前: <input type="text" name="name" required></label><br>
<label>コメント:<br>
<textarea name="comment" rows="6" cols="60" required></textarea></label><br>
<input type="submit" value="送信">
</form>
</body>
</html>

input タグと textarea タグに属性 required を追加します。これでこのフォームの入力は必須になり、空のまま submit すると、ブラウザ側で入力を促すエラーメッセージが表示されます。

このほかにも HTML5 には便利な機能がたくさん追加されています。興味のある方は 参考 URL をお読みくださいませ。

●JavaScript を使った入力チェック

次は、JavaScript (DOM) を使って入力データをチェックしてみましょう。プログラムは次のようになります。

リスト : テンプレートファイル (bbs3.ejs)

<!DOCTYPE html>
<html lang="ja">

・・・ 省略 ・・・

<form method="post" onsubmit="return check(this)">
<label>名前: <input type="text" name="name"></label><br>
<label>コメント:<br>
<textarea name="comment" rows="6" cols="60"></textarea></label><br>
<input type="submit" value="送信">
</form>
<script>
function check(e) {
  if (!e.name.value || !e.comment.value) {
    alert("名前とコメントを入力してください");
    return false;
  }
  return true;
}
</script>
</body>
</html>

form タグの onsubmit に "return check(this)" を登録します。submit ボタンを押すと関数 check() が呼び出されて、その返り値が false ならばデータの送信を行いません。引数 this には form 要素 (HTMLFormElement のオブジェクト) が渡されます。

form に含まれる要素はその name 属性の値で求めることができます。その入力データは value プロパティで取得することができます。ラジオボタンなど同じ名前を持つ要素が複数ある場合、それらの要素は配列に格納されていることに注意してください。JavaScript の場合、空文字列は偽として扱われるので、e.name.value または e.comment.value が偽であれば alert() で警告メッセージを表示して false を返します。そうでなければ true を返します。

form タグの onsubmit 属性のかわりに、form 要素のプロパティ onsubmit にコールバック関数を登録する方法もあります。プログラムは次のようになります。

リスト : テンプレートファイル (bbs4.ejs)

<!DOCTYPE html>
<html lang="ja">

・・・ 省略 ・・・

<form id="form" method="post">
<label>名前: <input type="text" name="name"></label><br>
<label>コメント:<br>
<textarea name="comment" rows="6" cols="60"></textarea></label><br>
<input type="submit" value="送信">
</form>
<script>
const form = document.getElementById("form");
form.onsubmit = () => {
  if (!form.name.value || !form.comment.value) {
    alert("名前とコメントを入力してください");
    return false;
  }
  return true;
};
</script>
</body>
</html>

form タグに id="form" をセットし、getElementById() で form 要素を取得します。コールバック関数はプロパティ onsubmit に登録します。これでも入力データをチェックすることができます。

●掲示板からキーワードを検索する

最後に、掲示板からキーワードを検索する機能を追加してみましょう。SQLite の場合、デフォルトでは正規表現を使用した検索をサポートしていませんが、like 句または glob 句を使ってデータを検索することができます。

select カラム名 form テーブル名 where 検索カラム like 'キーワード'
select カラム名 form テーブル名 where 検索カラム glob 'キーワード'

like 句で文字列を比較するとき、ワイルドカードに % と _ を使用することができます。

% : 任意の 0 文字以上の文字列
_ : 任意の 1 文字

glob 句の場合、bash の Globbing と同様のメタ文字を使用することができます。Globbing については、拙作のページ お気楽 bash 超入門 : グロブ (glob) をお読みくださいませ。今回は like 句を使うことにします。

●検索フォームの追加

まずは最初に、検索キーワードを入力するフォームをテンプレートファイルに追加します。プログラムは次のようになります。

リスト : 検索フォームの追加 (bbs5.ejs)

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>簡易掲示板</title>
  <style>
    body {
      margin-left: 2em; margin-right: 2em;
    }
    .bbs {
      font-family: sans-serif;
      font-size: medium;
    }
  </style>
</head>
<body>
<% for (let data of posts) { %>
<%= data.id %>. <%= data.name %> - <%= data.date %><br>
<pre class="bbs">
<%= data.comment %>
</pre>
<hr>
<% } %>
<% if (msg) { %>
<p><b><%= msg %></b></p>
<% } %>
<form method="post">
<input type="hidden" name="cmd" value="send">
<label>名前: <input type="text" name="name" required></label><br>
<label>コメント:<br>
<textarea name="comment" rows="6" cols="60" required></textarea></label><br>
<input type="submit" value="送信">
</form>
<hr>
<form method="post">
<input type="hidden" name="cmd" value="search">
<label>検索:
  <input type="radio" name="column" value="name"> 名前 
  <input type="radio" checked name="column" value="comment"> コメント </label><br>
<label>キーワード: <input type="text" name="keyword" required></label><br>
<ul>
  <li>% : 任意の 0 文字以上の文字列 
  <li>_ : 任意の 1 文字
</ul>
<input type="submit" value="検索">
</form>
<hr>
</body>
</html>

投稿と検索を区別するため、input タグの type="hidden" で隠しデータ cmd を設定します。投稿の時は cmd の値を send に、検索のときは search にします。submit ボタンを押すと他のデータと一緒に隠しデータも送信されます。検索は名前とコメントを区別して行います。これをラジオボタンで指定します。あとはキーワードを入力するテキストボックスを用意するだけです。

●サーバー側のプログラム

サーバー側のプログラムは次のようになります。

//
// bbs1.js : 簡易掲示板 (検索機能の追加)
//
//          Copyright (C) 2017 Makoto Hiroi
//
const http = require('http'),
      fs = require('fs'),
      ejs = require('ejs'),
      qs = require('querystring'),
      sqlite = require('sqlite3'),
      template = fs.readFileSync(__dirname + '/public_html/bbs5.ejs', 'utf-8'),
      db = new sqlite.Database(__dirname + '/public_html/bbs.sqlite'),
      server = http.createServer();

function renderForm(db, res, msg = null) {
  db.all('select * from bbs', (err, posts) => {
      const data = ejs.render(template, {
      posts: posts,
      msg: msg
    });
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.write(data);
    res.end();
  });
}

function insertData(query, db, res) {
  if (query.name != '' && query.comment != '') { 
    db.run('insert into bbs (name, comment, date) values (?, ?, ?)',
           [query.name, query.comment, (new Date()).toLocaleString()]);
    renderForm(db, res);
  } else {
    renderForm(db, res, "名前とコメントを入力してください");
  }
}

function searchData(query, db, res) {
  db.all(`select * from bbs where ${query.column} like '${query.keyword}'`, (err, posts) => {
    if (err) {
      res.writeHead(404, {'Content-Type': 'text/plain'});
      res.write(`${err}`);
      res.end();
      return;
    }
    const data = ejs.render(template, {
      posts: posts,
      msg: ""
    });
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.write(data);
    res.end();
  });
}

db.serialize();
db.run('create table if not exists bbs (id integer primary key autoincrement, name text, comment text, date text)');

server.on('request', (req, res) => {
  if (req.method == 'POST') {
    let data = "";
    req.on('data', x => data += x);
    req.on('end', () => {
      const query = qs.parse(data);
      if (query.cmd == 'send') {
        insertData(query, db, res);
      } else {
        searchData(query, db, res);
      }
    });
  } else {
    renderForm(db, res);
  }
});

server.listen(1337, 'localhost');
console.log("server listening...");

server.on() のコールバック関数でデータの受信が終了したとき、クエリ文字列を解析して cmd が send ならば投稿処理を、そうでなければ検索処理を関数 searchData() で行います。searchData() では SQLite のコマンドをテンプレート文字列 `...` で組み立てます。テンプレート文字列は ECMAScript2015 (ES2015) から導入された機能です。詳しい説明は拙作のページ お気楽 ECMAScript2015 超入門:テンプレート文字列 をお読みください。

あとは db.all() でコマンドを実行して検索結果を取得します。エラーが発生した場合は、エラーメッセージをブラウザに返します。そうでなければ、取得したデータを ejs.render() で HTML 形式のテキストに変換してブラウザに返すだけです。

これでプログラムは完成です。興味のある方は実際に動かしてみてください。最低限の機能しかないので、いろいろ改造したり、見栄えを良くしてみるのも面白いと思います。

●参考 URL

  1. HTML5“とか”アプリ開発入門 - @IT, (白石俊平さん)
  2. HTML5 - HTML | MDN

Copyright (C) 2017 Makoto Hiroi
All rights reserved.

[ Home | Light | JavaScript | Node.js ]