Express Tutorial Part 5: Displaying library data

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

概述

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

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

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

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

使用異步的異步流控制

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

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

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

  • 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.

為什么需要這個(gè)?

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

如果控制器只需要一個(gè)異步操作來(lái)獲取呈現(xiàn)頁(yè)面所需的信息,那么實(shí)現(xiàn)就很容易 - 我們只需在回調(diào)中呈現(xiàn)模板。 下面的代碼片段顯示了這樣一個(gè)函數(shù),它提供了一個(gè)模型的計(jì)數(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 } );
? });
}

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

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

異步操作并行

方法 async.parallel() 用于 并行運(yùn)行多個(gè)異步操作。

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

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

下面的例子顯示了當(dāng)我們傳遞一個(gè)對(duì)象作為第一個(gè)參數(shù)時(shí)如何工作。 如您所見(jiàn),結(jié)果是在與傳入的原始函數(shù)具有相同屬性名稱(chēng)的對(duì)象中返回

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

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

串聯(lián)異步操作

方法 async.series() 用于 當(dāng)后續(xù)函數(shù)不依賴(lài)于早期函數(shù)的輸出時(shí),順序地運(yùn)行多個(gè)異步操作。 它基本上被聲明和行為與 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)語(yǔ)言規(guī)范聲明對(duì)象的枚舉順序未定義,因此可能不會(huì)按照在所有平臺(tái)上指定的順序調(diào)用函數(shù) 。 如果順序真的很重要,那么你應(yīng)該傳遞一個(gè)數(shù)組而不是一個(gè)對(duì)象,如下所示。

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'] 
  }
); 

依賴(lài)的異步操作

方法 async.waterfall() 用于 當(dāng)每個(gè)操作依賴(lài)于前一個(gè)操作的結(jié)果時(shí),順序地運(yùn)行多個(gè)異步操作。

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

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包管理器安裝異步模塊,以便我們可以在我們的代碼中使用它。 您可以通過(guò)在 LocalLibrary 項(xiàng)目的根目錄中打開(kāi)提示并輸入以下命令,以通常的方式執(zhí)行此操作:

npm install async --save

模板引物

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

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

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

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

模板配置

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

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

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

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

模板語(yǔ)法

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

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

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

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

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

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

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

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

如果標(biāo)簽后面沒(méi)有等號(hào),則內(nèi)容被視為純文本。 在純文本中,可以使用#{} !{} 語(yǔ)法插入轉(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}.

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

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

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 語(yǔ)法執(zhí)行循環(huán)/迭代操作。 在下面的代碼片段中,我們循環(huán)遍歷一個(gè)數(shù)組來(lái)顯示一個(gè)變量列表(注意,使用\'li =\'來(lái)評(píng)估"val"作為下面的變量,迭代的值也可以傳遞到 模板作為變量!

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

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

擴(kuò)展模板

在整個(gè)網(wǎng)站中,通常所有頁(yè)面都有一個(gè)通用的結(jié)構(gòu),包括頭,頁(yè)腳,導(dǎo)航等的標(biāo)準(zhǔn)HTML標(biāo)記。而不是強(qiáng)制開(kāi)發(fā)人員在每個(gè)頁(yè)面復(fù)制這個(gè)"樣板",

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

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

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

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

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

extends layout

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

LocalLibrary基本模板

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

打開(kāi) /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

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

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

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

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

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

主頁(yè)

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

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

路線

我們?cè)?a href="/webstart/Express_Nodejs/routes">上一個(gè)教程中創(chuàng)建了索引頁(yè)路線。提醒您,所有路線功能都在 /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');
}

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

控制器

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

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

打開(kāi) /controllers/bookController.js 。 在文件頂部附近,您應(yīng)該看到導(dǎo)出的 index()函數(shù)。

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

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

將以上所有代碼替換為以下代碼片段。 第一件事是import( require())所有的模型(以粗體突出顯示)。 我們需要這樣做,因?yàn)槲覀儗⑹褂盟鼈儊?lái)獲得我們的記錄數(shù)。 然后導(dǎo)入 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()方法被傳遞一個(gè)對(duì)象,其函數(shù)用于獲取每個(gè)模型的計(jì)數(shù)。 這些功能都是同時(shí)啟動(dòng)的。 當(dāng)所有它們都完成時(shí),最終回調(diào)被調(diào)用與results參數(shù)中的計(jì)數(shù)(或錯(cuò)誤)。

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

注意:上面 async.parallel()的回調(diào)函數(shù)有點(diǎn)不尋常,因?yàn)槲覀冧秩卷?yè)面無(wú)論是否有錯(cuò)誤(通常您可能使用單獨(dú)的 用于處理錯(cuò)誤顯示的執(zhí)行路徑)。

視圖

打開(kāi) /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}

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

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

注意:我們沒(méi)有轉(zhuǎn)義計(jì)數(shù)值(即我們使用!{} 語(yǔ)法),因?yàn)橛?jì)算的是計(jì)數(shù)值。 如果信息由最終用戶提供,那么我們將轉(zhuǎn)義變量來(lái)顯示。

它是什么樣子的?

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

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

圖書(shū)列表頁(yè)

接下來(lái),我們將實(shí)現(xiàn)我們的圖書(shū)列表頁(yè)面。 此頁(yè)面需要顯示數(shù)據(jù)庫(kù)中所有圖書(shū)的列表及其作者,每個(gè)圖書(shū)標(biāo)題是其關(guān)聯(lián)的圖書(shū)詳細(xì)信息頁(yè)面的超鏈接。

控制器

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

打開(kāi) /controllers/bookController.js 。 找到導(dǎo)出的 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 對(duì)象,選擇只返回 title 代碼>,因?yàn)槲覀儾恍枰渌侄?它也將返回 _id 和虛擬字段)。 這里我們還調(diào)用 Book 上的 populate(),指定作者字段 - 這將用完整的作者詳細(xì)信息替換存儲(chǔ)的圖書(shū)作者ID。

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

視圖

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

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.

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

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

這里感興趣的是,每本書(shū)被定義為兩行,使用管道為第二行(上面突出顯示)。 需要這種方法,因?yàn)槿绻髡咝彰谏弦恍?,那么它將是超鏈接的一部分?/p>

它是什么樣子的?

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

; width:918px;">

BookInstance列表頁(yè)

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

控制器

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

打開(kāi) /controllers/bookinstanceController.js 。 找到導(dǎo)出的 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 對(duì)象。 然后將 populate()的調(diào)用鏈接到 book 字段 - 這將替換每個(gè) BookInstance 代碼> Book 文檔。

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

視圖

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

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.

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

它是什么樣子的?

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

; width:1200px;">

使用時(shí)刻進(jìn)行日期格式化

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

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

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

安裝時(shí)刻

在項(xiàng)目的根目錄中輸入以下命令:

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

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

更新視圖

打開(kāi) /views/bookinstance_list.pug 并將 due_back 的所有實(shí)例替換為 due_back_formatted 。

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

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

作者列表頁(yè)

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

控制器

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

打開(kāi) /controllers/authorController.js 。 找到靠近文件頂部的導(dǎo)出的 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)用時(shí),會(huì)將任何錯(cuò)誤(或 null )作為第一個(gè)參數(shù),或者成功的所有作者的列表。 如果有錯(cuò)誤,它會(huì)調(diào)用帶有錯(cuò)誤值的下一個(gè)中間件函數(shù),如果不是,則會(huì)呈現(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.

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

它是什么樣子的?

運(yùn)行應(yīng)用程序并打開(kāi)瀏覽器以 http:// localhost:3000 / 。 然后選擇所有作者鏈接。 如果一切都正確設(shè)置,頁(yè)面應(yīng)該看起來(lái)像下面的屏幕截圖。

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

類(lèi)型列表頁(yè)面挑戰(zhàn)!

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

; width:600px;">

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

  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 文件。 實(shí)現(xiàn)一個(gè)匹配上面的屏幕截圖/要求的布局(這應(yīng)該有一個(gè)非常類(lèi)似于作者列表視圖的結(jié)構(gòu)/格式)。

類(lèi)型詳細(xì)信息頁(yè)面

類(lèi)別 網(wǎng)頁(yè)需要顯示特定類(lèi)型實(shí)例的信息,使用它(自動(dòng)生成) _id 字段值標(biāo)識(shí)。 該頁(yè)面應(yīng)顯示類(lèi)型名稱(chēng),以及類(lèi)型中的所有圖書(shū)的列表(每個(gè)圖書(shū)都鏈接到圖書(shū)的詳細(xì)信息頁(yè)面)。

控制器

打開(kāi) /controllers/genreController.js ,然后導(dǎo)入文件頂部的 async 圖書(shū)模塊。

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

找到導(dǎo)出的 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()來(lái)并行查詢流派名稱(chēng)及其相關(guān)聯(lián)的圖書(shū),在(if)兩個(gè)請(qǐng)求都成功完成時(shí)回調(diào)渲染頁(yè)面。

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

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

視圖

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

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ū)別是我們不使用傳遞給第一個(gè)標(biāo)題的 title (雖然它是在底層的 layout.pug 模板中用來(lái)設(shè)置頁(yè)面 標(biāo)題)。

它是什么樣子的?

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

; width:1000px;">

書(shū)詳細(xì)信息頁(yè)

書(shū)詳細(xì)信息頁(yè)需要顯示特定 Book 的信息,該信息使用其(自動(dòng)生成的) _id 字段值標(biāo)識(shí), 每個(gè)相關(guān)的副本在庫(kù)( BookInstance )。 無(wú)論我們?cè)诤翁庯@示作者,類(lèi)型或書(shū)籍實(shí)例,都應(yīng)將其鏈接到該項(xiàng)目的相關(guān)詳細(xì)信息頁(yè)面。

控制器

打開(kāi) /controllers/bookController.js 。 找到導(dǎo)出的 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 ,因?yàn)槲覀冊(cè)趯?shí)施主頁(yè)控制器時(shí)已經(jīng)導(dǎo)入了這些模塊。

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

視圖

創(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.

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

注意:與書(shū)籍相關(guān)聯(lián)的類(lèi)型列表在模板中實(shí)現(xiàn),如下所示。 這會(huì)在與書(shū)相關(guān)聯(lián)的每個(gè)類(lèi)別之后添加逗號(hào),這意味著在最后一個(gè)項(xiàng)目后面還將有一個(gè)逗號(hào)。

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

沒(méi)有默認(rèn)方式獲取有關(guān)Pug最后一次迭代的信息,因此要?jiǎng)h除此逗號(hào),您必須在JavaScript中構(gòu)建此字符串并將其傳遞給您的模板(可能作為與當(dāng)前書(shū)相關(guān)聯(lián)的虛擬屬性)。

它是什么樣子的?

運(yùn)行應(yīng)用程序并打開(kāi)瀏覽器以 http:// localhost:3000 / 。 然后選擇所有圖書(shū)鏈接,然后選擇一本圖書(shū)。 如果一切設(shè)置正確,您的頁(yè)面應(yīng)該看起來(lái)像下面的屏幕截圖。

; width:1200px;">

作者詳細(xì)頁(yè)面

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

控制器

打開(kāi) /controllers/authorController.js

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

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

找到導(dǎo)出的 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 及其關(guān)聯(lián)的 Book 實(shí)例, )兩個(gè)請(qǐng)求都成功完成。 該方法與上述流派詳細(xì)信息頁(yè)中描述的完全相同。

視圖

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

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é)中演示。

它是什么樣子的?

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

; width:1000px;">

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

BookInstance詳細(xì)信息頁(yè)面

BookInstance 詳細(xì)信息頁(yè)面需要顯示使用其(自動(dòng)生成的) _id 字段值標(biāo)識(shí)的每個(gè) BookInstance 的信息。 這將包括 Book 名稱(chēng)(作為圖書(shū)詳細(xì)信息頁(yè)的鏈接)以及記錄中的其他信息。

控制器

打開(kāi) /controllers/bookinstanceController.js 。 找到導(dǎo)出的 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});
? ? });
? ??
};

請(qǐng)注意:我們不需要為此控制器添加 async Book 。

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

視圖

創(chuàng)建 /views/bookinstance_detail.pug 并復(fù)制下面的內(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é)中演示。

它是什么樣子的?

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

; width:1000px;">

挑戰(zhàn)

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

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

滿足這一挑戰(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)頁(yè):一個(gè)主頁(yè),顯示每個(gè)模型的實(shí)例,以及我們的圖書(shū),圖書(shū)實(shí)例,作者和類(lèi)型的列表和詳細(xì)頁(yè)面。 一路上,我們獲得了許多關(guān)于控制器的基礎(chǔ)知識(shí),在使用異步操作時(shí)管理流控制,使用 pug 創(chuàng)建視圖,使用我們的模型查詢數(shù)據(jù)庫(kù),如何將信息傳遞到模板 您的視圖,以及如何創(chuàng)建和擴(kuò)展模板。 那些完成挑戰(zhàn)的人也將學(xué)習(xí)一些關(guān)于使用時(shí)刻的日期處理。

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

    也可以看看

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

    掃描二維碼

    下載編程獅App

    公眾號(hào)
    微信公眾號(hào)

    編程獅公眾號(hào)