JavaScript
CakePHP
AWS
rest
勉強会

CSS・Bootstrapによるデザイン - AWS上で構築するRESTfulアプリ勉強会~Web開発ワークショップ~【第8回】マニュアル

More than 3 years have passed since last update.

:large_blue_circle: はじめに

本投稿は、2015/8/28に行われた、CSS・Bootstrapによるデザイン - connpassの内容についてまとめた資料です。

:warning:今後の予定
AWS上で構築するRESTfulアプリ勉強会~Web開発ワークショップ~ - connpass

今回は、Bootstrapを使用して画面をキレイにしていきます。

:warning:2015/8/31 追記
サンプルソースで一部typoおよびレイアウトが崩れる間違いが有りましたので修正しました。

:large_blue_circle: Bootstrapとは?

一言で言うと、CSSフレームワークです。
Bootstrap側で用意されたCSSを使用することで、統一的なデザインを適用することが出来ます。

  • よくあるデザインのパーツが揃っているので、自分で頑張ってCSSを書かなくて良い
  • レスポンシブデザイン対応なので、PC、モバイルにワンソースで対応可能

といったあたりで非常に恩恵を受けられます。
Bootstrapだけで対応できない場合はもちろん独自CSSを書きますが、Bootstrapで用意されているスタイルを上書きすることもできるので、うまくやれば自力でCSSを書く必要を最小限に抑えることが出来ます。

使い方

基本的には、HTMLエレメントに対しclass="bootstrapで用意されているクラス名"と設定するだけでいい感じに統一性のあるデザインにできます。
中には、HTMLを規定通りに書けばナビゲーションバーを表示したり、モーダル画面をポップアップしたりする機能もありますが、
全ての機能を紹介することは出来ないので、全体のレイアウトを決める上で重要かつレスポンシブデザインを行う上で核となる機能である
グリッドシステム
について説明します。

:large_blue_circle: グリッドシステム

containerの中に複数のrow(行)rowの中に複数のcol(列)が含まれる構造を取ります。
で、colの詳細を設定することで、1つのrow内に横に並べるcolの数をコントロールすることが出来ます。

colは、col-{size}-{列数制御値}
というフォーマットで記述します。

この時、「sizeで指定したサイズ以上の画面の横幅がある場合、列数制御値で指定した値の合計が12になるようにcolを指定したエレメントを配置する」、という動きをします。

...わかりにくいですね(笑)
もう少し噛み砕きます。

まず、画面の横幅をbootstrapでは下記に分類しています。

size sizeに指定する文字 ざっくり分類
〜767px xs(xtra small) スマホ
768px〜991px sm(small) タブレット
992px〜1199px md(medium) ノートパソコン
1200px〜 lg(large) デスクトップパソコン

例えば、一つのrow内に6個のcolを作ったとします。下記のような感じ

サンプル
<div class="container">
    <div class="row">   
      <div class="col-sm-4 bg-info">success</div>
      <div class="col-sm-4 bg-danger">danger</div>
      <div class="col-sm-4 bg-info">success</div>
      <div class="col-sm-4 bg-danger">danger</div>
      <div class="col-sm-4 bg-info">success</div>
      <div class="col-sm-4 bg-danger">danger</div>
    </div>
</div>

bg-info, bg-dangerはバック色を指定するclassです。後ほどボタンの説明でもsuccess, danger等が出てきます。

colは、col-sm-4としていますので、

  • 画面幅 = sm(タブレット以上)
  • colのサイズ = 全て4

を指定しています。つまり、

  • タブレット以上の横幅がある場合、3列表示する。
  • スマホの場合は1列で表示する

となるわけです。

...まだちょっとわかりにくいですね(笑)
実際に動かしてみましょう!

bootstrapをテストできるサービス

これ。ログインしなくても使えます。

Bootply - The Bootstrap Playground

bootply.png

これで上のサンプルを入力し、試してみた結果です。

横幅768px以上(808px)

sample_sm1.png

横幅768px未満(765px)

sample_sm2.png

col設定を複数入れる

col設定を複数入れることで、

  • スマホの場合は1列
  • タブレットは2列
  • パソコンなら3列
  • 画面が大きいパソコンは4列

という制御もできます。
下記サンプルを実際に試してみましょう!

複数col設定
<br><br>
<div class="container">
    <div class="row">   
      <div class="col-xs-12 col-sm-6 col-md-4 col-lg-2 bg-info">success</div>
      <div class="col-xs-12 col-sm-6 col-md-4 col-lg-2 bg-danger">danger</div>
      <div class="col-xs-12 col-sm-6 col-md-4 col-lg-2 bg-info">success</div>
      <div class="col-xs-12 col-sm-6 col-md-4 col-lg-2 bg-danger">danger</div>
      <div class="col-xs-12 col-sm-6 col-md-4 col-lg-2 bg-info">success</div>
      <div class="col-xs-12 col-sm-6 col-md-4 col-lg-2 bg-danger">danger</div>
    </div>
</div>

:large_blue_circle: その他、サンプルを幾つか

class設定のサンプルをいつくか紹介します。
下記サンプルは公式サイトからの引用です。
これらのソースを実際に動かしてみましょう。

※他にもたくさんあります。公式サイトを活用しましょう。
英語ですが、ソースとサンプルが載ってるので全然問題ありません!

ボタンの形と色

公式サイトから引用
<!-- Standard button -->
<button type="button" class="btn btn-default">Default</button>

<!-- Provides extra visual weight and identifies the primary action in a set of buttons -->
<button type="button" class="btn btn-primary">Primary</button>

<!-- Indicates a successful or positive action -->
<button type="button" class="btn btn-success">Success</button>

<!-- Contextual button for informational alert messages -->
<button type="button" class="btn btn-info">Info</button>

<!-- Indicates caution should be taken with this action -->
<button type="button" class="btn btn-warning">Warning</button>

<!-- Indicates a dangerous or potentially negative action -->
<button type="button" class="btn btn-danger">Danger</button>

<!-- Deemphasize a button by making it look like a link while maintaining button behavior -->
<button type="button" class="btn btn-link">Link</button>
  • btnでボタンの形
  • `btn-XXXX(primary, success, info, warning, danger)で色

が決まります。

実行結果

buttons.png

テーブル

公式サイトから引用
<table class="table">
      <thead>
        <tr>
          <th>#</th>
          <th>First Name</th>
          <th>Last Name</th>
          <th>Username</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <th scope="row">1</th>
          <td>Mark</td>
          <td>Otto</td>
          <td>@mdo</td>
        </tr>
        <tr>
          <th scope="row">2</th>
          <td>Jacob</td>
          <td>Thornton</td>
          <td>@fat</td>
        </tr>
        <tr>
          <th scope="row">3</th>
          <td>Larry</td>
          <td>the Bird</td>
          <td>@twitter</td>
        </tr>
      </tbody>
    </table>
  • class=tableでデフォルトのスタイル設定

実行結果
table.png

後は、例えば下記を追加すれば簡単にスタイルを変えられます。試してみましょう!

class 効果
table-striped 行ごとに色を変える
table-bordered 枠線を入れる
table-hover マウスオーバ時に行を強調

:large_blue_circle: 今回の内容

これ(前回)を、
before.png

こんな感じにします。

after.png

ワークショップメニュー

  • 事前準備
  • Lesson1 実験
  • Lesson2 実装

という感じですすめます。

:large_blue_circle: 事前準備

事前準備は毎回同じなので、別エントリにまとめています。
全12回の勉強会でやっているGitの使い方 - AWS上で構築するRESTfulアプリ勉強会~Web開発ワークショップ~ - Qiitaを参照してください。
やることは、

  • gitのブランチを整えて今回用ブランチvol/08を作成する

です。まずこれをやりましょう。

:warning: それと、第5回と第6回に不参加の方は、テーブルの修正が必要です。

  • 第5回
    • ユーザ登録用のテーブル作成(ログイン機能実装のため)
  • 第6回
    • TODO一覧テーブルへの列追加(owner列とassignee列。担当者アサイン機能実装のため)

をやってますので、それぞれ下記リンク先を参照して実施して下さい。
ユーザ登録用のテーブル作成
TODO一覧テーブルへの列追加

Bootstrapのダウンロードと配備

Bootstrap公式サイト(version3)はここです。

v4-alpha版も出てますが今回は安定版の3を使用します。

公式サイトからダウンロードします。

https://github.com/twbs/bootstrap/releases/download/v3.3.5/bootstrap-3.3.5-dist.zipをwgetコマンドで。

zipファイルをダウンロード後、/var/www/study/rest-study/app/webroot/上にファイルを展開します。
SSHで開発サーバにログイン後、下記の通りコマンドを実行します。

ダウンロード、展開
cd /var/www/study/rest-study/app/webroot
wget https://github.com/twbs/bootstrap/releases/download/v3.3.5/bootstrap-3.3.5-dist.zip
unzip bootstrap-3.3.5-dist.zip
mv bootstrap-3.3.5-dist bootstrap
rm bootstrap-3.3.5-dist.zip

上記実行すると、下記のようなディレクトリツリーになります。
元のファイルとミニファイされたファイルが混在していますのでミニファイ版を使うことにしてそれ以外の不要なものは消してしまいましょう。
※このまま置いておいても問題ありません。

app/webroot/bootstrap
├── css
│   ├── bootstrap-theme.css        #消す
│   ├── bootstrap-theme.css.map    #消す
│   ├── bootstrap-theme.min.css
│   ├── bootstrap.css              #消す
│   ├── bootstrap.css.map          #消す
│   └── bootstrap.min.css
├── fonts
│   ├── glyphicons-halflings-regular.eot
│   ├── glyphicons-halflings-regular.svg
│   ├── glyphicons-halflings-regular.ttf
│   ├── glyphicons-halflings-regular.woff
│   └── glyphicons-halflings-regular.woff2
└── js
    ├── bootstrap.js               #消す
    ├── bootstrap.min.js           
    └── npm.js                     #消す
app/webroot/bootstrap
├── css
│   ├── bootstrap-theme.min.css
│   └── bootstrap.min.css
├── fonts
│   ├── glyphicons-halflings-regular.eot
│   ├── glyphicons-halflings-regular.svg
│   ├── glyphicons-halflings-regular.ttf
│   ├── glyphicons-halflings-regular.woff
│   └── glyphicons-halflings-regular.woff2
└── js
    └── bootstrap.min.js
  • :white_check_mark: Gitのブランチを整えて、vol/08ブランチを作成。
  • :white_check_mark: テーブル修正がまだの方は修正する。
  • :white_check_mark: Bootstrap関連ファイルをダウンロード&展開

準備ができたら、Lesson1です!

:large_blue_circle: Lesson1 実験

Bootstrap Editor and Playground for JavaScript, CSS, HTML5 and jQuery.
でいろいろ実験してみましょう。
まずは上で示したいくつかのサンプル。
興味あるものを好きなだけ試してみてください。

:large_blue_circle: Lesson2 実装

まずはbootstrapのCSS、Javascriptファイルを使えるようにします。
その後はデザインです!

CSS, Javascriptの設定

CSSはdefault.ctpに追記、javascriptはrequire-configに追記です。

編集するファイル一覧

編集 file 編集概要
修正 app/webroot/js/require-config.js 読み込み設定
修正 app/webroot/js/main.js 読み込み
修正 app/View/Layouts/default.ctp デザイン修正
追加 app/webroot/css/app.css カスタムCSS

require-config.js

bootstrapのjavascriptを読み込む設定をします。

app/webroot/js/require-config.js
 // require設定
 var require = {

    // キャッシュ防止
    urlArgs: "v=" + (new Date()).getTime(),

    // モジュール読み込みのbaseUrlを指定
    baseUrl: '/rest-study/js/',

    // ファイルのpathを指定
    paths : {
        'jquery' : 'lib/jquery-2.1.3.min',
        'underscore' : 'lib/underscore-min',
        'backbone' : 'lib/backbone-min',
        'marionette' : 'lib/backbone.marionette.min',
+       'bootstrap' : '../bootstrap/js/bootstrap.min'
    },

    // ファイルの依存関係を指定
    shim : {
        'jquery' : {
            exports : '$'
        },
        'underscore' : {
            deps : ['jquery'],
            exports : '_'
        },
        'backbone' : {
            deps : ['jquery', 'underscore'],
            exports : 'Backbone'
        },
        'marionette' : {
            deps : ['backbone'],
            exports : 'Marionette'
        },
+       'bootstrap' : {
+           deps : ['jquery']
+       }
    }
}; 
  • pathsにbootstrapをインストールしたパスを指定
    • baseUrl: '/rest-study/js/'の指定があるので注意
  • shimにbootstrapがjqueryに依存している設定を追加

main.js

require-confis.jsの設定だけだと読み込まれないので、読み込みます。

app/webroot/js/main.js
//開始
 console.log('load main');
 require([
    'marionette',
+   'bootstrap'
 ], 
 function(){
    console.log('run main');
    require(['app'], function(Application){
        console.log('run main2');
        window.application = new Application();
    });
 }); 
  • require関数の引数に、require-configで指定した名前bootstrapを指定して読み込みます。

まずはここまでやってみましょう。

  • :white_check_mark: app/webroot/js/require-config.jsを上記の通り修正。
  • :white_check_mark: app/webroot/js/main.jsを上記の通り修正。
  • :white_check_mark: 動作確認(まだ何も変わりません。変わらず動くことを確認しましょう)!

HTMLを再デザイン

前回までのhtml(default.ctp)は、「とりあえず表示できれば良い」という最低限のものしか書いていません。
今回、グリッドシステム等を使用するために、HTMLの構造も少し修正します。
そしてメインとなるclassの設定をします。
また、bootstrapのみでなく、自作のスタイルも設定しています。

:sunny::sunny::sunny: 下記はあくまで一例です。好きなようにデザインしてみましょう!:sunny::sunny::sunny:
※一度このままやってみて後で修正するといいかもしれません。

まず、下記default.ctp内でbootstrap.min.cssの読み込み部分だけ追記してみてください。
これだけですでにデザインが変わっているはずです!
ボタンの見た目が少し変わっている程度ですが、確認しましょう。

default.ctp

今回の修正のメインとなるファイルです。
修正量が多いので、diffでなく全量載せています。
diffはこちらからどうぞ 第8回 実装 · app/View/Layouts/default.ctp

app/View/Layouts/default.ctp
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <!-- Bootstrap CSS -->
    <link href="bootstrap/css/bootstrap.min.css" rel="stylesheet">
    <!-- Application CSS -->
    <link href="css/app.css" rel="stylesheet">
    <title>シンプルTODOアプリ</title>
</head>
<body>
    <!-- ヘッダ -->
    <div id="header" class="container"></div>
    <!-- コンテンツ -->
    <div id="main" class="container"></div>

    <!-- ヘッダのテンプレート -->
    <script type="text/template" id="header-template">
        <div>
            <div class="row header">
                <div class="form-group col-xs-12">
                    <form class="form-inline pull-right">
                        <label for="logout"><%- username %><%- name %></label>
                        <input type="button" class="btn btn-default btn-sm" id="logout" value="ログアウト">
                    </form>
                </div>
            </div>
        </div>
    </script>

    <!-- TODO一覧表示のレイアウトテンプレート -->
    <script type="text/template" id="todo-layout-template">
        <div>
            <h1>TODOリスト</h1>
            <div id="todo-lists"></div>
        </div>
    </script>

    <!-- TODO一覧表示のテンプレート -->
    <script type="text/template" id="todo-composite-template">
        <div class="row">
            <div class="col-xs-12">
                <span class="row form-inline">
                    <div class="input-group col-sm-6 col-xs-12">
                        <label for="new-todo" class="visible-xs">Todo</label>
                        <textarea class="form-control todo-item-text" rows="3" id="new-todo" placeholder="Todo?" autofocus></textarea>
                    </div>
                    <div class="input-group col-sm-3 col-xs-12">
                        <label for="user-list" class="visible-xs">担当者</label>
                        <select class="form-control" name="assignee" id="user-list"></select>
                    </div>
                    <div class="input-group col-sm-3 col-xs-12">
                        <label class="visible-xs"></label>
                        <input type="button" id="addTodo" class="btn btn-primary btn-md todo-action-button" value="追加">
                    </div>
                </span>
            </div>
        </div>
        <div class="row">
            <div class="col-xs-12">
                <table class="table table-striped table-bordered table-hover">
                    <thead>
                        <tr class="success">
                            <th class="col-sm-6" colspan="2">ToDo</th>
                            <th class="col-sm-2">オーナ</th>
                            <th class="col-sm-2">担当</th>
                            <th class="col-sm-2" colspan="2"></th>
                        </tr>
                    </thead>
                    <tbody></tbody>
                </table>
            </div>
        </div>
    </script>

    <!-- TODO一行分のテンプレート(上のtbody部分に挿入される) -->
    <script type="text/template" id="todo-item-template">
        <td colspan="2">
            <div class="checkbox">
                <label class="todo-item-text">
                    <input type="checkbox" class="toggle" <%- status === '1' ? 'checked' : '' %>><%- todo %>
                </label>
            </div>
        </td>
        <td>
            <span><%- Owner.name %></span>
        </td>
        <td>
            <span><%- Assignee.name %></span>
        </td>
        <td class="text-center">
            <div class="btn-group">
                <a class="btn btn-danger remove-link todo-item-button" href="#">削除</a>
            </div>
        </td>
        <td>
            <div class="btn-group">
                <a class="btn btn-success detail-link todo-item-button" href="#todo-lists/<%- id %>">詳細</a>
            </div>
        </td>
    </script>

    <!-- 詳細画面のレイアウトテンプレート -->
    <script type="text/template" id="todo-detail-layout-template">
        <div id="todo-item" class="container"></div>
    </script>

    <!-- 詳細画面の表示内容テンプレート -->
    <script type="text/template" id="todo-detail-item-template">
        <div class="row">
            <div class="col-xs-12">
                <h2>Todo #<%- id %></h2>
            </div>
        </div>
        <div class="row">
            <div class="col-xs-12">
                <div>
                    <form role="form">
                        <div class="row">
                            <div class="form-group col-sm-6">
                                <label for="edit-todo">Todo</label>
                                <textarea class="form-control todo-item-text" rows="6" id="edit-todo" autofocus placeholder="Todo?"><%- todo %></textarea>
                            </div>
                        </div>
                        <div class="row">
                            <div class="form-group col-sm-3">
                                <label for="user-list">担当者</label>
                                <select class="form-control"  name="assignee" id="user-list">
                                </select>
                            </div>
                        </div>
                        <div class="row">
                            <div class="form-group col-xs-12">
                                <input type="button" class="btn btn-primary todo-action-button" id="updateTodo" value="更新"></input>
                                <input type="button" class="btn btn-default todo-action-button" id="updateCancel" value="キャンセル"></input>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </script>

    <!-- ログイン画面テンプレート -->
    <script type="text/template" id="login-layout-template">
        <form role="form">
            <h2>ログイン</h2>
            <div class="row">
                <div class="col-sm-1">
                </div>
                <div class="form-group col-sm-5">
                    <label for="username">ユーザ名</label>
                    <input class="form-control" type="text" id="username" placeholder="username" autofocus></input>
                </div>
            </div>
            <div class="row">
                <div class="col-sm-1">
                </div>
                <div class="form-group col-sm-5">
                    <label for="password">パスワード</label>
                    <input class="form-control" type="password" id="password" placeholder="password"></input>
                </div>
                </div>
            <div class="row">
                <div class="col-sm-1">
                </div>
                <div class="form-group col-sm-12">
                    <input type="button" class="btn btn-success todo-action-button" id="login" value="ログイン"></input>
                </div>
            </div>
        </form>
        <form role="form">
            <h2>ユーザ登録</h2>
            <div class="row">
                <div class="col-sm-1">
                </div>
                <div class="form-group col-sm-5">
                    <label for="username">ユーザ名</label>
                    <input class="form-control" type="text" id="signup-username" placeholder="username"></input>
                </div>
            </div>
            <div class="row">
                <div class="col-sm-1">
                </div>
                <div class="form-group col-sm-5">
                    <label for="password">氏名</label>
                    <input class="form-control" type="text" id="signup-name" placeholder="name"></input>
                </div>
            </div>
            <div class="row">
                <div class="col-sm-1">
                </div>
                <div class="form-group col-sm-5">
                    <label for="password">パスワード</label>
                    <input class="form-control" type="password" id="signup-password" placeholder="password"></input>
                </div>
            </div>
            <div class="row">
                <div class="col-sm-1">
                </div>
                <div class="form-group col-sm-12">
                    <input type="button" class="btn btn-warning todo-action-button" id="signup" value="登録"></input>
                </div>
            </div>
        </form>
    </script>

    <!-- require -->
    <script type="text/javascript" src="js/require-config.js"></script>
    <script type="text/javascript" src="js/lib/require.js" data-main="main.js"></script>

</body>
</html>
  • ポイント
    • bootstrap.min.cssを読み込んだ後にapp.css(独自CSS)を読み込んでいます。
      • bootstrapで定義されているCSSを一部app.css上書きしているため、後で読み込みます。
    • app.cssではボタンの横幅など、独自CSSを定義しています。
    • 随所で「グリッドシステム」を使っています。
  • 部分的にコピペして、Bootplyで試しながら確認してみましょう。

app.css

app/webroot/css/app.css
/* インラインフォーム内を下寄せ(bootstrap設定上書き) */
@media (min-width: 768px) {
    .form-inline .input-group {
      display: inline-block;
      vertical-align: bottom;
    }
}

/* Todo一覧内のボタンの幅 */
.todo-item-button {
    width: 70px;
}

/* その他のボタンの幅 */
.todo-action-button {
    width: 120px;
}

/* テーブル内全て改行、縦中央寄せ */
.table > tbody > tr > td {
    word-break: break-all;
    vertical-align: middle;
}

/* Todoのフォントサイズを大きく */
.todo-item-text {
    font-size: large;
}

/* ヘッダ部分のレイアウト */
.header {
    margin: 5px;
    padding: 10px 10px 0px 10px;
    background-color: darkslategray;
    color: lightcyan;
}
  • すべてコメントにあるとおりです:grin:

実装

では、実際に修正して画面のデザインを確認をしてみましょう。

  • :white_check_mark: app/View/Layouts/default.ctpを上記の通り修正。
  • :white_check_mark: app/webroot/css/app.cssを上記の通り作成して追加。
  • :white_check_mark: 動作確認!
  • :white_check_mark: Gitにコミット

:warning: GitHubでのdiff表示へのリンク

第8回 実装 · suzukishouten-study/rest-study@190c35c

以上です!

次回予告

ダウンロード・アップロード機能の実装 - connpassです。
CSVファイルのアップロードなんかは業務アプリケーションではよく有りますね。
ダウンロードも単なるリンクでなくサーバで生成してダウンロードできるようにします。
次回はサーバ、クライアント両方さわるので結構大変かな!がんばりましょー!

コメント/フィードバックお待ちしております。

参加者の方も、そうでない方もお気づきの点があればお願い致します。