2014 年 3 月 20 日 04:53 | 抄本javascript
Joe Lennoncss
高級移動應用程序開發人員html
Joe Lennon 是來自愛爾蘭科克市的軟件開發人員,他今年 26 歲。Joe 是 Apress 即將發行的 Beginning CouchDB 一書的做者,爲 IBM developerWorks 撰寫了許多技術文章和教程。在業餘時間裏,Joe 喜歡踢足球,改進一些小玩意和玩他的 Xbox 360 遊戲機。前端
在 IBM Bluemix 雲平臺上開發並部署您的下一個應用。java
開始您的試用node
最近,在向大學生們介紹 HTML5 的時候,我想要對他們進行問卷調查,並向他們顯示實時更新的投票結果。鑑於此目的,我決定快速構建一個用於此目的的問卷調查應用程序。我想要一個簡單的架構,不須要太多不一樣的語言和框架。所以,我決定對全部一切都使用 JavaScript — 對服務器端使用 Node.js 和 Express,對數據庫使用 MongoDB,對前端用戶界面使用 AngularJS。angularjs
這個 MEAN 堆棧(Mongo、Express、Angular 和 Node)只須要一天便可完成,遠比 Web 應用程序開發和部署所用的 LAMP 堆棧(Linux、Apache、MySQL 和 PHP)簡單得多。web
運行該應用程序ajax
在 JazzHub 上獲取源代碼mongodb
我選擇使用 JazzHub 來管理個人項目的源代碼。它不只爲個人代碼提供了一個完整的版本控制系統,還爲在雲中編輯代碼提供了一個在線 IDE,以及用於項目管理的敏捷特性。JazzHub 很容易與 Eclipse 相集成,Eclipse 還提供了一些插件,支持對平臺( 好比 Bluemix 或 Cloud Foundry)的一鍵式部署。
0
基本瞭解 Node.js 和 Node.js 開發環境
具備如下這些 Node.js 模塊:Express framework、Jade、Mongoose 和 socket.io
AngularJS JavaScript 框架
MongoDB NoSQL 數據庫
Eclipse IDE,已安裝了 Nodeclipse 插件
0
在 Eclipse 中,切換到 Node 透視圖,並建立一個新的 Node Express 項目。若是您建立了一個 JazzHub 項目,請像我所作的那樣,使用相同的名稱爲您的 Node Express 項目命名。選擇使用 Jade 做爲模板引擎。Eclipse 會自動下載所需的 npm 模塊,以便建立一個簡單 Express 應用程序。
0
在 Project Explorer 中,找到位於您項目的根目錄中的 app.js,右鍵單擊並選擇 Run As > Node Application。這將啓動一個 Web 服務器並將應用程序部署到該服務器。 接下來,打開瀏覽器並導航到 http://localhost:3000。
0
這個問卷調查應用程序對常見用戶界面和佈局使用了 Bootstrap 框架。如今,讓咱們對 Express 應用程序作一些改動來反映這一點。首先,打開 routes/index.js,將標題屬性更改成 Polls
:
123 exports.index = function(req, res){ res.render('index', { title: 'Polls' }); };
接着,更改 views/index.jade 模板以包含 Bootstrap。Jade 是一種速記模板語言,可編譯成 HTML。它使用縮進消除了對結束標籤的需求,極大地縮小了模板的大小。您只須要使用 Jade 做爲主頁面佈局便可。在下一步中,還可使用 Angular 局部模板向這個頁面添加功能。
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
想要查看對您的應用程序所作的更改,請結束 Eclipse 中的 Web 服務器進程,再次運行 app.js 文件:
注意:在使用 Jade 模板時,注意適當縮進您的代碼,不然您會遇到麻煩。另外,還要避免使用混合縮進樣式,若是您嘗試這樣作,Jade 將會報錯。
0
若是要使用 Angular,首先須要在您的 HTML 頁面中包含它,還須要在 HTML 頁面中添加一些指令。在 views/index.jade 模板中,對 html
元素進行以下更改:html(lang='en', ng-app='polls')
。
在該文件的標頭中添加如下腳本元素: :
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')
接下來,更改模板中的 body
元素,添加一個 ng-controller
屬性(稍後使用該屬性將用戶界面綁定到控制器邏輯代碼中):body(ng-controller='PollListCtrl')
。
最後,更改模板中的最後一個 div
元素,以便包含一個 ng-view
屬性: div(ng-view)
。
0
Angular 中使人影響較爲深入的特性就是數據綁定,在後臺模型發生改變時,該功能會自動更新您的視圖。這極大地減小了須要編寫的 JavaScript 的數量,由於它對凌亂的 DOM 操做任務進行了抽象。
在默認狀況下,Express 發佈了靜態資源,好比 JavaScript 源文件、CSS 樣式表以及位於您項目的公共目錄中的圖像。在公共目錄中,建立一個名爲 javascripts 的新的子目錄。在這個子目錄中,建立一個名爲 app.js 的文件。該文件將包含用於應用程序的 Angular 模塊,並且還定義了用於用戶界面的路由和模板:
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 控制器定義了應用程序的範圍,爲要綁定的視圖提供數據和方法。
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() {}; }
0
爲了呈現來自控制器的數據,Angular 使用了局部 HTML 模板,該模板容許您使用佔位符和表達式來包含數據和執行操做,好比條件和迭代操做。在公共目錄中,建立一個名爲 partials 的新的子目錄。咱們將爲咱們的應用程序建立 3 個局部模板,第一個局部模板將會展現可用投票的列表,咱們將使用 Angular 經過一個搜索字段輕鬆過濾該列表。
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>
第二個局部模板容許用戶查看投票。它使用 Angular 切換指令來肯定用戶是否已投票,並根據這些判斷,顯示一個就這次問卷調查進行投票的表格,或者一個包含顯示問卷調查結果的圖表。
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>
第三個也是最後一個局部模板定義了支持用戶建立新的問卷調查的表單。它要求用戶輸入一個問題和三個選項。提供一個按鈕,以便容許用戶添加額外的選項。稍後,咱們將驗證用戶至少輸入了兩個選項 — 由於若是沒有幾個選項,就不能稱之爲問卷調查。
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>
最後,爲了顯示結果,咱們須要向 style.css 添加一些 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; }
此時,若是您運行該應用程序,就會看到一個空的問卷調查列表。若是您試着建立一個新的問卷調查,就能看到此表單並添加更多的選項,但您不能保存該問卷調查。咱們將在下一步中詳細介紹全部這些內容。
0
爲了存儲數據,該應用程序使用了 MongoDB 驅動程序和 Mongoose npm 模塊。它們容許應用程序與 MongoDB 數據庫進行通訊。要得到這些模塊,請打該應用程序根目錄中的 package.json 文件,並在依賴關係部分中添加如下這些代碼行:。
12 "mongodb": ">= 1.3.19", "mongoose": ">= 3.8.0",
保存文件,在 Project Explorer 中右鍵單擊並選擇 Run As > npm install。這將安裝 npm 模塊和其餘全部依賴關係。
0
在您應用程序的名爲 models 的根目錄中建立一個新的子目錄,並在這個子目錄中建立一個名爲 Poll.js 的新文件。在這個文件中,咱們定義了咱們的 Mongoose 模型,該模型將用於查詢數據,並以結構化數據的形式將這些數據保存到 MongoDB 。
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] });
0
接下來,在您應用程序的根目錄下的 app.js 文件中設置一些路由,以便建立一些 JSON 端點,這些端點可用於根據 Angular 客戶端代碼來查詢和更新 MongoDB。找到 app.get('/', routes.index)
行,並將下列代碼添加到這一行的後面: :
123 app.get('/polls/polls', routes.list); app.get('/polls/:id', routes.poll); app.post('/polls', routes.create);
如今,您須要實現這些功能。將 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); } }); };
0
此時,設置後臺,以便啓用查詢,並將問卷調查數據保存到數據庫,但咱們須要在 Angular 中作一些更改,以便讓它知道如何與數據庫進行通訊。使用 Angular 服務很容易完成這項任務,它將與服務器端進行通訊的過程包裝到了簡單的函數調用中:
123456 angular.module('pollServices', ['ngResource']). factory('Poll', function($resource) { return $resource('polls/:pollId', {}, { query: { method: 'GET', params: { pollId: 'polls' }, isArray: true } }) });
在建立的這一文件以後,您須要將它包括在您的 index.jade 模板中。在文件標頭部分的最後一個腳本元素的下面添加如下這行代碼:script(src='/javascripts/services.js')
。
您還須要告訴您的 Angular 應用程序使用這個服務模塊。要實現這一點,請打開 public/javascripts/app.js 並將第一行更改成可讀,以下所示:angular.module('polls', ['pollServices'])
。
最後,更改 Angular 控制器,以便使用該服務在數據庫中進行查詢和存儲問卷調查數據。在 public/javascripts/controllers.js 文件中,將 PollListCtrl
更改以下:。
1234 function PollListCtrl($scope, Poll) { $scope.polls = Poll.query(); } ...
更新 PollItemCtrl
函數,以便根據問卷調查的 ID 來查詢某個問卷調查:
123456 ... function PollItemCtrl($scope, $routeParams, Poll) { $scope.poll = Poll.get({pollId: $routeParams.pollId}); $scope.vote = function() {}; } ...
相似地,更改 PollNewCtrl
函數,以便在提交表單時將新調查數據發送到服務器。
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'); } }; }
0
您已經離成功不遠了!此時,應用程序應該容許用戶查看和搜索問卷調查數據、建立新的問卷調查並查看單個問卷調查的投票選項。在運行該應用程序以前,請確保您已經本地運行 MongoDB。這一般和打開一個終端或命令提示符以及運行 mongod
命令同樣簡單。確保在您運行應用程序時終端窗口處於打開狀態:
在運行應用程序以後,在您的瀏覽器中導航到 http://localhost:3000 並建立一些問卷調查。若是您單擊一個問卷調查,您就可以看到可用的選項,可是,您沒法實際對該問卷調查進行投票,或者暫時看不到問卷調查結果。咱們將在下一步和最後一步中對此進行介紹。
0
Web Sockets 容許服務器端直接與客戶端通訊以及向客戶端發送消息。
剩下的唯一須要構建的特性就是投票功能。該應用程序容許用戶進行投票,在他們投票後,會在全部已鏈接的客戶端上實時更新投票結果。使用 socket.io 模塊很容易完成這項工做,如今就讓咱們來實現它吧。
打開您應用程序的根目錄中的 package.json 文件,將下列代碼添加到依賴關係部分:"socket.io": "~0.9.16"
。
保存文件,在 Package Explorer 中右鍵單擊,而後選擇 Run As > npm install 來安裝 npm 模塊。
接下來,打開應用程序根目錄中的 app.js 文件, 刪除位於文件末尾的 server.listen...
代碼塊,將其替換爲:
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')); });
接下來,修改 index.jade 模塊以包含 socket.io 客戶端庫。在運行該應用程序時,該庫會自動在指定的位置上變得可用,所以不須要擔憂本身如何尋找該文件。確保想包含模板中的 angular-resource 庫的行的後面包含此文件:script(src='/socket.io/socket.io.js')
。
最後,您須要建立投票功能,以便在用戶向 socket.io 發送消息時保存新的投票,並在具備更新結果時將消息發送給全部客戶端。將 添加到路由目錄中的 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); }); }); }); };
注意:若是您想知道爲何應用程序會在常規 API 地址屬性的前面查找標頭 'x-forwarded-for'
,由於這將確保當應用程序被部署到負載平衡環境中時,所使用的是正確的客戶端 IP。若是您將該應用程序部署到 Bluemix 或 Cloud Foundry,這對於應用程序是否能正常工做相當重要。
0
Web Sockets 的後端功能現已建立完畢。目前剩下要作的工做是綁定前端,以發送和監聽套接字事件。最佳方法是添加一個新的 Angular 服務。使用如下代碼替換 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
控制器,以便它可以監聽和發送用於投票的 Web Socket 消息。將原始控制器替換爲:
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'); } }; } ...
0
問卷調查應用程序現已建立完成。確保 mongod 仍在運行,並在 Eclipse 中再次運行 Node 應用程序。在瀏覽器中輸入 http://localhost:3000,導航到一個問卷調查並進行投票。隨後您就能夠看到結果。要查看實時更新,請找到您的本地 IP 地址,並用該地址替換 localhost
。在您的局域網中,使用不一樣的機器(甚至智能手機或平板電腦也能夠)導航到這個地址。當您在另外一個設備上進行投票時,結果會顯示在該設備上,並且會自動發佈到您的主要計算機瀏覽器上:
0
您剛纔建立的這個問卷調查應用程序是一個不錯的起點,但還有很大的改進空間。在計劃建立這類應用程序時,我喜歡使用一種敏捷方法來定義用戶案例,並將項目劃分爲幾塊來實現。對於這個項目,我使用了 JazzHub,經過將項目的附屬代碼和源代碼一塊兒保存在一個雲託管的存儲庫中,JazzHub 使得開發變得很是簡單。
若是您對您的應用程序感到很滿意,下一步就是跟全世界的人分享它。在過去,即便部署一個很是簡單的應用程序,可能也會是一場噩夢,但值得慶幸的是,那些日子已經一去不復返了。使用 IBM 新興的兼容 Cloud Foundry 的 Bluemix 平臺,您只需幾分鐘就能夠經過最少的配置將您的應用程序部署到雲中,一點都不麻煩。
0
這對於開發人員,如今是一個很好的時機。咱們手頭有大量框架和工具,它們使得開發大量應用程序不只更簡單、更快速,並且更加使人感到愉快。在本文中,您學習瞭如何使用被稱爲 MEAN 體系結構(Mongo、Express、Angular 和Node)的技術構建一個應用程序。該堆棧可能只須要一天時間就能夠完成任務,遠遠超過了 LAMP 體系結構(Linux、Apache、MySQL 和 PHP),在 Web 應用程序開發和部署方面,該體系結構也許一樣會超越 LAMP 體系結構。對我而言,我已經火燒眉毛躍躍欲試了。