LoginSignup
0
0

More than 1 year has passed since last update.

掲示板のページャー最終ページ処理(Node.jsのpromise.allがわかった)

Last updated at Posted at 2022-02-12

はじめに

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>>がホバーするとフォーカスが当たっている。
nextが無限にいけるかつホバー時にフォーカスする.png
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で何らかの処理を行わせることがある。

bulletinboards.js
//中略
hrefDisplayPrev:pg <= 0 ? "javascript:void(0)" : `/bulletin/${pg -1}`
hrefDisplayNext:last_page_flag <= 1 ? "javascript:void(0)" : `/bulletin/${pg +1}`

同様にホバー時にフォーカスしない処理

bulletinboards.js
//中略
cssOnOffPrev:pg <=0 ? "off" : "on"
cssOnOffNext:last_page_flag <= 1 ? "off" : "on"
public/stylesheets/style.css
//中略
.off{
  pointer-events: none;
}
.on{
  pointer-events: auto;

}

詰まったところ

これでうまくいくと思ったら・・・
全レコード数を取得するのに、Promise Objectが返ってきてしまう。エラーにもならず、connsole.logはでるのに・・・

Promise Objectが返ってきてしまう

投稿数を丸で囲った.png

上記のコード

検索しまくった結果、

  • 全レコード取得、全レコード取得
  • 上記の両方が終わってから、レンダリング ➡ Promise.allの利用

にすればいい、と気づくのに数日かかりました。

コード

routes\bulletinboards.js
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;
views\bulletin\index.ejs
<!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 %>" >&lt;&lt; prev</a>投稿数:<%= count %>
               </li>
               <li class="page-item">
                   <a href="<%= hrefDisplayNext %>" class="page-link <%= cssOnOffNext %>">Next &gt;&gt;</a>
               </li>
           </ul>
        </div>
    </body>
</html>
views\bulletin\data_item.ejs
<% 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>
<% } %>
app.js
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;
models\bulletinboard.js
'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;
};
public\stylesheets\style.css
body {
  padding: 50px;
  font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
}

a {
  color: #00B7FF;
}

.off{
  pointer-events: none;
}
.on{
  pointer-events: auto;
}

終わりに

本からの変更点だけならコードがもっと短くなるのですが、記事をみただけで動くようにすると、長くなってしまいました。
自分自身初心者で写経自体が勉強なので、同じような人もいると信じて・・・

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0