[译] 第十五天:Meteor - 用Meteor的Scratch开发Web App
前言
目前为止,这个系列我们讨论了Bower, AngularJS, GruntJS和PhoneGap JavaScript技术。今天,决定回到JavaScript上学习一个叫Meteor的框架。虽然Meteor有很好的介绍文档,不过没有入门指导,我更倾向入门指导因为可以让我们快速开始一项技术。这篇博客,我们来学习怎样用Meteor框架开发一个投票程序。
Meteor是什么?
Meteor是下一代开源平台,用于在最短时间内开发实时Web Apps. 它有一套和现存JavaScript框架如AngularJS, BackboneJS等非常不同的体系。一般的,当我们用backbone或者anular,客户端(Angular或者Backbone)与REST后端通信,我们可以用Java, NodeJS, PHP等写REST后端。
Meteor里, DDP(Distributed Data Protocol)协议用来传输客户端和服务器间的数据。DDP是用来解决客户端JavaScipt开发者面对的最大问题的一种标准方法:查询服务器端的数据库,然后发送结果给客户端,再把数据库的所有更新推送到客户端。
服务器端的Meteor程序基于Node和MongoDB, 客户端和服务器端的程序都用Meteor API开发,以后,开发者可以有除了MongoDB之外的其他数据库选择。
为什么学习Meteor?
如果你想说服自己为什么要学Meteor, 看看这七条核心准则。
程序用例
这篇博客,我们来开发一个投票程序,允许用户对问题发表和投票,程序可以做以下事:
- 当用户到程序的'/' url,他可以看到问题列表。用户用Twitter登录后可以提新问题或者对已有问题投票,如下图所示,我们可以看到Yes和No按钮是不能使用的,因为用户没有登录。
- 用户点击Sign in with Twitter,他就有权限使用投票程序,授权后,用户可以新加投票问题或者对现有问题投票。
- 最后,用户可以新增问题或者投票现有问题。
Github仓库
今天的demo放在github: day15-epoll-meteor-demo.
安装Meteor
Meteor很容易上手,如果你用的Mac或者Linux,打开终端输入以下命令。
$ curl https://install.meteor.com | /bin/sh
Windows用户,请参照文档。
创建Meteor程序
创建Meteor程序很简单,装好Meteor后,只需运行创建命令。
$ meteor create epoll
它会在你机器上创建一个epoll的目录,再放一些模板文件,项目结构如图。
我们来一个个看看这些文件:
- .meteor文件夹用来存放 meteor指定文件,它还包含了一个.gitignore文件夹用来忽视本地文件夹,本地文件夹存放MongoDB数据库文件和程序编译文件。.meteor下的packages文件指定了程序所用的所有包,你可以想象成npm包,Meteor提供一些功能如包,我们在后面会用到几个包。release文件提供meteor版本,这里,我们用的最新版本0.6.6.3.
- Epoll.css用来指定程序的CSS样式。
- Epoll.html是程序的HTML语言,Meteor框架当前使用handlebars最为默认模板引擎,根据文档介绍,meteor以后可能也会支持其他模板引擎。
- Epoll.js是meteor程序的核心,epoll.js JavaScript文件在服务器和客户端都有部署,这使得开发者只需对功能编程一次就可以在服务器和客户端都运行。meteor创建的epoll.js模板如下:
if (Meteor.isClient) { Template.hello.greeting = function () { return "Welcome to epoll."; }; Template.hello.events({ 'click input' : function () { // template data, if any, is available in 'this' if (typeof console !== 'undefined') console.log("You pressed the button"); } }); } if (Meteor.isServer) { Meteor.startup(function () { // code to run on server at startup }); }
以上代码中,Meteor.isServer和Meteor.isClient标签用来区分服务器代码和客户端代码。
要运行程序,文件目录改到epoll输入以下命令:
$ cd epoll
$ meteor
程序运行在http://localhost:3000. 点击click按钮,我们可以从谷歌开发工具中看到消息You pressed the button.
在epoll.js里对greeting做以下改动:
Template.hello.greeting = function () { return "The Missing Meteor Tutorial!!!"; };
改动会自动更新然后加载页面。
MongoDB在哪?
之前提到过,Meteor用MongoDB存储数据,当我们装Meteor包时,它会自动下载最新MongoDB,可以看到MongoDB安装在<user.home>/.meteor目录下,我花了些时间分析MongoDB在哪运行,用ps -ef命令找到了安装路径。
$ ps -ef|grep mongo 501 1704 1687 0 2:22PM ttys001 0:09.28 /Users/shekhargulati/.meteor/tools/0b2f28e18b/mongodb/bin/mongod --bind_ip 127.0.0.1 --smallfiles --nohttpinterface --port 3002 --dbpath /Users/shekhargulati/day15/epoll/.meteor/local/db
在我机器上,MongoDB运行在端口3002, 这避免了已有的MongoDB的默认端口27017, 数据库目录指向程序目录的.meteor文件夹。
Meteor智能包
前面提到Meteor以包的形式实现功能,这些包在浏览器和服务器上都能工作,要查看Meteor支持的所有包,运行以下命令:
$ meteor list
添加包用Meteor add移除用meteor remove.
添加Twittter Bootstrap包
我们用Twitter Bootstrap显示页面格式,要添加Bootstrap,输入以下命令。
$ meteor add bootstrap
对Twitter Bootrap包仅有的提示是这个不是最新版本,Meteor包支持的版本是2.3.2.
添加Twitter Authentication包
在这个投票应用中我们用Twitter Authentication来确保功能,一个用户要先被Twitter授权,才能投票或者新增提问。
Meteor提供accounts-ui包来给程序添加登陆插件,要添加这个包,运行以下命令:
$ meteor add accounts-ui
现在添加授权方给程序,这个程序我们用的是Twitter,不过也用facebook, github, google, webo或者meetup.
$ meteor add accounts-twitter
添加这个包后,需要更新epoll.html来显示Twitter登陆按钮,更新如下:
<head> <title>Epoll : Share your opinion online, anywhere, anytime</title> </head> <body> <div class="navbar navbar-static-top navbar-inverse"> <div class="navbar-inner"> <div class="container"> <a class="brand" href="/">Epoll</a> <ul class="nav pull-right"> <li> {{loginButtons}} </li> </ul> </div> </div> </div> <div class="container" id="main"> {{> banner}} </div> </body> <template name="banner"> <div class="container"> <div class="row"> <div class="span6"> <div class="well"> <h4>Sign in using Twitter to submit new questions or to vote on existing questions.</h4> {{loginButtons}} </div> </div> </div> </div> </template>
同时在epoll.css添加格式。
/* CSS declarations go here */
.login-display-name{color: white }
.login-button{background-color: white}
#main {
padding-top:20px;
}
程序会自动更新,你可以看到如图页面:
点击Configure Twitter Login, 会被要求输入登录twitter的用户名和密码。
要得到配置信息,需要新建twitter程序然后保存配置,保存之后,就可以用twitter登录。
授权后自己的账号后,可以登陆程序,完成后可以退出。
新增用户会在MongoDB的用户集合里创建,要查看用户,用mongo客户端连接MongoDB数据库。
$ ~/.meteor/tools/0b2f28e18b/mongodb/bin/mongo --port 3002 MongoDB shell version: 2.4.6 connecting to: 127.0.0.1:3002/test > show dbs local 0.03125GB meteor 0.0625GB > use meteor switched to db meteor > show collections meteor_accounts_loginServiceConfiguration system.indexes users > db.meteor_accounts_loginServiceConfiguration.find() { "service" : "twitter", "consumerKey" : "xxx", "secret" : "xxx", "_id" : "xxx" } > > > db.users.find().pretty() { "createdAt" : ISODate("2013-11-11T18:03:23.488Z"), "_id" : "xx", "services" : { "twitter" : { "id" : "66993334", "screenName" : "shekhargulati", "accessToken" : "xxx-xxx", "accessTokenSecret" : "xxx", "profile_image_url" : "http://pbs.twimg.com/profile_images/378800000254412405/e4adcf8fb7800c3e5f8141c561cb57e4_normal.jpeg", "profile_image_url_https" : "https://pbs.twimg.com/profile_images/378800000254412405/e4adcf8fb7800c3e5f8141c561cb57e4_normal.jpeg", "lang" : "en" }, "resume" : { "loginTokens" : [ { "token" : "xxx", "when" : ISODate("2013-11-11T18:03:23.489Z") } ] } }, "profile" : { "name" : "Shekhar Gulati" } } >
定义程序布局
Meteor创建的模板没有遵循定义给Meteor程序布局的最好体验,epoll.js对客户端和服务器共享,任何人都可以通过浏览器开发工具查看epoll.js.
很多时候我们不想把客户端和服务器端之间的所有东西都共享,比如如果我们有些特定的服务器端的代码,我们不想Meteor发送给客户端。用Meteor,我们可以用client和server目录区分客户端和服务器端,在epoll文件夹下创建client和server目录。
$ cd epoll
$ mkdir client server
现在在client下创建epollclient.js, server下创建epollserver.js.
$ touch client/epollclient.js $ touch server/epollserver.js
把客户端代码从epoll.js移到client/epollclient.js.
Template.hello.greeting = function () { return "The Missing Meteor Tutorial!!!"; }; Template.hello.events({ 'click input' : function () { // template data, if any, is available in 'this' if (typeof console !== 'undefined') console.log("You pressed the button"); } });
同样,移动服务器端代码到server/epollserver.js
Meteor.startup(function () { // code to run on server at startup });
再删除epoll.js文件。
$ rm -f epoll.js
移除不安全的包
所有的Meteor程序都安装了一个特殊的包insecure, 这个包给客户端权限去数据库操作,这应该从程序里移除,Meteor文档同样也建议移除它。
默认的,一个新meteor程序包含自动推送和不安全的包,他们共同使得客户端对服务器端的数据库有所有读/写权限。他们是很有用的原型工具,但显然对产品应用不合适。
要移除不安全的包,输入以下命令。
$ meteor remove insecure
添加问题
现在我们来添加给登录用户提交新问题的功能。
<head> <title>Epoll : Share your opinion online, anywhere, anytime</title> </head> <body> <div class="navbar navbar-static-top navbar-inverse"> <div class="navbar-inner"> <div class="container"> <a class="brand" href="/">Epoll</a> <ul class="nav pull-right"> <li> {{loginButtons}} </li> </ul> </div> </div> </div> <div class="container" id="main"> {{#if currentUser}} {{> addquestion}} {{/if}} {{#unless currentUser}} {{> banner}} {{/unless}} </div> </body> <template name="banner"> <div class="container"> <div class="row"> <div class="span6"> <div class="well"> <h4>Sign in using Twitter to submit new questions or to vote on existing questions.</h4> {{loginButtons}} </div> </div> </div> </div> </template> <template name="addquestion"> <textarea rows="3" class="input-xxlarge" name="questionText" id="questionText" placeholder="Add Your Question"></textarea> <br/> <input type="button" class="btn-info add-question" value="Add Question"/> </template>
以上html只有在用户已经登录到应用后才会加载addQuestion模板,如果退出应用,也不能看到新加问题的模块。
要实现这个功能需要更新客户端和服务器端代码。
在client/epollclient.js添加如下代码:
Template.addquestion.events({ 'click input.add-question' : function(event){ event.preventDefault(); var questionText = document.getElementById("questionText").value; Meteor.call("addQuestion",questionText,function(error , questionId){ console.log('added question with Id .. '+questionId); }); document.getElementById("questionText").value = ""; } });
以上代码:
- 首先绑定点击事件到'add-question'类的输入类型。
- 然后组织默认点击事件,从dom获得问题。
- 接下来调用Meteor服务端addQuestion方法,服务器会对数据的任意危险操作如增删改给出响应,客户端不会看到这些实现,也不能私自更改数据,服务器来做所有的操作。
现在来添加服务端代码,先定义个新的Questions集合接口,然后对他操作,Meteor用minimongo作为API接口,要查看minimongo支持的所有操作,查看Meteor.Collection文档。
Questions = new Meteor.Collection("questions"); Meteor.startup(function () { // code to run on server at startup }); Meteor.methods({ addQuestion : function(questionText){ console.log('Adding Question'); var questionId = Questions.insert({ 'questionText' : questionText, 'submittedOn': new Date(), 'submittedBy' : Meteor.userId() }); return questionId; } });
现在到程序用户界面,提交新问题。
也可以看MongoDB里的数据。
> db.questions.find().pretty() { "questionText" : "Is Sachin Tendulkar the greatest batsman of all time?", "submittedOn" : ISODate("2013-11-11T18:23:02.541Z"), "submittedBy" : "Jnu6oXoAZ2um57rZ8", "_id" : "nhqvgDcZqgZgLdDB7" }
列出所有问题
接下来要实现的功能是列出所有问题,没有登录应用的用户同样也应该看到所有的问题。
在主div下添加如下代码。
{{> questions}}
然后,添加问题模板到epoll.html.
<template name="questions"> <h2>All Questions</h2> {{#each items}} {{> question}} {{/each}} </template> <template name="question"> <div> <p class="lead"> {{questionText}} <a class="btn btn-small btn-success yes {{#unless currentUser}}disabled{{/unless}}" href="#"><i class="icon-thumbs-up"></i> Yes {{yes}}</a> <a class="btn btn-small btn-danger no {{#unless currentUser}}disabled{{/unless}}" href="#"><i class="icon-thumbs-down"></i> No {{no}}</a> </p> </div> </template>
以上代码很清晰,唯一需要提醒的是在问题模板里的unless控制结构的用法,它确保了如果用户没有登录,那就用disabled 类。
要得到所有的问题,需要用客户端的Questions集合接口来获取所有文档,在client/epollclient.js添加以下代码:
Questions = new Meteor.Collection("questions"); Template.questions.items = function(){ return Questions.find({},{sort:{'submittedOn':-1}}); };
实现投票
最后一个需要实现的功能是允许登录的用户投票,不需要对html做任何更改,因为都已经加到模板里了。
在client/epollclient.js添加以下代码:
Template.question.events({ 'click': function () { Session.set("selected_question", this._id); }, 'click a.yes' : function (event) { event.preventDefault(); if(Meteor.userId()){ var questionId = Session.get('selected_question'); console.log('updating yes count for questionId '+questionId); Meteor.call("incrementYesVotes",questionId); } }, 'click a.no': function(){ event.preventDefault(); if(Meteor.userId()){ var questionId = Session.get('selected_question'); console.log('updating no count for questionId '+questionId); Meteor.call("incrementNoVotes",questionId); } } });
以上代码:
- 绑定点击事件到问题模板,当我们点击任何问题时,它都会在会话里设置questionId, 会话(session)在客户端提供一个全局对象,使得你可以用来存储任意一对kay-value值。
- 用户点击'Yes'标签后,我们可以从会话里获得选择的questionId, 然后从服务器调用incrementYesVotes方法,同样也会用Meteor.userId()方法检查用户应该在投票计算前登录应用。
- 当用户点击'No'标签后,从服务端调用incrementNoVotes方法。
最后,在server/epollserver.js添加incrementYesVotes和incrementNoVotes, 用Meteor的collection更新功能来计算增加量。
incrementYesVotes : function(questionId){ console.log(questionId); Questions.update(questionId,{$inc : {'yes':1}}); }, incrementNoVotes : function(questionId){ console.log(questionId); Questions.update(questionId,{$inc : {'no':1}}); }
所以每次用户点击yes或者no时,count都会更新,你可以通过http://localhost:3000试试应用。
发布Meteor应用
有几种方式可以发布Meteor应用,可以发布到Meteor提供的测试服务器上,也可以到OpenShift上,要发布到OpenShift上,请参考Ryan的博客。
要发布到Meteor测试服务器上,运行以下命令:
$ meteor deploy epoll
这个应用运行在http://epoll.meteor.com/
这就是今天的内容,继续给反馈吧。
原文:https://www.openshift.com/blogs/day-15-meteor-building-a-web-app-from-scratch-in-meteor