主页

我们要创建的第一个页面是网站的主页,可以从站点 (/) 或目录 (catalog/) 的根目录访问。此页面将显示一些描述站点的静态文本,以及数据库中各种记录类型的动态计算的“计数”。

我们已经为首页创建了一个路由。为了完成页面,我们需要更新我们的控制器函数,从数据库获取记录的“计数”,并创建一个我们可以用来渲染页面的视图(模板)。

注意:我们将使用 Mongoose 来获取数据库信息。在继续之前,您可能希望重新阅读 Mongoose 基础知识 部分中关于 搜索记录 的内容。

路由

我们在 上一篇教程 中创建了索引页面的路由。提醒一下,所有路由函数都定义在 /routes/catalog.js 中。

js
// GET catalog home page.
router.get("/", book_controller.index); // This actually maps to /catalog/ because we import the route with a /catalog prefix

作为参数传递的图书控制器索引函数 (book_controller.index) 在 /controllers/bookController.js 中定义了一个“占位符”实现。

js
exports.index = async (req, res, next) => {
  res.send("NOT IMPLEMENTED: Site Home Page");
};

正是这个控制器函数,我们对其进行扩展以从我们的模型中获取信息,然后使用模板(视图)进行渲染。

控制器

索引控制器函数需要获取有关数据库中有多少 BookBookInstance(全部)、BookInstance(可用)、AuthorGenre 记录的信息,在模板中渲染这些数据以创建 HTML 页面,然后将其返回到 HTTP 响应中。

打开 /controllers/bookController.js。在该文件的顶部附近,您应该会看到导出的 index() 函数。

js
const Book = require("../models/book");

exports.index = async (req, res, next) => {
  res.send("NOT IMPLEMENTED: Site Home Page");
};

用以下代码片段替换以上所有代码。这首先导入(require())所有模型。我们需要这样做,因为我们将使用它们来获取文档计数。

js
const Book = require("../models/book");
const Author = require("../models/author");
const Genre = require("../models/genre");
const BookInstance = require("../models/bookinstance");

exports.index = async (req, res, next) => {
  // Get details of books, book instances, authors and genre counts (in parallel)
  const [
    numBooks,
    numBookInstances,
    numAvailableBookInstances,
    numAuthors,
    numGenres,
  ] = await Promise.all([
    Book.countDocuments({}).exec(),
    BookInstance.countDocuments({}).exec(),
    BookInstance.countDocuments({ status: "Available" }).exec(),
    Author.countDocuments({}).exec(),
    Genre.countDocuments({}).exec(),
  ]);

  res.render("index", {
    title: "Local Library Home",
    book_count: numBooks,
    book_instance_count: numBookInstances,
    book_instance_available_count: numAvailableBookInstances,
    author_count: numAuthors,
    genre_count: numGenres,
  });
};

我们使用 countDocuments() 方法来获取每个模型的实例数量。此方法在模型上调用,带有一个可选的匹配条件集,并返回一个 Query 对象。通过调用 exec() 可以执行查询,它返回一个 Promise,该 Promise 要么以结果兑现,要么在发生数据库错误时被拒绝。

由于文档计数查询是相互独立的,我们使用 Promise.all() 并行运行它们。该方法返回一个新 Promise,我们 await 它完成(在此函数内,执行在 await 处暂停)。当所有查询完成时,由 all() 返回的 Promise 兑现,继续执行路由处理函数,并用数据库查询结果填充数组。

然后,我们调用 res.render(),指定一个名为 'index' 的视图(模板),以及将数据库查询结果映射到视图模板的对象。数据以键值对的形式提供,并且可以在模板中使用键进行访问。

注意:如果您在 Pug 模板中使用了未传递的键/变量,则它将渲染为空字符串,并在表达式中评估为 false。其他模板语言可能要求您传递用于所有使用的对象的值。

请注意,代码非常简单,因为我们可以假设数据库查询会成功。如果任何数据库操作失败,抛出的异常将导致 Promise 被拒绝,Express 会将错误传递给链中的 next 中间件处理程序。

视图

打开 /views/index.pug 并用以下文本替换其内容。

pug
extends layout

block content
  h1= title
  p Welcome to #[em LocalLibrary], a very basic Express website developed as a tutorial example on the Mozilla Developer Network.

  h2 Dynamic content

  p The library has the following record counts:

  ul
    li #[strong Books:] !{book_count}
    li #[strong Copies:] !{book_instance_count}
    li #[strong Copies available:] !{book_instance_available_count}
    li #[strong Authors:] !{author_count}
    li #[strong Genres:] !{genre_count}

视图很简单。我们扩展了 layout.pug 基本模板,覆盖了名为 'content' 的 block。第一个 h1 标题将是传递给 render() 函数的 title 变量的转义文本——请注意 h1= 的用法,以便将后面的文本视为 JavaScript 表达式。然后,我们包含一个介绍 LocalLibrary 的段落。

在“动态内容”标题下,我们列出了每个模型的副本数量。请注意,模板中的数据值是调用路由处理函数中的 render() 时指定的键。

注意:我们没有转义计数值(即,我们使用了 !{} 语法),因为计数值是计算出来的。如果信息是由最终用户提供的,那么我们会转义变量以进行显示。

它看起来怎么样?

此时,我们应该已经创建了显示索引页面所需的一切。运行应用程序,然后在浏览器中打开 https://:3000/。如果一切设置正确,您的网站应该看起来像下面的截图。

Home page - Express Local Library site

注意:您还不能使用侧边栏链接,因为那些页面的 URL、视图和模板尚未定义。如果您尝试,您将收到类似“未实现:图书列表”的错误,具体取决于您单击的链接。这些字符串字面量(将被替换为实际数据)是在“controllers”文件中存在的不同控制器中指定的。

后续步骤