[译] 第十五天:Meteor - 用Meteor的Scratch开发Web App

前言

目前为止,这个系列我们讨论了Bower, AngularJS, GruntJSPhoneGap 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程序基于NodeMongoDB, 客户端和服务器端的程序都用Meteor API开发,以后,开发者可以有除了MongoDB之外的其他数据库选择。 

为什么学习Meteor?

如果你想说服自己为什么要学Meteor, 看看这七条核心准则 

程序用例

这篇博客,我们来开发一个投票程序,允许用户对问题发表和投票,程序可以做以下事:

  1. 当用户到程序的'/' url,他可以看到问题列表。用户用Twitter登录后可以提新问题或者对已有问题投票,如下图所示,我们可以看到YesNo按钮是不能使用的,因为用户没有登录。

           

  1. 用户点击Sign in with Twitter,他就有权限使用投票程序,授权后,用户可以新加投票问题或者对现有问题投票。

           

  1. 最后,用户可以新增问题或者投票现有问题。

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的目录,再放一些模板文件,项目结构如图。

我们来一个个看看这些文件:

  1. .meteor文件夹用来存放 meteor指定文件,它还包含了一个.gitignore文件夹用来忽视本地文件夹,本地文件夹存放MongoDB数据库文件和程序编译文件。.meteor下的packages文件指定了程序所用的所有包,你可以想象成npm包,Meteor提供一些功能如包,我们在后面会用到几个包。release文件提供meteor版本,这里,我们用的最新版本0.6.6.3.
  2. Epoll.css用来指定程序的CSS样式。
  3. Epoll.html是程序的HTML语言,Meteor框架当前使用handlebars最为默认模板引擎,根据文档介绍,meteor以后可能也会支持其他模板引擎。
  4. Epoll.jsmeteor程序的核心,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
      });
    }
View Code

           以上代码中,Meteor.isServerMeteor.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!!!";
             };
View Code

改动会自动更新然后加载页面。

MongoDB在哪?

之前提到过,MeteorMongoDB存储数据,当我们装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
View Code

 

在我机器上,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>
View Code

同时在epoll.css添加格式。

/* CSS declarations go here */
.login-display-name{color: white }
.login-button{background-color: white}
 #main {
    padding-top:20px;
}
View Code

程序会自动更新,你可以看到如图页面:

点击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"
    }
}

> 
View Code

定义程序布局

Meteor创建的模板没有遵循定义给Meteor程序布局的最好体验,epoll.js对客户端和服务器共享,任何人都可以通过浏览器开发工具查看epoll.js. 

 

很多时候我们不想把客户端和服务器端之间的所有东西都共享,比如如果我们有些特定的服务器端的代码,我们不想Meteor发送给客户端。用Meteor,我们可以用clientserver目录区分客户端和服务器端,在epoll文件夹下创建clientserver目录。

$ 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");
    }
});
View Code

 

同样,移动服务器端代码到server/epollserver.js

Meteor.startup(function () {
    // code to run on server at startup
 });
View Code

 

再删除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>
View Code

 

以上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 = "";
 
    }
});
View Code

 

以上代码:

  1. 首先绑定点击事件到'add-question'类的输入类型。
  2. 然后组织默认点击事件,从dom获得问题。
  3. 接下来调用Meteor服务端addQuestion方法,服务器会对数据的任意危险操作如增删改给出响应,客户端不会看到这些实现,也不能私自更改数据,服务器来做所有的操作。 

 

现在来添加服务端代码,先定义个新的Questions集合接口,然后对他操作,Meteorminimongo作为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;
  }
});
View Code

 

现在到程序用户界面,提交新问题。

也可以看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"
}
View Code

列出所有问题

接下来要实现的功能是列出所有问题,没有登录应用的用户同样也应该看到所有的问题。 

 

在主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>
View Code

 

以上代码很清晰,唯一需要提醒的是在问题模板里的unless控制结构的用法,它确保了如果用户没有登录,那就用disabled 类。 

 

要得到所有的问题,需要用客户端的Questions集合接口来获取所有文档,在client/epollclient.js添加以下代码:

Questions = new Meteor.Collection("questions");
 
Template.questions.items = function(){
    return Questions.find({},{sort:{'submittedOn':-1}});
};
View Code

实现投票

最后一个需要实现的功能是允许登录的用户投票,不需要对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);
      }
    }
 });
View Code

 

以上代码:

  1. 绑定点击事件到问题模板,当我们点击任何问题时,它都会在会话里设置questionId, 会话(session)在客户端提供一个全局对象,使得你可以用来存储任意一对kay-value值。
  2. 用户点击'Yes'标签后,我们可以从会话里获得选择的questionId, 然后从服务器调用incrementYesVotes方法,同样也会用Meteor.userId()方法检查用户应该在投票计算前登录应用。
  3. 当用户点击'No'标签后,从服务端调用incrementNoVotes方法。 

 

最后,在server/epollserver.js添加incrementYesVotesincrementNoVotes, Meteorcollection更新功能来计算增加量。

incrementYesVotes : function(questionId){
    console.log(questionId);
    Questions.update(questionId,{$inc : {'yes':1}});
  },
 
incrementNoVotes : function(questionId){
    console.log(questionId);
    Questions.update(questionId,{$inc : {'no':1}});
}
View Code

 

所以每次用户点击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

posted on 2013-12-31 17:58  百花宫  阅读(1492)  评论(0编辑  收藏  举报