# NodeJS(Max)第 7 節: The Model View Controller (MVC)
> Udemy課程:[NodeJS - The Complete Guide (MVC, REST APIs, GraphQL, Deno)
](https://www.udemy.com/course/nodejs-the-complete-guide/)
`20231211Mon.~20231214Thu.`
## 7-97. What is the MVC?
* Models:
* Represnet your data in your code
* Work with your data(eg. save, fetch)
* Views:
* What the user sees
* Decoupled from your applucation code
* Controllers:(主要就是**router**)
* Connecting your Modles and your Views
* Contains the "in-between" logic

****
## 7-98. Adding Controllers
接著要把controller相關的程式碼給獨立出來,首先在專案根目錄底下建立資料夾"controllers"。

至於controllers中想要如何分配檔案,是沒有一定規則的。例如說我可以就目前的routes去區分成兩個controllers admin跟shop,但我也可以拆成products來放所有跟商品有關的程式碼,再一個來放user相關logic的檔案等等,他是沒有標準答案的。
那這裡就照著老師的作法,先在"controllers"資料夾底下建立一個檔案叫做`products.js`,用來放一些跟product有關的logic。
**▎原先的admin.js**

接著把其中的middleware function移至controllers底下統一管理。

**▎利用controller的admin.js**
**products.js > 建立controller**
```javascript!
exports.getAddProduct = (req, res, next) => {
res.render("add-product", {
pageTitle: "Add Product",
path: "/admin/add-product"
})
}
```
**admin.js > 使用controller**
```javascript!
...略...
const productsController = require("../controllers/products");
// /admin/add-product => GET
router.get('/add-product', productsController.getAddProduct);
...略...
```

****
## 7-100. Adding a Product Model
再來要建立MVC中的models,我們先建立資料夾名為"models",底下建立一個"product.js"的檔案,用來處理product邏輯。
**product.js**
```javascript!
const products = []; //暫時用變數取代資料庫
module.export = class Product{
constructor(title){
this.t = title;
}
save(){
products.push(this);
}
static fetchAll(){
return products;
}
}
```
### **▊ static**
```javascript!
static fetchAll(){
return products;
}
```
其中static的那行不太理解。
static老師的解釋:「I will add the static keyword which JS offers, which make sure that I can can call this method directly on the class ifself and not on an instantiated object。」
看起來static必須直接由class來調用,而不是由物件建立出來的實例來使用。因此有statice關鍵自為首的話,可以通過class來調用這些屬性或函式,而不需要先創建class的實例對象。
所以如果在static函式中使用this,this指向的會是class name,而非實例物件(instance)。
再看這篇文章[JavaScript static 关键字是干嘛的?](https://juejin.cn/post/6940556583482949663),看他比較有用static以及沒用static函式的差異,我覺得在這裡`fectchAll()`會用static的原因,應該是因為我想要直接看到整個products陣列裡面有什麼東西,所以我不應該是透過class的實例來查看,而是直接從class來查看products陣列變數。
### **▊ 完成**
**▎models/product.js**
```javascript!
const products = []; //暫時用變數取代資料庫
module.exports = class Product{
constructor(title){
this.t = title;
}
save(){
products.push(this);
}
static fetchAll(){
return products;
}
}
```
**▎controllers/product.js**
```javascript!
const Product = require("../models/product");
exports.getAddProduct = (req, res, next) => {
res.render("add-product", {
pageTitle: "Add Product",
path: "/admin/add-product"
})
}
exports.postAddProduct = (req, res, next) => {
const product = new Product(req.body.title); //送出表單 > 發送POST request > 建立Product實例
product.save(); // save()會把this(也就是自己,product)推進products陣列中
res.redirect('/');
}
exports.getProducts = (req, res, next) => {
const products = Product.fetchAll();
res.render("shop", {
prods: products,
pageTitle: "shop", path: "/"
});
}
```
## 7-101. Storing Data in Files Via the Model
接著要把data利用model存到一個檔案中,而非像先前只是存在變數裡。
**▎models/product.js**
看Product class其中的save function,這是用來儲存資料到一個檔案中的方法。
```javascript!
const fs = require("fs");
const path = require("path");
module.exports = class Product{
...略...
save(){
const p = path.join(path.dirname(require.main.filename), "data", "product.json");
fs.readFile(p, (err, fileContent) => {
let products = [];
if(!err){
products = JSON.parse(fileContent);
}
products.push(this);
fs.writeFile(p, JSON.stringify(products), err => {
console.log(err);
});
})
}
...略...
}
```
### **▊ fs.readFile(file[, options], callback)**
> 參考資料:[Node.js官方文件](https://nodejs.org/dist/latest-v6.x/docs/api/fs.html#fs_fs_readfile_file_options_callback)
* file:給定要讀取的file位置。
* [option]:可不設定,預設為不encoding。
* callback:接收兩個參數err跟data,其中data代表file的內容。
```javascript!
fs.readFile(p, (err, fileContent) => {
let products = [];
if(!err){
products = JSON.parse(fileContent);
}
})
```
### **▊ fs.writeFile(file, data[, options], callback)**
> 參考資料:[Node.js官方文件](https://nodejs.org/dist/latest-v6.x/docs/api/fs.html#fs_fs_writefile_file_data_options_callback)
* file:給定file的位置,作為要將data寫進去的地方。
* data:要寫進file的內容,可以是string或是buffer。
* [option]:可不設定,預設為encode成"utf8"。
* callback:接收一個參數err,用來處理接error的方法。
```javascript!
fs.writeFile(p, JSON.stringify(products), err => {
console.log(err);
});
```
### **▊ JSON.parse()**
會把一個JSON 字串轉換成JavaScript 的數值或是物件。
### **▊ JSON.stringify()**
將一個JavaScript 物件或值轉換為JSON 字串。
****
## 7-102. Fetching Data from Files Via the Model
> 參考資料:[JavaScript 中的同步與非同步(上):先成為 callback 大師吧!](https://blog.huli.tw/2019/10/04/javascript-async-sync-and-callback/)
接著要把data利用model從一個檔案中取得(fetching data)。
我們可能會想著要照前一章節的寫法寫,所以寫出下圖的code,但這之中有個問題,就是下圖code寫法會照成error。


原因在於`fs.readfile()`屬於asynchronous code(非同步)。

以下是我自己的想法,如有錯誤歡迎指正!
1. 首先,fetchAll()被呼叫,被呼叫的函式會被放入stack中,直到函式執行結束,或是return值

2. 依序執行下來,所以先宣告p變數

3. fs.readFile()被呼叫,但他是非同步的,所以我們不會等他讀取完檔案,而是繼續往下一行看下去。
於是readFile()被呼叫後放入stack中,他讀取檔案的工作會轉交給nodeapi處理,轉交工作後readFile()沒事了就可以pop出stack。
readFile()與setTimeout()相似,皆為非同步的函式,而setTimeout()呼叫後會將計時工作轉交給webAPI處理。

4. 碰到了右括號,fetchAll()函式結束,因此可以pop出stack,但是...完全沒有return任何值,就有點像進去菜市場(stack)逛一圈,結果沒買到東西,就出來了(pop out)。
即便等nodeAPI讀取完檔案,return了值,fetchAll()也永遠拿不到了(因為他已經離開菜市場)
而這也是為什麼這樣的寫法會造成我們前面得到的error:


~~~~~接下來看看正確的寫法!~~~~~
這裡會在fetchAll()運用到callback function,讓callback function 幫忙hold住後,並做render()。

**▎models/product.js**
看Product class其中的static fetchAll function,這是用來從某個檔案取得資料的方法。
```javascript!
const fs = require("fs");
const path = require("path");
module.exports = class Product{
...略...
static fetchAll(cb){
const p = path.join(path.dirname(require.main.filename), "data", "product.json");
fs.readFile(p, (err, fileContent) => {
if(err){ //有err代表讀取不到p,可能是還未建立product.json
cb([]);
}
cb(JSON.parse(fileContent));
})
}
...略...
}
```
**▎controllers/products.js**
```javascript!
...略...
exports.getProducts = (req, res, next) => {
Product.fetchAll((products) => {
res.render("shop", {
prods: products,
pageTitle: "shop", path: "/"
});
});
}
...略...
```
****
More on MVC: https://developer.mozilla.org/en-US/docs/Glossary/MVC