Day 15:Meteor —— 從零開始建立一個 Web 應用

編者注:咱們發現了有趣的一系列文章《30天學習30種新技術》,正在翻譯中,一天一篇更新,年終禮包。下面是第15天的內容。css


到目前爲止咱們討論了BowerAngularJSGruntJSPhoneGap等JavaScript技術。今天是「30天學習30種新技術」挑戰的第15天,我決定重返JavaScript,學習Meteor框架。雖然Meteor的文檔至關好,可是它缺乏爲初學者準備的教程。我以爲教程的學習效果更好,由於教程能夠幫助你快速上手一種技術。本文將介紹如何利用 Meteor 框架構建一個epoll應用。
請輸入圖片描述html

Meteor是什麼?

Meteor是新一代的開發即時web應用的開源框架,它能幫助你在最少的時間內完成開發。它的理念和AngularJS、BackboneJS等框架大不相同。當咱們在backbone 和 angular 上工做時,客戶端(Angular或Backbone)和REST後端通信。咱們能夠用任何技術寫 REST 後端,例如 Java、NodeJS、PHP。前端

Meteor使用DDP(分佈式數據協議)在客戶端和服務器間傳送數據。客戶端JavaScript開發者須要解決的首要問題是:向後端的數據庫發起查詢,發送數據到客戶端,當數據庫變更時,推送變更到客戶端。DDP是解決這一問題的標準作法。git

Meteor應用的後端基於Node和MongoDB。前端和後端的應用同時使用Meteor的API。將來開發者能夠選擇 MongoDB 以外的其餘數據庫。github

爲何使用Meteor?

請閱讀Meteor的七大原則web

應用案例

本文中咱們將搭建一個 epoll 應用,該應用容許用戶發佈問題並投票。這個應用能夠作到:mongodb

  • 當用戶訪問/時,會看到一個問題列表。用戶須要經過Twitter登陸,以便投票或發佈新問題。以下圖所示,因爲未登陸,投票按鈕不可用。
    請輸入圖片描述chrome

  • 當用戶點擊Sign in with Twitter以後,他將受權 epoll 應用使用他的帳號。受權成功以後,用戶能夠投票或發佈新問題。
    請輸入圖片描述shell

GitHub倉庫

今天的示例應用的代碼能夠從GitHub取得。數據庫

安裝Meteor

開始使用Meteor很容易。若是你使用Mac或Linux,只需輸入以下命令:

curl https://install.meteor.com | /bin/sh

Windows用戶請參閱文檔

建立Meteor應用

建立Meteor應用很容易。安裝以後,運行create命令便可。

meteor create epoll

這將建立epoll目錄,該目錄下有一些模板文件。項目結構以下所示:
請輸入圖片描述

讓咱們解釋下這個結構:

  1. meteor文件夾下保存meteor特定的文件。.gitignore忽略存儲MongoDB數據庫文件和應用文件的local文件夾。packages指明本應用所需的包。你能夠把它們當作是npm包。Meteor以包的形式提供功能。本文中會使用一些包。release保存了meteor版本。本文使用的版本是0.6.6.3

  2. epoll.css決定應用的CSS樣式。

  3. epoll.html是應用的HTML標記。目前meteor只支持handlebars模板引擎,不過將來可能支持其餘模板引擎

  4. epoll.js是meteor應用的核心。epoll.js同時部署在服務器段和客戶端。這容許開發者一次編寫、兩端使用。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.isServerMeteor.isClient區分了服務器端和客戶端的代碼。

meteor命令能夠運行應用:

cd epoll
meteor

能夠經過 http://localhost:3000 訪問應用。點擊按鈕後,在chrome developer tools中你能夠看到You pressed the button.信息。
請輸入圖片描述

修改epoll.js的歡迎部分:

Template.hello.greeting = function () {
    return "The Missing Meteor Tutorial!!!";
  };

變更會自動應用,頁面也會自動刷新。
請輸入圖片描述

MongoDB在哪?

前面提到Meteor使用MongoDB來存儲數據。當咱們安裝meteor的時候,它同時會下載最新版的MongoDB。咱們能夠看到,MongoDB安裝在<user.home>/.meteor目錄。使用ps -ef能夠找到MongoDB的安裝位置。

; 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端口,以免和其餘默認運行於27017端口的MongoDB衝突。

智能的Meteor包管理

前面提到Meteor以包的形式實現功能。這些包在瀏覽器和服務器上都能使用。運行如下命令能夠得知Meteor支持的全部包:

meteor list

使用meteor addmeteor remove命令來添加刪除包。

添加Twitter Bootstrap包

咱們將使用Twitter Bootstrap做爲用戶界面的風格。

meteor add bootstrap

注意,Meteor包不必定是最新版。

添加Twitter受權包

在咱們的應用中,用戶須要首先經過Twitter受權才能投票或添加問題。Meteor提供了accounts-ui包,能夠爲咱們的應用添加登陸組件:

meteor add accounts-ui

而後咱們添加受權提供者。在這個應用中,咱們使用Twitter,不過咱們其實也可使用facebook、github、google、weibo或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登陸了。
請輸入圖片描述

受權以後咱們能夠登陸應用。使用完畢以後,咱們能夠登出。
請輸入圖片描述

MongoDB會在用戶集合內建立新用戶。咱們可使用mongo命令鏈接數據庫查看:

; ~/.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建立的模板應用有一個問題,客戶端和服務器段的epoll.js代碼是同樣的。任何人的均可以使用瀏覽器的開發工具查看epoll.js

若是咱們不想將服務器端的特有代碼發送到客戶端,咱們可使用clientserver目錄來分隔代碼。

cd epoll
mkdir client server

在兩個目錄下分別建立epollclient.jsepollserver.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`:

```sh
rm -f epoll.js

移除insecure

每個Meteor應用預裝了insecure包。這個應用讓用戶端能夠在數據庫上實施一切操做。對於原型開發這頗有用,可是一般不適合生產環境。

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>

僅當用戶登陸的時候纔會渲染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 = "";

    }
});

以上代碼中:

  1. 咱們首先將點擊input事件綁定到add-question類。

  2. 接着咱們阻止默認的點擊事件,從DOM中獲取問題文本。

  3. 而後咱們調用Meteor服務器的方法addQuestion。由服務器負責插入、更新、刪除數據等有風險的操做。客戶端看不到實現,也沒法私自修改數據。

如今咱們須要修改server/epollserver.js。咱們首先定義一個名爲Questions的新集合。而後咱們會操做這個集合。Meteor使用minimongo做爲API接口。參閱Meteor.Collection.documentation查看minimongo支持的全部操做。

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

問題列表

咱們接下來要實現的功能是問題列表。用戶不需登陸,就能夠看到全部問題的列表。

main div中加入:

{{> questions}}

而後添加問題模板:

<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 css。

爲了獲取全部問題,咱們須要在客戶端使用Question集合來獲取全部文本。在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);
      }
    }
 });

上面的代碼實現了:

  1. 綁定點擊事件到問題模板。點擊任意問題的時,在session中設置questionId。session提供了一個客戶端的全局對象,你能夠在裏面存儲任意的鍵值對。

  2. 當用戶點擊Yes按鈕時,咱們會從session中取得選中的questionId,而後在服務器端調用incrementYesVotes方法。咱們使用Meteor.userId()來確保用戶已經登陸了。

  3. 當用戶點擊No按鈕時,咱們在服務器端調用incrementNoVotes函數。

最後咱們在server/epollserver.js加入incrementYesVotesincrementNoVotes函數。咱們使用Meteor的集合更新功能來增長計數器。

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按鈕以後,計數器會更新。你能夠訪問 http://localhost:3000 試驗一番。

部署Meteor應用

部署Meteor應用有不少種方法。咱們能夠在Meteor提供的測試服務器上部署,也能夠部署到OpenShift。

若是你打算部署到OpenShift上,請參閱Ryan這篇博客

運行如下命令能夠部署到Meteor測試服務器:

meteor deploy epoll

應用能夠經過 http://epoll.meteor.com/ 訪問。

今天就是這些了。歡迎繼續反饋。


原文:Day 15: Meteor——Building a Web App From Scratch in Meteor
翻譯整理: Segmentfault

相關文章
相關標籤/搜索