「Webサービスを(少しずつ)構築する」 第2回
=> 上記は別ページ参照
なお、この課程ではテンプレートエンジンとして pugを使っているので、(別ページ解説にはないが)--pug
オプションを
つけてアプリケーション生成をしておくといいだろう。
expess app1 --pug
-c
オプションを指定しておいてもいいこの両者は、後からJSコードの書き換えでも対応できるので あまり気に病まなくてもいいが。
このファイルを require している行 と、
app.use(‘/’,index-router)
のようにそれを使う(use)宣言をしている行があることを 確認しておくこと。
つまり、サーバに対して資源 ‘/’の要求が入った時に、 この index-router が呼ばれることになる。
たとえば index.pug は、前述の index-router (routes/index.js)から
res.render('index',{...})
で呼び出されて、
そのレンダリング(HTML生成、と考えていい)にあたって、 index.pug から変換されたページが送られる。
res.render
の第2引数で渡された変数と値の関係が生成された画面に反映されていることも確認しておくこと。たとえば public/images に 何か画像ファイルを置くと (a.pngとすると)、
http://localhost:3000/images/a.png
でブラウザに画像が表示されるだろう。
以下のような行があることを確認しておこう。
app.set('views', path.join(__dirname, 'views'));
// views ディレクトリに関する設定
app.set('view engine', 'pug');
// pugを view engine(テンプレートエンジン)として使う設定
app.use(express.static(path.join(__dirname, 'public')));
// 前述の publicディレクトリに関する設定
実験1: views にある index.pug を、前回作った pugファイルに 置き換えてしまおう。
あとで戻せるよう、index.pug を index_1.pug のような名前に変えておいてから、
自作 pugファイルを 当該ディレクトリ(views)にコピーして、
index.pug に名前を変える、という手順を踏むといい。
これで 前回のページが Webサーバ経由で表示されることになる。
実験2: 別URLでアクセスできるようにする。
{}
に変えてもいいし、app.js を編集し、以下の2行を適切な場所に置く (似たような行の後に挿入する)。
var memoRouter = require('./routes/memo')
app.use('/memo', memoRouter)
あらためて起動する必要がある。
node-dev をインストール(これを一回やっておくとそのPC上で今後のプロジェクトにも有効)。
npm install node-dev -g
当該アプリケーションの packages.json の、
scripts:
-> start:
の行を以下のように変更
"start": "node ./bin/www"
↓
"start": "node-dev ./bin/www"
するとそのアプリケーションは、npm start で起動後、 ソースに変更があれば自動的に再起動される。
app.js の末尾に、変数に値を設定する文を追加してみる。
data=[
{date:'2018/11/1',memo:'まずは使ってみました'},
{date:'2018/11/2',memo:'今日はいい天気でした'}
]
routes/memo.js でそのデータを pug経由でテンプレートに渡す(res.renderの第2引数の中に包んで渡す)。
res.render('memo', {list:data});
list
)ことができる。views/memo.pug の table内で複数のtrを記述する部分を、 繰り返しによる記法に変更する。
table
tr
th No.
th 日付
th 内容
each item, index in list
tr
td= index+1
td= item.date
td= item.memo
-
を前置して)
JavaScriptの文法で繰り返しを記述するという方法もある(ここでは省略するが)。つまり前記第1項で app.js の中で初期設定したような、
{date:'2018/11/1',memo:'まずは使ってみました'}
のような値のこと。
問題点:
解決策:
このオブジェクトの属性値という形で、値のやりとりを行うことは可能だろう。
そこで、以下のような書き方が有効であることを確認されたい。
routes/memo.js で
res.render('memo', {list:router.data});
app.js で
memoRouter.data=[ ... ]
なお、属性名として、すでに利用されている名前を使うと 衝突を起こして動作が異常になる可能性があるので、
活用するオブジェクトについて予め知っておく必要はある。
たとえば以下のような手順で確認できる。
routes/memo.js の先頭2行を(コピペでいい)入力する。
var express = require('express');
var router = express.Router();
ここで、router
と入力してみると、routerオブジェクト
の各属性の名前と値が表示される。
> router
{ [Function: router]
params: {},
_params: [],
caseSensitive: undefined,
mergeParams: undefined,
strict: undefined,
stack: [] }
>
こんな結果が得られる。
データへのアクセスを考えると、データの
以下のように進めていく。
GETの場合、データは以下のようなURLで(リクエストパラメータあるいはGETパラメータ として)送られる。
http://localhost:3000/memo/add?memo=いま入力されたデータ
これは本課程のこの後の段階でAjaxを使うことを想定したことと、
INPUTよりもBUTTONのほうが新しい技術であることを考慮しての選択。
FORMの紹介でよく使われるのは、 2つの種類のinput要素(text と submit)を FORMに包む例。以下ではそのよく見る例を使って補足解説を行う。
form(action='/memo/add')
iput(type='text',name='memo')
input(type='submit' value='送信')
type='text'
属性のinput要素が入力フィールドで、memo
)が、
送信データ(URLの、?
の後ろ)の、
=
の左辺として使われる。=
の右辺は勿論、その入力欄に入力された文字列。BUTTON要素の場合は要素の中身 (HTMLでは開始タグと終了タグの間に挟まれた部分) として「送信」などの文字列が設定されるが、
input要素では、value属性の値として与えている (HTMLの文法の中ではおそらく歴史的事情で一貫性のない部分)。
TEXTAREAとBUTTONを使うFORMの使い方として、 以下に使用例とポイントをいくつか述べる。
form(method='post', action='/memo/add')
textarea(name='memo',rows="4" cols="40") 今日のメモ
button 送信
サーバ側の動作を記述する(ための場所):
routes/memo.js に以下のような行を追加してみよう。
router.post('/add',(req,res)=>{
console.log(req.body.memo)
res.send(`get memo=${req.body.memo}`)
})
(req,res)=>
の部分
(無名関数を引数として渡す、JavaScriptではよくある場面)は、
function(req,res)
(古い書き方)としてもいい。サーバ側の動作:
入力された値を、配列に追加して、保存しておく。
routes/memo.js の、router.postメソッド(の第2引数に渡す無名関数)の中身を、以下のように変える。
router.data.push({
date: new Date().toLocaleDateString(),
memo: req.body.memo
})
さらに、画面表示を行うべく、その後ろに、
router.get('/',...)
の中と同じレンダリング処理を追加。
res.render('memo', {list:router.data})
これまでのコードでは入力受理したデータはメモリ上に (オブジェクト或いは配列として)展開されるため、
アプリケーションを再起動すると消失してしまう。
インストール:
npm install nedb
(以下はコードの変更箇所)
ライブラリの読み込みと
データアクセスのためのオブジェクト生成
var nedb = require('nedb'),
db = new nedb({filename: 'memo.db', autoload:true})
データ列を res.render に渡すよう変更:
memo.js では res.render を同じ形で2回呼んでるので、 それを包む関数をまず以下のように定義しておき、
function showdb(res) {
db.find({},(err,docs)=>{
res.render('memo', {list:docs})})
}
その上で、res.render を呼んでる箇所(2つ)を、
showdb(res)
に置き換えるのがいいだろう。
同期呼び出しの場合は:
読み書きを行う関数を 呼び出す 関数が終了し 値が返ってくる その値を使って次の処理
という流れで進められるが、
非同期呼び出しでは:
読み書きを行う関数が呼び出される その際にコールバック関数が渡される コールバック関数の中に主な処理が書かれている 読み書きが開始されると、非同期関数の実行は終了する。 読み書きのための処理は続いている。 読み書きが(全部または一部)終了すると (さっき渡されていた)コールバック関数が呼ばれ、 その関数の本体に基づいて、データの処理が行われる。
という流れになる。
データを読み出すメソッド find では、(そのデータを使うために呼び出している訳だから)処理が必要になる。
ここでは、受け取ったデータを(まとめて)テンプレートエンジン(ここでは pug) に渡して renderingさせて、画面に表示するのが目的。 * なので、find に渡すコールバック関数の中で res.renderを呼び出している。
Cursor
オブジェクトを返す。Cursor
のメソッドで、さらに値としてCursor
を返すものとして、こうして構成されたメソッドチェーンの最終段で、 exec の引数としてコールバック関数を渡す (findの第2引数だったものをそのまま使えばいい)。
db.find({}).sort({'date':-1}).exec((err,docs)=>{
db.find の行はこのように変更することになるだろう。 * データファイルの確認: