2013/12/30

テンプレートエンジンjadeのmixinを使ってDRYを促進する。

jadeとは?

express.jsの標準テンプレートエンジンとして採用されているのが jade 。Rails上のerbとかもそうですが、うっかりすると同じようなテンプレートをいちいち繰り返して書いていることに気づいて、悲しい気持ちになるのだけど、jadeのmixinという機能を使うことで、全てではないけど悲しい事態がある程度解消できた、という話です。

mixin

リファレンスには、

Mixins allow you to create reusable blocks of jade.
と書いてあって、つまり、再利用可能なjadeブロックを作ることができる、ということ。

mixinを定義する

mixinを定義するには、


mixin name1
  block

mixin name2(args)
  block

のように、mixinキーワードを使う。定義したmixinを使うには、
+ name1
+ name2('foo')
のように、+mixin名という形で使う。 mixinの定義内には、jadeの構文が書けるし、jadeのブロックが渡せるので、
mixin article(title)
  .article
    h1= title
    block
と定義しておいて、
+article3('Title')
  p This is a description
とすると、

Title

This is a description

とレンダリングされる。

mixin適用前の悲しいテンプレート

jadeを使って作っているサイトで、mixinを知らずに書いていた残念なコードがこちら。

div.row-fluid
  div.span8.offset2
    div.row-fluid
      div.span6.well
        h3
          a(href='/labs/jscharts') JSCharts
        p.
          experiments using JSCharts.
      div.span6.well
        h3
          a(href='/labs/smoothiejs') Smoothie Charts
        p.
          experiments using Smoothie Charts.

    div.row-fluid
      div.span6.well
        h3
          a(href='/labs/chartjs') Chart.js
        p.
          experiments using Chart.js
リンク、タイトル、概要のセットで構成されたブロックが3つあるんだけど、同じ構造を3回繰り返し書いていて、DRYじゃない。

mixin適用後の美しい?テンプレート

↑のテンプレートにmininを定義してやって、DRYな形でリファクタリングしたコードがこちら。

// mixinの定義
mixin article(href, title, desc)
  div.span6.well
    h3
      a(href=href)= title
    p= desc

// mixin使用
div.row-fluid
  div.span8.offset2
    div.row-fluid
      +article('/labs/jscharts', 'JSCharts', 'experiments using JSCharts.')
      +article('/labs/smoothiejs', 'Smoothie Charts', 'experiments using Smoothie Charts.')

    div.row-fluid
      +article('/labs/chartjs', 'Chart.js', 'experiments using Chart.js.')

まったく同じコンテンツをレンダリングするjadeのコードが、よりシンプルかつエレガントになった。mixinをうまく使うことで、jadeテンプレートがDRYになることがわかる。実際にリファクタリングしたページは、こちらで公開しています。

2013/12/27

tween.jsでアニメーション - DOM要素を落下させてみる。

tween.js

githubのreadmeに、

Javascript Tweening Engine
とあるとおり、トゥイーンとするライブラリで、DOMオブジェクトのアニメーションが簡単に作れるっぽい。以前、three.jsでカレンダーを3Dにしてみた。で作ったページでも使っているけど、その時はtween.jsで何ができるか詳しく調べていなかったので、今回tween.jsに注目してちょっと試してみた。

DOM要素を順番に落下させる。

↓のようなサンプルを作ってみた。

ページ上にいくつかある青いのは<span>要素なんだけど、これを並べておいて、左から順番に落下させるというアニメーション。 実際に動くページは、こちら

ソース

htmlは(bootsrap2.3を使ってる)、

01
02
03
:
で、横並びの数字バッヂを作った。

Javascriptは、

function init() {
  var boxes = $('box');
  for (var i = 0; i < boxes.length; i++) {
    var box = $(boxes[i]);
    box.css('position', 'relative');
    var elem = { y: 0, box: box };
    var clallback = function () {
      this.box.css('top', this.y + 'px');
    };
    var tween = new TWEEN.Tween(elem)
          .to({ y: 400 }, 2000)            // 2000msかけてyを400まで持っていく。
          .easing(TWEEN.Easing.Bounce.Out) // Bounce.Outを使ってバウンドさせる。
          .onUpdate(callback)              // アップデート時のコールバックを指定。
          .delay(1000 + i * 200)           // となりの要素よりも200ms遅れて動き出す。
          .start();
  }
};

function animate() {
  requestAnimationFrame(animate);
  TWEEN.update();
};

$(function() {
  init();
  animate();
});
のようにした。あ、jQueryも使ってます。対象の動かし方を決めるTWEEN.Easingにはたくさん種類があって、今ひとつどれがどれなのか把握できていないので、別の機会に全部試してみようと思ってる。あと、TWEEN.Tweenはチェーンが組めるので、いろいろなアニメーションをつなげることで、結構複雑なアニメーションが実現できる。チェーンを使ったサンプルは、本家の例にもある。

2013/12/22

nginxのhttp_geoip_moduleでアクセスログにクライアントIPの地理情報を出力する。

http_geoip_module

nginxのhttp_geoip_moduleを使うとクライアントのIPアドレスから地理情報を取得できる、と知ったので試してみた。nginxのドキュメントは、ここにあります。

http_geoip_moduleは、MaxMind社が公開するIPアドレス-地理情報マッピングデータベースを利用する、ということで、http_geoip_moduleを利用するには、同社のデータファイルやもろもろのツールなどをインストール必要があるらしい。

geoip-databaseとlibgeoip-dev

Debian系のOSでは、aptでgeoip-databaseとlibgeoip-devをインストールできる。やってみると、geoip-databaseは予めインストールされとった。(ちなみに、Ubuntuっす。)

$ sudo apt-get install geoip-database
Reading package lists... Done
Building dependency tree       
Reading state information... Done
geoip-database is already the newest version.
0 upgraded, 0 newly installed, 0 to remove and 12 not upgraded.
## もう入ってた・・
$ sudo apt-get install libgeoip-dev
Reading package lists... Done
Building dependency tree       
Reading state information... Done
The following extra packages will be installed:
  geoip-bin
The following NEW packages will be installed:
  geoip-bin libgeoip-dev
0 upgraded, 2 newly installed, 0 to remove and 12 not upgraded.
Need to get 227 kB of archives.
After this operation, 1021 kB of additional disk space will be used.
Do you want to continue [Y/n]? y
Get:1 http://jp.archive.ubuntu.com/ubuntu/ precise/main geoip-bin amd64 1.4.8+dfsg-2 [42.4 kB]
Get:2 http://jp.archive.ubuntu.com/ubuntu/ precise/main libgeoip-dev amd64 1.4.8+dfsg-2 [185 kB]
Fetched 227 kB in 0s (641 kB/s)      
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
 LANGUAGE = "en_US:",
 LC_ALL = (unset),
 LC_CTYPE = "ja_JP.UTF-8",
 LC_COLLATE = "ja_JP.UTF-8",
 LC_MESSAGES = "ja_JP.UTF-8",
 LANG = "en_US.UTF-8"
    are supported and installed on your system.
perl: warning: Falling back to the standard locale ("C").
locale: Cannot set LC_CTYPE to default locale: No such file or directory
locale: Cannot set LC_MESSAGES to default locale: No such file or directory
locale: Cannot set LC_ALL to default locale: No such file or directory
Selecting previously unselected package geoip-bin.
(Reading database ... 52114 files and directories currently installed.)
Unpacking geoip-bin (from .../geoip-bin_1.4.8+dfsg-2_amd64.deb) ...
Selecting previously unselected package libgeoip-dev.
Unpacking libgeoip-dev (from .../libgeoip-dev_1.4.8+dfsg-2_amd64.deb) ...
Processing triggers for man-db ...
locale: Cannot set LC_CTYPE to default locale: No such file or directory
locale: Cannot set LC_MESSAGES to default locale: No such file or directory
locale: Cannot set LC_ALL to default locale: No such file or directory
Setting up geoip-bin (1.4.8+dfsg-2) ...
Setting up libgeoip-dev (1.4.8+dfsg-2) ...

GeoIPデータファイル

geoip-databaseが予めインストールされていたため、IPアドレス-国情報マッピングファイル(GeoIP.dat)は、/usr/share/GeoIP/にすでにファイルがあったのだけど、これだけだと都市名の情報が引けないので、GeoLiteCity.datとというファイルを別途ダウンロードする必要がある。そして、GeoIP.datと同じディレクトリに置く。

$ wget http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz
$ gnuzip GeoLiteCity.dat.gz
$ sudo mv GeoLiteCity.dat.gz /usr/share/GeoIP/

nginx

nginxは、cofigureの時点でhttp_geoip_moduleを有効化しないといけない。なので、今回は最新の安定版のソースをダウンロードしてきて、ビルドしなおした。(↓の./configure時のオプションは、実際は他にも色々指定してます。)

$ cd /usr/local/src
$ sudo wget http://nginx.org/download/nginx-1.4.4.tar.gz
$ tar zxf nginx-1.4.4.tar.gz
$ sudo ./configure --prefix='/usr/local/nginx' --with-http_geoip_module
$ sudo make
$ sudo make install

nginx.conf

http_geoip_moduleを利用するために、nginx.confにちょっと追記した。

geoip_countryディレクティブ
GeoIP.datのパスを指定して、$geoip_country_code、$geoip_country_code3、$geoip_country_nameを有効にする。
geoip_cityディレクティブ
GeoLiteCity.datのパスを指定して、$geoip_cityや$geoip_latitude、$geoip_longitudeなどを有効にする。

この2つのディレクティブは、ファイルパスを指定するのだけど、ここの例のようにファイル名だけ指定すると、ファイルオープンエラーとなるので、絶対パスを指定した(ら、うまく動きました。) で、$geoip_xxxxxという一連の変数が利用可能になるので、これらをアクセスログフォーマットに埋めてあげると、ログに出力されるようなる。今回設定したnginxは、fluentdでmongodbに蓄積させるためにltsv形式でログを出力しているので、geoip関連もltsv形式でログフォーマットに追加した。修正したnginx.confは、↓のようになった。
:

http {
    :
  geoip_country /usr/share/GeoIP/GeoIP.dat;
  geoip_city    /usr/share/GeoIP/GeoLiteCity.dat;
    :

  log_format ltsv "time:$time_local"
                  "\thost:$remote_addr"
                  "\tforwardedfor:$http_x_forwarded_for"
                  "\tuser:$remote_user"
                  "\treq:$request"
                  "\tmethod:$request_method"
                  "\turi:$request_uri"
                  "\tstatus:$status"
                  "\tsize:$body_bytes_sent"
                  "\treferer:$http_referer"
                  "\tua:$http_user_agent"
                  "\treqtime:$request_time"
                  "\tcache:$upstream_http_x_cache"
                  "\truntime:$upstream_http_x_runtime"
                  "\tapptime:$upstream_response_time"
                  "\tvhost:$host"
                  "\tgeoip_country_name:$geoip_city_country_name"    # <= 国名
                  "\tgeoip_country_code3:$geoip_city_country_code3"  # <= JPNとかUSAとか
                  "\tgeoip_city:$geoip_city"                         # <= 都市名
                  "\tgeoip_latitude:$geoip_latitude"                 # <= 緯度
                  "\tgeoip_longitude:$geoip_longitude";              # <= 経度
    :
}
:
nginx再起動して、何やらアクセスすると、ログとfluendが出力してくれるmongodbのドキュメントにも、地理情報が出力された。

2013/12/19

three.jsでカレンダーを3Dにしてみた。

Javascriptの3Dライブラリであるthree.jsのexampleに、元素周期表を3Dで表示するっていうカッコイイのがあります。

これを参考に何か作れないかな、と思ってカレンダーを作ってみよう、となり、出来たのがこちらのページです。

これらは、次のような特徴があります。

  • THREE.CSS3DObjectというオブジェクトを利用している。
  • tween.jsを利用している。

THREE.CSS3DObject

three.jsのドキュメントには無いので、githubのソースを読もうと思ってます。

tween.js

Javascriptのアニメーションライブラリです。カレンダーでは、全然使いこなせていませんが、かなり凝ったアニメーションが簡単に作れるスグレモノみたいです。

three.jsのCSS3DObjectとtween.jsを組み合わせるだけで、相当カッコイイものが作れそうなんで、調べつつ試しつつしていこうと思っています。