iT邦幫忙

2017 iT 邦幫忙鐵人賽
DAY 29
2
Modern Web

Node.JS - 30 天入門學習筆記系列 第 29

Day29 - 實作 TODO List (Controller)

今天,來到鐵人賽第二十九天。
你以為,明天就結束了嗎?
安可,安可。
我們來看看今天的進度吧!

前二天,我們已經把 TODO List 的 Model 及 View的部份搞定了。
今天,要利用 Controller 將這二方做一個互通有無。

其實,在node.js 的 Controller ,就是其 web 應用程式 app.js 的三二事,及其router要做的事情而已。我們將分別來看看。

還記得,我們昨天在做View的時候,為了方便起見,做了一個簡單的 app.js 嗎?
找到這一段:

app.js

http://ithelp.ithome.com.tw/upload/images/20161229/20103526o8s3HqTbG6.png

現在,我們要把它抽出來,變成一個router檔,讓app.js 引入router檔案,而不寫在一起!
所以,上面的三行程式,會變成這樣:

app.js
http://ithelp.ithome.com.tw/upload/images/20161229/20103526YC706oFVX9.png

接著,開新檔案,將上面第10行~ 第14行,複製,貼上新檔案,取名 todo.js ,放入 ./routes/ 資料夾底下,這就是我們的router檔案!!

./routes/todo.js

var express=require('express');
var router = express.Router();
var dataset=require('./recordset.js');
 
router.get('/todo',function(req,res){
         res.render('restfulTP',{itemlist:dataset});
});
 
module.exports=router;

而原本的 app.js 將變成這樣:

app.js

var express = require('express');
var todoRouter= require('./routes/todo');
var app = express();
 
//set view engine
app.set("view engine","jade")
//set view directory
app.set("views",__dirname+"/views")
 
app.use('/restful', todoRouter);
app.use('/restful',express.static(__dirname+'/public'));
app.listen(3000,function(){
    console.log('Ready...for 3000');
});

我們可以試著執行 node app.js,並且打開瀏覽器:
http://ithelp.ithome.com.tw/upload/images/20161229/20103526T7xeTbMcQv.png

依舊,可以正常執行!!!
瞧!當我們把router,提取出來,這樣程式是不是更清楚明瞭?!

接下來,我們來增加一些東西,在app.js !

為了要正確的接收表單傳過來的 data ,別忘了,我們之前有學過,要引入 body-parser!
所以,開頭加上

var bodyParser = require( 'body-parser' );

並且加入

// configure app to use bodyParser()
// this will let us get the data from Request
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.text());

最後,完成的 app.js 會像這樣:

app.js

var express = require('express');
var bodyParser = require( 'body-parser' );
var todoRouter= require('./routes/todo');
var app = express();
 
//var dataset=require('./recordset.js');  //資料集...方便測試View流程使用
//set view engine
app.set("view engine","jade")
//set view directory
app.set("views",__dirname+"/views")
 
// configure app to use bodyParser()
// this will let us get the data from Request
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.text());
 
// Apply this router on (/restful)
app.use('/restful', todoRouter);
app.use('/restful',express.static(__dirname+'/public'));
 
app.listen(3000,function(){
    console.log('Ready...for 3000');
});

再次,試著執行 node app.js,並且打開瀏覽器,看是否正常執行?
(我們只是簡單做了引入body-parser模組,並且使用它的middleware而已!當然可以囉!)
http://ithelp.ithome.com.tw/upload/images/20161229/20103526kxqzsIKXJA.png

好了, 我們已經完成了 app.js 的相關引入及設定,接下來,沒你(app.js)的事了!
之後,都是 router 的事了!!(哇!!是不是很簡單呢?)

路由撰寫

當初,我們的 router,引入了 ./recordset.js 這個資料集檔。
當時,只是為了測試view,而做的!

現在,我們不用它了!而是,直接引入我們之前在 model 做好的 CRUD 檔案,如下:

./routes/todo.js

// load mongodb-CURL
var modelCreate=require('../model/todocreatedb.js');
var modelUpdate=require('../model/todoupdatedb.js');
var modelRemove=require('../model/todoremovedb.js');
var modelQuery=require('../model/todoquerydb.js');

接著,為了測試每個路由的進入,小編加了載入中介軟體函式:(這段可有可無..)

./routes/todo.js

// middleware that is specific to this router
router.use(function(req, res, next) {
    console.log('Something is happening.');
    next(); // make sure we go to the next routes and don't stop here
});

接著,還記得,我們按鈕要做的事嗎?開始吧.......................

載入全部資料

./routes/todo.js

// READ ALL & FORM (/restful/todo)
router.get('/todo',function(req,res){
   //mongodb find all.....
   modelQuery.QueryGet({},function(record){
     if(req.xhr)
         res.render('recordTP',{layout:false, itemlist:record});
       else
         res.render('restfulTP',{itemlist:record});
   });
 
});

因為是載入全部,所以,我們的搜尋的條件資料集是 {}。

這邊,有二個render ,其中一個有用 layout,代表第一次載入。
其他時候,則只單純 改變 partial的部份,所以,不用 layout!

要特別注意的是,我們按下搜尋,如果,沒有填資料,代表路由是走 /todo,而非 /todo/:id ,在同樣GET的情況下,會跑到這裡執行。

新增資料

./routes/todo.js

// CREATE (/restful/todo)
router.post('/todo', function(req, res) {
    // ...
     var dataset=[{message:req.body.momsg}];
 
     modelCreate.InsertNew(dataset,function(msg){
     return res.redirect('/restful/todo');
     });
 
    //res.send('you push a request to create');
});

我們接收,從表單送出的 req.body.momsg,也就是 message,轉成資料集,送給 InserNew方法,做新增!
做完新增,回到 /restful/todo 路由!(載入全部資料)

依關鍵字搜尋資料

./routes/todo.js

// READ (/restful/todo/:id)
// 這邊要注意的:這裡的id是網址列後的搜尋字串
// 在用 req.params是根據路由給的參數名稱, 與req.body的不同處!
// 如果你怕會搞混, 請修改!
router.get('/todo/:id', function(req, res) {
    // mongodb find one or all...
     var dataset={message:req.params.id}
     modelQuery.QueryGet(dataset,function(record){
      if(req.xhr)
         res.render('recordTP',{layout:false, itemlist:record});
       else
         res.render('restfulTP',{itemlist:record});
     });
    //res.send('you push a request to read one');
});

我們接收從URL接收過來的 req.params.id 做為關鍵字, 依此做查詢的條件,轉成資料集,送給 QueryGet 方法,做查詢!
再將結果 render 給 recordTP 或是 restfulTP。

這邊,會與載入全部資料的部份有些雷同之處!

要注意的是,如果,搜尋的關鍵字部份留下空白,則不會到此路由運行。
因為,我們這裡的路由是 /todo/:id,如果參數部份留下空白,則直接去 /todo。

更新資料

./routes/todo.js

// UPDATE ((/restful/todo/:id))
router.put('/todo/:id', function(req, res) {
    // ...
     var dataset={id:parseInt(req.params.id),message:req.body.momsg};
     modelUpdate.UpdateSave(dataset,function(record){ 
 
         res.render('oneTP',{layout:false,
                oneid:record.id,onemsg:record.message});
 
     });
    //res.send('you push a request to put! ' + req.body.moid+req.body.momsg);
});

這邊,我們將從表單送回來,要更新的訊息 req.body.momsg 也就是 message,以及 URL 所帶的 req.params.id 也就是 id。
轉成資料集,送回給UpdateSave,做該筆id資料的更新!

再將結果,render 給 oneTP view。

刪除資料

./routes/todo.js

// DELETE (/restful/todo/:id)
router.delete('/todo/:id', function(req, res) {
    // ...
    var dataset={id:parseInt(req.params.id)}
    //console.log(dataset);
    modelRemove.RemoveSave(dataset,function(msg){
         res.send(msg);
     });
});

這邊,我們將從URL接收過來的id,即要被刪除的id,轉成資料集,送回給 RemoveSave方法,做該筆資料的刪除。再回傳 ‘刪除成功’ 之類的相關訊息給前端。

別忘了,最後要加 module.exports=router; 以提供給 app.js使用!
以上,是我們的路由器撰寫,完成!

最後,完成的 router檔,會像這樣:

./routes/todo.js

var express=require('express');
var router = express.Router();
 
// load mongodb-CURL
var modelCreate=require('../model/todocreatedb.js');
var modelUpdate=require('../model/todoupdatedb.js');
var modelRemove=require('../model/todoremovedb.js');
var modelQuery=require('../model/todoquerydb.js');
 
// middleware that is specific to this router
router.use(function(req, res, next) {
    console.log('Something is happening.');
    next(); // make sure we go to the next routes and don't stop here
});
 
// READ ALL & FORM (/restful/todo)
router.get("/todo", function(req,res){
   //mongodb find all.....
   modelQuery.QueryGet({},function(record){
     if(req.xhr)
         res.render("recordTP",{layout:false, itemlist:record});
       else
         res.render("restfulTP",{itemlist:record});
   });
 
});
 
// CREATE (/restful/todo)
router.post("/todo", function(req, res) {
    // ...
     var dataset=[{message:req.body.momsg}];
 
     modelCreate.InsertNew(dataset,function(msg){
     return res.redirect("/restful/todo");
     });
 
    //res.send("you push a request to create");
});
 
// READ (/restful/todo/:id)
// 這邊要注意的:這裡的id是網址列後的搜尋字串
// 在用 req.params是根據路由給的參數名稱, 與req.body的不同處!
// 如果你怕會搞混, 請修改!
router.get("/todo/:id", function(req, res) {
    // mongodb find one or all...
     var dataset={message:req.params.id}
     modelQuery.QueryGet(dataset,function(record){
      if(req.xhr)
         res.render("recordTP",{layout:false, itemlist:record});
       else
         res.render("restfulTP",{itemlist:record});
     });
    //res.send("you push a request to read one");
});
 
// UPDATE ((/restful/todo/:id))
router.put("/todo/:id", function(req, res) {
    // ...
     var dataset={id:parseInt(req.params.id),message:req.body.momsg};
     modelUpdate.UpdateSave(dataset,function(record){ 
 
         res.render("oneTP",{layout:false,
                oneid:record.id,onemsg:record.message});
 
     });
    //res.send("you push a request to put! " + req.body.moid+req.body.momsg);
});
 
// DELETE (/restful/todo/:id)
router.delete("/todo/:id", function(req, res) {
    // ...
    var dataset={id:parseInt(req.params.id)}
    //console.log(dataset);
    modelRemove.RemoveSave(dataset,function(msg){
         res.send(msg);
     });
});
 
module.exports=router;

你們看,在router裡,我們也只是做負責**“控制資料"** 傳進至Model處理 與 送出 給View呈現。
我們並沒有在裡面做任何有關商業邏輯的事情,也沒有在裡面draw任何的html。

完全符合MVC 在做Controller,也必須注意到的 關注點分離

在Controller,有二個檔案,一個是主要的 app.js,另外,則是router檔 todo.js。

瞧,是不是用 RESTful架構 及 MVC,把專案清楚容易的完成了?

最後,可以執行 node app.js,開始把玩這個 TODO List了!

Demo畫面:
http://ithelp.ithome.com.tw/upload/images/20161229/20103526hNo7Ng6MWo.png

完整的程式檔

https://github.com/circleuniv/todo

這一份 TODO List 還可以改進的地方

  1. 在block的切割部份可能不夠完善,會影響到 js 的載入,必須在每一次 render 有多餘的載入。
  2. 重複的訊息,可以過濾。
  3. 既然是TODO,也可以加上日期。
  4. 可以加入登入驗證才能po文的功能
  5. ....

上一篇
Day28 - 實作 TODO List (View)
下一篇
Day30 - 實作 繼續向前
系列文
Node.JS - 30 天入門學習筆記32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
天天好日子
iT邦新手 4 級 ‧ 2018-04-25 23:38:15

不好意思 我下載你的檔案來執行 可是我出現了Cannot GET /
這是哪裡有問題?

我要留言

立即登入留言