前回は、Express で作成したデータを JSON ファイルへ保存しました。

今回は、JSON ファイルへ保存したデータを使って、一覧画面から詳細画面へ遷移させます。

コードは、前回のコードの EJS を少し修正して使います。

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>
      <%} %> <% } 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>

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");

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>");
});

app.listen(8000, () => console.log("Server is running ..."));

models/menu.js

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

const menus = [];

module.exports = class Menu {
  constructor(title) {
    this.menuTitle = title;
  }

  save() {
    const menuPath = path.join(
      path.dirname(require.main.filename),
      "data",
      "menus.json"
    );
    fs.readFile(menuPath, (err, fileContent) => {
      let menus = [];
      if (!err) {
        menus = JSON.parse(fileContent);
      }
      menus.push(this);
      fs.writeFile(menuPath, JSON.stringify(menus), (err) => {
        console.log(err);
      });
    });
  }

  static fetchAll(data) {
    const menuPath = path.join(
      path.dirname(require.main.filename),
      "data",
      "menus.json"
    );
    fs.readFile(menuPath, (err, fileContent) => {
      if (err) {
        data([]);
      }
      data(JSON.parse(fileContent));
    });
  }
};

controllers/menus.js

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

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

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

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

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.post("/menu", menuController.postAddMenu);

module.exports = router;

詳細画面のパスを作成

メニュー一覧画面から一覧画面へ遷移するためのパスを作成します。

パスは、menu/メニュー ID とします。

models/menu.js にメニュー ID を格納するコンストラクターを準備します。

constructor(title) {
  this.menuTitle = title;
}

こちらを削除して、以下のコードに書き換えます。

id とは関係ありませんが、description も設定しておきます。

constructor(id, title, description) {
  this.id = id;
  this.title = title;
    this.description = description;
}

index.ejs に詳細画面へのリンクを作成しましょう。

パスに先程作成した、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>
  <%} %> <% } else { %>
  <p>注文がありません</p>
  <% } %>
</main>

models/menu.js の Menu クラスにあるsaveで、ランダムな ID を設定します。

toStringを 36 進数にすることで、0〜9 と a〜z が使えるようにしています。

また、ランダムな数の初め 2 文字は、『1.』なので、slice(2,8)ではじめ 2 文字をカットしています。

save() {
  this.id = (Math.random() + 1).toString(36).slice(2, 8);
  const menuPath = path.join(
    path.dirname(require.main.filename),
    "data",
    "menus.json"
  );
  fs.readFile(menuPath, (err, fileContent) => {
    let menus = [];
    if (!err) {
      menus = JSON.parse(fileContent);
    }
    menus.push(this);
    fs.writeFile(menuPath, JSON.stringify(menus), (err) => {
      console.log(err);
    });
  });
}

一度、確認してみましょう。

image2

image3

ランダムな ID が生成されました。

ホーム画面の『詳細画面』をクリックすると、

image4

image5

ページを作成していませんが、URL は、ID と一致しています。

詳細画面のページを作成

詳細画面のページを EJS で作成します。

詳細画面は、index.ejs をコピーして編集します。

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>

models フォルダの menu.js の Menu クラス内に、fetchMenuを作成します。

コードは、fetchAll の内容をコピーして修正します。

static fetchMenu(data) {
  const menuPath = path.join(
    path.dirname(require.main.filename),
    "data",
    "menus.json"
  );
  fs.readFile(menuPath, (err, fileContent) => {
    if (err) {
      data([]);
    }
    data(JSON.parse(fileContent));
  });
}

今回は、id も必要になるので、引数に id も設定しておきます。

static fetchMenu(id, data) {

menus.json の中から、引数の ID と同じデータを取得するようにします。

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

console.log を確認すると、

image6

id と一致するデータを取得できています。

こちらを data にのせます。

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);
    console.log(menuDetail);
    data(menuDetail);
  });
}

次に controllers フォルダの menus.js でモデルで作成したデータを取得できるようにします。

出力は、先程作成した、menu-dtatail.ejs にします。

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

routes フォルダの menu.js に『menu/メニュー ID』へ遷移するよう、ルーティング処理します。

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

では、ホーム画面の『詳細画面』をクリックして、画面遷移を確認します。

image7

image8

設定した通り、メニューの ID を URL にして画面遷移することができました。

ブログ一覧