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

# はじめに
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、SQLite３、Sequelize ） 
+ Express Generatorでアプリケーション作成
+ データベース作成
+ モデル作成
+ マイグレーション実行  
こうやって書きだすと、結構めんどくさいですね・・・  
本はwindowsがメインですが,macのやり方も載ってます。  
データベースはSQLite3がメインですが、SequelizeのところではMySQL、PostgressSQLのことも載ってます。  
Web検索すると、[Node.js + Express + Sequelize で Web アプリ作成手順](https://kita127.hatenablog.com/entry/2021/07/18/173700)　が一通り参考になりそう。


# 本から追加した部分
本の通りコーディングすると、&lt;&lt;prevもnext&gt;&gt;もレコード数に関係なく無限に遷移していきます。
・写真の例ではレコードは13ケしかないのにページが「２」（ページは0始まり）
・下の印をつけたnext＞＞がホバーするとフォーカスが当たっている。
<img width="600" alt="nextが無限にいけるかつホバー時にフォーカスする.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1573391/0925238a-b801-84d7-ff81-ad7734ec33e9.png" >
UIを上げるために、それ以上遷移できないときは、フォーカスを外す+遷移できないようにしてみました。

### 遷移できるかできないかの判定  
#### prevは簡単  
0ページより少ないページはない→今のページが０ならそれよりprevは遷移できない。  
    
#### nextは少しめんどくさい
最終ページFlag　=　全レコード数/（現在のページ数　＊　１ページ当たりのレコード数）  　
最終ページFlag　<= １なら、nextに遷移できない。

### 上記判定後の処理
遷移できない時はaタグのhref属性値を「javascript:void(0)」にしてあげる。
遷移できる時は「`/bulletin/${pg -1}`」
[javascript:void(0) とは](https://note.affi-sapo-sv.com/js-void.php)
> htmlのリンクタグ（aタグ）をクリックしたとき、画面遷移をさせずにjavascriptで何らかの処理を行わせることがある。

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

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

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

}
```

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

<img width="600" alt="投稿数を丸で囲った.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1573391/0b3f4b9f-9671-22d0-0984-1cbdba738e0b.png">

### 上記のコード
検索しまくった結果、

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

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

# コード
```js: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;
```


```rhtml: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>
```

```rhtml: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>
<% } %>
```

```js: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;
```

```js: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;
};
```

```css: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;
}
```

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