2012年12月15日土曜日

Express アプリケーションを CoffeeScript で書く

実行環境
OS : 10.8.2
node : v0.8.6
express : v3.0.4
coffee-script : 1.4.0

以前の記事で、クライアントサイドの JavaScript を CoffeeScript 化しましたが、今回はサーバサイドの JavaScript を CoffeeScript で書けるようにします。

CoffeeScript をインストール


CoffeeScript は npm で、-g オプション を付けてインストールします。
$sudo npm install -g coffee-script

これで、コンソールから coffee コマンドが使えるようになります。
$ coffee -v
CoffeeScript version 1.4.0

Express が生成する JavaScript を CoffeeScript に変換する


既存の JavaScriptファイル(以下 jsファイル)を CoffeeScriptファイル(以下 coffeeファイル)に変換するには、js2coffee を使います。
$ npm install -g js2coffee

これで、コンソールから js2coffeeコマンドが使えるのようになるので、Expressが自動生成した jsファイルを、coffeeファイルに変換します。

$ express coffeeTest && cd coffeeTest && npm install

$ js2coffee app.js > app.coffee
$ js2coffee routes/index.js > routes/index.coffee

アプリケーションを起動


後は、app.coffee を 
$ coffee app.coffee

で実行すれば、Expressアプリケーションが起動します。
もちろん、app.coffee 以外のファイルもすべて CoffeeScript で記述できます。


コンパイル済み jsファイルを保存したい場合


上記の方法だと、コンパイル済みの jsファイルはファイルシステムに保存されません。今回は、単純に coffeeファイルと同じ場所に jsファイルを生成してもよいのですが、管理しやすいように、ファイルの配置を工夫してみます。

例えば、 『src ディレクトリに coffee ファイルを、app ディレクトリにコンパイル済み jsファイルを保存する』 場合は、ディレクトリ構成は下記のようになると思います。(app.coffee を移動すると、view と static のパスを書き換える必要があるので注意してください。)

ディレクトリ構成例
coffeeTest/
  |_ app/ <= コンパイル済みの js ファイルはすべてここに生成される
  |
  |_ src/ <= coffeeScript はすべてここに保存する
  |  |_ app.coffee
  |  |_ routes/
  |    |_ index.coffee 
  |
  |_ node_modules/
  |_ public/
  |_ views/
  |_ package.json
src ディレクトリ以下の coffeeファイルを appディレクトリ以下にコンパイルするには、下記コマンドを実行します。 ( -b は関数のラッパーをはずす、-o はコンパイル先ディレクトリを指定するオプションです。)
$ coffee -bco ./app ./src
後は app.js をターゲットにしてアプリケーション起動します。
$ node ./app/app.js
もしくは、アプリケーションのルートに server.js などのファイルを作成し、そこで require('./app/app') してしまっても良いかもしれません。

CoffeeScriptの変更を監視する


coffeeファイルを変更するたびにいちいち coffeeコマンドを実行するのは面倒なので、 coffee コマンドを -w オプションつきで実行して、src ディレクトリに変更があるたびに自動でコンパイルを実行するようにします。
$ coffee -wbco ./app ./src
さらに、このコマンドをCakefileにtask登録しておけば、一発で監視を開始できるのでおすすめです。

Cakefile
spawn = require('child_process').spawn
task 'watch', ->
  watch = spawn('coffee', ['-wbco', './app', './src'])

  watch.stdout.on 'data', (buffer)->
    if(buffer)
      console.log buffer.toString().trim() 

  watch.stderr.on 'data', (buffer)->
    if(buffer)
      console.log buffer.toString().trim() 
実行
$ cake watch

テンプレートモジュールを使う


以上、手動で CoffeeScript の設定を行ってきましたが、 express-coffeeモジュールを使うと、CoffeeScript で記述する前提でExpressアプリケーションのひな形を生成してくれます。testスクリプトもCoffeeScriptでかけるようになっているので、是非チェックしてみてください。

2012年12月9日日曜日

[node.js, undersore.js]複数の非同期処理の完了を待ち受ける

実行環境
node.js v0.8.3
undersore.js 1.4.3

node.js のプログラムで、複数の非同期処理の終了を待ってから後続の処理を実行する場合、underscore.js の after 関数がとても便利だったので紹介します。

underscore.js のインストール
$ npm install underscore

after 関数

underscore.js の after 関数では、第1引数で渡した数値回、戻り値で返す関数が実行されると、第2引数のコールバックが実行されます、、といってもよくわからないので、
例として、複数のファイルを非同期で削除し、すべてのファイルの削除が完了したらメッセージを表示するプログラムを作成しました。

//カレントディレクトリの、foo, bar, hoge というファイルを非同期で削除する。
//すべてのファイルの削除が完了したら、コンソールにメッセージを表示する。
var fs = require('fs'),
     _ = require('underscore')

var targets = ['foo', 'bar', 'hoge'];

//ファイルの削除を待ち受ける関数
var displayResult  = _.after(targets.length, function(){
  console.log('ファイルの削除が完了しました。');
});

_.each(targets, function(target){
  fs.exists(target, function(exists){
    if(exists){
      fs.unlink(target, function(err){
        if(err) throw err;
        console.log(target + " を削除しました。");
        displayResult(); //ファイルの削除を通知
      });
    } else {
      displayResult(); //ファイルの削除を通知
    }
  });
});

console.log('最初にここが実行されます。');

実行結果

最初にここが実行されます。
hoge を削除しました。
bar を削除しました。
foo を削除しました。
ファイルの削除が完了しました。

想定通りの順番で処理が実行されました!

しかし、上記のサンプルを見てもお分かりの通り、こんなに短いプログラムでも、処理が行ったり来たりしていて、もうスパゲッティ化の兆候が現れはじめてます。。
複数の非同期処理を制御する場合は、かなり慎重にプログラミングする必要がありますね。

でも、after だけでなく、underscore.js には、便利な関数(extend, each, map など)がたくさん用意されているので、積極的に利用していこうと思います。

まとめ


  • 複数の非同期処理の完了を待ち受ける場合は、 underscore.js の after 関数が便利。
  • コードのスパゲッティ化には十分注意する!

2012年12月1日土曜日

[node.js + express]クライアントサイドの JavaScript を CoffeeScript化 & 複数ファイルを結合 & 縮小化する

(2013/1/14 追記) サーバーサイドのプログラムをCoffeeScript で記述する方法をこちらにまとめました。

実行環境
OS: Mac OS X 10.8.2
node: v.0.8.3
express: v3.0.3
connect-assets: v2.3.3

CoffeeScript の導入


クライアントサイドの CoffeeScript のコンパイルや、複数の JavaScript ファイルの結合、縮小化(Rails の Asset Pipleline 機能と同等の機能)をするには、connect-assets モジュールが便利です。
今回の記事では、 connect-assets で JavaScript を扱う基本的な方法を説明します。(なお、このモジュールでは css も同様に扱えますが、この記事では触れていません。)


connect-assets の設定


require してミドルウェアを設定します。
app.use(require('connect-assets')());


CoffeeScript(JavaScript) ファイルの保存場所


アプリケーションの直下に asset/js というディレクトリを作成し、ここに .coffee(.js) ファイルを保存します。
myApplication/
  |_ assets/
      |_ js/
          |_application.coffee
          |_jquery.js


view から JavaScript をロード


view からコンパイル済みの JavaScript をロードするには、 js() 関数にファイル名を渡します。
この関数はコンパイル済みの JavaScript を指す <script> タグを文字列で返すので、jade で実行する場合は下記のように != で呼び出す必要があります。
!= js('application')

レンダリング結果は下記のとおり
<script src="/js/application.js"></script>


ファイルの依存関係


CoffeeScript(JavaScript)ファイルの依存関係は、コメントで定義します。例えば、以下のようなファイル構成で、
myApplication/
  |_ assets/
      |_ js/
         |_ application.coffee
         |_ foo.coffee
         |_ bar.coffee

application.coffee が foo.coffee と bar.coffee に依存している場合は、application.coffee で以下のように依存関係を定義します。(JavaScript の場合は //= )

application.coffee
#= require foo.coffee bar.coffee

この状態で jade で != js('application') を実行すると、下記の結果が得られます。
<script src='/js/foo.js'></script>
<script src='/js/bar.js'></script>
<script src='/js/application.js'></script>

特定のディレクトリ以下のファイルをすべて依存ファイルとして指定するには、下記のように記述します。
#= require_tree ./


結合・縮小化


アプリケーションを production モードで実行している場合は、依存ファイルの結合と、ファイルの縮小化が実行されます。結合・縮小化済みファイルは、デフォルトでは builtAssets ディレクトリに保存されるので、あらかじめ assets ディレクトリと同じ構成で builtAssets を作成しておく必要があります。
myApplication/
  |_ assets/
  |  |_ js/
  |     |_ application.coffee //foo.coffee と bar.coffee に依存
  |     |_ foo.coffee
  |     |_ bar.coffee
  |
  |_ builtAssets/
     |_ js/

この状態で production モードでアプリケーションを実行し、下記 jade ファイルを実行すると、
!= js('application')

以下のように結合・縮小化されたファイルが生成されました。
myApplication/
  |_ assets/
  |  |_ js/
  |     |_ application.coffee
  |     |_ foo.coffee
  |     |_ bar.coffee
  |
  |_ builtAssets/
     |_ js/
        |_ application-38a0083f4a868f2797aba944e5ce3ad6.js

js() が生成する <script> タグは、src 属性でこのファイルを指定します。
結合前のファイルに変更があるたびに、新しいファイルが生成されますが、js() 関数は常に最新のファイル指定する <script> タグを返してくれます。

さらに、結合済みファイルは Expires ヘッダがかなり長く設定されるので、とても効率の良い JavaScript の配布が可能となります。

まとめ


  • connect-assets モジュールを使うと、JavaScript の結合と縮小化、 CoffeeScript での記述が可能になる。
  • .js, .coffee ファイルは、 assets ディレクトリに配置する。
  • view からは、js() 関数で JavaScript をロードする。
  • アプリケーションを production モードで実行すると、コンパイル、結合、圧縮済みのファイルが、 builtAssets ディレクトリに生成される。

2012年11月28日水曜日

[node.js + express]フォームで送信されたデータにアクセスする

実行環境
OS Mac OS X 10.8.2
node v.0.8.3
express v3.0.3

bodyParser ミドルウェア


クライアントから express アプリケーションへ HTTP の POST メソッドでデータ送信した場合、( 正確には Content-Type が application/x-www-form-urlencodedか multipart/form-data のリクエストを送信した場合) リクエストが bodyParser ミドルウェアを経由すると、req.body オブジェクトにメッセージボディの値が追加されます。

また、メッセージボディの各パラメータ名が JavaScript のオブジェクトとして解釈できる場合は、オブジェクトにパースしてから req.body に追加してくれます。
もちろん、配列やネストしたオブジェクトでも問題なくパースしてくれます。

例として、entry というリソースを生成するフォームを jade で作成しました。

_form.jade
form(method="POST", action="/entries")

  label お名前
    input(type="text", name="entry[author][name]", id="entry[author][name]")
  br
  label タイトル 
    input(type="text", name="entry[title]", id="entry[title]")
  br
  label 本文 
    textarea(name="entry[body]", id="entry[body]")
  br
  lebel カテゴリ(複数可)
    input(type="checkbox", name="entry[category][]", value="diary")
    | 日記
    input(type="checkbox", name="entry[category][]", value="memo")
    | メモ 
  br
  input(type="submit")

上記フォームからのリクエストが bodyParser ミドルウェアを経由すると、 req.body.entry は下記のようなオブジェクトになります。

{ entry:
   { author: { name: 'takeshi' },
     title: 'test',
     body: 'This is test.',
     category: [ 'diary', 'memo' ] 
   }
}

関連性のある値をまとめることで、コードの可読性が上がるとともに、新しいオブジェクトを生成する際など、以下のような簡潔な記述が可能となります。

app.js
app.use(express.bodyParser());

var Entry = require('./models').Entry; 
app.post('/entries', function(req, res){

  //mongoose で Entry オブジェクトを生成
  var new_entry = new Entry(req.body.entry);
  new_entry.save(function(err){
    res.redirect('/entries');
  });
})


まとめ

  • bodyParser ミドルウェアを使うと、 req.body にリクエストボディの値が追加される。
  • リクエストボディのパラメータ名を JavaScript のオブジェクトとして解釈できる値にすると、オブジェクトにパースして req.body に追加してくれる。

2012年11月24日土曜日

[node.js + express]フォームデータ を PUT や DELETE で送信する

実行環境
node v0.8.3
express v3.0.3

HTTPメソッドの書き換え


methodOverride ミドルウェアを使うと、フォームのデータを PUT や DELETE といった、POST, GET 以外の HTTPメソッドで(擬似的に)サーバーに送信することが可能になります。

設定は簡単で、送信したい HTTPメソッド名を、_method という名前でフォームから POST で送信するだけです。下記は、フォームデータを DELETE メソッドとして送信する jade のサンプルです。
form(method="POST", action="/entries/#{entry._id}")
  input(type="hidden", name="_method", value="DELETE" )

  input(type="submit", value="削除" )

このフォームで設定されているメソッドはあくまで POST のため、フォームデータは通常通り POST メソッドで送信されます。
しかし methodOverride を使用しているアプリケーションは、送信された _method の値から、リクエストは DELETE メソッドで送信されたと解釈します。(実際には、req.method の値を _method の値で上書きしています。methodOverride の詳しい処理は こちら のソースコードを確認してみてください。)

そのため、上記のフォームから送信されたリクエストは、app.post() ではなく、app.delete() でマッピングされたルートとマッチします。
app.delete('/entries/:id', routes.entries.destroy);

RESTful な URL


methodOverride の HTTPメソッド書き換え機能を利用することで、以下のような RESTful な URL を構築することができます。

app.js
app.get ('/entries', routes.entries.index);
app.post('/entries', routes.entries.create);

app.get('/entries/new'     , routes.entries.new);
app.get('/entries/:id/edit', routes.entries.edit);

app.get   ('/entries/:id', routes.entries.show);
app.put   ('/entries/:id', routes.entries.update);
app.delete('/entries/:id', routes.entries.destroy);

まとめ


methodOverride ミドルウェア を使うと、 form から 任意の HTTP メソッドでデータをサーバーに送信できる。
HTTPメソッド名は、_method という名前で送信する。

2012年11月21日水曜日

[node.js + express] 環境ごとに設定を切り替える

実行環境
node.js v0.8.14
express v3.0.3

環境変数 NODE_ENV


express は、アプリケーションの実行環境を、app.settings.env の値で判断しています。
app.settings.env は、 expressアプリケーション実行時に、環境変数 NODE_ENV の値で初期化されます。(環境変数 NODE_ENV が定義されていない場合は、'development' で初期化します。)

以下のコマンドで、アプリケーションの実行時に環境を指定できます。
$ export NODE_ENV=production && node app.js

app.configure で環境ごとの設定


app.configure では、1番目の引数で設定を反映させる環境を, 2番目の引数で設定用コールバックを指定します。
1番目の値が実際の環境( NODE_ENV, app.settings.env )と一致するときのみ、第2引数のコールバックを実行します。

また、 引数にコールバックのみ渡した場合は、すべての環境で設定用コールバックが呼び出されます。

app.js
//全環境共通の設定
app.configure(function(){
});

//開発環境用の設定
app.configure('development', function(){
});

//本番環境用の設定
app.configure('production', function(){
});

サンプル


express コマンドで生成した app.js を確認すると、development 環境のみの設定として、errorHandler ミドルウェア を呼び出しています。

app.js
app.configure('development', function(){
  app.use(express.errorHandler());
});

このミドルウェアは、エラー内容をフォーマットしてレスポンスで返してくれるため、開発時には非常に役に立ちます。
しかし、本番環境で実行してしまうと、アプリケーションの内部情報をユーザーに表示させることになってしまうため、開発環境のみに限定した設定となっています。

そのため、本番環境では、エラー時には専用のエラーページを表示させる、などの対策が必要になります。

app.js
app.configure('development', function(){
  app.use(express.errorHandler());
});

app.configure('production', function(){
  app.use(function(err, req, res, next){
    console.error(err.stack);
    res.status(500);
    res.render('500', {title: "エラーが発生しました。"});
  });
})

まとめ


express アプリケーションの環境は、環境変数 NODE_ENV で設定する。
app.configure() で、環境ごとの設定を定義する。

2012年11月18日日曜日

[node.js + express]express で エラーをハンドルするミドルウェアを作成する

エラーハンドル用のミドルウェア


エラーハンドル用のミドルウェアと通常のミドルウェアとの違いは、受け取る引数の数です。
エラーハンドル用のミドルウェアは、引数を4つ受け取ります。1番目の引数に渡されるのは、エラーオブジェクトです。

(2012/12/01 追記)エラーハンドル用のミドルウェアは、通常ミドルウェアのチェーンの最後尾で定義します。

app.js
app.configure(function(){

  //省略

  app.use(function(err, req, res, next){
    console.error(err.stack);
    res.send(500, "エラーが発生しました。")
  })
})

(2012/12/01 追記)エラーハンドル用ミドルウェアに処理を渡す


エラーハンドル用ミドルウェアに処理を渡すには、next の引数にエラーオブジェクトを渡して呼び出します。
例えば、routes 内で発生したエラーをエラーハンドルようミドルウェアに渡したい場合は、下記の用に next を呼び出します。
app.get('/entries', function(req, res, next){
  Entry.find(function(err){
    if (err) next(err);

    //通常の処理
  );
);

複数のエラーハンドル用ミドルウェアを連結する


複数のエラーハンドル用ミドルウェアを連結させる場合も、 next に err を渡して次のミドルウェアを呼び出します。
これにより、特定のエラーのみ処理するミドルウェアを作るなど、より細かくエラー処理を制御できます。

下の例では、3つのエラーハンドル用ミドルウェアを定義していますが、それぞれのミドルウェアでは、

  • 標準エラー出力にエラー内容を書き出す
  • XMLHttpRequest のリクエストに対する処理で発生したエラーの場合は 、 json でメッセージを返す
  • それ以外のエラーでは、専用のエラーページを表示させる

という処理を行っています。

app.js
app.configure(function(){

  //省略

  app.use(function(err, req, res, next){
      console.error(err.stack);
      next(err);
  });

  app.use(function(err, req, res, next){
    if(req.xhr){
      res.json(500, {message: err.message, stack: err.stack});

    } else {
      next(err)
    }
  })

  app.use(function(err, req, res, next){
    console.error(err.stack);
    res.status(500);
    res.render('500', {title: "エラーが発生しました。"});
  })
})


Connect の errorHandler ミドルウェア


ちなみに、express new コマンドで生成したアプリケーションのデフォルトでは、エラーが発生すると、エラー内容がコンソールに表示され、見やすいエラーページが表示されます。
この処理を行っているのは、 app.configure で設定されている、 Connect の errorHandler ミドルウェア(http://www.senchalabs.org/connect/errorHandler.html)です。

app.js
app.configure('development', function(){
  app.use(express.errorHandler());
});

まとめ


エラーハンドリング用のミドルウェアは、4つの引数を受け取り、最初の引数にエラーオブジェクトが渡される。
エラーハンドリングのミドルウェアを連結させる場合は、エラーオブジェクトを渡した next を呼び出す。


参照URL
http://expressjs.com/guide.html#error-handling

2012年11月16日金曜日

[node.js + express]存在しないURLへのリクエストに対して404エラーページを表示させる

express new でアプリケーションの雛形を生成した場合、存在しないURLにリクエストを送信すると、単純に text/plain でエラーメッセージが表示されます。

Cannot GET /do_you_exist

開発段階ではこれで何の問題もないのですが、本番運用時にはこれではいくらなんでもそっけない、ということで、
今回は、存在しないURLへのリクエストに対して、常に404エラー用のページを表示させる方法を紹介します。
(以下サンプルはすべてnode v0.8.3 express 3.0.0beta4 で確認しました)


ミドルウェアでルーティング


express コマンドで生成した app.js では app.configure で設定しているミドルウェアのチェーンの最後で、app.router と express.static をセットしています。(下記の例では、stylus もセットしています。)

app.js
app.configure(function(){

  //省略

  app.use(app.router);
  app.use(require('stylus').middleware(__dirname + '/public'));
  app.use(express.static(__dirname + '/public'));
});

これらのミドルウェアでは、リクエストに対するルーティングを行っていて、リクエストにマッチするルート(もしくはアセット)が存在すれば、設定された処理を実行してレスポンスを返し、以降のミドルウェアは呼び出さない、という仕組みになっています。

よって、これらのミドルウェアよりも後に配置されるミドルウェアは、ルーティングにマッチしないリクエストを受け取った時のみ実行される、ということになります。

つまり、ここに404ページへ誘導するミドルウェアを作成すればOKです。
下の例では、テンプレートを指定していますが、静的なhtmlでももちろん大丈夫です。(レスポンスのステータスに 404 をセットするのを忘れずに! )

app.js
app.configure(function(){

  //省略

  app.use(app.router);
  app.use(require('stylus').middleware(__dirname + '/public'));
  app.use(express.static(__dirname + '/public'));

  app.use(function(req, res, next){
    res.status(404);
    res.render('404', {title: "お探しのページは存在しません。"});
  });

もちろん、404エラーページを表示させずに、ログインページやトップページにリダイレクトさせても問題ないでしょう。


まとめ


404エラーページへのルーティングは、middleware で制御する。
404エラーページへルーティングする middleware は、正規のルーティングを行っている milldeware よりも後に配置する。

2012年11月12日月曜日

[node.js + express]express3.x で flash メッセージを使おう

前回の投稿からずいぶんたってしまいましたが、、最近はnode + express + mongodb でアプリケーションを作っているので、その中で学んだことを少しずつまとめていきたいと思います。

今回は、expressバージョン3.x 以降で、flashメッセージを使う方法を紹介します。

express では、バージョン2.x 以前では req.flash というメソッドが提供されていましたが、3.x では廃止されたようです。
https://github.com/visionmedia/express/wiki/Migrating-from-2.x-to-3.x

代替手段として、 session もしくは、connect-flashモジュールを使う方法が挙げられているので、この2つの方法を使ってflashメッセージ機能を実装してみます。

session を使って実装


まずはsessionにflashメッセージをセットします。
なお、実行にはcookieParser と session ミドルウェアが必須となりますので、注意してください。

app.js
app.get('/entries', function(req, res){

    if(!req.user){
      req.session.messages = ["ログインしてください"];
      res.redirect('/login');
    }

    //省略
  });

session に格納されたメッセージを、view から参照できるようにします。
各 route でメッセージを view に渡しても良いのですが、ここでは、view から直接アクセスできる、 res.locals に flashメッセージを格納するミドルウェアを作成しています。
これで、リクエストの度に res.locals.messages は req.session.messages の値で初期化されます。
req.session.messages をクリアするのをわすれずに!

app.js
app.configure(function(){
    //省略

    app.use(express.cookieParser('your secret here'));
    app.use(express.session());

    app.use(function(req, res, next){
      res.locals.messages = req.session.messages;
      req.session.messages = null;
      next();
    });

    //省略
  });

view からは、messages にアクセスすることで、flashメッセージを取得できます。

login.jade
- if(messages)
    each message in messages
      p= message 

connect-flash モジュールを使う


connect-flash(https://github.com/jaredhanson/connect-flash)モジュールを使えば、より簡単にflash機能を実装できます。
設定方法は、connect-flash をミドルウェアとして登録するだけです。
なお、connect-flash の実行には、cookieParser と session ミドルウェア が必須なので、この2つよりも後に connect-flash を呼び出す必要があります。

app.js
var flash = require('connect-flash');

  //省略

  app.configure(function(){

    //省略

    app.use(express.cookieParser('your secret here'));
    app.use(express.session());

    app.use(flash());

    //省略

  });

以上で、req オブジェクトに flash() が追加されました。
メッセージの設定と取得方法は以下のとおりです。

flashメッセージを設定

app.js
app.get('/entries', function(req, res){
    if(!req.user){
      req.flash('alert', 'ログインしてください。');
      res.redirect('/login');
    }

    //省略
  });

flashメッセージを取得

app.js
app.get('/login', function(req, res){
    res.render('login', {message: req.flash('alert')});
  });

まとめ


express3.x 以降では、req.flash の変わりにsession を使うか、connect-flash モジュールを使う。
特別な理由がない限り、しっかりテスト済みの connect-flash モジュールを使うべきでしょう。

2012年6月16日土曜日

[JavaScript][小技]lengthを使わないで配列の要素に順番にアクセスする方法

JavaScriptで定番の、for文を使って配列の要素に順番にアクセスする処理ですが、素直に書くと以下のようになります。
var ar = [2, 3, 5, 7, 9];
for (var i = 0; i < ar.length; i+=1) {
    //ar[i]を使った処理
}
もしくは、ループの度にlengthプロパティにアクセスしないように、配列の長さを初期化時に保存しておきます。
var ar = [2, 3, 5, 7, 9];
for (var i = 0, len = ar.length; i < len;  i+=1) {
    //ar[i]を使った処理
}

ちょっとだけエレガントに..


上記の書き方で全く問題はないのですが、 ちょっとしたテクニックとして、この処理は以下のように置き換えられます。
var ar = [2, 3, 5, 7, 9];
for (var i = 0, elem; elem = ar[i]; i+=1) {
    //elem を処理
}
インデックスが配列の長さを越えるとundefinedが返ってきて、ループが終了します。 さらに、ループの判定時に配列の要素を変数に格納しているので、その後の処理がすっきりするのもなかなか良いです。

 同様に、ECMAScript5 で導入されたarray.forEach メソッドを使っても、配列要素への順次アクセスが可能です。
var ar = [2, 3, 5, 7, 9];
ar.forEach(function(e, i){
    console.log(["Index ", i, " = ", e].join(''));
});
DOMのリストに対しては、forEachのcallメソッドを呼び出せばokです。
var divs = document.getElementsByTagName("div");
[].forEach.call(divs, function(div, i){
    console.log(div.nodeName);
});

まとめ


forを使った配列のループにはlengthを使わなくてok!

2012年2月26日日曜日

[JavaScript]イベントの委譲(delegation)

JavaScriptでは、イベントのバブリングを利用し、親要素にイベント処理を委譲することができます。
イベント委譲の利点は、イベントを発生させる要素ごとにイベントを設定しなくて済み、コードの見通しがよくなることです。

sample


サンプルとして、[start], [stop], [reset] ボタン3つで操作する、10分の1秒単位で計測するストップウォッチを作成しました。

ポイントとしては、
  • 各ボタンの親要素である、id="controller" のdiv要素にのみ、onclick イベントリスナを設定している。(22行目)
  • div要素内でonclickイベントが発生したら、発生元のid属性をチェックし、各ボタンごとの処理を呼び出している。(30行目)

ことです。

サンプルコード

jQueryでイベント委譲する場合


jQuery では、.delegate() メソッドが用意されているので、より簡単にイベント委譲を実装できます。

$("div#controller").delegate("input#start", "click", handler);

まとめ

  • 同一要素内でたくさんのイベントリスナをセットする場合は、イベント委譲でまとめられないか検討する!



参考文献
JavaScript パターン p196 - p197
JavaScriptパターン ―優れたアプリケーションのための作法
JavaScriptパターン ―優れたアプリケーションのための作法

2012年2月14日火曜日

[jQuery] jQueryオブジェクト から DOM オブジェクトに変換する

あまり使いどころはなさそうだけど・・

get() で返されるのはDOM オブジェクト


jQueryオブジェクトの集合に対して、.get()メソッド(Ajax のgetとは異なる)を実行すると、
jQuery オブジェクトではなく、DOM オブジェクトの配列が返されます。

また、get() にインデックスを渡すか、jQueryオブジェクトの集合に[] でアクセスすれば、インデックスで指定した要素のみDOM オブジェクトとして取得することも可能です。

jQueryのアプリケーションで DOM オブジェクトのメソッドやプロパティtが使いたかったら、.get()でDOMオブジェクトに変換すればOK!

動作確認サンプル (Chrome 17 で確認しました。)


まとめ

  • .get() で返されるのは、DOM オブジェクト!!

参考文献
jQuery クックブック p59 - p62
jQueryクックブック
jQueryクックブック

2012年2月11日土曜日

[jQuery] jQuery.animation() でスクロールをアニメーション処理

ページ内リンクへの移動をアニメーションで実行したいと思い立ってjQueryのマニュアルを確認したところ、
特別なプラグインを導入しなくても、jQuery.animate() メソッドで実現できることがわかったのでまとめました。

.animate() に渡せるプロパティー


.animate() メソッドでは、アニメーションの動きを、cssプロパティと値を定義したオブジェクトを渡して指定します。
基本的に、数値を指定できるcssプロパティ(width, height, opacity など)のみ指定できますが、
cssのプロパティ以外でも、scrollTop ,scrollLeft など、アニメーション可能なプロパティが定義されています。

今回は下方向に画面をスクロールさせたいので、アニメーションさせるプロパティに"scrollTop"を指定します。

scrollTop で下方向にスクロール


というわけで、class に"scrollAnchor" が指定されたアンカーをクリックすると、
リンク先の要素までウィンドウをスクロールするプログラムを作成してみました。
(ちなみに、JavaScriptが無効になっていても移動は可能です)

ポイントとしては、
  • .offset()メソッドで、リンク先の要素のドキュメント内の縦方向の相対位置を取得している
  • animationメソッドはbody要素に対して実行している
  • ことです。

サンプル(jQuery-1.7.1 で実行。動作は chrome 16 , IE6で確認しました。)

<!-- 以上省略 -->
<body>
 <a href="#scrollDest" class="scrollAnchor">scroll!</a>

 <div id="container">
     <div style="height: 500"></div>
     <div style="height: 500"></div>
     <div style="height: 1000; width: 500; background-color: black" id="scrollDest"></div>
 </div>

 <script type="text/javascript" src="./lib/jquery-1.7.1.js"></script>
 <script type="text/javascript">

     var body = $("body:first");
     $("a.scrollAnchor").click(function(evt){
         // デフォルトのanchorの処理を停止
         evt.preventDefault();           

         var destId = $(this).attr("href");

         // .offset()で、リンク先要素のドキュメント内の相対位置を取得
         var destTop = $(destId).offset().top;   

         // ウィンドウをスクロールさせるために、body要素に対してanimateメソッドを呼び出す
         body.animate({scrollTop: destTop}, 1000);
     });

 </script>
</body>
<!-- 以下省略 -->


要素内でのスクロール


overflow: scroll もしくは overflow: hidden が設定されている要素内で、特定の子要素までスクロールさせたい場合のサンプルです。
このケースでは、.position() メソッドで要素内の位置を取得しています。

サンプル

<!-- 以上省略 -->
<body>
 <div id="container" style="height: 1000; overflow: scroll">
     <a href="#scrollDest" class="scrollAnchor">scroll!</a>
     <div style="height: 500"></div>
     <div style="height: 500"></div>
     <div style="height: 1000; width: 500; background-color: black" id="scrollDest"></div>
 </div>

 <script type="text/javascript" src="./lib/jquery-1.7.1.js"></script>
 <script type="text/javascript">
     var container = $("div#container");

     $("a.scrollAnchor").click(function(evt){
         evt.preventDefault();

         var destId = $(this).attr("href");
         var destTop = $(destId).position().top;   // .position()で要素内の相対位置を取得

         container.animate({scrollTop: destTop}, 1000);
     });

 </script>
</body>
<!-- 以下省略 -->


まとめ


  • ページ内要素への移動をアニメーション化する場合は、jQuery.animate() メソッドに、"scrollTop" プロパティを持つオブジェクトを渡す
  • ウィンドウをスクロールさせるのか、特定の要素内でスクロールさせるのかに注意する

参照URL
.animate() - jQuery API:
http://api.jquery.com/animate/

参照書籍
jQuery クックブック
jQueryクックブック
jQueryクックブック

2012年2月8日水曜日

[JavaScript] parseInt() と Number.toString() で 数値 <=> 文字列 変換

文字列を数値に変換 parseInt(string, radix)


数値を表す文字列を10進数の整数の値に変換するには、組み込みのparseInt() 関数を使います。
ユーザーからの入力や、jsonで受け取ったのデータを数値として扱う場合などは、必ずparseInt() で数値に変換してから、処理を行います。

sample (以下すべて chrome 16 で実行しました。)
console.log(parseInt("10px", 10));   //10 stringは、数値と解釈できる文字で始まっていれば、後の変換できない文字は無視される

console.log(parseInt("1111", 2));    //15
console.log(parseInt("0xFFFF", 16)); //65535

console.log(parseInt("hoge", 10));   //NaN 数値変換不能な文字列が与えられた場合は、NaN が返る


注意点としては、省略可能な第2引数の radix で、文字列が表している数値の基数を必ず指定することです。

もし radix を指定しないと、自動的に基数が判定されてしまうので、
たとえば、"010" など,"0" で始まる文字列を,頭の0を除いて10進数として扱いたい場合に、"010" は8進数の数値を表現している文字列と解釈され、8 が返されます。

sample
var str = "010"

//radix を省略した場合
console.log(parseInt(str));  //8 <= "010" が8進数をあらわす数値として解釈された

//"010" を10進数の数値として解釈
console.log(parseInt(str, 10)); //10

数値を文字列に変換する Number.toString(radix)


反対に、10進数で表された数値を、任意の基数で表された数値の文字列に変換する場合は、 NumberオブジェクトのtoString()メソッドを使います。
console.log( (15).toString(2) );     // "1111"
console.log( (65535).toString(16) ); // "ffff"

まとめ


  • 文字列を数値に変換するには、組み込み関数の parseInt(string, radix) を使う
  • parseInt の第2引数には、必ず、文字列があらわしている数値の radix を渡す!
  • parseIntの逆で、数値を文字列に変換したい場合はNumber.toString()

参照URL
Ecma-262.pdf 15.1.2.2 parseInt(string, radix)
http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf

toString - MDN
https://developer.mozilla.org/ja/JavaScript/Reference/Global_Objects/Number/toString