最近,在向大學(xué)生們介紹 HTML5 的時候,我想要對他們進(jìn)行問卷調(diào)查,并向他們顯示實時更新的投票結(jié)果。鑒于此目的,我決定快速構(gòu)建一個用于此目的的問卷調(diào)查應(yīng)用程序。我想要一個簡單的架構(gòu),不需要太多不同的語言和框架。因此,我決定對所有一切都使用 JavaScript — 對服務(wù)器端使用 Node.js 和 Express,對數(shù)據(jù)庫使用 MongoDB,對前端用戶界面使用 AngularJS。
這個 MEAN 堆棧(Mongo、Express、Angular 和 Node)只需要一天即可完成,遠(yuǎn)比 Web 應(yīng)用程序開發(fā)和部署所用的 LAMP 堆棧(Linux、Apache、MySQL 和 PHP)簡單得多。
我選擇使用 JazzHub 來管理我的項目的源代碼。它不僅為我的代碼提供了一個完整的版本控制系統(tǒng),還為在云中編輯代碼提供了一個在線 IDE,以及用于項目管理的敏捷特性。JazzHub 很容易與 Eclipse 相集成,Eclipse 還提供了一些插件,支持對平臺( 比如BlueMix或 Cloud Foundry )的一鍵式部署。
在 Eclipse 中,切換到 Node 透視圖,并創(chuàng)建一個新的 Node Express 項目。如果您創(chuàng)建了一個 JazzHub 項目,請像我所做的那樣,使用相同的名稱為您的 Node Express 項目命名。選擇使用 Jade 作為模板引擎。Eclipse 會自動下載所需的 npm 模塊,以便創(chuàng)建一個簡單 Express 應(yīng)用程序。
在 Project Explorer 中,找到位于您項目的根目錄中的 app.js,右鍵單擊并選擇 Run As > Node Application 。這將啟動一個 Web 服務(wù)器并將應(yīng)用程序部署到該服務(wù)器。 接下來,打開瀏覽器并導(dǎo)航到 http://localhost:3000 。
圖 1. Starter Express 應(yīng)用程序
這個問卷調(diào)查應(yīng)用程序?qū)ΤR娪脩艚缑婧筒季质褂昧薆ootstrap 框架?,F(xiàn)在,讓我們對 Express 應(yīng)用程序做一些改動來反映這一點。首先,打開 routes/index.js,將標(biāo)題屬性更改為 Polls
:
清單 1. routes/index.js
exports.index = function(req, res){ res.render('index', { title: 'Polls' }); };
接著,更改 views/index.jade 模板以包含 Bootstrap。Jade 是一種速記模板語言,可編譯成 HTML。它使用縮進(jìn)消除了對結(jié)束標(biāo)簽的需求,極大地縮小了模板的大小。您只需要使用 Jade 作為主頁面布局即可。在下一步中,還可以使用 Angular 局部模板向這個頁面添加功能。
清單 2. views/index.jade
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
想要查看對您的應(yīng)用程序所做的更改,請結(jié)束 Eclipse 中的 Web 服務(wù)器進(jìn)程,再次運行 app.js 文件:
圖 2. 問卷調(diào)查應(yīng)用程序樣板文件
注意:在使用 Jade 模板時,注意適當(dāng)縮進(jìn)您的代碼,否則您會遇到麻煩。另外,還要避免使用混合縮進(jìn)樣式,如果您嘗試這樣做,Jade 將會報錯。
如果要使用 Angular,首先需要在您的 HTML 頁面中包含它,還需要在 HTML 頁面中添加一些指令。在 views/index.jade 模板中,對 html
元素進(jìn)行如下更改: html(lang='en', ng-app='polls')
。
在該文件的標(biāo)頭中添加以下腳本元素: :
清單 3. 將腳本元素加載到 Angular 和 Angular Resource 模板
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')
接下來,更改模板中的 body
元素,添加一個 ng-controller
屬性(稍后使用該屬性將用戶界面綁定到控制器邏輯代碼中): body(ng-controller='PollListCtrl')
。
最后,更改模板中的最后一個 div
元素,以便包含一個 ng-view
屬性:div(ng-view)
。
Angular 中令人影響較為深刻的特性就是數(shù)據(jù)綁定,在后臺模型發(fā)生改變時,該功能會自動更新您的視圖。這極大地減少了需要編寫的 JavaScript 的數(shù)量,因為它對凌亂的 DOM 操作任務(wù)進(jìn)行了抽象。
在默認(rèn)情況下,Express 發(fā)布了靜態(tài)資源,比如 JavaScript 源文件、CSS 樣式表以及位于您項目的公共目錄中的圖像。在公共目錄中,創(chuàng)建一個名為 javascripts 的新的子目錄。在這個子目錄中,創(chuàng)建一個名為 app.js 的文件。該文件將包含用于應(yīng)用程序的 Angular 模塊,而且還定義了用于用戶界面的路由和模板:
清單 4. public/javascripts/app.js
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
// 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() {}; }
為了呈現(xiàn)來自控制器的數(shù)據(jù),Angular 使用了局部 HTML 模板,該模板允許您使用占位符和表達(dá)式來包含數(shù)據(jù)和執(zhí)行操作,比如條件和迭代操作。在公共目錄中,創(chuàng)建一個名為 partials 的新的子目錄。我們將為我們的應(yīng)用程序創(chuàng)建 3 個局部模板,第一個局部模板將會展示可用投票的列表,我們將使用 Angular 通過一個搜索字段輕松過濾該列表。
清單 6. public/partials/list.html
<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>
第二個局部模板允許用戶查看投票。它使用 Angular 切換指令來確定用戶是否已投票,并根據(jù)這些判斷,顯示一個就此次問卷調(diào)查進(jìn)行投票的表格,或者一個包含顯示問卷調(diào)查結(jié)果的圖表。
清單 7. public/partials/item.html
<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>
第三個也是最后一個局部模板定義了支持用戶創(chuàng)建新的問卷調(diào)查的表單。它要求用戶輸入一個問題和三個選項。提供一個按鈕,以便允許用戶添加額外的選項。稍后,我們將驗證用戶至少輸入了兩個選項 — 因為如果沒有幾個選項,就不能稱之為問卷調(diào)查。
清單 8. public/partials/new.html
<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
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; }
此時,如果您運行該應(yīng)用程序,就會看到一個空的問卷調(diào)查列表。如果您試著創(chuàng)建一個新的問卷調(diào)查,就能看到此表單并添加更多的選項,但您不能保存該問卷調(diào)查。我們將在下一步中詳細(xì)介紹所有這些內(nèi)容。
為了存儲數(shù)據(jù),該應(yīng)用程序使用了 MongoDB 驅(qū)動程序和 Mongoose npm 模塊。它們允許應(yīng)用程序與 MongoDB 數(shù)據(jù)庫進(jìn)行通信。要獲得這些模塊,請打該應(yīng)用程序根目錄中的 package.json 文件,并在依賴關(guān)系部分中添加以下這些代碼行:。
清單 10. 向依賴關(guān)系添加下列代碼
"mongodb": ">= 1.3.19", "mongoose": ">= 3.8.0",
保存文件,在 Project Explorer 中右鍵單擊并選擇 Run As > npm install 。這將安裝 npm 模塊和其他所有依賴關(guān)系。
在您應(yīng)用程序的名為 models 的根目錄中創(chuàng)建一個新的子目錄,并在這個子目錄中創(chuàng)建一個名為 Poll.js 的新文件。在這個文件中,我們定義了我們的 Mongoose 模型,該模型將用于查詢數(shù)據(jù),并以結(jié)構(gòu)化數(shù)據(jù)的形式將這些數(shù)據(jù)保存到 MongoDB 。
清單 11. models/Poll.js
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] });
接下來,在您應(yīng)用程序的根目錄下的 app.js 文件中設(shè)置一些路由,以便創(chuàng)建一些 JSON 端點,這些端點可用于根據(jù) Angular 客戶端代碼來查詢和更新 MongoDB。找到app.get('/', routes.index)
行,并將下列代碼添加到這一行的后面: :
清單 12. 創(chuàng)建 JSON 端點
app.get('/polls/polls', routes.list); app.get('/polls/:id', routes.poll); app.post('/polls', routes.create);
現(xiàn)在,您需要實現(xiàn)這些功能。將 routes/index.js 文件的內(nèi)容替換為下列代碼:
清單 13. routes/index.js
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); } }); };
此時,設(shè)置后臺,以便啟用查詢,并將問卷調(diào)查數(shù)據(jù)保存到數(shù)據(jù)庫,但我們需要在 Angular 中做一些更改,以便讓它知道如何與數(shù)據(jù)庫進(jìn)行通信。使用 Angular 服務(wù)很容易完成這項任務(wù),它將與服務(wù)器端進(jìn)行通信的過程包裝到了簡單的函數(shù)調(diào)用中:
清單 14. public/javascripts/services.js
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)頭部分的最后一個腳本元素的下面添加以下這行代碼: script(src='/javascripts/services.js')
。
您還需要告訴您的 Angular 應(yīng)用程序使用這個服務(wù)模塊。要實現(xiàn)這一點,請打開 public/javascripts/app.js 并將第一行更改為可讀,如下所示: angular.module('polls', ['pollServices'])
。
最后,更改 Angular 控制器,以便使用該服務(wù)在數(shù)據(jù)庫中進(jìn)行查詢和存儲問卷調(diào)查數(shù)據(jù)。在 public/javascripts/controllers.js 文件中,將 PollListCtrl
更改如下:。
清單 15. public/javascripts/controller.js
function PollListCtrl($scope, Poll) { $scope.polls = Poll.query(); } ...
更新 PollItemCtrl
函數(shù),以便根據(jù)問卷調(diào)查的 ID 來查詢某個問卷調(diào)查:
清單 16. public/javascripts/controller.js (continued)
... function PollItemCtrl($scope, $routeParams, Poll) { $scope.poll = Poll.get({pollId: $routeParams.pollId}); $scope.vote = function() {}; } ...
類似地,更改 PollNewCtrl
函數(shù),以便在提交表單時將新調(diào)查數(shù)據(jù)發(fā)送到服務(wù)器。
清單 17. public/javascripts/controller.js (continued)
... 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'); } }; }
您已經(jīng)離成功不遠(yuǎn)了!此時,應(yīng)用程序應(yīng)該允許用戶查看和搜索問卷調(diào)查數(shù)據(jù)、創(chuàng)建新的問卷調(diào)查并查看單個問卷調(diào)查的投票選項。在運行該應(yīng)用程序之前,請確保您已經(jīng)本地運行 MongoDB。這通常和打開一個終端或命令提示符以及運行 mongod
命令一樣簡單。確保在您運行應(yīng)用程序時終端窗口處于打開狀態(tài):
圖 3. 查看一個問卷調(diào)查的選項
在運行應(yīng)用程序之后,在您的瀏覽器中導(dǎo)航到 http://localhost:3000 并創(chuàng)建一些問卷調(diào)查。如果您單擊一個問卷調(diào)查,您就能夠看到可用的選項,但是,您無法實際對該問卷調(diào)查進(jìn)行投票,或者暫時看不到問卷調(diào)查結(jié)果。我們將在下一步和最后一步中對此進(jìn)行介紹。
Web Sockets 允許服務(wù)器端直接與客戶端通信以及向客戶端發(fā)送消息。
剩下的惟一需要構(gòu)建的特性就是投票功能。該應(yīng)用程序允許用戶進(jìn)行投票,在他們投票后,會在所有已連接的客戶端上實時更新投票結(jié)果。使用 socket.io 模塊很容易完成這項工作,現(xiàn)在就讓我們來實現(xiàn)它吧。
打開您應(yīng)用程序的根目錄中的 package.json 文件,將下列代碼添加到依賴關(guān)系部分: "socket.io": "~0.9.16"
。
保存文件,在 Package Explorer 中右鍵單擊,然后選擇 Run As > npm install 來安裝 npm 模塊。
接下來,打開應(yīng)用程序根目錄中的 app.js 文件, 刪除位于文件末尾的server.listen...
代碼塊,將其替換為:
清單 18. app.js
... 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')); });
接下來,修改 index.jade 模塊以包含 socket.io 客戶端庫。在運行該應(yīng)用程序時,該庫會自動在指定的位置上變得可用,因此不需要擔(dān)心自己如何尋找該文件。確保想包含模板中的 angular-resource 庫的行的后面包含此文件: script(src='/socket.io/socket.io.js')
。
最后,您需要創(chuàng)建投票功能,以便在用戶向 socket.io 發(fā)送消息時保存新的投票,并在具有更新結(jié)果時將消息發(fā)送給所有客戶端。將 添加到路由目錄中的 index.js 文件的結(jié)尾處:
清單 19. routes/index.js
// 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)用程序會在常規(guī) API 地址屬性的前面查找標(biāo)頭 'x-forwarded-for'
,因為這將確保當(dāng)應(yīng)用程序被部署到負(fù)載平衡環(huán)境中時,所使用的是正確的客戶端 IP。如果您將該應(yīng)用程序部署到 BlueMix 或 Cloud Foundry,這對于應(yīng)用程序是否能正常工作至關(guān)重要。
Web Sockets 的后端功能現(xiàn)已創(chuàng)建完畢。目前剩下要做的工作是綁定前端,以發(fā)送和監(jiān)聽套接字事件。最佳方法是添加一個新的 Angular 服務(wù)。使用以下代碼替換 public/javascripts 文件夾中的 services.js 文件:
清單 20. public/javascripts/services.js
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
... 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'); } }; } ...
問卷調(diào)查應(yīng)用程序現(xiàn)已創(chuàng)建完成。確保 mongod 仍在運行,并在 Eclipse 中再次運行 Node 應(yīng)用程序。在瀏覽器中輸入 http://localhost:3000,導(dǎo)航到一個問卷調(diào)查并進(jìn)行投票。隨后您就可以看到結(jié)果。要查看實時更新,請找到您的本地 IP 地址,并用該地址替換 localhost
。在您的局域網(wǎng)中,使用不同的機(jī)器(甚至智能手機(jī)或平板電腦也可以)導(dǎo)航到這個地址。當(dāng)您在另一個設(shè)備上進(jìn)行投票時,結(jié)果會顯示在該設(shè)備上,而且會自動發(fā)布到您的主要計算機(jī)瀏覽器上:
圖 4. 查看問卷調(diào)查結(jié)果
您剛才創(chuàng)建的這個問卷調(diào)查應(yīng)用程序是一個不錯的起點,但還有很大的改進(jìn)空間。在計劃創(chuàng)建這類應(yīng)用程序時,我喜歡使用一種敏捷方法來定義用戶案例,并將項目劃分為幾塊來實現(xiàn)。對于這個項目,我使用了 JazzHub,通過將項目的附屬代碼和源代碼一起保存在一個云托管的存儲庫中,JazzHub 使得開發(fā)變得非常簡單。
如果您對您的應(yīng)用程序感到很滿意,下一步就是跟全世界的人分享它。在過去,即使部署一個非常簡單的應(yīng)用程序,可能也會是一場噩夢,但值得慶幸的是,那些日子已經(jīng)一去不復(fù)返了。使用 IBM 新興的兼容 Cloud Foundry 的BlueMix平臺,您只需幾分鐘就可以通過最少的配置將您的應(yīng)用程序部署到云中,一點都不麻煩。
這對于開發(fā)人員,現(xiàn)在是一個很好的時機(jī)。我們手頭有大量框架和工具,它們使得開發(fā)大量應(yīng)用程序不僅更簡單、更快速,而且更加令人感到愉快。在本文中,您學(xué)習(xí)了如何使用被稱為 MEAN 體系結(jié)構(gòu)(Mongo、Express、Angular 和Node)的技術(shù)構(gòu)建一個應(yīng)用程序。該堆棧可能只需要一天時間就可以完成任務(wù),遠(yuǎn)遠(yuǎn)超過了 LAMP 體系結(jié)構(gòu)(Linux、Apache、MySQL 和 PHP),在 Web 應(yīng)用程序開發(fā)和部署方面,該體系結(jié)構(gòu)也許同樣會超越 LAMP 體系結(jié)構(gòu)。對我而言,我已經(jīng)迫不及待躍躍欲試了。