管理画面を作る：AdminLTE Laravel組込み編


管理画面用のフレームワークである[AdminLTEの構造を見てみた](http://qiita.com/zaburo/items/9447fa64e8bc302e2324)ので、今度は、PHPのフレームワークであるLaravel5.1に組み込んで見たいと思います。

なお、Laravelの基本的な知識はあるものとして話を進めます。

##準備

###laravelのインストール
laravelのインストールはComposerで行うことにします。そもそも、Composerが入っていない人は準備して下さい。

```bash
composer create-project laravel/laravel test
```

###AdminLTEの導入
AdminLTEのCSSやJSを設置する場所は、laravelのpublicフォルダになるかと思います。
AdminLTEをダウンロードしてきて直接展開してもいいですし、bowerでインストールしてもいいです。
ここでは、bowerを利用します。Bowerが入っていない人は準備して下さい。

```bash
cd test/public
bower install admin-lte
```
そうすると、public/bower_components/admin-lte以下にファイルが展開されます。
主なフォルダとしては、

* dist AdminLTEの本体系CSS,JS
* bootstrap ベースとなっているBootstrap3.x系ファイル
* plugins jQueryなどなど

それ以外は、サンプルだったりするので、消してもいいかなと思います。
これで、とりあえずファイルの準備は完了です。

##Laravelと統合

ここでいうLaravelとの統合とは、Laravelのテンプレートエンジンであるbladeとパーシャル機能を使った部品化です。

###テンプレート化への前準備
bladeでテンプレート化する前に、そもそもLaravelでAdminLTEで作成した画面が正しく表示されるようにします。

基本編で用意した最低構成のファイルをコピーして、resources/views/以下にadminlte.blade.phpとして保存します。

また、このページがhttp://localhost:8000/admin 等で呼び出しできるようにRouteを設定します。

```php
Route::get('admin',function(){
	return view('adminlte');
});
```
アクセスしてみると、表示はされるものの、CSS等が適用されておらず、表示が崩れています。

これは、admin.blade.phpから、adminLTE等のCSS,JSが正しく参照されていないからです。

\<head>および\<body>の最後のCSSやJSの参照パスをpublic/bower-components/admin-let/以下を参照するように修正します。

以下が、正しく適用したものです。
CDN参照のものはそのままで、ローカルリソースに対して、{{asset("bower_components/admin-lte/~")}}をつけてやる感じです。なお、asset()はpublicフォルダとして解釈されます。

```html
<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <title>adminLTE test</title>
    <!-- for responsive -->
    <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
    <!-- bootstrap -->
    <link href="{{asset("bower_components/admin-lte/bootstrap/css/bootstrap.min.css")}}" rel="stylesheet" type="text/css" />
    <!-- font awesome -->
    <link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" rel="stylesheet" type="text/css" />
    <!-- ionicons -->
    <link href="https://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css" rel="stylesheet" type="text/css" />
    <!-- adminLTE style -->
    <link href="{{asset("bower_components/admin-lte/dist/css/AdminLTE.min.css")}}" rel="stylesheet" type="text/css" />
    <link href="{{asset("bower_components/admin-lte/dist/css/skins/skin-blue.min.css")}}" rel="stylesheet" type="text/css" />
</head>
<body class="skin-blue">
    <div class="wrapper">

        <!-- トップメニュー -->
        <header class="main-header">

            <!-- ロゴ -->
            <a href="" class="logo">管理画面</a>

            <!-- トップメニュー -->
            <nav class="navbar navbar-static-top" role="navigation">
                <ul class="nav navbar-nav">
                    <li><a href="">顧客管理</a></li>
                </ul>
            </nav>

        </header><!-- end header -->


        <!-- サイドバー -->
        <aside class="main-sidebar">
            <section class="sidebar">
                <ul class="sidebar-menu">

                    <!-- メニューヘッダ -->
                    <li class="header">機能一覧</li>

                    <!-- メニュー項目 -->
                    <li><a href="">新規登録</a></li>

                </ul>
            </section>
        </aside><!-- end sidebar -->


        <!-- content -->
        <div class="content-wrapper">

            <!-- コンテンツヘッダ -->
            <section class="content-header">
                <h1>ページタイトル</h1>
            </section>

            <!-- メインコンテンツ -->
            <section class="content">

                <!-- コンテンツ1 -->
                <div class="box">
                    <div class="box-header with-border">
                        <h3 class="box-title">ボックスタイトル</h3>
                    </div>
                    <div class="box-body">
                        <p>ボックスボディー</p>
                    </div>
                </div>

            </section>

        </div><!-- end content -->


        <!-- フッター -->
        <footer class="main-footer">
            <div class="pull-right hidden-xs">Version1.0</div>
            <strong>Copyright &copy; 2015</strong>, All rights reserved.
        </footer><!-- end footer -->


    </div><!-- end wrapper -->
    <!-- JS -->

    <!-- jquery -->
    <script src="{{asset("bower_components/admin-lte/plugins/jQuery/jQuery-2.1.4.min.js")}}" type="text/javascript"></script>
    <!-- bootstrap -->
    <script src="{{asset("bower_components/admin-lte/bootstrap/js/bootstrap.min.js")}}" type="text/javascript"></script>
    <!-- adminLTE -->
    <script src="{{asset("bower_components/admin-lte/dist/js/app.min.js")}}" type="text/javascript"></script>
</body>
</html>
```

http://localhost:8000/admin にアクセスすると、

![adminlte](http://www.bluecode.jp/qiita/adminlte006.png)

と、表示されればOKです。

###テンプレート化する

ベースとなるファイルで正しく表示ができるようになったので、これを分解して、テンプレート化します。
テンプレートの構造は、

* layout.blade.php

を主ファイルとし、共通部分を

* header.blade.php
* sidebar.blade.php
* footer.blade.php

に分散させます。

####layout.blade.phpを作る

layout.blade.phpをviews以下に作成し、先ほど動作を確認したadminlte.blade.phpのコードをコピペします。
adminlte.blade.phpをlayoutにリネームしても大丈夫です。

####header部分を置き換える

layout.blade.phpの\<header>...\</header>部分をカットし、header.blade.phpとしてペースト・保存します。

```php
<!-- トップメニュー -->
<header class="main-header">

    <!-- ロゴ -->
    <a href="" class="logo">管理画面</a>

    <!-- トップメニュー -->
    <nav class="navbar navbar-static-top" role="navigation">
        <ul class="nav navbar-nav">
            <li><a href="">顧客管理</a></li>
        </ul>
    </nav>

</header><!-- end header -->
```
さらに、layout.blade.phpの\<header>...\</header>があった部分を、

```php
@include('header')
```
のように分離したheader部分を読み込むように置き換えます。

同様に、sidebar, footerなども置き換えます。

####sidebarを置き換える
サイドバーの位置にあるコードをカットし、sidebar.blade.phpとして保存します。

```php
<!-- サイドバー -->
<aside class="main-sidebar">
    <section class="sidebar">
        <ul class="sidebar-menu">

            <!-- メニューヘッダ -->
            <li class="header">機能一覧</li>

            <!-- メニュー項目 -->
            <li><a href="">新規登録</a></li>

        </ul>
    </section>
</aside><!-- end sidebar -->
```
サイトバーがあった位置、\<aside class="main-sidebar">...\</aside class="main-sidebar">を

```php
@include('sidebar')
```
に置き換えます。
####footerを置き換える
フッターの位置にあるコードをカットし、footer.blade.phpとして保存します。

```php
<!-- フッター -->
<footer class="main-footer">
    <div class="pull-right hidden-xs">Version1.0</div>
    <strong>Copyright &copy; 2015</strong>, All rights reserved.
</footer><!-- end footer -->
```
フッターがあった\<footer>...\</footer>を、

```php
@include('footer')
```
に置き換えます。

####contentを置き換える

content部分は動的に変わるところなので、include()ではなく、yield('content')に置き換えます。
header等は、関連記述全体を置き換えましたが、contentでは\<div class="content-wrapper">\</div>部は残しています。

これで、テンプレート化は終了です。
layout.blade.php全体を見てみます。共通部分が外部ファイルへ切りだされ、動的部分は@yield('content')となりました。

```php
<!doctype html>
<head>
    <meta charset="utf-8">
    <title>adminLTE test</title>
    <!-- for responsive -->
    <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
    <!-- bootstrap -->
    <link href="{{asset("bower_components/admin-lte/bootstrap/css/bootstrap.min.css")}}" rel="stylesheet" type="text/css" />
    <!-- font awesome -->
    <link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" rel="stylesheet" type="text/css" />
    <!-- ionicons -->
    <link href="https://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css" rel="stylesheet" type="text/css" />
    <!-- adminLTE style -->
    <link href="{{asset("bower_components/admin-lte/dist/css/AdminLTE.min.css")}}" rel="stylesheet" type="text/css" />
    <link href="{{asset("bower_components/admin-lte/dist/css/skins/skin-blue.min.css")}}" rel="stylesheet" type="text/css" />
</head>
<body class="skin-blue">
    <div class="wrapper">

        <!-- ヘッダー -->
        @include('header')


        <!-- サイドバー -->
        @include('sidebar')


        <!-- content -->
        <div class="content-wrapper">

            <!-- コンテンツ -->
            @yield('content')

        </div><!-- end content -->


        <!-- フッター -->
        @include('footer')


    </div><!-- end wrapper -->
    <!-- JS -->

    <!-- jquery -->
    <script src="{{asset("bower_components/admin-lte/plugins/jQuery/jQuery-2.1.4.min.js")}}" type="text/javascript"></script>
    <!-- bootstrap -->
    <script src="{{asset("bower_components/admin-lte/bootstrap/js/bootstrap.min.js")}}" type="text/javascript"></script>
    <!-- adminLTE -->
    <script src="{{asset("bower_components/admin-lte/dist/js/app.min.js")}}" type="text/javascript"></script>
</body>
</html>
```
だいぶ見通しがいい感じになりました。

###表示してみる

では、作成したテンプレートを利用してコンテンツを表示してみます。
とりあえず、さきほど@yield('content')に置き換えた部分を、下記のようにsample.blade.phpとして保存します。

```php
@extends('layout')

@section('content')

<!-- コンテンツヘッダ -->
<section class="content-header">
    <h1>ページタイトル</h1>
</section>

<!-- メインコンテンツ -->
<section class="content">

    <!-- コンテンツ1 -->
    <div class="box">
        <div class="box-header with-border">
            <h3 class="box-title">ボックスタイトル</h3>
        </div>
        <div class="box-body">
            <p>ボックスボディー</p>
        </div>
    </div>

</section>

@endsection
```

layout.blade.phpをレイアウトとして読み込むように@extends('layout')とし、@yield('content')に置き換わる部分を@section('content')...@sectionendで囲いました。

そして、ルートを適切に書き換えます。sample.blade.phpを見に行くようにします。

```php
Route::get('admin',function(){
	return view('sample');
});
```

/admimにアクセスしてみます。

![adminlte](http://www.bluecode.jp/qiita/adminlte006.png)

見た目はまったく変わっていませんが、テンプレート化された状態で表示されています。

###ファイルをまとめる

Laravelのプロジェクトを作成するたび、こんな操作をしてられません。とりあえず、views直下に作成したファイル群をファイルにまとめます。後は、まとめたファイルをgithub等で管理し、都度cloneするなり、各種自動化ツールで、取得するようにすればいいでしょう（もちろん手動でもいいですが）。

ここでは、views以下にadminlteというフォルダを作って、そこに集約します。
修正が必要なファイルは２つ。

* layout.blade.php
* sample.blade.php

です。

####テンプレートの修正

まず、layout.blade.php側でinclude()の記述を修正します。

>@include('header')　=> @include('adminlte.header')

頭に、adminlte.を付与します。headerだけでなく、sidebar,footerについても行います。

####テンプレート利用側の修正

sample.blade.phpの頭でextends()を修正します。

>@extends('layout') => @extends('adminlte.layout')

とします。
完了したら、/adminにアクセスして、表示に問題がないか確認して下さい。

##応用

いくつかの機能がLaravelで正しく動くか試してみます。

###ページネーション
LaravelはBootstrap3互換のページネーションタグを出力しますが、それがAdminLTEで動くかの動作確認をしてみたいと思います。

結論から言えばとりあえずは動きました。ただ、AdminLTEが用意しているページネーションのスタイルに完全に合わせるためには、Laravel側でページネーションをカスタマイズする必要があります。

>ちなみに、表示している顧客データはダミー情報生成ツール[Faker](https://github.com/fzaninotto/Faker)で生成した架空のものですが、偶然実名と一致することもあるでしょうから、モザイクかけときます。

![adminlte](http://www.bluecode.jp/qiita/adminlte007.png)

標準でmigrateできるUserを利用し、Seederを使ってデータを生成しました。
で、コントローラーで、

```php
public function index()
{
    //
    $query = User::query();
    $users = $query->paginate(10);
    return view('users')->with('users',$users);
```
などとし、Viewで、テンプレートを使い、

```html
@extends('adminlte.layout')

@section('content')

<!-- コンテンツヘッダ -->
<section class="content-header">
    <h1>顧客管理</h1>
</section>

<!-- メインコンテンツ -->
<section class="content">

    <!-- コンテンツ1 -->
    <div class="box">
        <div class="box-header with-border">
            {{-- <h3 class="box-title">顧客一覧</h3> --}}

            <form class="form-inline" style="margin:15px;">
                <div class="form-group">
                    <label>検索：</label>
                    <input type="text" name="keyword" class="form-control" style="width:200px;">
                </div>
                <div class="form-group">
                    <input type="submit" value="検索" class="btn btn-primary">
                </div>
            </form>

        </div>
        <div class="box-body">
            <table class="table table-bordered">
            <tr>
                <th>ID</th>
                <th>氏名</th>
                <th>email</th>
            </tr>
            @foreach($users as $user)
                <tr>
                    <td>{{$user->id}}</td>
                    <td>{{$user->name}}</td>
                    <td>{{$user->email}}</td>
                </tr>
            @endforeach
            </table>
        </div>
        <div class="box-footer clearfix">
            {!! $users->render() !!}
        </div>
    </div>
</section>

@endsection
```

としています。ポイントはboxのheaer,footer等をちゃんと利用することでしょうか。
残念ながら一部直接styleを追加して補正しています。。。まあ、仕方ないでしょう。
補正してるところは、

* 検索用のFormに15pxのマージン追加。
* 検索用のinput typeの長さを200pxに。

です。

###Chart.js

AdminLTEでは、いくつかのChartプラグインが最初から含まれているようですが、ここでは簡単に試せるchart.jsを見てみます。

結論から言えば、少し癖があるものの動きまし。とはいえ、Laravel依存なのはchart.jsへのPathくらいですが。

![adminlte](http://www.bluecode.jp/qiita/adminlte008.png)

但し、幾つかやること・注意点があります。

####chart.jsを読み込む
読み込みを共通に行っているlaytou.blade.phpにChart.jsを読み込むための記述をします。
また、javascriptを記述する専用の@include('scripts')を各種JSの読み込み後に入れました（下記は抜粋部）。

```php
<!-- JS -->

<!-- jquery -->
<script src="{{asset("bower_components/admin-lte/plugins/jQuery/jQuery-2.1.4.min.js")}}" type="text/javascript"></script>
<!-- bootstrap -->
<script src="{{asset("bower_components/admin-lte/bootstrap/js/bootstrap.min.js")}}" type="text/javascript"></script>
<!-- adminLTE -->
<script src="{{asset("bower_components/admin-lte/dist/js/app.min.js")}}" type="text/javascript"></script>
<!-- chart.js -->
<script src="{{asset("bower_components/admin-lte/plugins/chartjs/Chart.min.js")}}" type="text/javascript"></script>

@include('scripts')
```

####canvasの配置

Chart.jsでは、canvasに描画されるので、HTMLにChart描画用のcanvasを配置します。
ここではid="line"と配置しています。

```php
@extends('adminlte.layout')

@section('content')

<!-- コンテンツヘッダ -->
<section class="content-header">
    <h1>チャート表示</h1>
</section>

<!-- メインコンテンツ -->
<section class="content">

    <!-- コンテンツ1 -->
    <div class="box">
        <div class="box-header with-border">
            <h3 class="box-title">LineChart</h3>
        </div>
        <div class="box-body">
            <div class="chart">
                <canvas id="line" height="200" width="800"></canvas>
            </div>
        </div>
    </div>

</section>

@endsection
```
Canvasを\<div class="chart">...\</div>で囲ってやることで、Windowsサイズに合わせてリサイズされるようになるようです（まあ、ただ伸び縮みしてるだけですが）。ただ、その伸び縮みロジックがいまいち？？？な感じです。縦横を均等ではなく、一定の比率を取ってやるほうがちゃんと機能するようです。詳細は機械があれば調べますが。。。

####JSを書く

描画用のJavascriptと記述します。ここでは、scripts.blade.phpに切り出したので、そこに書きます。

```php
<script>
$(function(){

	//動的要素
	var axis = ["A","B","C","D"];
	var data = [10,20,30,20];

	//描画設定
	var lineChartData = {
		labels:axis,
		datasets:[
			{
				fillColor:"rgba(220,220,220,0.5)",
				strokeColor:"rgba(220,220,220.1)",
				pointColor:"rgba(220,220,220,1)",
				pointStrokeColor:"#FFF",
				data:data
			}
		]
	};

	//特定のルート時のみ適用
	var url = window.location;
	if(url.pathname == "/chart"){

		var ctx = $("#line")[0].getContext("2d");
		new Chart(ctx).Line(lineChartData);

	}

});
</script>
```

基本はchart.jsのページ等を参考に書くだけです。あと、今回は、layout.blade.phpに@include('scripts')としたので、関係無いページでもJSが実行されます。存在しないIDを参照するとエラーになるので、特定のルート（ここでは/chart）の時だけ、描画処理を行うように記述しています。

動的要素のところをAPIから取得すれば、汎用性が上がります。LaravelでAPIつくるのは[ここ](http://qiita.com/zaburo/items/c054b47ed0a7be465bcf)に書いてます。

###Form

いよいよLaraveとは無関係ですが、この流れでFormのチェックもしておきます。Form関連はBootstrapとほぼ同じようです。

![adminlte](http://www.bluecode.jp/qiita/adminlte009.png)

コードは下記の通り。
\<form>タグの位置をbox-headerやbox-footerの位置を考慮しながら置くと、いい感じになるようです。

```html
@extends('adminlte.layout')

@section('content')

<!-- header -->
<section class="content-header">
	<h1>Formのテスト</h1>
</section>

<!-- body -->
<section class="content">

	<!-- box col-12 -->
	<div class="box box-primary">
		<div class="box-header with-border">
			Simple From
		</div>
		<form>
		<div class="box-body">
				<div class="form-group">
					<label>Email</label>
					<input type="email" class="form-control">
				</div>
				<div class="form-group has-error">
					<label>Password</label>
					<input type="text" class="form-control">
					<span class="help-block">パスワードがおかしいです。</span>
				</div>
				<div class="form-group">
					<label>Select</label>
					<select class="form-control">
						<option>option1</option>
						<option>option2</option>
						<option>option3</option>
					</select>
				</div>
				<div class="form-group">
					<label>File Upload</label>
					<input type="file">
				</div>
				<div class="checkbox">
					<label>
						<input type="checkbox"><b>remember</b>
					</label>
				</div>
				<p><b>Radio</b></p>
				<div class="radio-inline">
					<label>
						<input type="radio" name="gender" value="man"><b>男</b>
					</label>
				</div>
				<div class="radio-inline">
					<label>
						<input type="radio" name="gender" value="woman"><b>女</b>
					</label>
				</div>
				<div class="form-group">
					<label>TextArea</label>
					<textarea class="form-control" rows="3"></textarea>
				</div>
		</div>
		<div class="box-footer">
			<button type="submit" class="btn btn-primary">Submit</button>
		</div>
		</form>
	</div>
</section>

@endsection
```


###参考

基本的な流れは[ここ](https://almsaeedstudio.com/blog/integrate-adminlte-with-laravel)を参考にしました。
参考になりました。