前回は、MongoDB のデータを、ブラウザに一覧表示しました。

今回は、MongoDB のデータを使って、一覧画面から詳細画面に遷移する方法を紹介します。

コードは、前回のコードを使用します。

views/index.ejs

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title><%= pageTitle %></title>
  </head>
  <body>
    <main>
      <h1>メニュー</h1>
      <p>メニュー一覧</p>
      <% if(menus.length > 0) { for (let menu of menus){ %>
      <p><%= menu.title %></p>
      <a href="/menu/<%= menu.id %>">詳細画面</a>
      <div>
        <a href="/edit-menu/<%= menu.id %>">編集</a>
        <form action="/delete-menu" method="POST">
          <input type="hidden" value="<%= menu.id %>" name="menuId" />
          <button type="submit">削除</button>
        </form>
      </div>
      <%} %> <% } else { %>
      <p>注文がありません</p>
      <% } %>
    </main>
  </body>
</html>

views/menu.ejs

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="/css/styles.css" />
    <title><%= pageTitle %></title>
  </head>
  <body>
    <main>
      <form action="/menu" method="POST">
        <div class="form-control">
          <label for="title">メニュー名</label>
          <input type="text" name="title" />
        </div>
        <div class="form-control">
          <label for="description">内容</label>
          <input type="text" name="description" />
        </div>
        <button type="submit">メニューを追加する</button>
      </form>
    </main>
  </body>
</html>

views/menu-detail.ejs

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title><%= pageTitle %></title>
  </head>
  <body>
    <main>
      <h1><%= menu.title %></h1>
      <p><%= menu.description %></p>
    </main>
  </body>
</html>

views/menu-edit.ejs

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="/css/styles.css" />
    <title><%= pageTitle %></title>
  </head>
  <body>
    <main>
      <form action="/edit-menu" method="POST">
        <input type="hidden" name="id" value="<%= menu.id %>" />

        <div class="form-control">
          <label for="title">メニュー名</label>
          <input type="text" name="title" value="<%= menu.title %>" />
        </div>
        <div class="form-control">
          <label for="description">内容</label>
          <input
            type="text"
            name="description"
            value="<%= menu.description %>"
          />
        </div>
        <button type="submit">メニューを更新する</button>
      </form>
    </main>
  </body>
</html>

index.js

const express = require("express")
const app = express()
const path = require("path")

app.set("view engine", "ejs")
app.set("views", "views")

const menuRoutes = require("./routes/index.js")
const menus = require("./routes/menu.js")
const dbConnect = require("./utils/database").dbConnect

app.use(express.urlencoded({ extended: true }))

app.use(express.static(path.join(__dirname, "public")))

app.use("/", menus)
app.use(menuRoutes)

app.use((req, res, next) => {
  res.status(404).send("<h1>ページが見つかりません</h1>")
})

dbConnect(() => {
  app.listen(8000, () => console.log("Server is running ..."))
})

models/menu.js

const getDb = require("../utils/database").getDb

const fs = require("fs")
const path = require("path")

const menus = []

module.exports = class Menu {
  constructor(title, description) {
    this.title = title
    this.description = description
  }

  save() {
    return getDb()
      .collection("menu")
      .insertOne(this)
      .then(res => console.log(res))
      .catch(err => console.log(err))
  }

  static fetchAll() {
    return getDb()
      .collection("menu")
      .find()
      .toArray()
      .then(menus => {
        return menus
      })
      .catch(err => console.log(err))
  }

  static fetchMenu(id, data) {
    const menuPath = path.join(
      path.dirname(require.main.filename),
      "data",
      "menus.json"
    )

    fs.readFile(menuPath, (err, fileContent) => {
      if (err) {
        data([])
      }
      const menuData = JSON.parse(fileContent)
      const menuDetail = menuData.find(menu => menu.id === id)
      data(menuDetail)
    })
  }

  static deleteMenu(id) {
    const menuPath = path.join(
      path.dirname(require.main.filename),
      "data",
      "menus.json"
    )
    fs.readFile(menuPath, (err, fileContent) => {
      if (err) {
        data([])
      }
      const menuData = JSON.parse(fileContent)
      const updatedMenus = menuData.filter(menu => menu.id !== id)
      fs.writeFile(menuPath, JSON.stringify(updatedMenus), err => {
        console.log(err)
      })
    })
  }
}

controllers/menus.js

const Menu = require("../models/menu")

exports.getAddMenu = (req, res, next) => {
  res.render("menu", { pageTitle: "メニュー追加" })
}

exports.postAddMenu = (req, res, next) => {
  const title = req.body.title
  const description = req.body.description
  const menu = new Menu(title, description)
  menu.save()
  res.redirect("/")
}

exports.getAddMenus = (req, res, next) => {
  Menu.fetchAll()
    .then(menus => {
      res.render("index", {
        pageTitle: "メニュー一覧",
        menus,
      })
    })
    .catch(err => {
      console.log(err)
    })
}

exports.getMenu = (req, res, next) => {
  const id = req.params.menuId
  Menu.fetchMenu(id, menu => {
    res.render("menu-detail", {
      menu: menu,
      pageTitle: "詳細画面",
    })
  })
}

exports.postDeleteMenu = (req, res, next) => {
  const id = req.body.menuId
  Menu.deleteMenu(id)
  res.redirect("/")
}

exports.getEditMenu = (req, res, next) => {
  const id = req.params.menuId
  Menu.fetchMenu(id, menu => {
    res.render("menu-edit", {
      menu: menu,
      pageTitle: "メニュー内容編集",
    })
  })
}

exports.postEditMenu = (req, res, next) => {
  const id = req.body.id
  const updatedTitle = req.body.title
  const updatedDescription = req.body.description
  const updatedMenu = new Menu(id, updatedTitle, updatedDescription)
  updatedMenu.save()
  res.redirect("/")
}

routes/index.js

const express = require("express")
const router = express.Router()

const menuController = require("../controllers/menus")

router.get("/", menuController.getAddMenus)

module.exports = router

routes/menu.js

const express = require("express")
const router = express.Router()

const menuController = require("../controllers/menus")

router.get("/menu", menuController.getAddMenu)

router.get("/menu/:menuId", menuController.getMenu)

router.post("/menu", menuController.postAddMenu)

router.post("/delete-menu", menuController.postDeleteMenu)

router.get("/edit-menu/:menuId", menuController.getEditMenu)

router.post("/edit-menu", menuController.postEditMenu)

module.exports = router

utils/database.js

const mongodb = require("mongodb")
const MongoClient = mongodb.MongoClient

let _db

const dbConnect = callback => {
  MongoClient.connect(
    "mongodb+srv://Nao:abcdefgh@cluster0.q1atv.mongodb.net/myFirstDatabase?retryWrites=true&w=majority"
  )
    .then(item => {
      console.log("MongoDB Connected")
      _db = item.db()
      callback(item)
    })
    .catch(err => {
      console.log(err)
      throw err
    })
}

const getDb = () => {
  if (_db) {
    return _db
  }
  throw "データベースがありません"
}

exports.dbConnect = dbConnect
exports.getDb = getDb

モデルを修正する

まずは、モデルを修正します。

models フォルダの menu.js を開きます。

fetchAllの時と同様に、fetchMenugetDbcollectionを使用します。

static fetchMenu(id) {
  return getDb()
    .collection("menu")

}

find を使用します。

fine 内に ID を設定するので、一度 MongoDB の ID を確認してみましょう。

image2

MongoDB の ID は、_idとなっていますので、find内には、_idを設定します。

static fetchMenu(id) {
  return getDb()
    .collection("menu")
    .find({ _id: id })

}

_id は MongoDB の ID なので、このままでは文字列と比較することができません。

比較できるようにするには、MongoDB のObjectIdを使用して、新しいコンストラクターを作成します。

まずは、mongodbを呼び出します。

const mongodb = require("mongodb")

find 内で、MongoDB のObjectIdを使用します。

static fetchMenu(id) {
  return getDb()
    .collection("menu")
    .find({ _id: new mongodb.ObjectId(id) })
}

これで、ID が一致するデータを検索することができるようになりました。

1 つのデータを取得するために、nextを使います。

static fetchMenu(id) {
  return getDb()
    .collection("menu")
    .find({ _id: new mongodb.ObjectId(id) })
    .next()

}

データを取得できればmenuを返し、データを取得できなければconsole.logでエラーを表示するようにします。

static fetchMenu(id) {
  return getDb()
    .collection("menu")
    .find({ _id: new mongodb.ObjectId(id) })
    .next()
    .then((menu) => {
      return menu;
    })
    .catch((err) => console.log(err));
}

これで、モデルが完成しました。

コントローラーを作成する

次にコントローラーを作成します。

controllers フォルダの menus.js を開きます。

getAddMenusと同様に、getMenuを修正します。

Menu クラスのfetchMenuを使います。

exports.getMenu = (req, res, next) => {
  const id = req.params.menuId
  Menu.fetchMenu(id)
}

データを取得できた場合、メニューデータとタイトル『メニュー一覧』を menu-detail.ejs に表示するように設定します。

exports.getMenu = (req, res, next) => {
  const id = req.params.menuId
  Menu.fetchMenu(id).then(menu => {
    res.render("menu-detail", {
      menu: menu,
      pageTitle: "詳細画面",
    })
  })
}

エラーの場合は、console.logでエラーメッセージを表示します。

exports.getMenu = (req, res, next) => {
  const id = req.params.menuId
  Menu.fetchMenu(id)
    .then(menu => {
      res.render("menu-detail", {
        menu: menu,
        pageTitle: "詳細画面",
      })
    })
    .catch(err => {
      console.log(err)
    })
}

index.ejs の id を修正する

参照する ID が_idへ変更しましたので、index.ejs のidも修正します。

menu.idは、全てmenu._idへ修正しましょう。

<main>
  <h1>メニュー</h1>
  <p>メニュー一覧</p>
  <% if(menus.length > 0) { for (let menu of menus){ %>
  <p><%= menu.title %></p>
  <a href="/menu/<%= menu._id %>">詳細画面</a>
  <div>
    <a href="/edit-menu/<%= menu._id %>">編集</a>
    <form action="/delete-menu" method="POST">
      <input type="hidden" value="<%= menu._id %>" name="menuId" />
      <button type="submit">削除</button>
    </form>
  </div>
  <%} %> <% } else { %>
  <p>注文がありません</p>
  <% } %>
</main>

これで、一通り完了したので、一度確認してみます。

image3

野菜炒め定食の『詳細画面』をクリックすると、

image4

無事、詳細画面に変わりました。

URL の ID も反映されています。

ブログ一覧