- InitializrでHTML5対応版Bootstrapをダウンロード (初回)
- Bootswatchで好きなテーマをダウンロード(2回目)
- CakePHPのプラグインBootStrapをダウンロード(3回目)
- CakePHPのSearchプラグインをダウンロード(3回目)
- CakePHPのDebugKitプラグインをダウンロード(3回目)
- 簡単なデプロイ
- 掲示板を作ってみる
- 掲示板をブラッシュアップしてみる
- クソして寝る
■CakePHPと合体だ!
さて、前回はCakePHPデフォルトのデザインでオリジナルのHTMLを表示したけど、今回はデザインを一気に差し替えてしまおう!
そう、Bootstrapに!
そのためにはまず、デスクトップなどにおいたInitializrフォルダ内の中身を、.htaccess、nginx.conf以外のすべてのファイル、フォルダをCakePHPの/app/webroot/ に上書きしてしまおう。
移動じゃなくてコピーしておくと後が楽かもしれないけど、まぁ任せる。好きにするがよいよ。
なぜ.htaccess、nginx.confを使わないかというと、まずこんなに大量の記述をした.htaccessをページ表示するたびに読み込むのはナンセンスで、大元のhttpd.confにかけや!と言いたいわけだ。同じ理由でnginx.confも元ファイルにかけや!!オッラオラ!!フォルダを上書きしたら、今度はレイアウトファイルだ。
/app/View/Layouts/ に default.ctp ファイルを作成する。
このdefault.ctpの中身は、Initializrフォルダ内のindex.htmlをそのまま貼り付ければOK。
cssやjsのパスは先頭に『/』を付け加えよう。絶対パスで指定だ。
この状態でブラウザで見てみると、こんな感じになったはずだ。
一気に素敵すぎるデザインになって、へそからネギでも出てきそうになるが、慌ててはいけない。これは静的に固定された内容になっているので、WORKGIFTとして色々内容を変えて置かないといけない。
本来CakePHPにはMETAタグ、CSS、JavaScriptなどのヘルパーがあるんだけど、今回の程度なら別に使わないでもOKだし、使わないことによって余計なパースが減るかから数千分の1くらいは軽くなるはず。でもテーマ機能使うんだったらパスが動的に変わるようにヘルパーで書きなおしたほうが良いかもね!まずはdefault.ctpのTITLEタグ、Project Nameなどを適宜編集しよう。
今回はこのようにしておいた。
titleタグ | WORKGIFT |
---|---|
Project Name | WORKGIFT |
footerタグ | © <?php echo date('Y');?> WORKGIFT All Rights Reserved. |
さて、更に編集しよう。今度は、動的に指定された中身を表示するように、不要なコンテンツを削除し、テンプレート変数を当て込む。
削除するのはこの青くなってる部分だ。
削除したら、以下のタグ、<?php echo $content_for_layout; ?>を貼り付ける。この部分が適宜ビューファイルに入れ替わってくれうという事になる。
さて、この状態でブラウザで閲覧すると、こんな感じになる。
かなりカッコよくなったんじゃないかな。
いいね、ナイスじゃ。
TwitterBootstrapプラグインではLESSを使うのを前提にしているらしく、本来であればNodejsとnpmを使ったプラグインのインストールを済ませ、専用のシェルスクリプトからLESSファイルをコンパイルしてコピーするというフローが存在するが、今回は簡易化するため、LESS使わなくてもいけるじゃんという説明もしたいので、LESSは不要とするよ。
■フォームを見てみる
slywalker/TwitterBootstrapのREAMDMEにも書いてあるけど、このデザインでのフォームエレメントを確認することが可能だ。
Usageの『Output form input as Bootstrap format』の中身をそのまま使えばよい。
今回は /app/View/Pages/ に input.ctpというファイルを作り、そこにこの内容を貼り付けることとする。
さて、実はCakePHPのフォームエレメント、TwitterBpootstrapとはかなり違う構造をしてしまっている。
だからそのまま使うとページャが崩れてしまったりするわけだ。これは痛い。
幸いにもここらへん、CakePHP向けに修正されたヘルパーがTwitterBootstrapプラグインに実装されているので、それを使うこととする。
ほとんどすべてのページで使えるように、/app/Controller/ にAppController.php を作成し、以下のように設定しておこう。
これでもうプラグインを意識することなく、キレイなデザインでフォームを作成することができる。
さて、早速input.ctpを見てみようじゃないか。
しかしURLはどうなる?
とりあえず、URLのリクエストパラメータに何も指定がない場合、どのコントローラのどのアクションにどういうパラメータを渡すのかという指定を予め設定することができるんだが、それがPagesコントローラのdisplayアクションで、パラメータがhomeにっている。つまり/app/View/Pages/home.ctp を表示してくれるわけだ。
その部分を変更して、input.ctpを表示させようと思う。
そのファイルは、/app/Config/routes.php だ。
以下のような行があると思う。
Router::connect('/', array('controller' => 'pages', 'action' => 'display', 'home'));
この'home'を'input'にすればOK。
ブラウザで閲覧してみると、こんな感じになったはずだ。
もうイカしすぎて屁で空が飛べそうだ。
むしろ今2cmほど浮いたかも。
■掲示板を作る準備
さて、もうこれでデザインはFIXとしよう。このデザインで何ができるのか、というのは各自調べてもらいたい。
次は簡単な掲示板を作ってみようと思う。
その前に予備知識として、TwitterBootstrapではどういうHTMLの構造を使えばいいのか、どういうクラスを使って表現するのか、動作を伴うコンポーネントの使い方など、詳しい内容は本家Bootstrapのページを見ていただきたい。たいていここだけで全部まかなえるはずだ。
- Scaffolding(足場となる土台の作成)
- Base CSS(基本的なCSSの適用)
- Components(ボタン、タブ、ドロップダウンなどのガジェット)
- JavaScript for Bootstrap(ダイアログ、アラート、カルーセルなどのJSプラグイン)
このうち、レイアウトファイルを作るのであればScaffolding、ビューファイルを作るのであればBase CSSなどを参考にすれば、滞りなくクリエイティブをすすめることができると思う。
というわけで、いちいちこれらのページを見れる状態にしておくのが良いかもね。
使ってるIDEやツールによっては、スニペットとして貼り付けて送って手もあるよ。
では早速、掲示板を作るよ。細かいコントローラの説明などはしないので、気になるようだったらまず、CakePHP2の本家にあるチュートリアルを済ませてみるのが一番手っ取り早いと思う。
CakeBook2.x チュートリアルと例
■掲示板を作ろう
各種コーディングに先立ち、データベースを作っておくとしよう。
具体的には以下のようなテーブルを作っておくことにする。
ログインしたユーザだけが閲覧、書き込み、編集、削除ができるという感じだ。
DROP TABLE IF EXISTS `posts`; CREATE TABLE IF NOT EXISTS `posts` ( `id` int(5) unsigned NOT NULL AUTO_INCREMENT, `pid` int(5) unsigned NOT NULL, `user_id` int(3) unsigned NOT NULL, `subject` varchar(64) NOT NULL, `body` varchar(255) NOT NULL, `created` datetime NOT NULL, `modified` datetime NOT NULL, PRIMARY KEY (`id`), KEY `posts_idx` (`pid`,`user_id`,`subject`,`body`(191)) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; DROP TABLE IF EXISTS `users`; CREATE TABLE IF NOT EXISTS `users` ( `id` int(3) NOT NULL AUTO_INCREMENT, `username` varchar(32) NOT NULL, `password` varchar(40) NOT NULL, `nickname` varchar(32) NOT NULL, `created` datetime NOT NULL, `modified` datetime NOT NULL, PRIMARY KEY (`id`), KEY `users_idx` (`username`,`password`,`nickname`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
今回はユーザ登録は作らない。
なのでパスワードは自前でハッシュしよう。
/app/Config/core.phpに書き込んであるはずのSecurity.saltを使う。
仮に以下のような内容だったとすると、そのSecurity.saltの末尾に任意のパスワード文字列をくっつけてsha1()でハッシュ化すれば良い。
Security.salt:qccruzlqxvvtvxbKQmehkv03xljdzpilzroeeehv
パスワードにしたい文字列:password
コマンドラインだとこうなる。
# php -r "echo sha1('qccruzlqxvvtvxbKQmehkv03xljdzpilzroeeehvpassword').PHP_EOL;"
これで生成されたハッシュ値を、usersテーブルのpasswordフィールドで使おう。
残りのフィールドは適宜好きな情報を入れておくと良い。
例えばusernameフィールドには『bahn@example.com』、nicknameフィールドには君がいつも使っているニックネーム『バンバン☆ビガーパンツ』など。
phpMyAdminなどを使えば直ぐにユーザデータを作成できるだろう。
ユーザを作ったら早速、コーディングを開始しよう。
AppController.phpは、どのページからでも参照されるコントローラだ。だから共通した内容はだいたい全部ここに書いておけば良いことになる。
例えばログインしているのかしていないのか、など。
ログイン機能やセッション機能を使うので、このAppControllerを以下のように編集しておこう。
ついでに、初期設定的なプライベートメソッドの__init()を作っておいた。ここからログイン状態のチェック、ページネータのジャンプメニューの設定なども行なっておくことにした。
/app/Controller/AppController.php
class AppController extends Controller { public $components = array('Auth', 'Session', 'Cookie', 'DebugKit.Toolbar'); public $helpers = array( 'Session', 'Html' => array('className' => 'TwitterBootstrap.BootstrapHtml'), 'Form' => array('className' => 'TwitterBootstrap.BootstrapForm'), 'Paginator' => array('className' => 'TwitterBootstrap.BootstrapPaginator'), ); public function beforeFilter() { parent::beforeFilter(); $this->__init(); } /** * 初期設定用 * * @access private * @return null */ private function __init() { $this->__setPaginateNumbers(); $this->__setLoginStatus(); } /** * ログインステータスのチェック * * @access private * @return null */ private function __setLoginStatus() { $this->Session->delete('Auth.redirect'); if($this->Session->read('login')) { $this->set('login', $this->Session->read('login')); } else { $this->set('login', false); } } /** * ページネータのジャンプメニュー設定 * * @access private * @return null */ private function __setPaginateNumbers() { $pager_numbers = array( 'before' => ' - ', 'after'=> ' - ', 'modulus'=> 10, 'separator'=> null, 'tag' => 'span', 'class' => 'nums', ); $this->set('pager_numbers', $pager_numbers); } }
次に、ログイン機能を使うので、UsersController.phpも作っておこう。やることは単純で、ログインアクションとログアウトアクションくらいだ。
/app/Controller/UsersController.php
class UsersController extends AppController { public $name = 'Users'; public function login() { if ($this->request->is('post')) { if ($this->Auth->login()) { $this->Session->write('login', true); $this->redirect($this->Auth->redirect()); } } } public function logout() { $this->Session->delete('login'); $this->redirect($this->Auth->logout()); } }
掲示板の名前はPostsだ。
/app/Controller/PostsController.phpを作ろう。
このPostsコントローラは以下のように、ポストの追加アクション、編集アクション、削除アクション、一覧表示アクション、詳細アクションとなるメソッドを書いておこう。
/app/Controller/PostsController.php
App::uses('AppController', 'Controller'); class PostsController extends AppController { public $name = 'Posts'; public $uses = array('User', 'Post'); /** * 投稿一覧 * */ public function index() { $this->paginate = array( 'recursive' => 0, 'order' => array('Post.modified' => 'desc'), 'limit' => 10, ); $this->set('posts', $this->paginate('Post')); } /** * 投稿詳細 * * @param type $id * @throws NotFoundException */ public function view($id = null) { if(!$id) { throw new NotFoundException('(;´Д`)IDが無いポ'); } $this->Post->id = $id; $this->set('post', $this->Post->read(null, $id)); } /** * 新規投稿 * */ public function add() { if ($this->request->is('post')) { $this->request->data['Post']['user_id'] = $this->Auth->user('id'); if ($this->Post->save($this->request->data)) { $this->Session->setFlash('保存したYO!'); $this->redirect(array('action' => 'index')); } else { $this->Session->setFlash('保存できぬ!'); } } } /** * 投稿編集 * * @param type $id * @throws NotFoundException */ public function edit($id = null) { if(!$id) { throw new NotFoundException('(´・ω・`)IDが無いポ'); } $this->Post->id = $id; if ($this->request->is('get')) { $this->request->data = $this->Post->read(); } else { $this->request->data['Post']['user_id'] = $this->Auth->user('id'); if ($this->Post->save($this->request->data)) { $this->Session->setFlash('編集したYO!'); $this->redirect(array('action' => 'index')); } else { $this->Session->setFlash('編集できぬ!'); } } } /** * 投稿削除 * * @param type $id * @throws MethodNotAllowedException */ public function delete($id = null) { if($this->request->is('get')) { throw new MethodNotAllowedException('(#゚Д゚)ゴルァ!!'); } if ($this->Post->delete($id)) { $this->Session->setFlash('投稿された内容を消した!'); $this->redirect(array('action' => 'index')); } } }
一つ一つのアクションは結構単受に作ってある。
index()
Postの更新日降順で1ページ10件までのデータを取得し、ビュー変数postsにセット。
view()
このメソッドは引数にIDが必須なので、ない場合は例外を吐いて終了。
あった場合は該当IDがのPostデータを取得し、ビュー変数postに代入。
add()
リクエストメソッドがPOSTの場合、ユーザIDを追加してPostデータに保存。
edit()
このメソッドは引数にIDが必須なので、ない場合は例外を吐いて終了。
リクエストメソッドがGETでなければ、ユーザIDを追加してPostに代入。
そのまま上書き。
delete()
URLでID指定されると任意のIDを削除できてしまうので、リクエストメソッドがPOSTの場合のみ、削除するようにしてある。
さて、これでコントローラは終わりだ。
次はモデルを作ろう。
モデルはUserとPostの2つ。
1Userは複数のPostを持てるので、User has many Postとなる。
つまり、関連付けを行うとすると、$hasMany = array('Post');となる。
/app/Model/User.php
class User extends AppModel { public $name = 'User'; public $hasMany = array('Post'); }
逆の指定として、Post belongs to Userという指定ができる。
これはUserがひとつのPostをもてる時、Userが複数のPostをもてる時でも、逆からになるので一緒の表現になる。
それとPostデータの投稿時、編集時に、入力チェックを行うので、そのルールも記載しておくことにする。
今回は細かい指定はぜす、未入力のみNGとしてある。
/app/Model/Post.php
class Post extends AppModel { public $name = 'Post'; public $belongsTo = array('User'); public $validate = array( 'subject' => array( 'notempty' => array( 'rule' => array('notempty'), 'message' => '件名を入れる!!', ), ), 'body' => array( 'notempty' => array( 'rule' => array('notempty'), 'message' => '本文を入れる!!', ), ), ); }
これでモデルは終わりだ。
次はビューを作って完了となる。
もう少しだ。がんばろう。
まず、大元の土台となるレイアウトファイルを少しだけ編集しよう。
bodyタグ直後に、ページのヘッダの帯があるのだが、ここのメニューを
- ホーム
- ログアウト
そのための処理だ。
/app/View/Layouts/default.ctp
<div class="navbar navbar-fixed-top"> <div class="navbar-inner"> <div class="container"> <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse"> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </a> <a class="brand" href="#">WORKGIFT</a> <div class="nav-collapse"> <ul class="nav"> <li class="active"><a href="/">ホーム</a></li> <?php if($login):?> <li><?php echo $this->Html->link('ログアウト', '/users/logout', null, 'ログアウトする?');?></li> <?php endif?> </ul> </div><!--/.nav-collapse --> </div> </div> </div>liタグのあたりだけ編集してある。3つあったメニューを2個に減らし、先頭を「ホーム」、2個めを条件分岐で「ログアウト」とした。
それから、このdefault.ctpはファイルの最後の方にscriptタグが記載されているので、ビューファイルでjQueryを使うことができない(ビューファイルでjQueryなどを指定しても、ビューファイルの後にscriptタグでjQueryが読み込まれてしまうので動かない)。ここはまるごとheadタグ内に移動しておくことをおすすめする。
上記の変更も含め、具体的には以下のようにしておくのが良い。
/app/View/Layouts/default.ctp
<!doctype html> <!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7" lang="en"> <![endif]--> <!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8" lang="en"> <![endif]--> <!--[if IE 8]> <html class="no-js lt-ie9" lang="en"> <![endif]--> <!--[if gt IE 8]><!--> <html class="no-js" lang="en"> <!--<![endif]--> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <title>WORKGIFT</title> <meta name="description" content=""> <meta name="author" content=""> <meta name="viewport" content="width=device-width"> <link rel="stylesheet" href="/css/bootstrap.min.css"> <style> body { padding-top: 60px; padding-bottom: 40px; } </style> <link rel="stylesheet" href="/css/bootstrap-responsive.min.css"> <link rel="stylesheet" href="/css/style.css"> <script src="/js/libs/modernizr-2.5.3-respond-1.1.0.min.js"></script> <script src="//ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script> <script>window.jQuery || document.write('<script src="js/libs/jquery-1.7.2.min.js"><\/script>')</script> <script src="/js/libs/bootstrap/bootstrap.min.js"></script> <script src="/js/plugins.js"></script> <script src="/js/script.js"></script> <script> var _gaq=[['_setAccount','UA-XXXXX-X'],['_trackPageview']]; (function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0]; g.src=('https:'==location.protocol?'//ssl':'//www')+'.google-analytics.com/ga.js'; s.parentNode.insertBefore(g,s)}(document,'script')); </script> </head> <body> <div class="navbar navbar-fixed-top"> <div class="navbar-inner"> <div class="container"> <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse"> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </a> <a class="brand" href="#">WORKGIFT</a> <div class="nav-collapse"> <ul class="nav"> <li class="active"><a href="/">ホーム</a></li> <?php if($login):?> <li><?php echo $this->Html->link('ログアウト', '/users/logout', null, 'ログアウトする?');?></li> <?php endif?> </ul> </div><!--/.nav-collapse --> </div> </div> </div> <div class="container"> <?php echo $content_for_layout; ?> <hr> <footer> <p>© <?php echo date('Y');?> WORKGIFT All Rights Reserved.</p> </footer> </div> <!-- /container --> </body> </html>
さて、これでビュー編集の準備ができたわけだ。
早速行ってみよう。
作成するビューは以下の5つだけだ。
- /app/View/Users/login.ctp
- /app/View/Posts/index.ctp
- /app/View/Posts/view.ctp
- /app/View/Posts/add.ctp
- /app/View/Posts/edit.ctp
/app/View/Users/login.ctp
<div class="users form"> <?php echo $this->Session->flash('auth'); ?> <?php echo $this->Form->create('User');?> <fieldset> <legend><?php echo __('Eメールとパスワードを入力してログインしてください'); ?></legend> <div class=""> <?php echo $this->Form->input('username',array( 'prepend' => 'Eメール', 'label' => false, 'class' => 'span2', ));?> <?php echo $this->Form->input('password', array( 'prepend' => 'パスワード', 'label' => false, 'class' => 'span2', )); ?> </div> <?php echo $this->Form->submit('ログイン', array('class' => 'btn btn-primary'));?> </fieldset> <?php echo $this->Form->end();?> </div>
/app/View/Posts/index.ctp
<h2>投稿一覧</h2> <div class="btn-toolbar"> <div class="btn-group"> <a class="btn" href="/posts/add"><i class="icon-plus-sign"></i> 新規投稿</a> </div> </div> <?php echo $this->Paginator->pagination(); ?> <table class="table"> <tr> <th><?php echo $this->Paginator->sort('Post.id', 'Id');?></th> <th><?php echo $this->Paginator->sort('Post.subject', '件名');?></th> <th><?php echo $this->Paginator->sort('Post.user_id', '投稿者');?></th> <th><?php echo $this->Paginator->sort('Post.created', '投稿日');?></th> </tr> <?php foreach ($posts as $post): ?> <tr> <td><?php echo $post['Post']['id']; ?></td> <td><?php echo $this->Html->link($post['Post']['subject'], array( 'controller' => 'posts', 'action' => 'view', $post['Post']['id'])); ?></td> <td><?php echo $post['User']['nickname']; ?></td> <td><?php echo $post['Post']['created']; ?></td> </tr> <?php endforeach; ?> </table> <?php echo $this->Paginator->pagination(); ?>
/app/View/Posts/view.ctp
<h2>投稿詳細</h2> <div class="btn-toolbar"> <div class="btn-group"> <a class="btn" href="/"><i class="icon-chevron-left"></i> 戻る</a> <?php echo $this->Html->link('<i class="icon-edit"></i> 編集', array( 'action' => 'edit', $post['Post']['id']), array( 'class' => 'btn', 'escape' => false));?> <?php echo $this->Form->postLink('<i class="icon-remove-sign icon-white"></i> 削除', array('action' => 'delete', $post['Post']['id']), array('confirm' => '削除するよ!', 'class' => 'btn btn-inverse', 'escape' => false));?> </div> </div> <table class="table table-bordered"> <tr> <th>ID</th> <td><?php echo $post['Post']['id']?></td> </tr> <tr> <th>件名</th> <td><?php echo $post['Post']['subject']?></td> </tr> <tr> <th>内容</th> <td><?php echo nl2br($post['Post']['body'])?></td> </tr> <tr> <th>投稿日</th> <td><?php echo $post['Post']['created']?></td> </tr> <tr> <th>更新日</th> <td><?php echo $post['Post']['modified']?></td> </tr> </table>
/app/View/Posts/add.ctp
<h2>新規投稿</h2> <div class="btn-toolbar"> <div class="btn-group"> <a class="btn" href="/"><i class="icon-chevron-left"></i> 戻る</a> </div> </div> <?php echo $this->Form->create('Post');?> <table> <tr> <td><?php echo $this->Form->input('subject', array( 'type' => 'text', 'prepend' => '件名', 'label' => false, 'class' => 'span6'))?></td> </tr> <tr> <td><?php echo $this->Form->input('body', array( 'type' => 'textarea', 'prepend' => '内容', 'label' => false, 'class' => 'span6'))?></td> </tr> </table> <?php echo $this->Form->submit('投稿', array('class' => 'btn btn-primary', 'id' => 'submit'));?> <script type="text/javascript"> $(document).ready(function(){ $('#submit').click(function() { return confirm('投稿してしまうよ!!'); }); $('span.error-message').removeClass('help-inline error-message') .addClass('label label-important'); }); </script>
ファイル最後のscriptは、ポスト時に確認ダイアログを出す処理と、エラーメッセージがBootstrapのCSSになってなかったので、動的に当て込む処理の2つになっている。
/app/View/Posts/edit.ctp
<h2>投稿編集</h2> <div class="btn-toolbar"> <div class="btn-group"> <a class="btn" href="/"><i class="icon-chevron-left"></i> 戻る</a> </div> </div> <?php echo $this->Form->create('Post');?> <table> <tr> <td><?php echo $this->Form->input('subject', array( 'type' => 'text', 'prepend' => '件名', 'label' => false, 'class' => 'span6'))?></td> </tr> <tr> <td><?php echo $this->Form->input('body', array( 'type' => 'textarea', 'prepend' => '内容', 'label' => false, 'class' => 'span6'))?></td> </tr> </table> <?php echo $this->Form->hidden('user_id', array('value' => $this->request->data['Post']['user_id']));?> <?php echo $this->Form->submit('投稿', array('class' => 'btn btn-primary', 'id' => 'submit'));?> <script type="text/javascript"> $(document).ready(function(){ $('#submit').click(function() { return confirm('投稿してしまうよ!!'); }); $('span.error-message').removeClass('help-inline error-message') .addClass('label label-important'); }); </script>
だいたいこんな感じになったはずだ。
実際にログインして色々投稿をしてみたり、編集してみたり、削除してみていただきたい。
■終わり
Initializr、Bootstrap、TwitterBootstrapプラグインでここまで簡単に再現できることがお分かりいただけたと思う。
いろいろなアイデアを直ぐに具現化するためにも、こういったUIフレームワークというのは非常に有益だと思うし、現に多くのサイトですでに使われているわけで、実績もある。
じゃんじゃん使っていくのが良いのではないかと。
というわで終わりだ!!
次でラスト!!!
facebook
twitter
google+
fb share