Web アプリ作ってると、ページごとに異なる JavaScript を実行したいというケースは良くあります。
インラインで書く?
一番素直な方法は、テンプレートにインラインで JavaScript を書くことですが、下記の問題があり、あまり良い手ではありません。
- CoffeeScript で書いたり minify したり lint したりできない。
- 共通化できない。
- コードがあちこちに分散する。
- 高速化のために body の最後でライブラリを読んでると、それより後に書かないといけない。
- リソースの ID などを渡すためのに、JS のコードの一部を動的に生成すると、XSS の危険性が高まる。
- turbolinks で読み込まれたページだと実行されない。
URL Dispatcher で書く?
URL を解析して、それぞれの場合の処理を書くという方法もあります。
Rails でいう routes.rb みたいなものです。dispatcher や router などと呼ばれます。
- そこそこ規模が大きくても何とかなるjavascriptの設計(URL dispatcherの薦め) | tech.kayac.com - KAYAC engineers' blog
- Backbone.Router
しかし、これにもひとつ問題があります。
それは、 描画される内容と URL が必ずしも対応しない 、ということです。
内容と URL が対応しないケース
たとえば、Rails で rails g scaffold
で作成したコードの場合を見てます。
create アクションでバリデーションに失敗したときには、
- テンプレートは new アクションと同じ
- URL は index と同じ
に、なります。
このとき、new.html.erb で実行されるべきスクリプトは、どう書けばよいでしょうか?
もちろん、設計によって URL と描画される内容を完全に対応させられるのであれば、この方法でも問題ありませんが (SPA だとやりやすそうです)、そうでないケースも多いでしょう。
Pagehook
これらの問題を解決するために Pagehook というライブラリを作成しました。
Pagehook は、ページ中にある data-pagehook 属性を持つ要素を探して、その値に対応する関数を実行します。
<script type="application/json" data-pagehook="articles/index"></script>
実行する関数の定義は、下記のように行います。
Pagehook.register("articles/index", function(){
console.log("Called");
});
あとはページ読み込み時に Pagehook.handler
が実行されるようにイベントハンドラを設定します。
$(Pagehook.handler);
これで、ページの読み込みが完了した時に、console.log("Called");
が実行されます。
この方式だと、下記のような利点があります。
- 実行する関数の定義を別ファイルに分けられる。
- テンプレートに実行のトリガーとなる要素を置けるので、URL ではなくテンプレートと処理を対応させられる。
- テンプレートに書く要素はインラインスクリプトではないので、依存するライブラリの前に配置しても問題ない。
引数
URL を解析する系のライブラリには、よく、URL に含まれる ID などを抽出する機能があり、これによってスクリプトの動作を変えることができます。
Pagehook では、トリガーとなる要素の内容を JSON で書くと、呼び出される関数の引数として渡すことができます。
<script type="application/json" data-pagehook="articles/index">{"message":"Hello"}</script>
<script>
Pagehook.register("articles/index", function(data){
alert(data.message); // Hello と表示
});
</script>
この JSON をサーバ側で生成すれば、URL に含まれない情報も渡すことができます。
Rails で使いたい
pagehook-rails という gem を作りました。
Gemfile に gem "pagehook-rails"
追加して、下記でインストール完了。
$ bundle install
$ rails g pagehook:install
Generator でフック定義のひな形を生成できます。
$ rails g pagehook articles/index
create app/assets/javascripts/pagehook/articles/index.js
トリガーとなる要素をつくるためのヘルパーメソッドもあります。
<%= pagehook("articles/index", id: 123) %>
とすると、下記の様にレンダリングされます。
<script type="application/json" data-pagehook="articles/index">{"id":123}</script>