[rails] 特定のページのみで実行するJavascript


ruby on railsにおいて、特定のページでのみ実行するJavascriptが必要になることはよくあると思います。今回はassetsパイプラインのメリットを損なうことなく、それを実現するための方法を紹介します。

実現方法と考察

『特定のページでのみ実行するJavascript』を実現するための方法とし、およそ次のようなパターンがあると思います。

  1. View(.erbとか.slimとか)の中で他のjsファイルを読み込むか、直接Javascriptを書く
  2. applicationレイアウト上で、コントローラ名やアクション名を使って javascript_include_tag で読み込むファイルを切り替える
  3. 目的のページがどうかを判定してから処理を実行するようなJavascriptを書く

1.は可能な限り避けられる方法です。Viewの中にJavascriptのソースが混在することになりますし、当然ながらassetsパイプラインの対象になりません。例えば次の例のように、erbファイルの中から他のJSファイルを読み込むコードを記述したとすると・・・

当然ですが other_javascriptapplication.jsの中で読み込まないように、つまりassetsパイプラインの管理外にする必要があります。結果的にproduction環境ではひとつのJSファイルにまとまらないので、その点はデメリットになります。

2.layout/application.html.erbなんかで、読み込むJSファイルを切り替えてやる方法です。

コントローラ名に合わせたJSファイルを用意しておけば、そのコントローラアクセス時に自動的に読み込んでくれます。幾分スマートなやり方に見えますが、本質的には1.の方法となんら変わりません。各コントローラ用のJSファイルは application.jsからはずしておく必要があります。また(設定にもよりますが)コントローラ用JSファイルがないとproduction環境では500エラーになるので、特別な処理が必要でない場合も空のJSファイルを置いておかなければなりません。

そこで3.の方法です。これは、すべてのJSファイルを application.jsの管理下に置くので、assetsパイプラインの恩恵を受けることができます。各JSでは特定のページかどうか判定し、そうでなかったら空振りさせるという塩梅です。判定処理が必要になるというデメリットもありますが、他の2つの方法よりはベターだと思うので、今回はこいつを実装してみたいと思います。

ようやく実装

どうやって特定のページかどうか判断するかは、コントローラ名・アクション名を使うことにします。まずは application.html.erb(というかbodyタグを書いてるとこ)を編集します。

bodyのdata属性にコントローラ名とアクション名を付与するようにしました。ちなみにclassを使用する例もあるようですが、classはcssのためだけに使用し、JS用にはdata属性を使用するのがモダンだと思います。(jQuery流なので、いまさらモダンでもないけど・・・)

続いて、特定のページを判定する共通の関数を作成します。

使い方は Usageの部分にも書きましたが、 "コントローラ名#アクション名"という形式で第一引数を渡してやれば、そのページがロードされた後に第二引数のコールバック関数が実行されます。

例えばCustomersControllerがあったとして、そのコントローラのindexアクションでのみ処理したい場合は、

CustomersControllerの全てのアクションと、ProductsConrollerのindexアクションでのみ処理したい場合だったら、

てな感じです。

なお、サンプルではturbolinks使用するようなってますが、使ってないならonPageLoad関数の冒頭 $(document).on 'turbolinks:load', ->をjqueryのreadyに書き換えればOKです。

on_page_load.coffeeはgistにも置いてあるので良かったら使ってみてください。

関連する記事


[rails] 特定のページのみで実行するJavascript”に関する4件のコメント

  1. rails_beginner

    色々な方法を見てきましたが、このやり方が一番スマートだと思います。
    あなたのonPageLoad使わせて頂きます!

  2. 匿名

    3の方法は、jsコードが特定の"アクション(又はコントローラ)"と対になるようになりますが、JavaScriptのコード自体は"テンプレート"と対になっているはずなので、renderを用いた別のアクションからのテンプレート表示で動作しないということがデメリットですね...
    その場合、onPageLoadに呼び出されるアクションをすべて書くことになりますが、コントローラ側の条件分岐でrenderするテンプレートを分けていた場合に、意図せぬページでjsが実行されたりされなかったり...
    またそうなると、クライアントサイドのプログラム中で、呼び出されるアクション(サーバ側)のことを深く意識しないといけなくなる...

    「特定の"テンプレート"でのみ実行する」 かつ「アセットパイプラインにまとめられる」
    ↑を満たす方法があればいいのですが...難しいですね。

    1. itmammoth

      記事の著者

      おっしゃる通りだと思います。
      少し凝ったアプリだと、テンプレートを共用することがあると思うので、その場合はonPageLoadにアクションを列挙しなければなりません。

      > 「特定の"テンプレート"でのみ実行する」 かつ「アセットパイプラインにまとめられる」
      これが一番理想的だとは思うのですが、renderメソッドをフックするとか、ちょっと凝ったことをしないと実現できないんじゃないかと思ってます。

      という意味では、今回のサンプルは「Good enough(十分に良い)」ではあるかなと思ってます。

コメントを残す

メールアドレスが公開されることはありません。

*