先決條件: | 完成上一篇教程主題(包括 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有很多有用的方法(請查看文檔)。 一些更重要的功能是:
我們在 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ù)聲明的原始順序,而不是它們完成的順序)。
方法 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}
現(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;">
接下來,我們將實現(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
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'); });
注意:格式方法可以使用幾乎模式顯示日期。 用于表示不同日期組件的語法可以在此處找到。
打開 /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列表的相同方法(將生命周期的虛擬屬性添加到作者
模型)來改進此操作。
在本節(jié)中,您應該實現(xiàn)自己的類型列表頁面。 頁面應顯示數(shù)據(jù)庫中所有類型的列表,每個類型鏈接到其關聯(lián)的詳細信息頁面。 預期結果的屏幕截圖如下所示。
; width:600px;">
類型列表控制器函數(shù)需要獲取所有 Genre
實例的列表,然后將這些傳遞給模板進行渲染。
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 文件。 實現(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:] ? ? 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.
這個模板中的幾乎所有內(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
詳細信息頁面需要顯示使用其(自動生成的) _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;">
目前網(wǎng)站上顯示的大多數(shù)日期 使用默認的JavaScript格式(例如 2016年12月06日15:49:58 GMT + 1100(澳大利亞東部夏令時間) 本文旨在改進作者
生命周期信息(死亡/出生日期)和 BookInstance詳細信息頁面的日期顯示的外觀,以使用以下格式: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)頁:一個主頁,顯示每個模型的實例,以及我們的圖書,圖書實例,作者和類型的列表和詳細頁面。 一路上,我們獲得了許多關于控制器的基礎知識,在使用異步操作時管理流控制,使用 pug 創(chuàng)建視圖,使用我們的模型查詢數(shù)據(jù)庫,如何將信息傳遞到模板 您的視圖,以及如何創(chuàng)建和擴展模板。 那些完成挑戰(zhàn)的人也將學習一些關于使用時刻的日期處理。
在下一篇文章中,我們將基于我們的知識,創(chuàng)建HTML表單和表單處理代碼,以開始修改網(wǎng)站存儲的數(shù)據(jù)。
更多建議: