Skip to content

Latest commit

 

History

History
553 lines (435 loc) · 12.8 KB

NODE_EXPRESS_VIEW.md

File metadata and controls

553 lines (435 loc) · 12.8 KB

前言

在開始本章節以前先來談談MVC架構

#M(MODEL)V(VIEW)C(CONTROLL)

MVC是近年來流行的web架構,但這個概念並不限定在web上,連android都有實做類似的概念。 何謂MVC?你問工程師每個人的看法都不大一樣,但他們都一致同意好的工程師一定要會MVC。

在這裡提供wiki看法:

(控制器 Controller)- 負責轉發請求,對請求進行處理。

(視圖 View) - 介面設計人員進行圖形介面設計。

(模型 Model) - 程式設計師編寫程式應有的功能(實作演算法等等)、資料庫專家進行資料管理和資料庫設計(可以實作具體的功能)。

轉換成Node.js的說法就是:

(控制器 Controller)- server(express),接受參數(POST OR GET)後轉傳至要執行的程式,若有需要則回傳結果。

(視圖 View) - html部分,為了輸出Controller的訊息,會需要用到view engine,下面會介紹ejs跟angular.js

(模型 Model)- 邏輯處理

實例一 MVC基本範例

讓我們做個範例,建立一個名叫node_mvc_1目錄

你可以參考 https://github.com/y2468101216/node-wiki-gitbook/tree/master/src/node_mvc_1

結構:

node_mvc_1/
├── bin/
|   ├── download.js
├── download/
│   ├── a.txt
│   ├── b.txt
├── public/
│   ├── index.html
└── app.js

bin/download.js:

/**
 * Name:download.js
 * Purpose:Model download example
 * Author:Yun
 * Version:1.0
 * Update:2015-09-22
 */
module.exports = function(){
	this.checkFile = function(pathString, callback){
		var fs = require('fs');
		fs.stat(pathString,function(err, stats){
			if(!err){
				callback(true);
			}else{
				callback(false);
			}
		});
	}
}

public/index.html:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Node MVC 1</title>
</head>
<body>
	<div>
		<a href="./download/a.txt">download a</a>
	</div>
	<div>
		<a href="./download/b.txt">download b</a>
	</div>
	<div>
		<a href="./download/c.txt">download c</a>
	</div>
</body>
</html>

app.js:

/**
 * Name:app.js
 * Purpose:controller express example
 * Author:Yun
 * Version:1.0
 * Update:2015-09-22
 */

var express = require('express');
var app = express();

app.get('/', function(req, res) {
	var options = {
		root : __dirname + '/public/',
		dotfiles : 'deny',
		headers : {
			'x-timestamp' : Date.now(),
			'x-sent' : true
		}
	};

	res.sendFile('index.html', options, function(err) {
		if (err) {
			res.status(err.status).end();
		} else {
			// console.log('Sent:index.html');
		}
	});
});

app.get('/download/:name', function(req, res) {
	var downloadClass = require('./bin/download.js');
	var dl = new downloadClass();
	var pathString = './download/' + req.params.name;

	dl.checkFile(pathString, function(isFile) {
		if (isFile) {
			res.download(pathString);
		} else {
			res.send('error');
		}
	});
});

console.log('server is running');

app.listen(8080);

這是一個下載檔案的範例,在index.html裡有三個超連結分別對應a.txt、b.txt、c.txt。但你可以注意到在download目錄裡 並沒有c.txt,所以他應該會回傳error,不過今天的重點不在那邊,讓我們回頭來看app.js

app.js把檢查檔案是否存在的邏輯處理抽離另外做成一個class,讓app.js只單純處理controller的問題:轉傳給model,回傳給view。

這樣的好處是你易於維護,不會因為一個程式bug導致整個server crush,當然檔案跟程式碼會變得比較多,但整體而言複雜度是下降的。

#實例二 輸出文字 在Node.js裡面view engine分成兩個:jade跟ejs,我會談跟php想法比較接近的ejs。

如果跟我一樣有做過php的話,那你應該記得php本身即是個view engine這件事,但很可惜的是Node.js並不能直接 這樣輸出,但他有類似的東西可以讓你無痛轉換,沒錯!就是ejs

讓我們做個範例,建立一個名叫node_mvc_2_ejs目錄

你可以參考 https://github.com/y2468101216/node-wiki-gitbook/tree/master/src/node_mvc_2_ejs

先安裝ejs:

npm install ejs

結構:

node_mvc_2_ejs/
├── bin/
|   ├── login.js
├── views/
│   ├── index.html
└── app.js

login.js:

/**
 * Name:login.js
 * Purpose:ejs login example
 * Author:Yun
 * Version:1.0
 * Update:2015-09-22
 */

module.exports = function (){
	this.check = function (account, password, callback){
		var message = 'login success';
		var error = false;

		if(account != 'test' && error == false){
			message = 'account error';
			error = true;

		}

		if(password != 'test' && error == false){
			message = 'password error';
			error = true;
		}

		callback(message, error);
	}
}

index.html:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Node MVC 2 EJS</title>
</head>
<body>
<form action="/login" method="post">
	<div>
		<div><label>Account:</label></div>
		<input type="text" name="account" />
	</div>
	<div>
		<div><label>Password:</label></div>
		<input type="password" name="password" />
	</div>
	<div>
		<button type="submit">submit</button>
	</div>
	<% if (message) { %>
		<% if (error) { %>
			<label style="color:red;"><%= message %></label>
		<% } else { %>
		 	<label style="color:green;"><%= message %></label>
		 <% } %>
	 <% } %>
</form>
</body>
</html>

app.js:

/**
 * Name:app.js
 * Purpose:ejs express example
 * Author:Yun
 * Version:1.0
 * Update:2015-09-22
 */

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

var app = express();

//create application/x-www-form-urlencoded parser
app.use(bodyParser.urlencoded({
	extended : true
}));

app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
app.engine('html', require('ejs').renderFile);

//index page
app.get('/', function(req, res) {
    res.render('index.html',{message:false});
});

//login
app.post('/login',function(req, res){
	var loginClass = require('./bin/login.js');
	var login = new loginClass();
	login.check(req.body.account, req.body.password,function(returnMessage, isError){
		res.render('index.html',{message:returnMessage,error:isError});
	});
});

console.log('server is running');

app.listen(8080);

node_mvc_2_original.png

node_mvc_2_account_error.png

node_mvc_2_password_error.png

node_mvc_2_success.png

讓我們來討論一下app.js裡面多的東西

app.use(bodyParser.urlencoded({
	extended : true
}));

這段是說如果封包裡的body有東西的話,用qs library去爬,關於要採用qs library還是query string 請參考以下連結:http://stackoverflow.com/questions/29175465/body-parser-extended-option-qs-vs-querystring

app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
app.engine('html', require('ejs').renderFile);

app.set就是可以設定一些express的常數,讓他知道要去哪找。

第一行設定views的目錄在哪。

第二行設定要使用的view engine。

第三行設定說,如果是html的話一樣送給ejs解釋。也就是說現在你在目錄擺html跟ejs,都會被掃過看有沒有ejs語法。

#jade與ejs的優缺點 優點:

  1. client端呈現速度快
  2. 有學過view engine能夠快速上手

缺點:

  1. server端要耗用資源
  2. 版面醜陋
  3. 做假資料時難度會提升很多
  4. 對前端工程師不友善
  5. 要多學一種語言

#另一個選擇-使用client side js framework:angular.js or react.js

有鑒於view engine的眾多缺點,逐漸有部分人改採用angular.js跟react.js做為view engine的替代品。

讓我們做個範例,建立一個名叫node_mvc_3_angularjs目錄

你可以參考 https://github.com/y2468101216/node-wiki-gitbook/tree/master/src/node_mvc_3_angularjs

結構:

node_mvc_3_angularjs/
├── bin/
|   ├── login.js
├── public/
│   ├── index.html
└── app.js

login.js:

/**
 * Name:login.js
 * Purpose:ejs login example
 * Author:Yun
 * Version:1.0
 * Update:2015-09-22
 */

module.exports = function (){
	this.check = function (account, password, callback){
		var message = 'login success';
		var error = false;

		if(account != 'test' && error == false){
			message = 'account error';
			error = true;
		}

		if(password != 'test' && error == false){
			message = 'password error';
			error = true;
		}

		callback(message, error);
	}
}

index.html:

<!DOCTYPE html>
<html ng-app="node_mvc_app">
<head>
<meta charset="UTF-8">
<title>Node MVC 3 angularjs</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css">
<script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.6/angular.min.js"></script>
</head>
<body ng-controller="node_mvc_controller">
<form>
	<div>
		<div><label>Account:</label></div>
		<input type="text" name="account" ng-model="user.account" />
	</div>
	<div>
		<div><label>Password:</label></div>
		<input type="password" name="password" ng-model="user.password" />
	</div>
	<div>
		<button type="button" ng-click="loginCheck()">submit</button>
	</div>

</form>
<label ng-style="messageStyle">{{message}}</label>
</body>
<script type="text/javascript">
var app = angular.module('node_mvc_app', []);
app.controller('node_mvc_controller',function($scope, $http){
	$scope.loginCheck = function () {
		var req = {
			method: 'POST',
			url: 'login',
			headers: {
				'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'
			},
			data: $.param($scope.user)
		}
		$http(req).success(function(data){
			if(data.error){
				$scope.messageStyle = {color:'red'};
			}else{
				$scope.messageStyle = {color:'green'};
			}
			$scope.message = data.message;
		});
	}
});
</script>
</html>

app.js:

/**
 * Name:app.js
 * Purpose:ejs express example
 * Author:Yun
 * Version:1.0
 * Update:2015-09-22
 */

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

var app = express();

var options = {
		root : __dirname + '/public/',
		dotfiles : 'deny',
		headers : {
			'x-timestamp' : Date.now(),
			'x-sent' : true
		}
	};

//create application/x-www-form-urlencoded parser
app.use(bodyParser.urlencoded({
	extended : true
}));

//index page
app.get('/', function(req, res) {
    res.sendFile('index.html',options,function(err){
    	if(err){
    		console.log(err);
    	}
    });
});

//login
app.post('/login',function(req, res){
	var loginClass = require('./bin/login.js');
	var login = new loginClass();
	login.check(req.body.account, req.body.password,function(returnMessage, isError){
		res.send({message:returnMessage,error:isError});
	});
});

console.log('server is running');

app.listen(8080);

node_mvc_3_original.png

node_mvc_3_account_error.png

node_mvc_3_password_error.png

node_mvc_3_success.png

login.js本身並沒有變動,所以我們直接來看index.html

這邊我們改用ajax去取得是否有登入成功。ajax會給使用者比較好的體驗,不然反覆刷新頁面使用者也很煩, 而且資料不用多做設定就可以一直留在網頁上,關於angular.js的語法可以參考官網,這邊就不再多做說明。

app.js因為我們不再使用ejs了,所以我們將app.set的部分全拿掉,這也導致res.render必須拿掉。 所以我們改成res.sendFile。

res.sendFile('index.html',options,function(err){
	if(err){
		console.log(err);
	}
});

回傳的部分,server丟json出來是最好的,因為JavaScript對json的支援很好,而且在提供相同資訊的情況下 json遠比xml來的簡單。

app.post('/login',function(req, res){
	var loginClass = require('./bin/login.js');
	var login = new loginClass();
	login.check(req.body.account, req.body.password,function(returnMessage, isError){
		res.send({message:returnMessage,error:isError});
	});
});

#client side js framework的優缺點 優點:

  1. server耗用資源較少。
  2. 版面整潔
  3. 對前端友善
  4. 做假資料時比較輕鬆

缺點:

  1. include page會十分緩慢
  2. 必定要先載入該framework
  3. 使用者多時ajax可能會對server造成更多負擔

#結語 如上面所敘,兩種方法都有優缺點,開發時應該依照專案需求去做選擇,甚至可以兩種混用,不要被本篇文章侷限住了。

#參考資料