ページ毎に実行するスクリプトを定義できる Pagehook というライブラリを作りました

Web アプリ作ってると、ページごとに異なる JavaScript を実行したいというケースは良くあります。

インラインで書く?

一番素直な方法は、テンプレートにインラインで JavaScript を書くことですが、下記の問題があり、あまり良い手ではありません。

  • CoffeeScript で書いたり minify したり lint したりできない。
  • 共通化できない。
  • コードがあちこちに分散する。
  • 高速化のために body の最後でライブラリを読んでると、それより後に書かないといけない。
  • リソースの ID などを渡すためのに、JS のコードの一部を動的に生成すると、XSS の危険性が高まる。
  • turbolinks で読み込まれたページだと実行されない。

URL Dispatcher で書く?

URL を解析して、それぞれの場合の処理を書くという方法もあります。

Rails でいう routes.rb みたいなものです。dispatcher や 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>