javascript
使用 Node.js、Express、AngularJS 和 MongoDB 构建一个Web程序
為什么80%的碼農(nóng)都做不了架構(gòu)師?>>> ??
使用 Node.js、Express、AngularJS 和 MongoDB 構(gòu)建一個(gè)實(shí)時(shí)問卷調(diào)查應(yīng)用程序
2014 年 3 月 20 日?04:53 ?|??抄本
Joe Lennon
高級(jí)移動(dòng)應(yīng)用程序開發(fā)人員
Joe Lennon 是來(lái)自愛爾蘭科克市的軟件開發(fā)人員,他今年 26 歲。Joe 是 Apress 即將發(fā)行的?Beginning CouchDB?一書的作者,為 IBM developerWorks 撰寫了許多技術(shù)文章和教程。在業(yè)余時(shí)間里,Joe 喜歡踢足球,改進(jìn)一些小玩意和玩他的 Xbox 360 游戲機(jī)。
在 IBM Bluemix 云平臺(tái)上開發(fā)并部署您的下一個(gè)應(yīng)用。
開始您的試用
最近,在向大學(xué)生們介紹 HTML5 的時(shí)候,我想要對(duì)他們進(jìn)行問卷調(diào)查,并向他們顯示實(shí)時(shí)更新的投票結(jié)果。鑒于此目的,我決定快速構(gòu)建一個(gè)用于此目的的問卷調(diào)查應(yīng)用程序。我想要一個(gè)簡(jiǎn)單的架構(gòu),不需要太多不同的語(yǔ)言和框架。因此,我決定對(duì)所有一切都使用 JavaScript — 對(duì)服務(wù)器端使用 Node.js 和 Express,對(duì)數(shù)據(jù)庫(kù)使用 MongoDB,對(duì)前端用戶界面使用 AngularJS。
這個(gè) MEAN 堆棧(Mongo、Express、Angular 和 Node)只需要一天即可完成,遠(yuǎn)比 Web 應(yīng)用程序開發(fā)和部署所用的 LAMP 堆棧(Linux、Apache、MySQL 和 PHP)簡(jiǎn)單得多。
運(yùn)行該應(yīng)用程序
在 JazzHub 上獲取源代碼
我選擇使用?JazzHub?來(lái)管理我的項(xiàng)目的源代碼。它不僅為我的代碼提供了一個(gè)完整的版本控制系統(tǒng),還為在云中編輯代碼提供了一個(gè)在線 IDE,以及用于項(xiàng)目管理的敏捷特性。JazzHub 很容易與 Eclipse 相集成,Eclipse 還提供了一些插件,支持對(duì)平臺(tái)( 比如?Bluemix?或?Cloud Foundry)的一鍵式部署。
構(gòu)建該應(yīng)用程序的先決條件
0?
基本了解?Node.js?和 Node.js 開發(fā)環(huán)境
具有以下這些 Node.js 模塊:Express framework、Jade、Mongoose?和?socket.io
AngularJS?JavaScript 框架
MongoDB?NoSQL 數(shù)據(jù)庫(kù)
Eclipse IDE,已安裝了?Nodeclipse?插件
第 1 步. 構(gòu)建一個(gè)基礎(chǔ) Express 后臺(tái)
0?
在 Eclipse 中,切換到 Node 透視圖,并創(chuàng)建一個(gè)新的 Node Express 項(xiàng)目。如果您創(chuàng)建了一個(gè) JazzHub 項(xiàng)目,請(qǐng)像我所做的那樣,使用相同的名稱為您的 Node Express 項(xiàng)目命名。選擇使用 Jade 作為模板引擎。Eclipse 會(huì)自動(dòng)下載所需的 npm 模塊,以便創(chuàng)建一個(gè)簡(jiǎn)單 Express 應(yīng)用程序。
運(yùn)行這個(gè) Express 應(yīng)用程序
0?
在 Project Explorer 中,找到位于您項(xiàng)目的根目錄中的 app.js,右鍵單擊并選擇?Run As?>?Node Application。這將啟動(dòng)一個(gè) Web 服務(wù)器并將應(yīng)用程序部署到該服務(wù)器。 接下來(lái),打開瀏覽器并導(dǎo)航到?http://localhost:3000。
圖 1. Starter Express 應(yīng)用程序
配置基礎(chǔ)前端
0?
這個(gè)問卷調(diào)查應(yīng)用程序?qū)ΤR娪脩艚缑婧筒季质褂昧?Bootstrap 框架。現(xiàn)在,讓我們對(duì) Express 應(yīng)用程序做一些改動(dòng)來(lái)反映這一點(diǎn)。首先,打開 routes/index.js,將標(biāo)題屬性更改為?Polls:
清單 1. routes/index.js
123?????????exports.index?=?function(req,?res){?????????????res.render('index',?{?title:?'Polls'?});?????????};接著,更改 views/index.jade 模板以包含 Bootstrap。Jade 是一種速記模板語(yǔ)言,可編譯成 HTML。它使用縮進(jìn)消除了對(duì)結(jié)束標(biāo)簽的需求,極大地縮小了模板的大小。您只需要使用 Jade 作為主頁(yè)面布局即可。在下一步中,還可以使用 Angular 局部模板向這個(gè)頁(yè)面添加功能。
清單 2. views/index.jade
12345678910111213141516?????????doctype?5????????html(lang='en')??????????head????????????meta(charset='utf-8')????????????meta(name='viewport',?content='width=device-width,??initial-scale=1,?user-scalable=no')????????????title=?title????????????link(rel='stylesheet',?href='//netdna.bootstrapcdn.com/bootstrap/3.0.1/?css/bootstrap.min.css')????????????link(rel='stylesheet',?href='/stylesheets/style.css')??????????????????????????body????????????nav.navbar.navbar-inverse.navbar-fixed-top(role='navigation')??????????????div.navbar-header????????????????a.navbar-brand(href='#/polls')=?title????????????div.container??????????????div想要查看對(duì)您的應(yīng)用程序所做的更改,請(qǐng)結(jié)束 Eclipse 中的 Web 服務(wù)器進(jìn)程,再次運(yùn)行 app.js 文件:
圖 2. 問卷調(diào)查應(yīng)用程序樣板文件
注意:在使用 Jade 模板時(shí),注意適當(dāng)縮進(jìn)您的代碼,否則您會(huì)遇到麻煩。另外,還要避免使用混合縮進(jìn)樣式,如果您嘗試這樣做,Jade 將會(huì)報(bào)錯(cuò)。
第 2 步. 使用 AngularJS 提供前端用戶體驗(yàn)
0?
如果要使用 Angular,首先需要在您的 HTML 頁(yè)面中包含它,還需要在 HTML 頁(yè)面中添加一些指令。在 views/index.jade 模板中,對(duì)?html?元素進(jìn)行如下更改:
html(lang='en', ng-app='polls')。
在該文件的標(biāo)頭中添加以下腳本元素: :?
清單 3. 將腳本元素加載到 Angular 和 Angular Resource 模板
123?????????script(src='//ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular.min.js')????????script(src='//ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular-resource.min.js')接下來(lái),更改模板中的?body?元素,添加一個(gè)?ng-controller?屬性(稍后使用該屬性將用戶界面綁定到控制器邏輯代碼中):
body(ng-controller='PollListCtrl')。
最后,更改模板中的最后一個(gè)?div?元素,以便包含一個(gè)?ng-view?屬性:?div(ng-view)。
構(gòu)建 Angular 模塊
0?
Angular 中令人影響較為深刻的特性就是數(shù)據(jù)綁定,在后臺(tái)模型發(fā)生改變時(shí),該功能會(huì)自動(dòng)更新您的視圖。這極大地減少了需要編寫的 JavaScript 的數(shù)量,因?yàn)樗鼘?duì)凌亂的 DOM 操作任務(wù)進(jìn)行了抽象。
在默認(rèn)情況下,Express 發(fā)布了靜態(tài)資源,比如 JavaScript 源文件、CSS 樣式表以及位于您項(xiàng)目的公共目錄中的圖像。在公共目錄中,創(chuàng)建一個(gè)名為 javascripts 的新的子目錄。在這個(gè)子目錄中,創(chuàng)建一個(gè)名為 app.js 的文件。該文件將包含用于應(yīng)用程序的 Angular 模塊,而且還定義了用于用戶界面的路由和模板:
清單 4. public/javascripts/app.js
1234567891011?????????angular.module('polls',?[])??????????.config(['$routeProvider',?function($routeProvider)?{????????????$routeProvider.??????????????when('/polls',?{?templateUrl:?'partials/list.html',?controller:?PollListCtrl?}).??????????????when('/poll/:pollId',?{?templateUrl:?'partials/item.html',?controller:?PollItemCtrl?}).??????????????when('/new',?{?templateUrl:?'partials/new.html',?controller:?PollNewCtrl?}).??????????????otherwise({?redirectTo:?'/polls'?});??????????}]);Angular 控制器定義了應(yīng)用程序的范圍,為要綁定的視圖提供數(shù)據(jù)和方法。
清單 5. public/javascript/controllers.js
1234567891011121314151617181920??//?Managing?the?poll?list?function?PollListCtrl($scope)?{??????????$scope.polls?=?[];????????}?//?Voting?/?viewing?poll?results?function?PollItemCtrl($scope,?$routeParams)?{??????????$scope.poll?=?{};??????????$scope.vote?=?function()?{};????????}?//?Creating?a?new?poll?function?PollNewCtrl($scope)?{??????????$scope.poll?=?{????????????question:?'',????????????choices:?[{?text:?''?},?{?text:?''?},?{?text:?''?}]??????????};??????????$scope.addChoice?=?function()?{????????????$scope.poll.choices.push({?text:?''?});??????????};??????????$scope.createPoll?=?function()?{};????????}創(chuàng)建局部 HTML 和模板
0?
為了呈現(xiàn)來(lái)自控制器的數(shù)據(jù),Angular 使用了局部 HTML 模板,該模板允許您使用占位符和表達(dá)式來(lái)包含數(shù)據(jù)和執(zhí)行操作,比如條件和迭代操作。在公共目錄中,創(chuàng)建一個(gè)名為 partials 的新的子目錄。我們將為我們的應(yīng)用程序創(chuàng)建 3 個(gè)局部模板,第一個(gè)局部模板將會(huì)展示可用投票的列表,我們將使用 Angular 通過一個(gè)搜索字段輕松過濾該列表。
清單 6. public/partials/list.html
123456789101112131415161718192021222324252627?????????<div?class="page-header">??????????<h1>Poll?List</h1>????????</div>????????<div?class="row">??????????<div?class="col-xs-5">????????????<a?href="#/new"?class="btn?btn-default"><span?class="glyphicon?glyphicon-plus"></span>?New?Poll</a>??????????</div>??????????<div?class="col-xs-7">????????????<input?type="text"?class="form-control"?ng-model="query"?placeholder="Search?for?a?poll">??????????</div>????????</div>????????<div?class="row"><div?class="col-xs-12"><hr></div></div>????????<div?class="row"?ng-switch?on="polls.length">??????????<ul?ng-switch-when="0">????????????<li><em>No?polls?in?database.?Would?you?like?to?<a?href="#/new">create?one</a>?</li>??????????</ul>??????????<ul?ng-switch-default>????????????<li?ng-repeat="poll?in?polls?|?filter:query">??????????????<a?href="#/poll/{{poll._id}}">{{poll.question}}</a>????????????</li>??????????</ul>????????</div>????????<p> </p>第二個(gè)局部模板允許用戶查看投票。它使用 Angular 切換指令來(lái)確定用戶是否已投票,并根據(jù)這些判斷,顯示一個(gè)就此次問卷調(diào)查進(jìn)行投票的表格,或者一個(gè)包含顯示問卷調(diào)查結(jié)果的圖表。
清單 7. public/partials/item.html
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152?????????<div?class="page-header">??????????<h1>View?Poll</h1>????????</div>????????<div?class="well?well-lg">??????????<strong>Question</strong><br>{{poll.question}}????????</div>????????<div?ng-hide="poll.userVoted">??????????<p?class="lead">Please?select?one?of?the?following?options.</p>??????????<form?role="form"?ng-submit="vote()">????????????<div?ng-repeat="choice?in?poll.choices"?class="radio">??????????????<label>????????????????<input?type="radio"?name="choice"?ng-model="poll.userVote"??value="{{choice._id}}">????????????????{{choice.text}}??????????????</label>????????????</div>????????????<p><hr></p>????????????<div?class="row">??????????????<div?class="col-xs-6">????????????????<a?href="#/polls"?class="btn?btn-default"?role="button"><spanclass="glyphicon?glyphicon-arrow-left"></span>?Back?to?Poll??????????????</div>??????????????<div?class="col-xs-6">????????????????<button?class="btn?btn-primary?pull-right"?type="submit">?Vote?»</button>??????????????</div>????????????</div>??????????</form>????????</div>????????<div?ng-show="poll.userVoted">??????????<table?class="result-table">????????????<tbody>??????????????<tr?ng-repeat="choice?in?poll.choices">????????????????<td>{{choice.text}}</td>????????????????<td>??????????????????<table?style="width:?{{choice.votes.length?/poll.totalVotes*100}}%;">????????????????????<tr><td>{{choice.votes.length}}</td></tr>??????????????????</table>????????????????</td>??????????????</tr>????????????</tbody>??????????</table>????????????<p><em>{{poll.totalVotes}}?votes?counted?so?far.?<span?ng-show="poll.userChoice">You?voted?for?<strong>{{poll.userChoice.text}}</strong>.</span></em></p>??????????<p><hr></p>??????????<p><a?href="#/polls"?class="btn?btn-default"?role="button"><span?class="glyphicon?glyphicon-arrow-left"></span>?Back?to?Poll?List</a></p>????????</div>????????<p> </p>第三個(gè)也是最后一個(gè)局部模板定義了支持用戶創(chuàng)建新的問卷調(diào)查的表單。它要求用戶輸入一個(gè)問題和三個(gè)選項(xiàng)。提供一個(gè)按鈕,以便允許用戶添加額外的選項(xiàng)。稍后,我們將驗(yàn)證用戶至少輸入了兩個(gè)選項(xiàng) — 因?yàn)槿绻麤]有幾個(gè)選項(xiàng),就不能稱之為問卷調(diào)查。
清單 8. public/partials/new.html
12345678910111213141516171819202122232425262728293031323334353637?????????<div?class="page-header">??????????<h1>Create?New?Poll</h1>????????</div>????????<form?role="form"?ng-submit="createPoll()">??????????<div?class="form-group">????????????<label?for="pollQuestion">Question</label>????????????<input?type="text"?ng-model="poll.question"?class="form-control"?id="pollQuestion"?placeholder="Enter?poll?question">??????????</div>??????????<div?class="form-group">????????????<label>Choices</label>????????????<div?ng-repeat="choice?in?poll.choices">??????????????<input?type="text"?ng-model="choice.text"?class="form-control"?placeholder="Enter?choice?{{$index+1}}?text"><br>????????????</div>??????????</div>??????????????<div?class="row">????????????<div?class="col-xs-12">??????????????<button?type="button"?class="btn?btn-default"?ng-click="addChoice()"><span?class="glyphicon?glyphicon-plus"></span>?Add?another</button>????????????</div>??????????</div>??????????<p><hr></p>??????????<div?class="row">????????????<div?class="col-xs-6">??????????????<a?href="#/polls"?class="btn?btn-default"?role="button"><span?class="glyphicon?glyphicon-arrow-left"></span>?Back?to?Poll?List</a>????????????</div>????????????<div?class="col-xs-6">??????????????<button?class="btn?btn-primary?pull-right"?type="submit">?Create?Poll?»</button>????????????</div>??????????</div>??????????<p> </p>????????</form>最后,為了顯示結(jié)果,我們需要向 style.css 添加一些 CSS 聲明。將該文件的內(nèi)容替換為以下內(nèi)容:
清單 9. public/stylesheets/style.css
12345678910111213141516?????????body?{?padding-top:?50px;?}????????.result-table?{??????????margin:?20px?0;??????????width:?100%;??????????border-collapse:?collapse;????????}????????.result-table?td?{?padding:?8px;?}????????.result-table?>?tbody?>?tr?>?td:first-child?{??????????width:?25%;??????????max-width:?300px;??????????text-align:?right;????????}????????.result-table?td?table?{??????????background-color:?lightblue;??????????text-align:?right;????????}此時(shí),如果您運(yùn)行該應(yīng)用程序,就會(huì)看到一個(gè)空的問卷調(diào)查列表。如果您試著創(chuàng)建一個(gè)新的問卷調(diào)查,就能看到此表單并添加更多的選項(xiàng),但您不能保存該問卷調(diào)查。我們將在下一步中詳細(xì)介紹所有這些內(nèi)容。
第 3 步. 使用 Mongoose 在 MongoDB 中存儲(chǔ)數(shù)據(jù)
0?
為了存儲(chǔ)數(shù)據(jù),該應(yīng)用程序使用了 MongoDB 驅(qū)動(dòng)程序和 Mongoose npm 模塊。它們?cè)试S應(yīng)用程序與 MongoDB 數(shù)據(jù)庫(kù)進(jìn)行通信。要獲得這些模塊,請(qǐng)打該應(yīng)用程序根目錄中的 package.json 文件,并在依賴關(guān)系部分中添加以下這些代碼行:。
清單 10. 向依賴關(guān)系添加下列代碼
12??"mongodb":?">=?1.3.19",?"mongoose":?">=?3.8.0",保存文件,在 Project Explorer 中右鍵單擊并選擇?Run As?>?npm install。這將安裝 npm 模塊和其他所有依賴關(guān)系。?
創(chuàng)建一個(gè) Mongoose 模型
0?
在您應(yīng)用程序的名為 models 的根目錄中創(chuàng)建一個(gè)新的子目錄,并在這個(gè)子目錄中創(chuàng)建一個(gè)名為 Poll.js 的新文件。在這個(gè)文件中,我們定義了我們的 Mongoose 模型,該模型將用于查詢數(shù)據(jù),并以結(jié)構(gòu)化數(shù)據(jù)的形式將這些數(shù)據(jù)保存到 MongoDB 。
清單 11. models/Poll.js?
12345678910??var?mongoose?=?require('mongoose');?var?voteSchema?=?new?mongoose.Schema({?ip:?'String'?});?var?choiceSchema?=?new?mongoose.Schema({???????????text:?String,??????????votes:?[voteSchema]????????});????????exports.PollSchema?=?new?mongoose.Schema({??????????question:?{?type:?String,?required:?true?},??????????choices:?[choiceSchema]????????});定義數(shù)據(jù)存儲(chǔ)的 API 路由
0?
接下來(lái),在您應(yīng)用程序的根目錄下的 app.js 文件中設(shè)置一些路由,以便創(chuàng)建一些 JSON 端點(diǎn),這些端點(diǎn)可用于根據(jù) Angular 客戶端代碼來(lái)查詢和更新 MongoDB。找到?app.get('/', routes.index)?行,并將下列代碼添加到這一行的后面: :
清單 12. 創(chuàng)建 JSON 端點(diǎn)
123?????????app.get('/polls/polls',?routes.list);????????app.get('/polls/:id',?routes.poll);????????app.post('/polls',?routes.create);現(xiàn)在,您需要實(shí)現(xiàn)這些功能。將 routes/index.js 文件的內(nèi)容替換為下列代碼:
清單 13. routes/index.js
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455??var?mongoose?=?require('mongoose');?var?db?=?mongoose.createConnection('localhost',?'pollsapp');?var?PollSchema?=?require('../models/Poll.js').PollSchema;?var?Poll?=?db.model('polls',?PollSchema);????????exports.index?=?function(req,?res)?{??????????res.render('index',?{title:?'Polls'});????????};?//?JSON?API?for?list?of?polls????????exports.list?=?function(req,?res)?{???????????Poll.find({},?'question',?function(error,?polls)?{????????????res.json(polls);??????????});????????};?//?JSON?API?for?getting?a?single?poll????????exports.poll?=?function(req,?res)?{???var?pollId?=?req.params.id;??????????Poll.findById(pollId,?'',?{?lean:?true?},?function(err,?poll)?{?if(poll)?{?var?userVoted?=?false,??????????????????userChoice,??????????????????totalVotes?=?0;?for(c?in?poll.choices)?{?var?choice?=?poll.choices[c];??for(v?in?choice.votes)?{?var?vote?=?choice.votes[v];??????????????????totalVotes++;?if(vote.ip?===?(req.header('x-forwarded-for')?||?req.ip))?{????????????????????userVoted?=?true;????????????????????userChoice?=?{?_id:?choice._id,?text:?choice.text?};??????????????????}????????????????}??????????????}??????????????poll.userVoted?=?userVoted;??????????????poll.userChoice?=?userChoice;??????????????poll.totalVotes?=?totalVotes;??????????????res.json(poll);????????????}?else?{??????????????res.json({error:true});????????????}??????????});????????};?//?JSON?API?for?creating?a?new?poll????????exports.create?=?function(req,?res)?{?var?reqBody?=?req.body,??????????????choices?=?reqBody.choices.filter(function(v)?{?return?v.text?!=?'';?}),??????????????pollObj?=?{question:?reqBody.question,?choices:?choices};?var?poll?=?new?Poll(pollObj);??????????poll.save(function(err,?doc)?{?if(err?||?!doc)?{?throw?'Error';????????????}?else?{??????????????res.json(doc);????????????}?????????????});????????};使用 Angular 服務(wù)將數(shù)據(jù)綁定到前端
0?
此時(shí),設(shè)置后臺(tái),以便啟用查詢,并將問卷調(diào)查數(shù)據(jù)保存到數(shù)據(jù)庫(kù),但我們需要在 Angular 中做一些更改,以便讓它知道如何與數(shù)據(jù)庫(kù)進(jìn)行通信。使用 Angular 服務(wù)很容易完成這項(xiàng)任務(wù),它將與服務(wù)器端進(jìn)行通信的過程包裝到了簡(jiǎn)單的函數(shù)調(diào)用中:
清單 14. public/javascripts/services.js
123456?????????angular.module('pollServices',?['ngResource']).??????????factory('Poll',?function($resource)?{?return?$resource('polls/:pollId',?{},?{??????????????query:?{?method:?'GET',?params:?{?pollId:?'polls'?},?isArray:?true?}????????????})??????????});在創(chuàng)建的這一文件之后,您需要將它包括在您的 index.jade 模板中。在文件標(biāo)頭部分的最后一個(gè)腳本元素的下面添加以下這行代碼:
script(src='/javascripts/services.js')。
您還需要告訴您的 Angular 應(yīng)用程序使用這個(gè)服務(wù)模塊。要實(shí)現(xiàn)這一點(diǎn),請(qǐng)打開 public/javascripts/app.js 并將第一行更改為可讀,如下所示:
angular.module('polls', ['pollServices'])。
最后,更改 Angular 控制器,以便使用該服務(wù)在數(shù)據(jù)庫(kù)中進(jìn)行查詢和存儲(chǔ)問卷調(diào)查數(shù)據(jù)。在 public/javascripts/controllers.js 文件中,將?PollListCtrl?更改如下:。
清單 15. public/javascripts/controller.js
1234??function?PollListCtrl($scope,?Poll)?{??????????$scope.polls?=?Poll.query();????????}???...更新?PollItemCtrl?函數(shù),以便根據(jù)問卷調(diào)查的 ID 來(lái)查詢某個(gè)問卷調(diào)查:?
清單 16. public/javascripts/controller.js (continued)
123456?...?function?PollItemCtrl($scope,?$routeParams,?Poll)?{??????????$scope.poll?=?Poll.get({pollId:?$routeParams.pollId});??????????$scope.vote?=?function()?{};????????}?...類似地,更改?PollNewCtrl?函數(shù),以便在提交表單時(shí)將新調(diào)查數(shù)據(jù)發(fā)送到服務(wù)器。
清單 17. public/javascripts/controller.js (continued)
123456789101112131415161718192021222324252627282930313233343536?...?function?PollNewCtrl($scope,?$location,?Poll)?{??????????$scope.poll?=?{????????????question:?'',????????????choices:?[?{?text:?''?},?{?text:?''?},?{?text:?''?}]??????????};????????????$scope.addChoice?=?function()?{????????????$scope.poll.choices.push({?text:?''?});??????????};??????????$scope.createPoll?=?function()?{?var?poll?=?$scope.poll;?if(poll.question.length?>?0)?{?var?choiceCount?=?0;?for(var?i?=?0,?ln?=?poll.choices.length;?i?<?ln;?i++)?{?var?choice?=?poll.choices[i];?????????if(choice.text.length?>?0)?{??????????????????choiceCount++????????????????}??????????????}?????if(choiceCount?>?1)?{?var?newPoll?=?new?Poll(poll);???????????????????????newPoll.$save(function(p,?resp)?{?if(!p.error)?{?????????????????????$location.path('polls');??????????????????}?else?{????????????????????alert('Could?not?create?poll');??????????????????}????????????????});??????????????}?else?{????????????????alert('You?must?enter?at?least?two?choices');??????????????}????????????}?else?{??????????????alert('You?must?enter?a?question');????????????}??????????};????????}運(yùn)行應(yīng)用程序
0?
您已經(jīng)離成功不遠(yuǎn)了!此時(shí),應(yīng)用程序應(yīng)該允許用戶查看和搜索問卷調(diào)查數(shù)據(jù)、創(chuàng)建新的問卷調(diào)查并查看單個(gè)問卷調(diào)查的投票選項(xiàng)。在運(yùn)行該應(yīng)用程序之前,請(qǐng)確保您已經(jīng)本地運(yùn)行 MongoDB。這通常和打開一個(gè)終端或命令提示符以及運(yùn)行?mongod?命令一樣簡(jiǎn)單。確保在您運(yùn)行應(yīng)用程序時(shí)終端窗口處于打開狀態(tài):
圖 3. 查看一個(gè)問卷調(diào)查的選項(xiàng)
在運(yùn)行應(yīng)用程序之后,在您的瀏覽器中導(dǎo)航到 http://localhost:3000 并創(chuàng)建一些問卷調(diào)查。如果您單擊一個(gè)問卷調(diào)查,您就能夠看到可用的選項(xiàng),但是,您無(wú)法實(shí)際對(duì)該問卷調(diào)查進(jìn)行投票,或者暫時(shí)看不到問卷調(diào)查結(jié)果。我們將在下一步和最后一步中對(duì)此進(jìn)行介紹。
第 4 步. 使用 Socket.io 進(jìn)行實(shí)時(shí)投票
0?
Web Sockets 允許服務(wù)器端直接與客戶端通信以及向客戶端發(fā)送消息。
剩下的惟一需要構(gòu)建的特性就是投票功能。該應(yīng)用程序允許用戶進(jìn)行投票,在他們投票后,會(huì)在所有已連接的客戶端上實(shí)時(shí)更新投票結(jié)果。使用 socket.io 模塊很容易完成這項(xiàng)工作,現(xiàn)在就讓我們來(lái)實(shí)現(xiàn)它吧。
打開您應(yīng)用程序的根目錄中的 package.json 文件,將下列代碼添加到依賴關(guān)系部分:
"socket.io": "~0.9.16"。
保存文件,在 Package Explorer 中右鍵單擊,然后選擇?Run As?>?npm install?來(lái)安裝 npm 模塊。
接下來(lái),打開應(yīng)用程序根目錄中的 app.js 文件, 刪除位于文件末尾的?server.listen...?代碼塊,將其替換為:
清單 18. app.js
123456789?...?var?server?=?http.createServer(app);?var?io?=?require('socket.io').listen(server);????????io.sockets.on('connection',?routes.vote);????????server.listen(app.get('port'),?function(){??????????console.log('Express?server?listening?on?port?'?+?app.get('port'));????????});接下來(lái),修改 index.jade 模塊以包含 socket.io 客戶端庫(kù)。在運(yùn)行該應(yīng)用程序時(shí),該庫(kù)會(huì)自動(dòng)在指定的位置上變得可用,因此不需要擔(dān)心自己如何尋找該文件。確保想包含模板中的 angular-resource 庫(kù)的行的后面包含此文件:
script(src='/socket.io/socket.io.js')。
最后,您需要?jiǎng)?chuàng)建投票功能,以便在用戶向 socket.io 發(fā)送消息時(shí)保存新的投票,并在具有更新結(jié)果時(shí)將消息發(fā)送給所有客戶端。將 添加到路由目錄中的 index.js 文件的結(jié)尾處:
清單 19. routes/index.js
12345678910111213141516171819202122232425262728293031??//?Socket?API?for?saving?a?vote????????exports.vote?=?function(socket)?{??????????socket.on('send:vote',?function(data)?{?var?ip?=?socket.handshake.headers['x-forwarded-for']?||?socket.handshake.address.address;????????????????Poll.findById(data.poll_id,?function(err,?poll)?{?var?choice?=?poll.choices.id(data.choice);??????????????choice.votes.push({?ip:?ip?});????????????????????poll.save(function(err,?doc)?{?var?theDoc?=?{???????????????????question:?doc.question,?_id:?doc._id,?choices:?doc.choices,???????????????????userVoted:?false,?totalVotes:?0?????????????????};?for(var?i?=?0,?ln?=?doc.choices.length;?i?<?ln;?i++)?{?var?choice?=?doc.choices[i];??for(var?j?=?0,?jLn?=?choice.votes.length;?j?<?jLn;?j++)?{?var?vote?=?choice.votes[j];????????????????????theDoc.totalVotes++;????????????????????theDoc.ip?=?ip;?if(vote.ip?===?ip)?{??????????????????????theDoc.userVoted?=?true;??????????????????????theDoc.userChoice?=?{?_id:?choice._id,?text:?choice.text?};????????????????????}??????????????????}????????????????}???????????????????????socket.emit('myvote',?theDoc);????????????????socket.broadcast.emit('vote',?theDoc);??????????????});?????????????????});??????????});????????};注意:如果您想知道為什么應(yīng)用程序會(huì)在常規(guī) API 地址屬性的前面查找標(biāo)頭?'x-forwarded-for',因?yàn)檫@將確保當(dāng)應(yīng)用程序被部署到負(fù)載平衡環(huán)境中時(shí),所使用的是正確的客戶端 IP。如果您將該應(yīng)用程序部署到 Bluemix 或 Cloud Foundry,這對(duì)于應(yīng)用程序是否能正常工作至關(guān)重要。
添加一個(gè) Angular 服務(wù)將數(shù)據(jù)發(fā)送到 Web 套接字。
0?
Web Sockets 的后端功能現(xiàn)已創(chuàng)建完畢。目前剩下要做的工作是綁定前端,以發(fā)送和監(jiān)聽套接字事件。最佳方法是添加一個(gè)新的 Angular 服務(wù)。使用以下代碼替換 public/javascripts 文件夾中的 services.js 文件:
清單 20. public/javascripts/services.js
1234567891011121314151617181920212223242526272829?????????angular.module('pollServices',?['ngResource']).??????????factory('Poll',?function($resource)?{?return?$resource('polls/:pollId',?{},?{??????????????query:?{?method:?'GET',?params:?{?pollId:?'polls'?},?isArray:?true?}????????????})??????????}).??????????factory('socket',?function($rootScope)?{?var?socket?=?io.connect();?return?{??????????????on:?function?(eventName,?callback)?{????????????????socket.on(eventName,?function?()?{???var?args?=?arguments;??????????????????$rootScope.$apply(function?()?{????????????????????callback.apply(socket,?args);??????????????????});????????????????});??????????????},??????????????emit:?function?(eventName,?data,?callback)?{????????????????socket.emit(eventName,?data,?function?()?{?var?args?=?arguments;??????????????????$rootScope.$apply(function?()?{?if?(callback)?{??????????????????????callback.apply(socket,?args);????????????????????}??????????????????});????????????????})??????????????}????????????};??????????});最后,您需要編輯?PollItemCtrl?控制器,以便它能夠監(jiān)聽和發(fā)送用于投票的 Web Socket 消息。將原始控制器替換為:
清單 21. public/javascripts/controllers.js
12345678910111213141516171819202122232425262728?...?function?PollItemCtrl($scope,?$routeParams,?socket,?Poll)?{???????????$scope.poll?=?Poll.get({pollId:?$routeParams.pollId});??????????socket.on('myvote',?function(data)?{????????????console.dir(data);?if(data._id?===?$routeParams.pollId)?{??????????????$scope.poll?=?data;????????????}??????????});??????????socket.on('vote',?function(data)?{????????????console.dir(data);?if(data._id?===?$routeParams.pollId)?{??????????????$scope.poll.choices?=?data.choices;??????????????$scope.poll.totalVotes?=?data.totalVotes;????????????}?????????????});??????????$scope.vote?=?function()?{?var?pollId?=?$scope.poll._id,????????????????choiceId?=?$scope.poll.userVote;?if(choiceId)?{?var?voteObj?=?{?poll_id:?pollId,?choice:?choiceId?};??????????????socket.emit('send:vote',?voteObj);????????????}?else?{??????????????alert('You?must?select?an?option?to?vote?for');????????????}??????????};????????}???...查看最終產(chǎn)品
0?
問卷調(diào)查應(yīng)用程序現(xiàn)已創(chuàng)建完成。確保 mongod 仍在運(yùn)行,并在 Eclipse 中再次運(yùn)行 Node 應(yīng)用程序。在瀏覽器中輸入 http://localhost:3000,導(dǎo)航到一個(gè)問卷調(diào)查并進(jìn)行投票。隨后您就可以看到結(jié)果。要查看實(shí)時(shí)更新,請(qǐng)找到您的本地 IP 地址,并用該地址替換?localhost。在您的局域網(wǎng)中,使用不同的機(jī)器(甚至智能手機(jī)或平板電腦也可以)導(dǎo)航到這個(gè)地址。當(dāng)您在另一個(gè)設(shè)備上進(jìn)行投票時(shí),結(jié)果會(huì)顯示在該設(shè)備上,而且會(huì)自動(dòng)發(fā)布到您的主要計(jì)算機(jī)瀏覽器上:
圖 4. 查看問卷調(diào)查結(jié)果
下一步:進(jìn)一步開發(fā)和部署
0?
您剛才創(chuàng)建的這個(gè)問卷調(diào)查應(yīng)用程序是一個(gè)不錯(cuò)的起點(diǎn),但還有很大的改進(jìn)空間。在計(jì)劃創(chuàng)建這類應(yīng)用程序時(shí),我喜歡使用一種敏捷方法來(lái)定義用戶案例,并將項(xiàng)目劃分為幾塊來(lái)實(shí)現(xiàn)。對(duì)于這個(gè)項(xiàng)目,我使用了 JazzHub,通過將項(xiàng)目的附屬代碼和源代碼一起保存在一個(gè)云托管的存儲(chǔ)庫(kù)中,JazzHub 使得開發(fā)變得非常簡(jiǎn)單。
如果您對(duì)您的應(yīng)用程序感到很滿意,下一步就是跟全世界的人分享它。在過去,即使部署一個(gè)非常簡(jiǎn)單的應(yīng)用程序,可能也會(huì)是一場(chǎng)噩夢(mèng),但值得慶幸的是,那些日子已經(jīng)一去不復(fù)返了。使用 IBM 新興的兼容 Cloud Foundry 的?Bluemix?平臺(tái),您只需幾分鐘就可以通過最少的配置將您的應(yīng)用程序部署到云中,一點(diǎn)都不麻煩。
結(jié)束語(yǔ)
0?
這對(duì)于開發(fā)人員,現(xiàn)在是一個(gè)很好的時(shí)機(jī)。我們手頭有大量框架和工具,它們使得開發(fā)大量應(yīng)用程序不僅更簡(jiǎn)單、更快速,而且更加令人感到愉快。在本文中,您學(xué)習(xí)了如何使用被稱為 MEAN 體系結(jié)構(gòu)(Mongo、Express、Angular 和Node)的技術(shù)構(gòu)建一個(gè)應(yīng)用程序。該堆棧可能只需要一天時(shí)間就可以完成任務(wù),遠(yuǎn)遠(yuǎn)超過了 LAMP 體系結(jié)構(gòu)(Linux、Apache、MySQL 和 PHP),在 Web 應(yīng)用程序開發(fā)和部署方面,該體系結(jié)構(gòu)也許同樣會(huì)超越 LAMP 體系結(jié)構(gòu)。對(duì)我而言,我已經(jīng)迫不及待躍躍欲試了。
轉(zhuǎn)載于:https://my.oschina.net/iWage/blog/547794
《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的使用 Node.js、Express、AngularJS 和 MongoDB 构建一个Web程序的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: poj2955Brackets(区间DP
- 下一篇: Linux内核的同步机制---自旋锁