# NodeJS(Max)第 6 節: Working with Dynamic Content & Adding Templating Engines
> Udemy課程:[NodeJS - The Complete Guide (MVC, REST APIs, GraphQL, Deno)
](https://www.udemy.com/course/nodejs-the-complete-guide/)
`20231209Sat.~20231211Mon.`
## 6-79. Sharing Data Across Requests & Users
在[3-38](https://hackmd.io/@noz915/rJI7Y7sET#3-38-Using-the-Node-Modules-System)曾經提過exports用法。
可以是以下兩種,後者為簡寫方式,是用於輸出模組(module)的。
```!
module.exports=任何資料;
exports=任何資料;
```
而exports本身為一個物件,故也能擁有自己的屬性,所以當我們一個檔案中有多於一個模組想輸出,那麼就可以利用屬性的方式去輸出模組,如下圖所示。
此處的"products"變數將用於暫時作為儲存使用者輸入資料的容器。

輸出模組後,來到另一個檔案`app.js`,我們將在`app.js`中輸入(import)這養個模組進來使用。

**▎admin.js**

當我們送出表單的同時,就會發出POST request給server,因此在上方程式碼中,當收到POST request(即按下送出表單的按鈕),就會將`req.body.title`弄成一個物件,推到變數products陣列中儲存起來。
不過這樣的方法只會在本機端看到,當不同機器運行時,變數又會被清空。(現在只是試著取得POST request並把他暫時存取)
****
## 6-80. Templating Engines - An Overview
讓html動起來!
### **▊ Available Templating Engines**
* EJS
* Pug(Jade)
* Handlebars

看起來老師三個都會教😰😰😰😰我想先跳過...
****
## 6-81. Installing & Implementing Pug
一次把三個下載下來(當然要分開下載也是可以啦):
```javascript!
npm install --save ejs pug express-handlebars
```
那教程會從pug開始,在express中使用pug,首先要先告訴express,我們要使用templating engine來渲染動態的內容。(這裡用來我們處理templating engine的是express而非node,因為用node來處理會困難非常多)
### **▊ app.set(name, value)**
> 參考資料:
> 1. [express API文件](https://expressjs.com/en/4x/api.html#app.set)
> 2. [Using template engines with Express](https://expressjs.com/en/guide/using-template-engines.html)
`app.set(name, value)`允許我們在該express application上設置任何值變為全域的。
其中兩個name是最主要我們會用到的:view、view engine。

因此,我們會在`app.js`檔案中寫下以下兩行程式碼:
```javascript!
app.set("view engine", "pug");
app.set("views", "views");
```
1. `app.set("view engine", "pug");`
使用前記得先`npm install --save pug`。這裡是用來告訴express:「我現在要使用的templating engine是pug。」
2. `app.set("views", "views");`
這裡是用來告訴express:「我把template放在一個叫做views的資料夾中,所以你想解析請到views找檔案解析。」

### **▊ 使用pug**
建立`shop.pug`,利用vscode幫我們快速生成html5

生成的同時,vscode會直接幫我們轉成pug,不需要擔心

這裡直接放上成品的程式碼:(我好懶)
```pug!
doctype html
html(lang="en")
head
meta(charset="UTF-8")
meta(name="viewport", content="width=device-width, initial-scale=1.0")
title My Shop
link(rel="stylesheet", href="/css/main.css")
link(rel="stylesheet", href="/css/product.css")
body
header.main-header
nav.main-header__nav
ul.main-header__item-list
li.main-header__item
a.active(href="/") Shop
li.main-header__item
a(href="/admin/add-product") Add Product
```
但是完成後,執行`npm start`並不會渲染到我們的網頁上。因為目前我們在`app.set()`的部份,只告訴了express:「當我要渲染template時,請用pug這個template engine來渲染。」
但是,我們目前還沒去做「渲染」template這個動作!
### **▊ res.render() > 渲染template**
> 參考資料:[res.render(view [, locals] [, callback])](https://expressjs.com/en/4x/api.html#res.render)
所以回到`app.js`,當router的path為`"/"`時,我們要渲染這個template,就需要新的express函式:`res.render()`。
實作方式很簡單,如下所示:
**app.js**
```javascript!
router.get('/', (req, res, next) => {
res.render("shop");
});
```
其中,"shop"代表的是"shop.pug",不過預設就會是.pug檔案了,所以我們可以忽略,同樣地,也不需要寫出.pug檔案的path,因為預設會從views資料夾開始找起。
這些預設來自我們先前在`app.js`中,`app.set()`的設置:
```javascript!
app.set("view engine", "pug");
app.set("views", "views");
```

****
## 6-82. Outputting Dynamic Content
接著要添加一些動態的內容,同樣會用到上一章節提到的`res.render()`,把我們想加入的內容,用key value的方式裝在一個物件中作為參數傳入`res.render()`。
這裡我們先用靜態的內容,來看看他是如何運作、操作。
**▎shop.js**
這裡我們想在.pug檔案中加入新的title文字,所以把我們想加入的內容,map成key value的方式作為參數傳入`res.render()`。
```javascript!
router.get('/', (req, res, next) => {
res.render("shop", {docTitle: "shop"});
});
```
**▎shop.pug**
若想要使用`res.render()`中加入的物件,在pug中填入key值,並用`#{}`包住,如下所示:
```pug!
title #{docTitle}
```
**▎結果**

**▎程式碼圖片支援**

~~~~~再來加點動態的內容~~~~~
**▎shop.js**
一樣從`shop.js`中使用`res.render()`。
這裡會用到先前6-79中建立的products變數。
```javascript!
router.get('/', (req, res, next) => {
const products = adminData.products;
res.render("shop", {prods: products, docTitle: "shop"});
});
```
**▎shop.pug**
這裡因為是動態改變,我們不會知道有多少東西,所以會用類似迴圈的東西來處理,而在pug語法中即為`each ... in ...`。
```pug!
main
h1 My Products
p List of all the products...
.grid
each product in prods
article.card.product-item
header.card__header
h1.product__title #{product.title}
.card__image
img(src='https://cdn.pixabay.com/photo/2016/03/31/20/51/book-1296045_960_720.png' alt='A Book')
.card__content
h2.product__price $19.99
p.product__description A very interesting book about so many even more interesting things!
.card__actions
button.btn Add to Cart
```
**▎結果**

**▎程式碼圖片支援**


~~~~~加點判斷式~~~~
**▎shop.pug**
加上if else來判斷現在是否有products,如果沒有的話,我們可以讓他顯示"No Product"。
```pug!
main
if prods.length > 0
h1 My Products
p List of all the products...
.grid
each product in prods
article.card.product-item
header.card__header
h1.product__title #{product.title}
.card__image
img(src='https://cdn.pixabay.com/photo/2016/03/31/20/51/book-1296045_960_720.png' alt='A Book')
.card__content
h2.product__price $19.99
p.product__description A very interesting book about so many even more interesting things!
.card__actions
button.btn Add to Cart
else
h1 No Product
```
**▎結果**
沒有product時會如下顯示:

:::success
注意!
若我們修改了.pug檔案、html檔案等等,即便儲存了,回到瀏覽器之後,仍然需要重整網頁,nodemon不會幫我們刷新頁面,因為這些提及的檔案並非server side的程式碼,nodemon自然也不會去處理。
:::
> Pug official docs: https://pugjs.org/api/getting-started.html
****
## 6-85. Adding a Layout
6-84將html檔案都修改成pug檔(這裡就不再多加說明),會發現我們三個pug檔案有許多內容都重複了,如下所示為三個檔案皆重複的內容:
```pug!
doctype html
html(lang="en")
head
meta(charset="UTF-8")
meta(name="viewport", content="width=device-width, initial-scale=1.0")
title #{pageTitle}
link(rel="stylesheet", href="/css/main.css")
body
header.main-header
nav.main-header__nav
ul.main-header__item-list
li.main-header__item
a(href="/") Shop
li.main-header__item
a(href="/admin/add-product") Add Product
```
因此我們現在要用layout的方式將重複的部份提取出來、獨立出來。同樣在views的資料夾中建立一個資料夾叫做"layouts",並新增一個pug檔案叫做"main-layout.pug"。

把上方重複的程式碼全數貼到`main-layout.pug`裡面,不過這裡會用到一個關鍵字"block",因為重複到的link只有`main.css`,但有些pug檔需要額外的css,因此我們需用關鍵字"block"來處理。
```pug!
block stlyes
block content
```

接著依序來看看各個pug檔如何使用這個layout,這裡會使用到另一個關鍵字"extends"來指出要延伸哪個layout。
**▎404.pug**
```pug!
extends layouts/main-layout.pug
block content
h1 Page Not Found!
```
**▎add-product.pug**
```pug!
extends layouts/main-layout.pug
block styles
link(rel="stylesheet", href="/css/forms.css")
link(rel="stylesheet", href="/css/product.css")
block content
main
form.product-form(action="/admin/add-product", method="POST")
.form-control
label(for="title") Title
input(type="text", name="title")#title
button.btn(type="submit") Add Product
```
**▎shop.pug**
```pug!
extends layouts/main-layout.pug
block styles
link(rel="stylesheet", href="/css/main.css")
link(rel="stylesheet", href="/css/product.css")
block content
main
if prods.length > 0
h1 My Products
p List of all the products...
.grid
each product in prods
article.card.product-item
header.card__header
h1.product__title #{product.title}
.card__image
img(src='https://cdn.pixabay.com/photo/2016/03/31/20/51/book-1296045_960_720.png' alt='A Book')
.card__content
h2.product__price $19.99
p.product__description A very interesting book about so many even more interesting things!
.card__actions
button.btn Add to Cart
else
h1 No Product
```
****
## 6-86. Finishing the Pug Template
先注意看看原先的navbar,無論我們身處在哪個頁面,他都不會顯示,因為目前還沒在css加上active樣式。
不過這裡試著用點邏輯直接在pug檔案裡面添加active樣式,想法是身處在什麼頁面,該頁面在navbar上會變黃字。
先來看看目前的樣式:

**▎admin.js**
在`admin.js`的render處加上path key。
```javascript!
router.get('/add-product', (req, res, next) => {
res.render("add-product", {pageTitle: "Add Product", path: "/admin/add-product"})
});
```

**▎shop.js**
同樣地,在`shop.js`的render處加上path key。
```javascript!
router.get('/', (req, res, next) => {
const products = adminData.products;
res.render("shop", {prods: products, pageTitle: "shop", path: "/"});
});
```

**▎main-layout.pug**
在`main-layout.pug`頁面中加上三元運算子,當頁面的網址與我們設定的path相符時,會加上一個新的class叫做active,即讓navbar的a tag變黃色,否則class為一個空。
```javascript!
//對應到shop頁面
a(href="/", class=(path === "/" ? "active" : ""))
//對應到add-product頁面
a(href="/admin/add-product", class=(path === "/admin/add-product" ? "active" : ""))
```

**▎結果**


****
## 6-87~90
87~90都是在講解handlebars,先跳過不做筆記。
****
## 6-91. Working with EJS
> 1. 官方網站:[EJS](https://ejs.co/)
> 2. 之前上過EJS課程的筆記:[第 34 節](https://hackmd.io/@noz915/Skmisshyq)
> 
EJS載入方法同pug,記得先`npm install ejs --save`。
```javascript!
app.set("view engine", "ejs");
app.set("views", "views");
```
在pug中若要使用動態的內容,我們會使用`#{}`包起來,而在ejs則會使用`<%= %>`來包裹住。
至於if else判斷式在ejs中的寫法,只要前後使用`<% %>`tags來包住,裡面我們可以直接寫上原生的javascript寫法,不過一行就要包裹一次,如下所示:
```ejs!
<% if(prods.length > 0){ %>
...some html here...
<% }else{ %>
...some html here...
<% } %>
```
這裡不寫太多,之前在[第 34 節](https://hackmd.io/@noz915/Skmisshyq)就寫滿詳細夠用了!
****
## 6-92. Working on the Layout with Partials
我們希望可以把重複的東西從各個ejs拉出來行程一個layout,但ejs不同於pug,ejs沒有像pug一樣的語法extends、block來處理layout。
不過我們可以使用所謂叫做"partials"或是"includes"的東西來處理。
先在view資料夾中建立一個資料夾叫做"includes"(當然這個名字任我們隨意取),而底下建立三個檔案,分別是`end.ejs`、`head.ejs`、`navigation.ejs`。
因為ejs沒有block的方法,所以我們得把重複的部份給拆成不同檔案,之後才能才有辦法分開插入ejs檔中。
**▎head.ejs**
```htmlembedded!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Page Not Found</title>
<link rel="stylesheet" href="/css/main.css">
```
**▎navagation.ejs**
```htmlembedded!
<header class="main-header">
<nav class="main-header__nav">
<ul class="main-header__item-list">
<li class="main-header__item"><a href="/">Shop</a></li>
<li class="main-header__item"><a href="/admin/add-product">Add Product</a></li>
</ul>
</nav>
</header>
```
**▎end.ejs**
```htmlembedded!
</body>
</html>
```
### **▊ <%- include("layout的檔案路徑") %>**
而要如何插入到我們的ejs檔案呢?利用`<%- include("") %>`關鍵字。
**▎404.ejs**
```htmlembedded!
<%- include("includes/head.ejs") %>
</head>
<body>
<%- include("includes/navigation.ejs") %>
<h1>Page Not Found!</h1>
<%- include("includes/end.ejs") %>
```
**▎add-product.ejs**
```htmlembedded!
<%- include("includes/head.ejs") %>
<link rel="stylesheet" href="/css/forms.css">
<link rel="stylesheet" href="/css/product.css">
</head>
<body>
<header class="main-header">
<%- include("includes/navigation.ejs") %>
</header>
<main>
<form class="product-form" action="/admin/add-product" method="POST">
<div class="form-control">
<label for="title">Title</label>
<input type="text" name="title" id="title">
</div>
<button class="btn" type="submit">Add Product</button>
</form>
</main>
<%- include("includes/end.ejs") %>
```
**▎shop.ejs**
```htmlembedded!
<%- include("includes/head.ejs") %>
<link rel="stylesheet" href="/css/product.css">
</head>
<body>
<header class="main-header">
<%- include("includes/navigation.ejs") %>
</header>
<main>
<% if(prods.length > 0){ %>
<h1>My Products</h1>
<p>List of all the products...</p>
<div class="grid">
<% for(let product of prods){ %>
<article class="card product-item">
<header class="card__header">
<h1 class="product__title"><%= product.title %></h1>
</header>
<div class="card__image">
<img src="https://cdn.pixabay.com/photo/2016/03/31/20/51/book-1296045_960_720.png" alt="A Book">
</div>
<div class="card__content">
<h2 class="product__price">$19.99</h2>
<p class="product__description">A very interesting book about so many even more interesting things!</p>
</div>
<div class="card__actions">
<button class="btn">Add to Cart</button>
</div>
</article>
<% } %>
</div>
<% }else{ %>
<h1>No Product Found</h1>
<% } %>
</main>
<%- include("includes/end.ejs") %>
```
這裡想再加上active樣式如同[6-68](https://hackmd.io/@noz915/B1teoDbLa#6-86-Finishing-the-Pug-Template)一樣,不過我們改成用`<%= %>`來包裹住邏輯部份。
**navigation.ejs**
```htmlembedded!
<header class="main-header">
<nav class="main-header__nav">
<ul class="main-header__item-list">
<li class="main-header__item"><a href="/" class=" <%= path === '/' ? 'active' : '' %> ">Shop</a></li>
<li class="main-header__item"><a href="/admin/add-product" class=" <%= path === '/admin/add-product' ? 'active' : '' %> ">Add Product</a></li>
</ul>
</nav>
</header>
```