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クックブック