はじめに
Node.js超入門(掌田津耶乃 著)を読み勉強中です。
7章で掲示板をつくるのですが、ページャーを改良し、最後のページで「次のページ」を「フォーカスしない」+「クリックしてもページ遷移しない」よう改良するのに思いのほかつまづきました。
結論としてはpromiseのことがよくわかっていなかったのですが、今回のことでpromise.allの使い方がよくわかり、誰かの参考になればと思い投稿します。
環境
windows10
node 16.13.1
express 4.16.1
ejs 2.6.1
sqlite3 5.0.2
sequelize 6.14.0
sequelize-cli 6.4.1
bootstrap 4.3.1(CDNです)
ディレクトリ構成
Node.js超入門ではUserページでログイン、ユーザーの追加、訂正、削除などの機能もありますが、今回の内容には関係ないので、削りました。残っている機能はメッセージの追加とメッセージ一覧(ページャー付)です。modelはUserを削り、BoardをBulletinBoardに名前変えました。
root/(本ではEX-GEN-APP)
├ .vscode
├ bin
├ config
├ migrations
├ models
│ └ bulletinboard.js
│ └ index.js
├ node_modules
├ public
├ routes
│ └ bulletinboards.js
├ seeders
├ views
│ └ bulltin
│ └ data_item.ejs
│ └ index.ejs
└ app.js
└ db-dev.sqlite3
└ package.lock.json
└ package.json
環境構築から準備
「Node.js超入門」には詳しく載ってます。 項目だけ上げると、
- インストール(Node.js、EJS、Express、Express Generator、SQLite3、Sequelize )
- Express Generatorでアプリケーション作成
- データベース作成
- モデル作成
- マイグレーション実行
こうやって書きだすと、結構めんどくさいですね・・・
本はwindowsがメインですが,macのやり方も載ってます。
データベースはSQLite3がメインですが、SequelizeのところではMySQL、PostgressSQLのことも載ってます。
Web検索すると、Node.js + Express + Sequelize で Web アプリ作成手順 が一通り参考になりそう。
本から追加した部分
本の通りコーディングすると、<<prevもnext>>もレコード数に関係なく無限に遷移していきます。
・写真の例ではレコードは13ケしかないのにページが「2」(ページは0始まり)
・下の印をつけたnext>>がホバーするとフォーカスが当たっている。
UIを上げるために、それ以上遷移できないときは、フォーカスを外す+遷移できないようにしてみました。
遷移できるかできないかの判定
prevは簡単
0ページより少ないページはない→今のページが0ならそれよりprevは遷移できない。
nextは少しめんどくさい
最終ページFlag = 全レコード数/(現在のページ数 * 1ページ当たりのレコード数)
最終ページFlag <= 1なら、nextに遷移できない。
上記判定後の処理
遷移できない時はaタグのhref属性値を「javascript:void(0)」にしてあげる。
遷移できる時は「/bulletin/${pg -1}
」
javascript:void(0) とは
htmlのリンクタグ(aタグ)をクリックしたとき、画面遷移をさせずにjavascriptで何らかの処理を行わせることがある。
//中略
hrefDisplayPrev:pg <= 0 ? "javascript:void(0)" : `/bulletin/${pg -1}`
hrefDisplayNext:last_page_flag <= 1 ? "javascript:void(0)" : `/bulletin/${pg +1}`
同様にホバー時にフォーカスしない処理
//中略
cssOnOffPrev:pg <=0 ? "off" : "on"
cssOnOffNext:last_page_flag <= 1 ? "off" : "on"
//中略
.off{
pointer-events: none;
}
.on{
pointer-events: auto;
}
詰まったところ
これでうまくいくと思ったら・・・
全レコード数を取得するのに、Promise Objectが返ってきてしまう。エラーにもならず、connsole.logはでるのに・・・
Promise Objectが返ってきてしまう

上記のコード
検索しまくった結果、
- 全レコード取得、全レコード数取得
- 上記の両方が終わってから、レンダリング ➡ Promise.allの利用
にすればいい、と気づくのに数日かかりました。
コード
const express = require('express');
const router = express.Router();
const db = require('../models/index');
const {Op} =require('sequelize');
const pnum =10;//1ページ当たりの投稿数
//トップページへ
router.get('/',(req,res,next)=>{
res.redirect('/bulletin/0');
});
//トップページへ番号を付けてアクセス
router.get('/:page',(req,res,next)=>{
const pg = parseInt(req.params.page) ;
console.log(pg +"page-page");
// BulletinBoardの全データとレコード数の2つとも取って来るまで待つ
Promise.all([
db.BulletinBoard.findAll({//BulletinBoardの全データ
offset:pg*pnum,
limit:pnum,
order:[
['createdAt','DESC']
],
}),
db.BulletinBoard.count()
])
.then((results) =>{
// ページ移動が可能か(prev)
const record_num =results[1];//BulletinBoardのレコード数
const last_page_flag = record_num/ ((pg +1)*pnum)//最後のページかどうか判定するための変数
let data ={
title:"掲示板",
content:results[0],//BulletinBoardの全データ
page:pg,
hrefDisplayPrev:pg <= 0 ? "javascript:void(0)" : `/bulletin/${pg -1}`,
cssOnOffPrev:pg <=0 ? "off" : "on",
hrefDisplayNext:last_page_flag <= 1 ? "javascript:void(0)" : `/bulletin/${pg +1}`,
cssOnOffNext:last_page_flag <= 1 ? "off" : "on",
count: record_num
}
res.render('bulletin/index',data)
})
});
// メッセージの追加
router.post('/add',(req,res,next)=>{
db.sequelize.sync()
.then(() => db.BulletinBoard.create({
name:req.body.name,
message:req.body.msg
})
.then((brd) =>{
res.redirect('/bulletin');
})
.catch((err) =>{
res.redirect('/bulletin');
})
)
});
module.exports =router;
<!DOCTYPE html>
<html lang="ja">
<head>
<meta http-equiv="content-type" content="text/html" charset="utf8"/>
<title><%=title %></title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" crossorigin="anonymous">
<link rel="stylesheet" href="/stylesheets/style.css">
</head>
<body class="container">
<header><h1 class="display-4"><%=title %></h1></header>
<div role="main">
<p class="h4">Welcome to </p>
<form action="/bulletin/add" method="post">
<div class="row">
<div class="col-8">
<label for="name">投稿者氏名</label>
<input type="text" name="name" class="form-control">
<label for="msg">メッセージ</label>
<input type="text" name="msg" class="form-control">
</div>
<input type="submit" value="送信" class="btn btn-primary col-2">
</div>
</form>
<table class="table mt-5">
<thead>
<tr>
<td>Name</td>
<td>message</td>
<td>createdAt</td>
</tr>
</thead>
<% for(let i in content){ %>
<%- include('data_item',{val:content[i]}) %>
<% } %>
</table>
<ul class="pagination justify-content-center">
<li class="page-item">
<a href="<%= hrefDisplayPrev %>" class="page-link <%= cssOnOffPrev %>" ><< prev</a>投稿数:<%= count %>
</li>
<li class="page-item">
<a href="<%= hrefDisplayNext %>" class="page-link <%= cssOnOffNext %>">Next >></a>
</li>
</ul>
</div>
</body>
</html>
<% if (val !=null){ %>
<tr>
<th><%= val.name %></th>
<td class="col-7"><%= val.message %></td>
<td class="col-3"><%= new Date(val.createdAt).toLocaleString() %></td>
</tr>
<% } %>
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var indexRouter = require('./routes/index');
var app = express();
const session = require('express-session');
var users = require('./routes/users_p384'); //Js
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use(session({
secret: 'secretsecretsecret',
cookie: { maxAge: 2*60*1000 },
resave: false,
saveUninitialized: false,
}))//p385で追加してみた
app.use('/', indexRouter);
app.use('/users',users);//ここは変数の「users](11行目)
var boardsRouter =require('./routes/boards_p392');
app.use('/boards',boardsRouter);
var bulletinRouter =require('./routes/bulletinboards');
app.use('/bulletin',bulletinRouter);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
'use strict';
module.exports = (sequelize, DataTypes) => {
const BulletinBoard = sequelize.define('BulletinBoard',{
name: {
type:DataTypes.STRING,
validate:{
notEmpty:{
msg:"氏名は必須です"
}
}
},
message: {
type:DataTypes.STRING,
validate:{
notEmpty:{
msg:"メッセージは必須です"
}
}
}
}, {});
BulletinBoard.associate= function(models){
}
return BulletinBoard;
};
body {
padding: 50px;
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
}
a {
color: #00B7FF;
}
.off{
pointer-events: none;
}
.on{
pointer-events: auto;
}
終わりに
本からの変更点だけならコードがもっと短くなるのですが、記事をみただけで動くようにすると、長くなってしまいました。
自分自身初心者で写経自体が勉強なので、同じような人もいると信じて・・・