WEB開発をしていると、画面単位で使用するJSファイルやCSSファイルをどう管理するか、悩むことが良くあるのではないでしょうか。
そのせいでコントローラが複雑になってしまったり、コンフィグ化して管理場所が深い位置になってしまったり、スッキリと解決するのがなかなか難しい問題だと思います。共通化に失敗すれば、苦痛にしかなりません。
最近、LaravelのBladeテンプレートを用いて、その辺りがかなり解決できた(気がしている)ので、その例のメモです。Bladeテンプレートでなくても、継承式のテンプレートエンジンであれば、似たような事はできるんだろうと思います。
以下、Laravel 4.2を用いていますが、バージョンはたぶんあまり関係ありません。
1) 全体のレイアウトを管理するビューファイルを作成する
views/layouts/default.blade.php
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
@yield('append_css')
@yield('append_js')
</head>
<body>
<section id="content">
<h2>content</h2>
@yield('content')
</section>
<section id="content2">
<h2>content2</h2>
@yield('content2')
</section>
<section id="content3">
<h2>content3</h2>
@yield('content3')
</section>
</body>
</html>
このレイアウトファイルは、各画面用のビューファイルで @extends('layouts.default') されます。ポイントは @yield() している箇所です。それぞれの使い方は以下です。
@yield('append_css') ... 画面単位で使用するCSSを追加する領域
@yield('append_js') ... 画面単位で使用するJSを追加する領域
@yield('content') ... コンテンツ領域
@yield('content2') ... コンテンツ領域2
@yield('content3') ... コンテンツ領域3
この例では、3つのコンテンツ領域を縦に並べているだけですが、実際にはヘッダ領域になったり、サイドナビ領域になったり、メイン領域になったりするイメージです。
このビューファイルは、レイアウトの大枠しか知りません。
2) 画面用のビューファイルを作成する
views/contents/index/index.blade.php
@extends('layouts.default')
@section('content')
@include('contents/top/_foo')
@stop
@section('content2')
@include('contents/top/_bar')
@stop
@section('content3')
@include('contents/top/_baz')
@stop
それぞれのコンテンツ領域に、後述する細分化されたビューファイルを @include() して展開しています。
このビューファイルは、使用するレイアウトと、各コンテンツ領域でどのコンテンツを使用するかしか知りません。
3) 各コンテンツのビューファイルを作成する
views/contents/index/_foo.blade.php
<h3>foo</h3>
<?php
append_css('assets/css/contents/foo.css');
append_js('assets/js/contents/foo.js');
?>
views/contents/index/_bar.blade.php
<h3>bar</h3>
<?php
append_css('assets/css/contents/bar.css');
append_js('assets/js/contents/bar.js');
?>
views/contents/index/_baz.blade.php
<h3>baz</h3>
<?php
append_css('assets/css/contents/baz.css');
append_js('assets/js/contents/baz.js');
?>
これらのビューファイルは、自身のコンテンツ内容しか知りません。それぞれ内容は適当ですが、ポイントは append_css() と append_js() です。これは自作の関数で、以下になります。
<?php
if (! function_exists('append_css')) {
/**
* @param mixed $styles
*/
function append_css($styles)
{
static $appended = [];
if (is_scalar($styles)) {
$styles = [$styles];
}
foreach ($styles as $style) {
if (in_array($style, $appended)) {
continue;
}
$appended[] = $style;
View::startSection('append_css');
echo HTML::style($style);
View::appendSection();
}
}
}
if (! function_exists('append_js')) {
/**
* @param mixed $scripts
*/
function append_js($scripts)
{
static $appended = [];
if (is_scalar($scripts)) {
$scripts = [$scripts];
}
foreach ($scripts as $script) {
if (in_array($script, $appended)) {
continue;
}
$appended[] = $script;
View::startSection('append_js');
echo HTML::script($script);
View::appendSection();
}
}
}
@section('append_js')
{{ HTML::script('assets/js/contents/bar.js'); }}
@append
の方法で append すると、他ビューで同名のファイルが append された時、重複して読み込まれてしまいます。その防止策として、上記のように関数化しています。
これで、細分化されたコンテンツ単位で、依存するJSやCSSファイルが明確になりました。そのコンテンツが不要になれば、セットで削除するだけです。尚、他のビュー用のJSと連携する場合は、独自イベントを介するのが好ましいと思います。(Backbone.js的な考え方かも。)チーム作業時にコンフリクトしにくくなるメリットもあります。
デメリットは、コンテンツファイルが増えるとJSやCSSもファイル単位で増えるので、その結果、読み込みファイル数が多くなることです。JSは要らない場合もありますが、CSSはどんどん増えるはずなので、似たような考え方でSass等で管理した方が良いかもしれません。(実際には別の事情でそうしています。)
以下、参考までに、Sassを用いた簡単な管理例です。
A) 各コンテンツ用のSCSSファイル (上記の3と対) を作成する
B) 画面単位のSCSSファイル (上記の2と対) を作成して A を @import する
C) 画面単位のビューファイル (上記の2) で B のCSSファイルを append_css() する
D) 当然、各コンテンツのビューファイルでは append_css() しない
全体的に大切なことは、とりあえず適切な単位でファイル分割しておいて、各所で好きに組み合わせて使えること。でしょうか。
この方法はコントローラ側に一切手を付けなくて済むので、わりとおすすめなのですが、もっと良い方法があればぜひ教えて下さい。