摘要:鉴于此目的,我决定快速构建一个用于此目的的问卷调查应用程序。这将启动一个服务器并将应用程序部署到该服务器。图应用程序配置基础前端这个问卷调查应用程序对常见用户界面和布局使用了框架。
全栈教程。转自 使用 Node.js、Express、AngularJS 和 MongoDB 构建一个实时问卷调查应用程序
最近,在向大学生们介绍 HTML5 的时候,我想要对他们进行问卷调查,并向他们显示实时更新的投票结果。鉴于此目的,我决定快速构建一个用于此目的的问卷调查应用程序。我想要一个简单的架构,不需要太多不同的语言和框架。因此,我决定对所有一切都使用 JavaScript — 对服务器端使用 Node.js 和 Express,对数据库使用 MongoDB,对前端用户界面使用 AngularJS。
这个 MEAN 堆栈(Mongo、Express、Angular 和 Node)只需要一天即可完成,远比 Web 应用程序开发和部署所用的 LAMP 堆栈(Linux、Apache、MySQL 和 PHP)简单得多。
运行该应用程序
在 JazzHub 上获取源代码
我选择使用 JazzHub 来管理我的项目的源代码。它不仅为我的代码提供了一个完整的版本控制系统,还为在云中编辑代码提供了一个在线 IDE,以及用于项目管理的敏捷特性。JazzHub 很容易与 Eclipse 相集成,Eclipse 还提供了一些插件,支持对平台( 比如 Bluemix 或 Cloud Foundry)的一键式部署。
构建该应用程序的先决条件基本了解 Node.js 和 Node.js 开发环境
具有以下这些 Node.js 模块:Express framework、Jade、Mongoose 和 socket.io
AngularJS JavaScript 框架
MongoDB NoSQL 数据库
Eclipse IDE,已安装了 Nodeclipse 插件
第 1 步. 构建一个基础 Express 后台在 Eclipse 中,切换到 Node 透视图,并创建一个新的 Node Express 项目。如果您创建了一个 JazzHub 项目,请像我所做的那样,使用相同的名称为您的 Node Express 项目命名。选择使用 Jade 作为模板引擎。Eclipse 会自动下载所需的 npm 模块,以便创建一个简单 Express 应用程序。
运行这个 Express 应用程序在 Project Explorer 中,找到位于您项目的根目录中的 app.js,右键单击并选择 Run As > Node Application。这将启动一个 Web 服务器并将应用程序部署到该服务器。
接下来,打开浏览器并导航到 http://localhost:3000。
这个问卷调查应用程序对常见用户界面和布局使用了 Bootstrap 框架。现在,让我们对 Express 应用程序做一些改动来反映这一点。首先,打开 routes/index.js,将标题属性更改为 Polls:
exports.index = function(req, res){ res.render("index", { title: "Polls" }); };
接着,更改 views/index.jade 模板以包含 Bootstrap。Jade 是一种速记模板语言,可编译成 HTML。它使用缩进消除了对结束标签的需求,极大地缩小了模板的大小。您只需要使用 Jade 作为主页面布局即可。在下一步中,还可以使用 Angular 局部模板向这个页面添加功能。
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 将会报错。
第 2 步. 使用 AngularJS 提供前端用户体验如果要使用 Angular,首先需要在您的 HTML 页面中包含它,还需要在 HTML 页面中添加一些指令。在 views/index.jade 模板中,对 html 元素进行如下更改:
html(lang="en", ng-app="polls")。
在该文件的标头中添加以下脚本元素:
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 模块Angular 中令人影响较为深刻的特性就是数据绑定,在后台模型发生改变时,该功能会自动更新您的视图。这极大地减少了需要编写的 JavaScript 的数量,因为它对凌乱的 DOM 操作任务进行了抽象。
在默认情况下,Express 发布了静态资源,比如 JavaScript 源文件、CSS 样式表以及位于您项目的公共目录中的图像。在公共目录中,创建一个名为 javascripts 的新的子目录。在这个子目录中,创建一个名为 app.js 的文件。该文件将包含用于应用程序的 Angular 模块,而且还定义了用于用户界面的路由和模板:
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 控制器定义了应用程序的范围,为要绑定的视图提供数据和方法。
// 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() {}; }创建局部 HTML 和模板
为了呈现来自控制器的数据,Angular 使用了局部 HTML 模板,该模板允许您使用占位符和表达式来包含数据和执行操作,比如条件和迭代操作。在公共目录中,创建一个名为 partials 的新的子目录。我们将为我们的应用程序创建 3 个局部模板,第一个局部模板将会展示可用投票的列表,我们将使用 Angular 通过一个搜索字段轻松过滤该列表。
Poll List
- No polls in database. Would you like to create one?
第二个局部模板允许用户查看投票。它使用 Angular 切换指令来确定用户是否已投票,并根据这些判断,显示一个就此次问卷调查进行投票的表格,或者一个包含显示问卷调查结果的图表。
View Poll
Question
{{poll.question}}Please select one of the following options.
{{choice.text}}
{{choice.votes.length}} {{poll.totalVotes}} votes counted so far. You voted for {{poll.userChoice.text}} .
第三个也是最后一个局部模板定义了支持用户创建新的问卷调查的表单。它要求用户输入一个问题和三个选项。提供一个按钮,以便允许用户添加额外的选项。稍后,我们将验证用户至少输入了两个选项 — 因为如果没有几个选项,就不能称之为问卷调查。
Create New Poll
最后,为了显示结果,我们需要向 style.css 添加一些 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; }
此时,如果您运行该应用程序,就会看到一个空的问卷调查列表。如果您试着创建一个新的问卷调查,就能看到此表单并添加更多的选项,但您不能保存该问卷调查。我们将在下一步中详细介绍所有这些内容。
第 3 步. 使用 Mongoose 在 MongoDB 中存储数据为了存储数据,该应用程序使用了 MongoDB 驱动程序和 Mongoose npm 模块。它们允许应用程序与 MongoDB 数据库进行通信。要获得这些模块,请打该应用程序根目录中的 package.json 文件,并在依赖关系部分中添加以下这些代码行:。
"mongodb": ">= 1.3.19", "mongoose": ">= 3.8.0",
保存文件,在 Project Explorer 中右键单击并选择 Run As > npm install。这将安装 npm 模块和其他所有依赖关系。
创建一个 Mongoose 模型在您应用程序的名为 models 的根目录中创建一个新的子目录,并在这个子目录中创建一个名为 Poll.js 的新文件。在这个文件中,我们定义了我们的 Mongoose 模型,该模型将用于查询数据,并以结构化数据的形式将这些数据保存到 MongoDB 。
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] });定义数据存储的 API 路由
接下来,在您应用程序的根目录下的 app.js 文件中设置一些路由,以便创建一些 JSON 端点,这些端点可用于根据 Angular 客户端代码来查询和更新 MongoDB。找到 app.get("/", routes.index) 行,并将下列代码添加到这一行的后面:
:
app.get("/polls/polls", routes.list); app.get("/polls/:id", routes.poll); app.post("/polls", routes.create);
现在,您需要实现这些功能。将 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); } }); };使用 Angular 服务将数据绑定到前端
此时,设置后台,以便启用查询,并将问卷调查数据保存到数据库,但我们需要在 Angular 中做一些更改,以便让它知道如何与数据库进行通信。使用 Angular 服务很容易完成这项任务,它将与服务器端进行通信的过程包装到了简单的函数调用中:
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 更改如下:。
function PollListCtrl($scope, Poll) { $scope.polls = Poll.query(); } ...
更新 PollItemCtrl 函数,以便根据问卷调查的 ID 来查询某个问卷调查:
... function PollItemCtrl($scope, $routeParams, Poll) { $scope.poll = Poll.get({pollId: $routeParams.pollId}); $scope.vote = function() {}; } ...
类似地,更改 PollNewCtrl 函数,以便在提交表单时将新调查数据发送到服务器。
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"); } }; }运行应用程序
您已经离成功不远了!此时,应用程序应该允许用户查看和搜索问卷调查数据、创建新的问卷调查并查看单个问卷调查的投票选项。在运行该应用程序之前,请确保您已经本地运行 MongoDB。这通常和打开一个终端或命令提示符以及运行 mongod 命令一样简单。确保在您运行应用程序时终端窗口处于打开状态:
在运行应用程序之后,在您的浏览器中导航到 http://localhost:3000 并创建一些问卷调查。如果您单击一个问卷调查,您就能够看到可用的选项,但是,您无法实际对该问卷调查进行投票,或者暂时看不到问卷调查结果。我们将在下一步和最后一步中对此进行介绍。
第 4 步. 使用 Socket.io 进行实时投票Web Sockets 允许服务器端直接与客户端通信以及向客户端发送消息。
剩下的惟一需要构建的特性就是投票功能。该应用程序允许用户进行投票,在他们投票后,会在所有已连接的客户端上实时更新投票结果。使用 socket.io 模块很容易完成这项工作,现在就让我们来实现它吧。
打开您应用程序的根目录中的 package.json 文件,将下列代码添加到依赖关系部分:
"socket.io": "~0.9.16"。
保存文件,在 Package Explorer 中右键单击,然后选择 Run As
> npm install 来安装 npm 模块。
接下来,打开应用程序根目录中的 app.js 文件, 删除位于文件末尾的 server.listen... 代码块,将其替换为:
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 文件的结尾处:
// 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,这对于应用程序是否能正常工作至关重要。
添加一个 Angular 服务将数据发送到 Web 套接字。Web Sockets 的后端功能现已创建完毕。目前剩下要做的工作是绑定前端,以发送和监听套接字事件。最佳方法是添加一个新的 Angular 服务。使用以下代码替换 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 控制器,以便它能够监听和发送用于投票的 Web Socket 消息。将原始控制器替换为:
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"); } }; }查看最终产品
问卷调查应用程序现已创建完成。确保 mongod 仍在运行,并在 Eclipse 中再次运行 Node 应用程序。在浏览器中输入 http://localhost:3000,导航到一个问卷调查并进行投票。随后您就可以看到结果。要查看实时更新,请找到您的本地 IP 地址,并用该地址替换 localhost。在您的局域网中,使用不同的机器(甚至智能手机或平板电脑也可以)导航到这个地址。当您在另一个设备上进行投票时,结果会显示在该设备上,而且会自动发布到您的主要计算机浏览器上:
您刚才创建的这个问卷调查应用程序是一个不错的起点,但还有很大的改进空间。在计划创建这类应用程序时,我喜欢使用一种敏捷方法来定义用户案例,并将项目划分为几块来实现。对于这个项目,我使用了 JazzHub,通过将项目的附属代码和源代码一起保存在一个云托管的存储库中,JazzHub 使得开发变得非常简单。
如果您对您的应用程序感到很满意,下一步就是跟全世界的人分享它。在过去,即使部署一个非常简单的应用程序,可能也会是一场噩梦,但值得庆幸的是,那些日子已经一去不复返了。使用 IBM 新兴的兼容 Cloud Foundry 的 Bluemix 平台,您只需几分钟就可以通过最少的配置将您的应用程序部署到云中,一点都不麻烦。
结束语这对于开发人员,现在是一个很好的时机。我们手头有大量框架和工具,它们使得开发大量应用程序不仅更简单、更快速,而且更加令人感到愉快。在本文中,您学习了如何使用被称为 MEAN 体系结构(Mongo、Express、Angular 和Node)的技术构建一个应用程序。该堆栈可能只需要一天时间就可以完成任务,远远超过了 LAMP 体系结构(Linux、Apache、MySQL 和 PHP),在 Web 应用程序开发和部署方面,该体系结构也许同样会超越 LAMP 体系结构。对我而言,我已经迫不及待跃跃欲试了。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/18745.html
摘要:自年发布以来,走过了漫长的道路。一下子,工程师认为自己不只是前端开发者了。这种趋势被称为全栈的或纯的解决方案。可以认为它是文档结构的数据库,而不是由行列表组成的数据库。也是高度可测试的,这是很重要的。 JavaScript自1995年发布以来,走过了漫长的道路。已经有了几个主要版本的ECMAScript规范,单页Web应用程序也慢慢兴起,还有支持客户端的JavaScript框架。作为一...
摘要:一个标准性的事件就是年的横空出世。引擎快速处理能力和异步编程风格,让开发者从多线程中解脱了出来。其次,通过异步编程范式将其高并发的能力发挥的淋漓尽致。它也仅仅是一个处理请求并作出响应的函数,并无任何特殊之处。 showImg(https://segmentfault.com/img/remote/1460000010819116); 在正式学习 Express 内容之前,我们有必要从大...
摘要:年新星调查中显示,越来越流行,其热度已经逐渐超过了。及其框架位于全球最受欢迎使用最广泛的技术榜榜首。本文转载自框架的游戏年流行趋势英文原文JavaScript 生态系统复杂多变,各种框架让人眼花缭乱。究竟孰优孰劣,如今的发展趋势是怎样的,用人单位又需要怎样的人才?本文站在一个中立者的角度,客观分析了当前这场框架的游戏中,JavaScript 的流行趋势。 Javascript 的生态环境让我...
摘要:我所在的美团酒店事业部去年月份成立,新的业务新的开发团队,这一切使得我们的前后端分离推进的很彻底。日志监控平台日志监控平台是美团内部的一个日志收集系统,目前美团统一使用收集日志,具有接收格式日志的能力,而日志监控平台也是以格式日志来收集。 转自:美团技术团队 作者:美团技术团队 分享理由:很好的分享,可见,基于Node的前后端分离的架构是越显流行和重要,前端攻城狮们,No...
摘要:选择是因为它简单,适合高并发的服务,而且我们的开发人员能够熟练使用它,关于的优缺点我在知乎上也曾经回答过使用的优势和劣势都有哪些。 其实早就该写这篇博客了,一直说忙于工作没有时间,其实时间挤挤总会有的,可能就是因为懒吧!从2013年11月一直拖到现在,其实我是不怎么擅长写技术博客的,因为上学的时候语文不是很好,每次写作文都不知道自己在写啥,作为一开始就参与 Worktile 开发的技术...
阅读 1122·2021-11-24 09:39
阅读 3622·2021-09-02 15:21
阅读 2160·2021-08-24 10:01
阅读 721·2021-08-19 10:55
阅读 2446·2019-08-30 15:55
阅读 1211·2019-08-30 14:16
阅读 2991·2019-08-29 15:17
阅读 3234·2019-08-29 13:53