Express Tutorial Part 5: Displaying library data

2018-05-15 17:26 更新
先決條件: 完成上一篇教程主題(包括 Express教程第4部分:路由和控制器)。
目的: 要了解如何使用異步模塊和Pug模板語言,以及如何從我們的控制器函數(shù)中的URL獲取數(shù)據(jù)。

概述

在之前的教程文章中,我們定義了 Mongoose模型,我們可以使用這些模型與數(shù)據(jù)庫進行交互并創(chuàng)建一些初始庫記錄。 然后,我們創(chuàng)建了LocalLibrary網(wǎng)站所需的所有路線,但使用"虛擬控制器"函數(shù)(這些是骨架控制器函數(shù),只返回"未實現(xiàn)" 消息)。

下一步是為我們的庫信息頁面提供適當?shù)膶崿F(xiàn)(我們將在后面的文章中討論實現(xiàn)頁面的具體表單以創(chuàng)建,更新或刪除信息)。 這包括更新控制器函數(shù)以使用我們的模型提取記錄,并定義模板以向用戶顯示此信息。

我們將從提供概述/主題主題開始,解釋如何管理控制器函數(shù)中的異步操作以及如何使用Pug編寫模板。 然后,我們將為每個主要的"只讀"頁面提供實現(xiàn),并簡要說明它們使用的任何特殊或新功能。

在本文結束時,您應該對路由,異步函數(shù),視圖和模型在實踐中如何工作有一個良好的端到端理解。

使用異步的異步流控制

一些我們的LocalLibrary 頁面的控制器代碼將取決于多個異步請求的結果,這可能需要以某種特定的順序或并行運行。 為了在我們獲得所有必需的信息時管理流控制和渲染頁面,我們將使用受歡迎的節(jié)點 > async 模塊。

注意:還有其他一些方法可用于管理JavaScript中的異步行為和流量控制,包括最近的JavaScript語言功能,例如 / docs / Mozilla / Add-ons / Techniques / Promises"> Promises 。

Async有很多有用的方法(請查看文檔)。 一些更重要的功能是:

  • async.parallel() to execute any operations that must be performed in parallel.
  • async.series() for when we need to ensure that asynchronous operations are performed in series.
  • async.waterfall() for operations that must be run in series, with each operation depending on the results of preceding operations.

為什么需要這個?

我們在 Express 中使用的大多數(shù)方法是異步的 - 您指定要執(zhí)行的操作,傳遞回調(diào)。 該方法立即返回,并且在請求的操作完成時調(diào)用回調(diào)。 按照約定在 Express 中,回調(diào)函數(shù)傳遞一個錯誤值作為第一個參數(shù)(或成功時 null )和函數(shù)的結果 有任何)作為第二個參數(shù)。

如果控制器只需要一個異步操作來獲取呈現(xiàn)頁面所需的信息,那么實現(xiàn)就很容易 - 我們只需在回調(diào)中呈現(xiàn)模板。 下面的代碼片段顯示了這樣一個函數(shù),它提供了一個模型的計數(shù) SomeModel (使用Mongoose model_Model.count"class ="external"> count() 方法):

exports.some_model_count = function(req, res, next) {
 
  SomeModel.count({ a_model_field: 'match_value' }, function (err, count) {
    // ... do something if there is an err

?   // On success, render the result by passing count into the render function (here, as the variable 'data')
?   res.render('the_template', { data: count } );
? });
}

但是,如果您需要進行多個異步查詢,并且在所有操作完成之前無法呈現(xiàn)該頁面怎么辦? 一個樸素的實現(xiàn)可以"菊花鏈"請求,在之前的請求的回調(diào)中啟動后續(xù)請求,并在最終回調(diào)中呈現(xiàn)響應。 這種方法的問題是,我們的請求必須連續(xù)運行,即使它可能更有效地并行運行它們。 這也可能導致復雜的嵌套代碼,通常稱為回調(diào)地獄。

一個更好的解決方案是并行執(zhí)行所有請求,然后在所有查詢完成后有一個回調(diào)。 這是 Async 模塊簡化的流操作類型!

異步操作并行

方法 async.parallel() 用于 并行運行多個異步操作。

async.parallel()的第一個參數(shù)是要運行的異步函數(shù)的集合(數(shù)組,對象或其他可迭代)。 每個函數(shù)都傳遞一個 callback(err,result),它必須在完成時調(diào)用一個錯誤 err (可以是 null 可選結果值。

async.parallel()的可選第二個參數(shù)是當?shù)谝粋€參數(shù)中的所有函數(shù)都完成時將運行的回調(diào)。 將使用錯誤參數(shù)和包含單個異步操作結果的結果集合調(diào)用回調(diào)。 結果集合的類型與第一個參數(shù)的類型相同(即,如果你傳遞一個異步函數(shù)數(shù)組,最終的回調(diào)函數(shù)將被調(diào)用一個結果數(shù)組)。 如果任何并行函數(shù)報告錯誤,則早期調(diào)用回調(diào)(使用錯誤值)。

下面的例子顯示了當我們傳遞一個對象作為第一個參數(shù)時如何工作。 如您所見,結果是在與傳入的原始函數(shù)具有相同屬性名稱的對象中返回。

async.parallel({ 
? one: function(callback) { ... },
? two: function(callback) { ... },
? ...
? something_else: function(callback) { ... }
? }, 
  // optional callback
  function(err, results) {
?   // 'results' is now equal to: {one: 1, two: 2, ..., something_else: some_value}
? }
);

如果你改為傳遞一個函數(shù)數(shù)組作為第一個參數(shù),結果將是一個數(shù)組(數(shù)組順序結果將匹配函數(shù)聲明的原始順序,而不是它們完成的順序)。

串聯(lián)異步操作

方法 async.series() 用于 當后續(xù)函數(shù)不依賴于早期函數(shù)的輸出時,順序地運行多個異步操作。 它基本上被聲明和行為與 async.parallel()相同。

async.series({ 
? one: function(callback) { ... },
? two: function(callback) { ... },
? ...
? something_else: function(callback) { ... }
? }, 
  // optional callback after the last asyncrhonous function completes.
  function(err, results) {
?   // 'results' is now equals to: {one: 1, two: 2, ..., something_else: some_value} 
? }
);

注意:ECMAScript(JavaScript)語言規(guī)范聲明對象的枚舉順序未定義,因此可能不會按照在所有平臺上指定的順序調(diào)用函數(shù) 。 如果順序真的很重要,那么你應該傳遞一個數(shù)組而不是一個對象,如下所示。

async.series([
  function(callback) {
?   // do some stuff ...
?   callback(null, 'one'); 
? },
  function(callback) {
?   // do some more stuff ... 
?   callback(null, 'two'); 
? } 
?], 
? // optional callback
  function(err, results) {
  // results is now equal to ['one', 'two'] 
  }
); 

依賴的異步操作

方法 async.waterfall() 用于 當每個操作依賴于前一個操作的結果時,順序地運行多個異步操作。

每個異步函數(shù)調(diào)用的回調(diào)包含第一個參數(shù)的 null ,并產(chǎn)生后續(xù)參數(shù)。 系列中的每個函數(shù)都將前一個回調(diào)的結果參數(shù)作為第一個參數(shù),然后是回調(diào)函數(shù)。 當所有操作完成時,將使用最后一個操作的結果調(diào)用最終回調(diào)。 當您考慮下面的代碼片段(此示例來自 async 文檔)時,此工作方式更清楚:

async.waterfall([
  function(callback) {
    callback(null, 'one', 'two'); 
? }, 
? function(arg1, arg2, callback) { 
?   // arg1 now equals 'one' and arg2 now equals 'two' 
?   callback(null, 'three'); 
? }, 
? function(arg1, callback) {
?   // arg1 now equals 'three'
?   callback(null, 'done');
? }
], function (err, result) {
  // result now equals 'done'
}
);

安裝異步

使用NPM包管理器安裝異步模塊,以便我們可以在我們的代碼中使用它。 您可以通過在 LocalLibrary 項目的根目錄中打開提示并輸入以下命令,以通常的方式執(zhí)行此操作:

npm install async --save

模板引物

模板是定義輸出文件的結構或布局的文本文件,其中占位符用于表示在模板呈現(xiàn)時插入數(shù)據(jù)的位置(在 Express 稱為視圖)。

Express可與許多不同的模板呈現(xiàn)引擎一起使用。 在本教程中,我們使用帕格(以前稱為) 為我們的模板。 這是最受歡迎的Node模板語言,并且將其本身描述為用于編寫HTML的"干凈,空白敏感的語法,受到 Haml / a>"。

不同的模板語言使用不同的方法來定義數(shù)據(jù)的布局和標記占位符 - 一些使用HTML來定義布局,而其他模板語言使用不同的標記格式,可以編譯為HTML。 帕格是第二種類型; 它使用HTML的表示,其中任何行中的第一個單詞通常表示一個HTML元素,后續(xù)行上的縮進用于表示嵌套在這些元素中的任何內(nèi)容。 結果是一個頁面定義,直接翻譯為HTML,但可以說是更簡潔,更容易閱讀。

注意:使用 pug 的缺點是,它對縮進和空格很敏感(如果在錯誤的位置添加額外的空格,可能會得到無用的錯誤代碼)。 但是,一旦你有你的模板,他們很容易閱讀和維護。

模板配置

已配置為使用帕格 ="/ webstart / Express_Nodejs / skeleton_website">創(chuàng)建了骨架網(wǎng)站。 您應該在網(wǎng)站的 package.json 文件中查看包含pug模塊的依賴關系,以及 app.js 文件中的以下配置設置。 設置告訴我們,我們使用的是pug作為視圖引擎,并且 Express 應該在 / views 子目錄中搜索模板。

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');

如果查看views目錄,您將看到項目默認視圖的.pug文件。 其中包括首頁( index.pug )和基本模板( layout.pug )的視圖,我們需要將其替換為我們自己的內(nèi)容。

/express-locallibrary-tutorial  //the project root
  /views
    error.pug
    index.pug
    layout.pug

模板語法

下面的示例模板文件顯示了Pug的許多最有用的功能。

首先要注意的是,文件映射了一個典型的HTML文件的結構,第一個字(幾乎)每一行都是一個HTML元素,縮進用來表示嵌套的元素。 因此,例如, body 元素在 html 元素內(nèi),段落元素( p )在 body >元素等。非嵌套元素(例如單個段落)在單獨的行上。

doctype html
html(lang="en")
  head
    title= title
    script(type='text/javascript').
  body
    h1= title

    p This is a line with #[em some emphasis] and #[strong strong text] markup.
    p This line has un-escaped data: !{'<em> is emphasised</em>'} and escaped data: #{'<em> is not emphasised</em>'}. 
? ? ? | This line follows on.
    p= 'Evaluated and <em>escaped expression</em>:' + title

    <!-- You can add HTML comments directly -->
    // You can add single line JavaScript comments and they are generated to HTML comments
    //- Introducing a single line JavaScript comment with "http://-" ensures the comment isn't rendered to HTML 
    
    p A line with a link 
      a(href='/catalog/authors') Some link text
      |  and some extra text.
    
    #container.col
      if title
        p A variable named "title" exists.
      else
        p A variable named "title" does not exist.
      p.
        Pug is a terse and simple template language with a
        strong focus on performance and powerful features.
        
    h2 Generate a list
        
    ul
      each val in [1, 2, 3, 4, 5]
        li= val

元素屬性在其關聯(lián)元素后面的括號中定義。 在括號內(nèi),屬性以屬性名稱和屬性值對的逗號或空格分隔列表定義,例如:

  • script(type='text/javascript'), link(rel='stylesheet', href='/stylesheets/style.css')
  • meta(name='viewport' content='width=device-width initial-scale=1')

所有屬性的值都被轉(zhuǎn)義(例如,像"> "之類的字符被轉(zhuǎn)換為類似"& gt;" ),以防止注入JavaScript /跨站點腳本攻擊。

如果標記后面帶有等號,則以下文本將被視為JavaScript 表達式。 例如,在下面的第一行中, h1 標簽的內(nèi)容將是變量 title (在文件中定義或傳入 來自Express的模板)。 在第二行中,段落內(nèi)容是與 title 變量并置的文本字符串。 在這兩種情況下,默認行為是轉(zhuǎn)義該行。

h1= title 
p= 'Evaluated and <em>escaped expression</em>:' + title

如果標簽后面沒有等號,則內(nèi)容被視為純文本。 在純文本中,可以使用#{} !{} 語法插入轉(zhuǎn)義和非轉(zhuǎn)義數(shù)據(jù),如下所示。 您還可以在純文本中添加原始HTML。

p This is a line with #[em some emphasis] and #[strong strong text] markup. 
p This line has an un-escaped string: !{'<em> is emphasised</em>'}, an escaped string: #{'<em> is not emphasised</em>'}, and escaped variables: #{title}.

提示:您幾乎總是希望通過 #{} 語法從用戶轉(zhuǎn)義數(shù)據(jù)。 可以顯示可信的數(shù)據(jù)(例如,記錄的生成計數(shù)等),而不轉(zhuǎn)義值。

您可以使用管道(\' | \')在行的開頭指示" "external"> plain text "。 例如,下面顯示的附加文本將顯示在與前一個錨相同的行上,但不會鏈接。

a(href='http://someurl/') Link text
| Plain text

Pug允許您使用 if , else , else if 執(zhí)行條件操作,除非

if title
  p A variable named "title" exists
else
  p A variable named "title" does not exist

您還可以使用 each-in while 語法執(zhí)行循環(huán)/迭代操作。 在下面的代碼片段中,我們循環(huán)遍歷一個數(shù)組來顯示一個變量列表(注意,使用\'li =\'來評估"val"作為下面的變量,迭代的值也可以傳遞到 模板作為變量!

ul
  each val in [1, 2, 3, 4, 5]
    li= val

語法還支持注釋(可以在輸出中呈現(xiàn) - 或者不根據(jù)您的選擇),mixin來創(chuàng)建可重用的代碼塊,case語句和許多其他功能。 有關詳細信息,請參閱帕格文檔。

擴展模板

在整個網(wǎng)站中,通常所有頁面都有一個通用的結構,包括頭,頁腳,導航等的標準HTML標記。而不是強制開發(fā)人員在每個頁面復制這個"樣板",

em>允許您聲明一個基本模板,然后擴展它,只替換每個特定頁面不同的位。

例如,在我們的骨架項目中創(chuàng)建的基本模板 layout.pug 如下所示:

doctype html
html
  head
    title= title
    link(rel='stylesheet', href='/stylesheets/style.css')
  body
    block content

標簽用于標記可在導出模板中替換的內(nèi)容段(如果塊未重新定義,則使用其在基本類中的實現(xiàn))。

默認的 index.pug (為我們的骨架項目創(chuàng)建)顯示了我們?nèi)绾胃采w基本模板。 extends 標簽標識要使用的基本模板,然后使用 block section_name 來指示我們將覆蓋的部分的新內(nèi)容。

extends layout

block content
  h1= title
  p Welcome to #{title}

LocalLibrary基本模板

現(xiàn)在我們了解了如何使用Pug擴展模板,讓我們從為項目創(chuàng)建一個基本模板開始。 這將有一個側(cè)邊欄,其中包含我們希望在教程文章(例如,顯示和創(chuàng)建書籍,流派,作者等)中創(chuàng)建的網(wǎng)頁的鏈接,以及我們在每個網(wǎng)頁中覆蓋的主要內(nèi)容區(qū)域。

打開 /views/layout.pug ,然后將內(nèi)容替換為以下代碼。

doctype html
html(lang='en')
  head
    title= title
    meta(charset='utf-8')
    meta(name='viewport', content='width=device-width, initial-scale=1')
    link(rel='stylesheet', )
    script(src='https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js')
    script(src='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js')
    link(rel='stylesheet', href='/stylesheets/style.css')
  body
    div(class='container-fluid')
      div(class='row')
        div(class='col-sm-2')
          block sidebar
            ul(class='sidebar-nav')
              li 
                a(href='/catalog') Home
              li 
                a(href='/catalog/books') All books
              li 
                a(href='/catalog/authors') All authors
              li 
                a(href='/catalog/genres') All genres
              li 
                a(href='/catalog/bookinstances') All book-instances
              li 
                hr
              li 
                a(href='/catalog/author/create') Create new author
              li 
                a(href='/catalog/genre/create') Create new genre
              li 
                a(href='/catalog/book/create') Create new book
              li 
                a(href='/catalog/bookinstance/create') Create new book instance (copy)
                
        div(class='col-sm-10')
          block content

該模板使用(并包括)來自 Bootstrap 的JavaScript和CSS,以改進HTML頁面的布局和顯示方式。 使用Bootstrap或另一個客戶端web框架是一個快速的方式來創(chuàng)建一個有吸引力的網(wǎng)頁,可以在不同的瀏覽器大小擴展好,它也允許我們處理頁面演示,而無需進入任何細節(jié) - 我們只是 想在這里聚焦于服務器端代碼!

如果您已閱讀我們上述的模板引用,布局應該非常明顯。 請注意使用阻止內(nèi)容作為占位符,用于放置我們各個頁面的內(nèi)容。

基本模板還引用了一個提供一些額外樣式的本地css文件( styles.css )。 打開 /public/stylesheets/styles.css ,并將其內(nèi)容替換為以下CSS代碼:

.sidebar-nav {
    margin-top: 20px;
    padding: 0;
    list-style: none;
}

當我們到運行我們的網(wǎng)站,我們應該看到側(cè)邊欄出現(xiàn)! 在接下來的部分中,我們將使用上述布局來定義單個頁面。

主頁

我們將創(chuàng)建的第一個頁面是網(wǎng)站主頁,可以從網(wǎng)站(\'/\')或目錄(目錄/ )訪問。 這將顯示一些描述站點的靜態(tài)文本,以及數(shù)據(jù)庫中不同記錄類型的動態(tài)計算的"計數(shù)"。

我們已經(jīng)為主頁創(chuàng)建了一個路線。 為了完成頁面,我們需要更新我們的控制器函數(shù)來從數(shù)據(jù)庫中獲取記錄的"計數(shù)",并創(chuàng)建一個視圖(模板),我們可以使用它來渲染頁面。

路線

我們在上一個教程中創(chuàng)建了索引頁路線。提醒您,所有路線功能都在 /routes/catalog.js strong>:

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

/controllers/bookController.js 中定義回調(diào)函數(shù)參數(shù)( book_controller.index ):

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

它是這個控制器函數(shù),我們擴展以從我們的模型獲取信息,然后使用模板(視圖)渲染它。

控制器

索引控制器功能需要獲取有關 Book , BookInstance ,可用的 BookInstance , Author 代碼> Genre 記錄在數(shù)據(jù)庫中,在模板中呈現(xiàn)這些數(shù)據(jù)以創(chuàng)建一個HTML頁面,然后在HTTP響應中返回它。

請注意:我們使用 count() > 方法獲取每個模型的實例數(shù)。 這在具有可選的條件集合的模型上被調(diào)用,在第一個參數(shù)中匹配,第二個參數(shù)中有回調(diào)(如使用數(shù)據(jù)庫(使用Mongoose)中所述) ,您還可以返回 Query ,然后稍后使用回調(diào)執(zhí)行它。當數(shù)據(jù)庫返回計數(shù)時將返回回調(diào),并返回錯誤值(或 null )作為第一個參數(shù),記錄計數(shù)(如果有錯誤則為null)作為第二個參數(shù)。

SomeModel.count({ a_model_field: 'match_value' }, function (err, count) {
 // ... do something if there is an err
?// ... do something with the count if there was no error
?});

打開 /controllers/bookController.js 。 在文件頂部附近,您應該看到導出的 index()函數(shù)。

var Book = require('../models/book')

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

將以上所有代碼替換為以下代碼片段。 第一件事是import( require())所有的模型(以粗體突出顯示)。 我們需要這樣做,因為我們將使用它們來獲得我們的記錄數(shù)。 然后導入 async 模塊。

var Book = require('../models/book');
var Author = require('../models/author');
var Genre = require('../models/genre');
var BookInstance = require('../models/bookinstance');

var async = require('async');

exports.index = function(req, res) {   
    
    async.parallel({
        book_count: function(callback) {
            Book.count(callback);
        },
        book_instance_count: function(callback) {
            BookInstance.count(callback);
        },
        book_instance_available_count: function(callback) {
            BookInstance.count({status:'Available'}, callback);
        },
        author_count: function(callback) {
            Author.count(callback);
        },
? ? ? ? genre_count: function(callback) {
? ? ? ? ? ? Genre.count(callback);
? ? ? ? },
    }, function(err, results) {
        res.render('index', { title: 'Local Library Home', error: err, data: results });
    });
};

async.parallel()方法被傳遞一個對象,其函數(shù)用于獲取每個模型的計數(shù)。 這些功能都是同時啟動的。 當所有它們都完成時,最終回調(diào)被調(diào)用與results參數(shù)中的計數(shù)(或錯誤)。

成功時,回調(diào)函數(shù)調(diào)用 res.render() / code>,指定名為" index "的視圖(模板)和包含要插入其中的數(shù)據(jù)的對象(這包括包含模型計數(shù)的results對象)。 數(shù)據(jù)作為鍵值對提供,可以使用鍵在模板中訪問。

注意:上面 async.parallel()的回調(diào)函數(shù)有點不尋常,因為我們渲染頁面無論是否有錯誤(通常您可能使用單獨的 用于處理錯誤顯示的執(zhí)行路徑)。

視圖

打開 /views/index.pug ,并將其內(nèi)容替換為以下文字。

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.

? h1 Dynamic content

? if error
? ? p Error getting dynamic content.
? else
? ? p The library has the following record counts:

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

視圖很簡單。 我們擴展 layout.pug 基本模板,覆蓋名為"內(nèi)容"的。 第一個 h1 標題將是傳遞到 render()函數(shù)中的 title 變量的轉(zhuǎn)義文本, h1 = \',以便將以下文本視為JavaScript表達式。 然后我們包括一個介紹LocalLibrary的段落。

動態(tài)內(nèi)容標題下,我們檢查從 render()函數(shù)傳入的錯誤變量是否已定義。 如果是這樣,我們注意到錯誤。 如果沒有,我們從 data 變量中獲取并列出每個模型的副本數(shù)。

注意:我們沒有轉(zhuǎn)義計數(shù)值(即我們使用!{} 語法),因為計算的是計數(shù)值。 如果信息由最終用戶提供,那么我們將轉(zhuǎn)義變量來顯示。

它是什么樣子的?

在這一點上,我們應該創(chuàng)建顯示索引頁所需的一切。 運行應用程序并打開瀏覽器以 http:// localhost:3000 / 。 如果一切設置正確,您的網(wǎng)站應該看起來像下面的屏幕截圖。

請注意:您將無法使用側(cè)欄鏈接,因為尚未定義這些網(wǎng)頁的網(wǎng)址,視圖和模板。 如果您嘗試,您將收到錯誤,例如"NOT IMPLEMENTED:Book list",例如,根據(jù)您點擊的鏈接。 這些字符串文字(將被適當?shù)臄?shù)據(jù)替換)在居住在"controllers"文件中的不同控制器中指定。

圖書列表頁

接下來,我們將實現(xiàn)我們的圖書列表頁面。 此頁面需要顯示數(shù)據(jù)庫中所有圖書的列表及其作者,每個圖書標題是其關聯(lián)的圖書詳細信息頁面的超鏈接。

控制器

書列表控制器函數(shù)需要獲取數(shù)據(jù)庫中所有 Book 對象的列表,然后將它們傳遞到模板以進行呈現(xiàn)。

打開 /controllers/bookController.js 。 找到導出的 book_list()控制器方法,并將其替換為以下代碼(更改的代碼以粗體顯示)。

// Display list of all Books
exports.book_list = function(req, res, next) {

? Book.find({}, 'title author ')
? ? .populate('author')
? ? .exec(function (err, list_books) {
? ? ? if (err) { return next(err); }
? ? ? //Successful, so render
? ? ? res.render('book_list', { title: 'Book List', book_list: ?list_books});
? ? });
? ??
};

該方法使用模型的 find()函數(shù)返回所有 Book 對象,選擇只返回 title 代碼>,因為我們不需要其他字段(它也將返回 _id 和虛擬字段)。 這里我們還調(diào)用 Book 上的 populate(),指定作者字段 - 這將用完整的作者詳細信息替換存儲的圖書作者ID。

成功時,傳遞到查詢的回調(diào)會呈現(xiàn) book_list (。pug)模板,傳遞 title book_list )作為變量。

視圖

創(chuàng)建 /views/book_list.pug 并在下面的文字中復制。

extends layout

block content
? h1= title
??
? ul
? each book in book_list
? ? li?
? ? ? a(href=book.url) #{book.title}?
? ? ? | (#{book.author.name})

? else
? ? li There are no books.

該視圖擴展了 layout.pug 基本模板,并覆蓋了名為"內(nèi)容"的。 它顯示我們從控制器傳遞的 title (通過 render()方法),然后使用 >每個 - in - else 語法。 為顯示書名的每本書創(chuàng)建一個列表項,作為書的詳細頁面的鏈接,后面跟著作者姓名。 如果 book_list 中沒有圖書,則會執(zhí)行 else 子句,并顯示文本"沒有圖書"。

注意:我們使用 book.url 為每本圖書提供詳細記錄的鏈接(我們已實施此路線,但尚未實現(xiàn)此路線)。 這是 Book 模型的虛擬屬性,它使用模型實例的 _id 字段來生成唯一的URL路徑。

這里感興趣的是,每本書被定義為兩行,使用管道為第二行(上面突出顯示)。 需要這種方法,因為如果作者姓名在上一行,那么它將是超鏈接的一部分。

它是什么樣子的?

運行應用程序(請參閱相關命令的測試路線),然后打開瀏覽器 http:// localhost:3000 / 。 然后選擇所有圖書鏈接。 如果一切設置正確,您的網(wǎng)站應該看起來像下面的屏幕截圖。

; width:918px;">

BookInstance列表頁

接下來,我們將實現(xiàn)庫中所有圖書副本( BookInstance )的列表。 此頁面需要包括與 BookInstance 模型中的其他信息相關聯(lián)的 Book 的標題(鏈接到其詳細信息頁面) 包括每個副本的狀態(tài),印記和唯一ID。 唯一標識文本應鏈接到 BookInstance 詳細信息頁面。

控制器

BookInstance 列表控制器函數(shù)需要獲取所有圖書實例的列表,填充關聯(lián)的圖書信息,然后將列表傳遞到模板進行渲染。

打開 /controllers/bookinstanceController.js 。 找到導出的 bookinstance_list()控制器方法,并將其替換為以下代碼(更改的代碼以粗體顯示)。

// Display list of all BookInstances
exports.bookinstance_list = function(req, res, next) {

? BookInstance.find()
? ? .populate('book')
? ? .exec(function (err, list_bookinstances) {
? ? ? if (err) { return next(err); }
? ? ? //Successful, so render
? ? ? res.render('bookinstance_list', { title: 'Book Instance List', bookinstance_list: ?list_bookinstances});
? ? });
? ??
};

該方法使用模型的 find()函數(shù)返回所有 BookInstance 對象。 然后將 populate()的調(diào)用鏈接到 book 字段 - 這將替換每個 BookInstance 代碼> Book 文檔。

成功時,傳遞到查詢的回調(diào)會呈現(xiàn) bookinstance_list (。pug)模板,將 title bookinstance_list 作為變量。

視圖

創(chuàng)建 /views/bookinstance_list.pug 并在下面的文字中復制。

extends layout

block content
? h1= title

? ul
? each val in bookinstance_list
? ? li?
? ? ? a(href=val.url) #{val.book.title} : #{val.imprint} -?
? ? ? if val.status=='Available'
? ? ? ? span.text-success #{val.status}
? ? ? else if val.status=='Maintenance'
? ? ? ? span.text-danger #{val.status}
? ? ? else
? ? ? ? span.text-warning #{val.status}?
? ? ? if val.status!='Available'
? ? ? ? span ?(Due: #{val.due_back} )

? else
? ? li There are no book copies in this library.

這個視圖與所有其他人都是一樣的。 它擴展布局,替換內(nèi)容塊,顯示從控制器傳入的 title ,并遍歷 bookinstance_list 中的所有書副本。 對于每個副本,我們顯示其狀態(tài)(彩色編碼),如果圖書不可用,則其預期的返回日期。

它是什么樣子的?

運行應用程序,打開瀏覽器 http:// localhost:3000 / ,然后選擇所有圖書 - 實例鏈接。 如果一切設置正確,您的網(wǎng)站應該看起來像下面的屏幕截圖。

; width:1200px;">

使用時刻進行日期格式化

從我們的模型默認渲染日期是非常丑陋: Tue Dec 06 2016 15:49:58 GMT + 1100(AUS Eastern Daylight Time)。 在本節(jié)中,我們將介紹如何更新上一部分的 BookInstance列表頁面,以更友好的格式顯示 due_date 字段:2016年12月6日。

我們將使用的方法是在我們的 BookInstance 模型中創(chuàng)建一個虛函數(shù),返回格式化的日期。 我們將使用時刻進行實際格式化,這是一個輕量級JavaScript日期庫,用于解析,驗證,操作 ,以及格式化日期。

注意:可以使用時刻在我們的Pug模板中直接設置字符串格式,也可以在其他多個位置格式化字符串。 使用虛擬屬性允許我們以與我們得到 due_date 完全相同的方式獲得格式化的日期。

安裝時刻

在項目的根目錄中輸入以下命令:

npm install moment --save

創(chuàng)建虛擬屬性

  1. Open ./models/bookinstance.js.
  2. At the top of the page, import moment.
    var moment = require('moment');
  3. Add the virtual property due_back_formatted just after the url property.
    BookInstanceSchema
    .virtual('due_back_formatted')
    .get(function () {
      return moment(this.due_back).format('MMMM Do, YYYY');
    });

注意:格式方法可以使用幾乎模式顯示日期。 用于表示不同日期組件的語法可以在此處找到

更新視圖

打開 /views/bookinstance_list.pug 并將 due_back 的所有實例替換為 due_back_formatted 。

? ? ? if val.status!='Available'
? ? ? ? //span ?(Due: #{val.due_back} )
?? ? ? ?span ?(Due: #{val.due_back_formatted} )       

而已。 如果您轉(zhuǎn)到側(cè)邊欄中的所有圖書實例,您現(xiàn)在應該可以看到所有到期日更具吸引力!

作者列表頁

作者列表頁面需要顯示數(shù)據(jù)庫中所有作者的列表,每個作者名稱鏈接到其相關聯(lián)的作者詳細信息頁面。 出生日期和死亡日期應列在同一行的姓名之后。

控制器

作者列表控制器函數(shù)需要獲取所有 Author 實例的列表,然后將這些傳遞給模板進行渲染。

打開 /controllers/authorController.js 。 找到靠近文件頂部的導出的 author_list()控制器方法,并將其替換為以下代碼(更改的代碼以粗體顯示)。

// Display list of all Authors
exports.author_list = function(req, res, next) {

? Author.find()
? ? .sort([['family_name', 'ascending']])
? ? .exec(function (err, list_authors) {
? ? ? if (err) { return next(err); }
? ? ? //Successful, so render
? ? ? res.render('author_list', { title: 'Author List', author_list: ?list_authors});
? ? });

};

該方法使用模型的 find() sort() exec()函數(shù)返回所有 Author 按照 family_name 按字母順序排序。 傳遞給 exec()方法的回調(diào)被調(diào)用時,會將任何錯誤(或 null )作為第一個參數(shù),或者成功的所有作者的列表。 如果有錯誤,它會調(diào)用帶有錯誤值的下一個中間件函數(shù),如果不是,則會呈現(xiàn) author_list (。pug)模板,傳遞 title 的作者( author_list )。

視圖

創(chuàng)建 /views/author_list.pug ,并將其內(nèi)容替換為以下文字。

extends layout

block content
? h1= title
??
? ul
? each author in author_list
? ? li?
? ? ? a(href=author.url) #{author.name}?
? ? ? | (#{author.date_of_birth} - #{author.date_of_death})

? else
? ? li There are no authors.

視圖遵循與其他模板完全相同的模式。

它是什么樣子的?

運行應用程序并打開瀏覽器以 http:// localhost:3000 / 。 然后選擇所有作者鏈接。 如果一切都正確設置,頁面應該看起來像下面的屏幕截圖。

請注意:作者的外觀生命周期日期是丑陋的! 您可以使用與用于BookInstance列表的相同方法(將生命周期的虛擬屬性添加到作者模型)來改進此操作。

類型列表頁面挑戰(zhàn)!

在本節(jié)中,您應該實現(xiàn)自己的類型列表頁面。 頁面應顯示數(shù)據(jù)庫中所有類型的列表,每個類型鏈接到其關聯(lián)的詳細信息頁面。 預期結果的屏幕截圖如下所示。

; width:600px;">

類型列表控制器函數(shù)需要獲取所有 Genre 實例的列表,然后將這些傳遞給模板進行渲染。

  1. You will need to edit?genre_list() in?/controllers/genreController.js.?
  2. The implementation is almost exactly the same as the author_list() controller.
    • Sort the results by name, in ascending order.
  3. The template to be rendered should be named genre_list.pug.
  4. The template to be rendered should be passed the variables title ('Genre List') and list_genre (the list of genres returned from your Genre.find() callback.

為視圖創(chuàng)建 /views/genre_list.pug 文件。 實現(xiàn)一個匹配上面的屏幕截圖/要求的布局(這應該有一個非常類似于作者列表視圖的結構/格式)。

類型詳細信息頁面

類別 網(wǎng)頁需要顯示特定類型實例的信息,使用它(自動生成) _id 字段值標識。 該頁面應顯示類型名稱,以及類型中的所有圖書的列表(每個圖書都鏈接到圖書的詳細信息頁面)。

控制器

打開 /controllers/genreController.js ,然后導入文件頂部的 async 圖書模塊。

var Book = require('../models/book');
var async = require('async');

找到導出的 genre_detail ()控制器方法,并將其替換為以下代碼(更改的代碼以粗體顯示)。

// Display detail page for a specific Genre
exports.genre_detail = function(req, res, next) {

? ? async.parallel({
? ? ? ? genre: function(callback) {??
? ? ? ? ? ? Genre.findById(req.params.id)
? ? ? ? ? ? ? .exec(callback);
? ? ? ? },
? ? ? ??
? ? ? ? genre_books: function(callback) {? ? ? ? ? ??
? ? ? ? ? Book.find({ 'genre': req.params.id })
? ? ? ? ? .exec(callback);
? ? ? ? },

? ? }, function(err, results) {
? ? ? ? if (err) { return next(err); }
? ? ? ? //Successful, so render
?
? ? ? ? res.render('genre_detail', { title: 'Genre Detail', genre: results.genre, genre_books: results.genre_books } );
? ? });

};

該方法使用 async.parallel()來并行查詢流派名稱及其相關聯(lián)的圖書,在(if)兩個請求都成功完成時回調(diào)渲染頁面。

所需流派記錄的ID在網(wǎng)址末尾編碼,并根據(jù)路線定義( / genre /:id )自動提取。 通過請求參數(shù)在控制器中訪問ID: req.params.id 。 它用于 Genre.findById()以獲取當前類型。 它還用于獲取在其 genre 字段中具有類型ID的所有 Book 對象: Book.find({\'genre\':req.params.id })

呈現(xiàn)的視圖是 genre_detail ,它傳遞 title , genre 和此類別書籍列表的變量( genre_books / code>)。

視圖

創(chuàng)建 /views/genre_detail.pug 并填寫以下文字:

extends layout

block content

? h1 Genre: #{genre.name}
??
? div(style='margin-left:20px;margin-top:20px')

? ? h4 Books
? ??
? ? dl
? ? each book in genre_books
? ? ? dt?
? ? ? ? a(href=book.url) #{book.title}
? ? ? dd #{book.summary}

? ? else
? ? ? p This genre has no books

該視圖與所有其他模板非常相似。 主要區(qū)別是我們不使用傳遞給第一個標題的 title (雖然它是在底層的 layout.pug 模板中用來設置頁面 標題)。

它是什么樣子的?

運行應用程序并打開瀏覽器以 http:// localhost:3000 / 。 然后選擇所有類型鏈接,然后選擇一種類型(例如"幻想")。 如果一切設置正確,您的頁面應該看起來像下面的屏幕截圖。

; width:1000px;">

書詳細信息頁

書詳細信息頁需要顯示特定 Book 的信息,該信息使用其(自動生成的) _id 字段值標識, 每個相關的副本在庫( BookInstance )。 無論我們在何處顯示作者,類型或書籍實例,都應將其鏈接到該項目的相關詳細信息頁面。

控制器

打開 /controllers/bookController.js 。 找到導出的 book detail()控制器方法,并將其替換為以下代碼(更改的代碼以粗體顯示)。

// Display detail page for a specific book
exports.book_detail = function(req, res, next) {

? ? async.parallel({
? ? ? ? book: function(callback) { ? ??
? ? ? ??
? ? ? ? ? ? Book.findById(req.params.id)
? ? ? ? ? ? ? .populate('author')
? ? ? ? ? ? ? .populate('genre')
? ? ? ? ? ? ? .exec(callback);
? ? ? ? },
? ? ? ? book_instance: function(callback) {

? ? ? ? ? BookInstance.find({ 'book': req.params.id })
? ? ? ? ? //.populate('book')
? ? ? ? ? .exec(callback);
? ? ? ? },
? ? }, function(err, results) {
? ? ? ? if (err) { return next(err); }
? ? ? ? //Successful, so render
? ? ? ? res.render('book_detail', { title: 'Title', book: ?results.book, book_instances: results.book_instance } );
? ? });
? ??
};

注意:我們不需要 async BookInstance ,因為我們在實施主頁控制器時已經(jīng)導入了這些模塊。

該方法使用 async.parallel()并行查找 Book 及其關聯(lián)副本( BookInstances )。 該方法與上述流派詳細信息頁中描述的完全相同。

視圖

創(chuàng)建 /views/book_detail.pug 并添加以下文字。

extends layout

block content
? h1 #{title}: #{book.title}
??
? p #[strong Author:]?
? ? a(href=book.author.url) #{book.author.name}
? p #[strong Summary:] #{book.summary}
? p #[strong ISBN:] #{book.isbn}
? p #[strong Genre:]&nbsp;
? ? each val in book.genre
? ? ? a(href=val.url) #{val.name}
? ? ? |,?
??
? div(style='margin-left:20px;margin-top:20px')
? ? h4 Copies
? ??
? ? each val in book_instances
? ? ? hr
? ? ? if val.status=='Available'
? ? ? ? p.text-success #{val.status}
? ? ? else if val.status=='Maintenance'
? ? ? ? p.text-danger #{val.status}
? ? ? else
? ? ? ? p.text-warning #{val.status}?
? ? ? p #[strong Imprint:] #{val.imprint}
? ? ? if val.status!='Available'
? ? ? ? p #[strong Due back:] #{val.due_back}
? ? ? p #[strong Id:]&nbsp;
? ? ? ? a(href=val.url) #{val._id}
?
? ? else
? ? ? p There are no copies of this book in the library.

這個模板中的幾乎所有內(nèi)容都已在前面的章節(jié)中演示過。 一個新功能以粗體顯示,我們可以使用標記后的點符號來分配類。 因此 p.text-success 將被編譯為< p class ="text-success"> (也可以用Pug編寫為 p class ="text-success))。

注意:與書籍相關聯(lián)的類型列表在模板中實現(xiàn),如下所示。 這會在與書相關聯(lián)的每個類別之后添加逗號,這意味著在最后一個項目后面還將有一個逗號。

  p #[strong Genre:] 
    each val in book.genre
      a(href=val.url) #{val.name}
      |, 

沒有默認方式獲取有關Pug最后一次迭代的信息,因此要刪除此逗號,您必須在JavaScript中構建此字符串并將其傳遞給您的模板(可能作為與當前書相關聯(lián)的虛擬屬性)。

它是什么樣子的?

運行應用程序并打開瀏覽器以 http:// localhost:3000 / 。 然后選擇所有圖書鏈接,然后選擇一本圖書。 如果一切設置正確,您的頁面應該看起來像下面的屏幕截圖。

; width:1200px;">

作者詳細頁面

作者詳細信息頁面需要顯示關于指定的 Author 的信息,使用其(自動生成的) _id 字段值以及所有作者相關聯(lián)的對象。

控制器

打開 /controllers/authorController.js 。

將以下行添加到文件頂部以導入 async 圖書模塊(這些是我們的作者詳細信息頁所需的)。

var async = require('async');
var Book = require('../models/book');

找到導出的 author_detail()控制器方法,并將其替換為以下代碼(更改的代碼以粗體顯示)。

// Display detail page for a specific Author
exports.author_detail = function(req, res, next) {

? ? async.parallel({
? ? ? ? author: function(callback) { ? ??
? ? ? ? ? ? Author.findById(req.params.id)
? ? ? ? ? ? ? .exec(callback);
? ? ? ? },
? ? ? ? authors_books: function(callback) {
? ? ? ? ? Book.find({ 'author': req.params.id },'title summary')
? ? ? ? ? .exec(callback);
? ? ? ? },
? ? }, function(err, results) {
? ? ? ? if (err) { return next(err); }
? ? ? ? //Successful, so render

? ? ? ? res.render('author_detail', { title: 'Author Detail', author: results.author, author_books: results.authors_books } );
? ? });
? ??
};

該方法使用 async.parallel()并行查詢 Author 及其關聯(lián)的 Book 實例, )兩個請求都成功完成。 該方法與上述流派詳細信息頁中描述的完全相同。

視圖

創(chuàng)建 /views/author_detail.pug 并在以下文本中復制。

extends layout

block content

? h1 Author: #{author.name}
? p #{author.date_of_birth} - #{author.date_of_death}
??
? div(style='margin-left:20px;margin-top:20px')

? ? h4 Books
? ??
? ? dl
? ? each book in author_books
? ? ? dt?
? ? ? ? a(href=book.url) #{book.title}
? ? ? dd #{book.summary}

? ? else
? ? ? p This author has no books.

此模板中的所有內(nèi)容已在前面的章節(jié)中演示。

它是什么樣子的?

運行應用程序并打開瀏覽器以 http:// localhost:3000 / 。 然后選擇所有圖書鏈接,然后選擇一本圖書。 如果一切設置正確,您的網(wǎng)站應該看起來像下面的屏幕截圖。

; width:1000px;">

請注意:作者的外觀生命周期日期是丑陋的! 我們將在這個artice的最后挑戰(zhàn)中解決這個問題。

BookInstance詳細信息頁面

BookInstance 詳細信息頁面需要顯示使用其(自動生成的) _id 字段值標識的每個 BookInstance 的信息。 這將包括 Book 名稱(作為圖書詳細信息頁的鏈接)以及記錄中的其他信息。

控制器

打開 /controllers/bookinstanceController.js 。 找到導出的 bookinstance_detail()控制器方法,并將其替換為以下代碼(更改的代碼以粗體顯示)。

// Display detail page for a specific BookInstance
exports.bookinstance_detail = function(req, res, next) {

? ? BookInstance.findById(req.params.id)
? ? .populate('book')
? ? .exec(function (err, bookinstance) {
? ? ? if (err) { return next(err); }
? ? ? //Successful, so render
? ? ? res.render('bookinstance_detail', { title: 'Book:', bookinstance: ?bookinstance});
? ? });
? ??
};

請注意:我們不需要為此控制器添加 async Book 。

該方法調(diào)用 BookInstance.findById()與我們感興趣的特定作者的ID從URL中提取(使用路由),并通過請求參數(shù)在控制器內(nèi)訪問: "font-style:normal; font-weight:normal;"> req.params.id )。 然后調(diào)用populate()來獲取相關的 Book 的詳細信息。

視圖

創(chuàng)建 /views/bookinstance_detail.pug 并復制下面的內(nèi)容。

extends layout

block content

? h1 ID: #{bookinstance._id}
??
? p #[strong Title:]?
? ? a(href=bookinstance.book.url) #{bookinstance.book.title}
? p #[strong Imprint:] #{bookinstance.imprint}

? p #[strong Status:]?
? ? if bookinstance.status=='Available'
? ? ? span.text-success #{bookinstance.status}
? ? else if bookinstance.status=='Maintenance'
? ? ? span.text-danger #{bookinstance.status}
? ? else
? ? ? span.text-warning #{bookinstance.status}?
? ? ??
? if bookinstance.status!='Available'
? ? p #[strong Due back:] #{bookinstance.due_back}

此模板中的所有內(nèi)容已在前面的章節(jié)中演示。

它是什么樣子的?

運行應用程序并打開瀏覽器以 http:// localhost:3000 / 。 然后選擇所有圖書實例鏈接,然后選擇其中一個項目。 如果一切設置正確,您的網(wǎng)站應該看起來像下面的屏幕截圖。

; width:1000px;">

挑戰(zhàn)

目前網(wǎng)站上顯示的大多數(shù)日期 使用默認的JavaScript格式(例如 2016年12月06日15:49:58 GMT + 1100(澳大利亞東部夏令時間) 本文旨在改進作者生命周期信息(死亡/出生日期)和 BookInstance詳細信息頁面的日期顯示的外觀,以使用以下格式:2016年12月6日 。

注意:您可以使用相同的方法來使用圖書實例列表(添加虛擬屬性 使用作者模型,并使用時刻格式化日期字符串) 。

滿足這一挑戰(zhàn)的要求:

  1. Replace the variable due_back with due_back_formatted in the BookInstance detail page.
  2. Update the Author?module to add a lifespan virtual property. The lifespan should look like: date_of_birth - date_of_death, where both values have the same date format as BookInstance.due_back_formatted.
  3. Use Author.lifespan in all views where you currently explicitly use date_of_birth and date_of_death.

    概要

    我們現(xiàn)在為網(wǎng)站創(chuàng)建了所有的"只讀"網(wǎng)頁:一個主頁,顯示每個模型的實例,以及我們的圖書,圖書實例,作者和類型的列表和詳細頁面。 一路上,我們獲得了許多關于控制器的基礎知識,在使用異步操作時管理流控制,使用 pug 創(chuàng)建視圖,使用我們的模型查詢數(shù)據(jù)庫,如何將信息傳遞到模板 您的視圖,以及如何創(chuàng)建和擴展模板。 那些完成挑戰(zhàn)的人也將學習一些關于使用時刻的日期處理。

    在下一篇文章中,我們將基于我們的知識,創(chuàng)建HTML表單和表單處理代碼,以開始修改網(wǎng)站存儲的數(shù)據(jù)。

    也可以看看

    以上內(nèi)容是否對您有幫助:
    在線筆記
    App下載
    App下載

    掃描二維碼

    下載編程獅App

    公眾號
    微信公眾號

    編程獅公眾號