先決條件: | 完成上一篇教程主題(包括 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)查看文檔)。 一些更重要的功能是:
我們?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ù)聲明的原始順序,而不是它們完成的順序)。
方法 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'] } );
方法 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
下面的示例模板文件顯示了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)參閱帕格文檔。
在整個(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}
現(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è)面。
我們將創(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"文件中的不同控制器中指定。
接下來(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;">
接下來(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;">
從我們的模型默認(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
完全相同的方式獲得格式化的日期。
在項(xiàng)目的根目錄中輸入以下命令:
npm install moment --save
var moment = require('moment');
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è)面需要顯示數(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)此操作。
在本節(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)行渲染。
genre_list()
in?/controllers/genreController.js.?author_list()
controller.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)別 網(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è)需要顯示特定 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:] ? ? 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:] ? ? ? ? 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è)面需要顯示關(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è)面需要顯示使用其(自動(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;">
目前網(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日 。
滿足這一挑戰(zhàn)的要求:
due_back
with due_back_formatted
in the BookInstance detail page.BookInstance.due_back_formatted
.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ù)。
更多建議: