JavaScript Meteor 0.4.2を試してみた

2012.10.23

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

Meteorを使ったリアルタイムWebを体感してみる

特徴として

  1. インストールが簡単
  2. サーバ側、クライアント側コードをJavaScriptで記述することができ、言語が統一できる。
  3. リロードいらずで修正したコードが同期される
  4. Node.jsをベースにしている
  5. テンプレートエンジンにHandlebarsを採用

等々、特徴はたくさんあります。Node.jsをベースにしているので、リアルタイム性の高いアプリケーションにはとても向いてるのではと思いサンプルアプリであるleaderbordを参考に投票アプリを作ってみました。

使用した環境は

  • Ubuntu 12.04 LTS
  • Meteor 0.4.2

シンプルなアプリなので、ファイルの構成としてはsample.html,sample.js,sample.cssの3つのファイルで構成されています。

以下がsample.htmlのサンプルコードです

<head>
  <title>sample</title>
</head>

<body>
  {{> pollview}}
</body>

<template name="pollview">
  <h1>言語人気投票</h1>
  <h2 id="toc-">下の言語文字列を直接クリックでそのまま投票となります。</h2>
  <hr>
  <div class="bar-chart">
  {{#each LangKind}}
    <dl {{graph}}>
    <dt>
      <b>{{name}}</b>
    </dt>
    <dd>
      {{> LangKindDetail}}
    </dd>
  </dl>
  {{/each}}
  </div>  
</template>

<template name="LangKindDetail">
  <b>{{score}}</b>
</template>

body内でpollviewというテンプレートをロードしています。pollviewのテンプレートはtemplateタグで囲まれた部分(9行目〜25行目)でpollviewの中で更にLangKindDetailというテンプレートをロードしています。each文で14行目〜23行目をループさせて登録されている言語数分HTMLを書き出しています。

次にsample.jsのコードです。まだまだJS初心者ということもあり半ば強引に作っているので、もっとスマートな書き方はあると思いますが愛嬌ということで(ーー;

LangKind = new Meteor.Collection("LangKind");

if (Meteor.isClient) { Template.pollview.greeting = function () { return LangKind.find({}, {sort: {score: -1, name: 1}}); };

Template.pollview.events({ 'click' : function () { Session.set("selected_language", this._id); // template data, if any, is available in 'this' // 投票数のUpdate LangKind.update(Session.get("selected_language"), {$inc: {score: 1}}); // 投票数の割合に応じてclass名を再設定する var elem = document.getElementById(this._id);

var upd_count = 0; var upd_score_cnt_this = 0; //選択された言語の投票数を取得する LangKind.find(this._id).forEach(function (post) { upd_score_cnt_this = post.score; upd_count += 1; });

// 全件のレコード数を取得する if (upd_count == 1){ var upd_score_cnt = 0; LangKind.find().forEach(function (post) { upd_score_cnt = upd_score_cnt + post.score; }); }

upd_score_cnt_res = Math.floor((upd_score_cnt_this / upd_score_cnt)*100);

if (upd_score_cnt_this == 0 || upd_score_cnt == 0){ upd_score_cnt_res = 0; }else{ upd_score_cnt_res = Math.floor((upd_score_cnt_this / upd_score_cnt)*100); }

// ページロード時にidを強引に設定しているので、ここでgetElementByIdを使ってclass名を再設定する if ( upd_score_cnt_res >= 0 && upd_score_cnt_res < 10){ if ( document.getElementById(this._id) != null){ document.getElementById(this._id).className="ratio-1"; } } if ( upd_score_cnt_res >= 10 && upd_score_cnt_res < 20){ document.getElementById(this._id).className="ratio-1"; } if ( upd_score_cnt_res >= 20 && upd_score_cnt_res < 30){ document.getElementById(this._id).className="ratio-2"; } if ( upd_score_cnt_res >= 30 && upd_score_cnt_res < 40){ document.getElementById(this._id).className="ratio-3"; } if ( upd_score_cnt_res >= 40 && upd_score_cnt_res < 50){ document.getElementById(this._id).className="ratio-4"; } if ( upd_score_cnt_res >= 50 && upd_score_cnt_res < 60){ document.getElementById(this._id).className="ratio-5"; } if ( upd_score_cnt_res >= 60 && upd_score_cnt_res < 70){ document.getElementById(this._id).className="ratio-6"; } if ( upd_score_cnt_res >= 70 && upd_score_cnt_res < 80){ document.getElementById(this._id).className="ratio-7"; } if ( upd_score_cnt_res >= 80 && upd_score_cnt_res < 90){ document.getElementById(this._id).className="ratio-8"; } if ( upd_score_cnt_res >= 90){ document.getElementById(this._id).className="ratio-9"; } } });

Template.pollview.LangKind = function () { return LangKind.find({}, {sort: {score: -1, name: 1}}); };

Template.pollview.graph = function () { if (LangKind.find(this._id).count() != 0) { var count = 0; var score_cnt_this = 0; LangKind.find(this._id).forEach(function (post) { score_cnt_this = post.score; count += 1; });

if (count == 1){ var score_cnt = 0; LangKind.find().forEach(function (post) { score_cnt = score_cnt + post.score; }); } }

// 投票数の割合に応じてclass名を変え、idを強引に追加 if (score_cnt_this == 0 || score_cnt == 0){ score_cnt_res = 0; }else{ score_cnt_res = Math.floor((score_cnt_this / score_cnt)*100); } if ( score_cnt_res >= 0 && score_cnt_res < 10){ score_cnt = "class=ratio-1 id=" + this._id; } if ( score_cnt_res >= 10 && score_cnt_res < 20){ score_cnt = "class=ratio-1 id=" + this._id; } if ( score_cnt_res >= 20 && score_cnt_res < 30){ score_cnt = "class=ratio-2 id=" + this._id; } if ( score_cnt_res >= 30 && score_cnt_res < 40){ score_cnt = "class=ratio-3 id=" + this._id; } if ( score_cnt_res >= 40 && score_cnt_res < 50){ score_cnt = "class=ratio-4 id=" + this._id; } if ( score_cnt_res >= 50 && score_cnt_res < 60){ score_cnt = "class=ratio-5 id=" + this._id; } if ( score_cnt_res >= 60 && score_cnt_res < 70){ score_cnt = "class=ratio-6 id=" + this._id; } if ( score_cnt_res >= 70 && score_cnt_res < 80){ score_cnt = "class=ratio-7 id=" + this._id; } if ( score_cnt_res >= 80 && score_cnt_res < 90){ score_cnt = "class=ratio-8 id=" + this._id; } if ( score_cnt_res >= 90){ score_cnt = "class=ratio-9 id=" + this._id + '"'; } return score_cnt; }; Template.pollview.selected = function () { return Session.equals("selected_language", this._id) ? "selected" : ''; }; }

if (Meteor.isServer) { Meteor.startup(function () { // データが存在しない場合に初期データを登録する if (LangKind.find().count() === 0) { var names = ["HTML5/CSS3/javascript", "Android Native App.", "iPhone/iPad Native App.", "Pure javascript framework", "PHP (Zend Framework)", "Ruby (on Rails)", ".NET framework", "etc."]; for (var i = 0; i < names.length; i++) LangKind.insert({name: names[i], score: 0}); } }); } [/javascript]

9行目でclickイベントを取得して、クリックされた言語のidをセッションにセットし、13行目でその言語に対して+1してデータベースへアップデートをしています。あとは投票された数と母数を取得し、その割合に応じてclassを設定しています。とりあえず動くところまでできたので、デプロイしてみました。
deployもとても簡単で下記コマンドで完了です。(一応上書きされないようにパスワードを設定しました。)

$ meteor deploy polllang --password
New Password: *****
Confirm Password: *****
Deploying to polllang.meteor.com.  Bundling ... uploading ... done.
Now serving at polllang.meteor.com

このデプロイで一般公開まで出来てしまいます。すごいですね。。。あとはhttp://polllang.meteor.comにアクセスするとアプリが表示されます。

Webサイトに事前に集計したい質問項目を変えて置いておけばリアルタイムに集計できてお手軽なコンポーネントとして使えそうですね

で、これを作り終えた矢先にMeteor 0.5.0がリリースされたようです。興味本位でアップデートしてみました。

$ meteor update
New version available: 0.5.0
  downloading [=============================] 100%... finished download
Since this system includes sudo, Meteor will request root privileges to
install. You may be prompted for a password. If you prefer to not use
sudo, please re-run this command as root.
sudo dpkg -i /tmp/meteor-temp-16ph0f7/meteor_0.5.0-1_amd64.deb
[sudo] password for tsukuma: 

v0.5.0

* This release introduces Meteor Accounts, a full-featured auth system that supports
  - fine-grained user-based control over database reads and writes
  - federated login with any OAuth provider (with built-in support for
    Facebook, GitHub, Google, Twitter, and Weibo)
  - secure password login
  - email validation and password recovery
  - an optional set of UI widgets implementing standard login/signup/password
    change/logout flows

  When you upgrade to Meteor 0.5.0, existing apps will lose the ability to write
  to the database from the client. To restore this, either:
  - configure each of your collections with
    [`collection.allow`](http://docs.meteor.com/#allow) and
    [`collection.deny`](http://docs.meteor.com/#deny) calls to specify which
    users can perform which write operations, or
  - add the `insecure` smart package (which is included in new apps by default)
    to restore the old behavior where anyone can write to any collection which
    has not been configured with `allow` or `deny`

  For more information on Meteor Accounts, see:
     http://docs.meteor.com/#dataandsecurity
     http://docs.meteor.com/#accounts_api

* The new function `Meteor.autorun` allows you run any code in a reactive
  context. See http://docs.meteor.com/#meteor_autorun

* Arrays and objects can now be stored in the `Session`; mutating the value you
  retrieve with `Session.get` does not affect the value in the session.

* On the client, `Meteor.apply` takes a new `wait` option, which ensures that no
  further method calls are sent to the server until this method is finished; it
  is used for login and logout methods in order to keep the user ID
  well-defined. You can also specifiy an `onReconnect` handler which is run when
  re-establishing a connection; Meteor Accounts uses this to log back in on
  reconnect.

* Meteor now provides a compatible replacement for the DOM `localStorage`
  facility that works in IE7, in the `localstorage-polyfill` smart package.

* Meteor now packages the D3 library for manipulating documents based on data in
  a smart package called `d3`.

* `Meteor.Collection` now takes its optional `manager` argument (used to
  associate a collection with a server you've connected to with
  `Meteor.connect`) as a named option. (The old call syntax continues to work
  for now.)

* Fix a bug where trying to immediately resubscribe to a record set after
  unsubscribing could fail silently.

* Better error handling for failed Mongo writes from inside methods; previously,
  errors here could cause clients to stop processing data from the server.


Patches contributed by GitHub users bradens, dandv, dybskiy, possibilities,
zhangcheng, and 75lb.

Upgrade complete.

何やら気になる記述が。。。DB書き込みのアプリの場合、どうやらクライアントからのアクセスが出来なくなるっぽい。。。もしかして、、、アップデート後にローカルでこのアプリを動かしてみるとやはり動かない。アップデートするときは事前にリリースノートを読んでからやりましょうw

という事で時間があったらMeteor 0.5.0上で動作させるようにしたいと思います。