CodeZine(コードジン)

特集ページ一覧

AngularJSとBootstrapを組み合わせてUIコンポーネントを作ってみよう

AngularJSで初めるJavaScriptフレームワーク開発スタイル 第12回

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2015/09/18 14:00

ダウンロード AngularJS_Part12.zip (517.4 KB)

目次

入力コンポーネントを作ってみる(1)

 続いて図2に示すようなボタングループのUIで構成するセレクトやラジオボタンと同様の機能を実現してみます。

図2 ボタングループ
図2 ボタングループ

 今回は先ほどの例と違いこれまでよりもずっと複雑になります。具体的には、以下の方法を説明します。

  1. 親子関係をもったディレクティブの作成方法
  2. 複数のディレクティブを連携するためのコントローラの作成と利用方法
  3. ngModelを使った値の取得と、値の設定などの方法

実際に作成するコンポーネントの概要

 今回作成するコンポーネントは図2に示すように複数のボタンをラジオボタンのように機能させます。実際にDOM上のHTMLはリスト4のようになります。

リスト4 DOM上に構築されるコード例
<div class="btn-group" role="group" >
        <button type="button" class="btn btn-primary">はい</button>
        <button type="button" class="btn btn-default">どちらでもない</button>
        <button type="button" class="btn btn-default">いいえ</button>
</div>

 実際にボタンを押した際にそれぞれのボタンのclass指定をbtn-defaultからbtn-primaryに変えることで、現在押されているボタンがわかるようにします。続いて、このコンポーネントを使用する際にHTMLテンプレートに記述するコードを考えます。ここで注意すべきことは、選択肢をどのように定義するかです。この設計によって作成するディレクティブのコードも変わってきます。1つのディレクティブで、属性で選択肢を管理するのが最も簡単な実装方法ではありますが、今回は、リスト5のようにタグの親子関係は同じように保つよう、buttonGroupディレクティブとbuttonItemディレクティブを作成します。

リスト5 コンポーネントの利用例
<button-group ng-model="value"> <!--(1)ng-modelが必ずある -->
    <!--(2)選択肢は、button-itemタグとして自由に追加、削除できる -->
    <button-item value="yes">はい</button-item>
    <button-item value="other">どちらでもない</button-item>
    <button-item value="no">いいえ</button-item>
</button-group>

 また、見た目以外では、(1)入力コンポーネントとしてのngModelを必須とするようにします。そして、(2)選択肢はbuttonItemディレクティブをbuttonGroupディレクティブの子として追加できるようし、選択された時の値をvalue属性で定義し、表示するボタンのラベル等はタグ内に自由に記述できるようにします。

ディレクティブの構造

 今回、図3に示すように少々ディレクティブの構造と関係が複雑になり、また、ディレクティブ同士の連携のためにコントローラも使用します。ただし、単純な親子関係があるようなディレクティブではおおよそこのような構造になると思います。

図3 作成するディレクティブ構造の概要
図3 作成するディレクティブ構造の概要

 この構造をもとにディレクティブの定義を行ったのがリスト6になります。

リスト6 ディレクティブの実装の全体(js/directives/ButtonGroup.jsの抜粋)
//(1)コンポーネントとして独立したモジュールとして定義する
var module = angular.module('ui.buttonGroup',[]);
module.directive('buttonGroup',[ButtonGroup]);
module.directive('buttonItem',[ButtonItem]);

//(2)buttonGroupディレクティブで使用するコントローラ
function ButtonGroupController($scope){
    //(省略)
}
//(3)buttonGroupディレクティブの定義
function ButtonGroup(){
    return {
        restrict : 'E',
        scope : {},
        replace    : true,
        controller : ['$scope',ButtonGroupController],
        require : ['buttonGroup','ngModel'], //(4)必要なディレクティブの指定(複数指定)
        transclude : true,                 //(5)ディレクティブ内の記述をテンプレートとして扱うか?
        template : '<div class="btn-group" role="group" ng-transclude></div>', //(6)テンプレート本文
        compile : function(element,attrs){   //(7)compileメソッドからlinkメソッドを返す
            var link = function($scope,$element,$attrs,controllers){
                //(省略)
            };
            return link;
        }
    }
}
//(8)buttonItemディレクティブの定義
function ButtonItem(){
    return {
        restrict : 'E',
        scope : {},
        replace : true,
        require : '^^buttonGroup',         //(9)必要なディレクティブの指定(1つのみ)
        transclude : true,
        template: '<button type="button" class="btn" ng-class="{ \'btn-primary\' : active,  \'btn-default\' : !active }" ng-transclude></button>',
        compile : function(element,attrs){
            var link = function($scope,$element,$attrs,buttonGroupController){
                //(省略)
            };
            return link;
        }
    }
}

 (1)ではコンポーネントを1つのモジュールとして定義し、その中で2つのディレクティブを定義しています。再利用性を高くするために別のモジュールとして定義しています。

 (2)ではbuttonGroupディレクティブのButtonGroupControllerの定義をします。ここではまず、コントローラとディレクティブの関係に着目して説明しますので中身は省略します。(3)ではbuttonGroupディレクティブの定義、(8)ではbuttonItemディレクティブの定義をしていますが、おおよそ同じような属性の構造になっているのがわかると思います。

 続いて(4)と(9)のrequire属性では、作成するディレクティブが他のディレクティブの依存(指定したディレクティブのコントローラ)の指定を行います。この指定に応じて、親子関係がチェックができることとlinkメソッドで取得できるコントローラが変わります。また、require属性では以下のような指定ができます。

表 require属性で指定できる記述
指定方法 説明
ディレクティブ名 自分自身のディレクティブと同じ要素に指定したディレクティブが必ず存在すること。
? + ディレクティブ名 自分自身のディレクティブと同じ要素に指定したディレクティブが存在すること。ただし、見つからない場合にはnullをコントローラに指定
^ + ディレクティブ名 指定したディレクティブが自分自身を含めて親ノードをたどって必ず存在すること。
?^ + ディレクティブ名 指定したディレクティブが自分自身を含めて親ノードをたどって存在すること。ただし、見つからない場合にはnullをコントローラに指定
^^ + ディレクティブ名 指定したディレクティブが自分自身を含めず親ノードをたどって必ず存在すること。
?^^ + ディレクティブ名 指定したディレクティブが自分自身を含めず親ノードをたどって存在すること。ただし、見つからない場合にはnullをコントローラに指定

 (5)ではtransclue属性をtrueに指定しています。こちらはディレクティブ配下のコンテンツをテンプレートとして扱うかの指定でtrueもしくは'element'が指定できます。子にbuttonItemディレクティブがあり、この記述をテンプレートとして実行する必要がありますのでtrueを指定します。'element'はその要素自身もテンプレートとして扱う場合でngRepeatなどで使われています。

 また、子をテンプレートとした場合に、実際には親テンプレートのどこにコンテンツを挿入するのかがわからなくなります。そのために、(6)のようにテンプレート側にはng-transclude属性を指定して、その場所を指定する必要があります。この関係を表したものが図4です。

図4 ディレクティブで指定したtranscludeとテンプレートの関係
図4 ディレクティブで指定したtranscludeとテンプレートの関係

 最後に、(7)のcompileメソッドとその中でリターンされているlinkメソッドについて、サンプル上はcompileメソッドでは何もしていませんので特にcompileメソッドを定義しなくてもよいのですが、今回は、説明もかねてcompileメソッドを定義しました。

 このcompileメソッドはテンプレートをパースする際に呼ばれるメソッドで、まだ、スコープが取得できないのと、実際にテンプレートにコンテンツが差し込まれる前の時点で処理をしたい場合に実装します。例えば、属性に指定した値に応じて、スコープを使わず自分で値を処理したい場合にもこのcompile処理の中で事前準備をするなどの利用ができます。

 また、compileメソッドを定義した場合には、そのメソッド内でlinkメソッドを返す必要があります。筆者の場合には、compileメソッドの実装が必要になるケースを想定して、このような構造をデフォルトとして記述しています。


  • LINEで送る
  • このエントリーをはてなブックマークに追加

バックナンバー

連載:AngularJSではじめるJavaScriptフレームワーク開発スタイル

もっと読む

著者プロフィール

  • WINGSプロジェクト 小林 昌弘(コバヤシ マサヒロ)

    <WINGSプロジェクトについて> 有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。個人紹介主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2018年11月時点での登録メンバは55名で、現在も執筆メンバを募集中。興味のある方は、どしど...

  • 山田 祥寛(ヤマダ ヨシヒロ)

    静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for ASP/ASP.NET。執筆コミュニティ「WINGSプロジェクト」代表。 主な著書に「入門シリーズ(サーバサイドAjax/XM...

あなたにオススメ

All contents copyright © 2005-2021 Shoeisha Co., Ltd. All rights reserved. ver.1.5