大塔です。今回は1日の業務時間の空き時間を使ってAngularJSの機能を使って即興で行きつけのお店をAngularJSを使ってアプリっぽくしてみました。あまり時間をかけられなかったため、かなり荒削りで恥ずかしい部分もありますが、AngularJSの機能をこれで紹介できればと思います。Directive,ngIncludeについて紹介します。なお、画像はお店の方の了承をいただいて使っています。4つのアイコンはクリックできますが、タブバーには何のイベントも設定していないので、ただの飾りです。
ディレクトリ構造は以下の図のようになっています。
index.html
app.js
まず、見慣れないタグがindex.htmlを見ていると飛び込んでくると思います。$ltbody>にはこの3つのタグと全体を囲うdivタグしか記述されていないことが見て取れると思います。
「ん???....こんなHTMLタグ見たことないぞ?」と思われかもしれません。AngularJSを触っている方はご存知かと思いますが、AngularJSではDirectiveという機能を使って自分でhtmlタグを作成することができます。このタグを定義している部分を見てみましょう。app.jsで定義してあります。
上記の記述はngAnimateというモジュールを読み込んでMonaAppというモジュールを新たに定義するという趣旨の記述です。ここに数珠つなぎのように.directive()でチェーンして、ディレクティブを定義しています。MonaAppというのは私が勝手に定義したモジュールです。index.htmlの冒頭でng-app=MonaAppという記述を行い、MonaAppを適用しています。
このMonaAppに属するディレクティブとして、<my-header>、<my-container>、<my-footter>という3つのディレクティブを定義しています。参考として<my-header>を見てみましょう。
AngularJSで定義したmyHeaderというディレクティブを実際にhtmlに記述する場合はmy-headerとなることに注意してください。第1引数でディレクティブ名を、第2引数でオブジェクトを戻り値として返す関数を定義しています。
中を見ていきましょう。
restrictはこのディレクティブをどのような形でhtml中で用いるかを定義するプロパティです。ここではE (Element) 、すなわちhtmlの要素、タグとして、ディレクティブを用いることを定義しています。他にもrestrict:Aとして<div my-header></div>htmlの属性 (Attribute) としてディレクティブを用いることができるように定義したり、色々な形でディレクティブを定義することができます。
templateUrlにはそのディレクティブ中で読み込むテンプレートのURLを定義します。ここではテンプレートとしてtemplates/header.htmlを指定しています。
実際にtemplates/header.htmlを見てみましょう。
この上記のHTMLがindex.html中の<my-header></my-header>という記述一行で読み込まれることになります。1つのファイル中のhtmlの記述が増えてくると、ごちゃごちゃして見にくくなってくる経験があると思います。AngularJSではディレクティブという機能を提供することによって、htmlをコンポーネントごとに分割できるようになっています。コンポーネントを作る以外にもdirectiveには様々なことができますので、是非、興味のある方は調べてみてください。
次に、ngIncludeについて紹介します。ngIncludeを用いることで動的に読み込むテンプレートを変更することができます。まずは<my-container>で読み込んでいるtemplates/container.htmlを見てみましょう。
ngIncludeを使ったことのない方はng-includeという見慣れない属性があることが見て取れると思います。ここではng-includeでtemplate.urlを指定しています。app.jsを見てみましょう。
ng-include="template"は上記の最下部の$scope.templateを参照しています。上記のコードを見ると$scope.templateには$scope.templates[0]、すなわち{ name: 'navigator.html', url: 'templates/navigator.html'}が代入されています。このオブジェクトのurlの部分、すなわち'templates/navigator.html'がng-include="template.url"という記述で読み込まれます。
このng-includeで読み込まれるhtmlテンプレートであるtemplates/navigator.htmlは下記のように記述されています。
ちょっと変な書き方ですが、4つの<li>タグにngClickという属性を指定しています。そのng-clickにはchangePage()という関数を定義しています。changePage()を定義しているapp.jsを見てみましょう。
ng-click="changePage()"はapp.jsの$scope.changePage()を参照します。changePage()関数は第1引数として、遷移先のページを表す文字列を受け取ります。そして、その文字列に対応して$scope.templateの値を切り替えています。すなわち、<li ng-click="changePage()">というタグをクリック、タップした時に対応するページにtemplates/container.htmlの<div id="animation" class="slide-animate" ng-include="template.url" ></div>は切り替わります。このようにngIncludeを用いることで動的にテンプレートを切り替えることができます。
<li>をクリックした際にアニメーションをしながらページが切り替わると思います。もう一度、templates/container.htmlを見てみましょう。
ng-include属性が定義してあるdivタグに"slide-animate"というCSSクラスが適用されているのが見て取れると思います。ngIncludeは自分にかけられているCSSのクラスを自動的に探して、そのCSSをページの切り替わり時に反映します。style.cssにこれらのクラスが定義してあるので見てみましょう。
上記のCSSクラスの内
新しく入ってくるテンプレート (新しい画面) に対しては.ng-enter.ng-enter-activeがCSSとして追加されます。追加されたCSSはアニメーション完了後に削除されます。画面から消えるテンプレート (元の画面) に対しては.ng-leaveng-leave-activeがCSSとして要素に追加されます。追加されたCSSはアニメーション完了後に削除されます。ここでは新しく入ってくる画面は画面の右端からアニメーションをしながら入ってきて、元の画面は今ある位置から画面右にスライドしながら消えていくCSSを適用しています。それぞれのアニメーションの秒数は0.43秒に設定しています。考え方がちょっと特殊なので、慣れるまでは使いこなすのが大変かと思いますが (自分もまだ使いこなせていません) アニメーションをかける関数などを用意しなくて良いので、その点では便利だと思います。ngAnimateに関してはこのブログが詳しいので、興味のある方はこちらを参照してみてください。
まったくもって完全に余談なのですが、ここのお店のランチセット(餃子、カレー、ハンバーグ)などはかなりおいしいです。近所に寄った時は是非ご賞味ください!!!
ディレクトリ構造は以下の図のようになっています。
index.html
- <!doctype
html> - <html
ng-app="MonaApp"> <head> <meta charset="UTF-8"/> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <link rel="stylesheet" href="css/normalize.css"/> <link rel="stylesheet" href="css/font-awesome.min.css"/> <link rel="stylesheet" type="text/css" href="css/style.css" /> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.3/angular.min.js"></script> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.3/angular-animate.min.js"></script> <script type="text/javascript" src="js/app.js"></script> - </head>
- <body>
<div ng-controller="MonaCtrl"> <my-header></my-header> <my-container></my-container> <my-footter></my-footter> </div> - </body>
- </html>
app.js
- var
MonaApp = angular.module('MonaApp', ['ngAnimate']) .directive('myHeader', function() { return { restrict: "E", templateUrl: 'templates/header.html' }; }) .directive('myContainer', function() { return { restrict: "E", templateUrl: 'templates/container.html' }; }) .directive('myFootter', function() { return { restrict: "E", templateUrl: 'templates/footer.html' }; }); function MonaCtrl($scope) { $scope.templates = [ { name: 'navigator.html', url: 'templates/navigator.html'}, { name: 'info.html', url: 'templates/info.html'}, { name: 'map.html', url: 'templates/map.html'}, { name: 'diet.html', url: 'templates/diet.html'}, { name: 'flower.html', url: 'templates/flower.html'} ]; $scope.changePage = function(page, isback){ if (typeof isback !== "undefined" && isback !== null) { $scope.isBack = isback; } switch (page) { case "home": $scope.template = $scope.templates[0]; break; case "info": $scope.template = $scope.templates[1]; break; case "map": $scope.template = $scope.templates[2]; break; case "diet": $scope.template = $scope.templates[3]; break; case "flower": $scope.template = $scope.templates[4]; break; } } $scope.template = $scope.templates[0]; - }
Directive
まず、見慣れないタグがindex.htmlを見ていると飛び込んでくると思います。$ltbody>にはこの3つのタグと全体を囲うdivタグしか記述されていないことが見て取れると思います。
- ....
<my-header></my-header> <my-container></my-container> <my-footter></my-footter> - ....
「ん???....こんなHTMLタグ見たことないぞ?」と思われかもしれません。AngularJSを触っている方はご存知かと思いますが、AngularJSではDirectiveという機能を使って自分でhtmlタグを作成することができます。このタグを定義している部分を見てみましょう。app.jsで定義してあります。
- var
MonaApp = angular.module('MonaApp', ['ngAnimate']) .directive('myHeader', function() { return { restrict: "E", templateUrl: 'templates/header.html' }; }) .directive('myContainer', function() { return { restrict: "E", templateUrl: 'templates/container.html' }; }) .directive('myFootter', function() { return { restrict: "E", templateUrl: 'templates/footer.html' }; });
- var
MonaApp = angular.module('MonaApp', ['ngAnimate'])
上記の記述はngAnimateというモジュールを読み込んでMonaAppというモジュールを新たに定義するという趣旨の記述です。ここに数珠つなぎのように.directive()でチェーンして、ディレクティブを定義しています。MonaAppというのは私が勝手に定義したモジュールです。index.htmlの冒頭でng-app=MonaAppという記述を行い、MonaAppを適用しています。
- <html
ng-app="MonaApp">
このMonaAppに属するディレクティブとして、<my-header>、<my-container>、<my-footter>という3つのディレクティブを定義しています。参考として<my-header>を見てみましょう。
- .directive('myHeader',
function() { return { restrict: "E", templateUrl: 'templates/header.html' }; })
AngularJSで定義したmyHeaderというディレクティブを実際にhtmlに記述する場合はmy-headerとなることに注意してください。第1引数でディレクティブ名を、第2引数でオブジェクトを戻り値として返す関数を定義しています。
中を見ていきましょう。
restrictはこのディレクティブをどのような形でhtml中で用いるかを定義するプロパティです。ここではE (Element) 、すなわちhtmlの要素、タグとして、ディレクティブを用いることを定義しています。他にもrestrict:Aとして<div my-header></div>htmlの属性 (Attribute) としてディレクティブを用いることができるように定義したり、色々な形でディレクティブを定義することができます。
templateUrlにはそのディレクティブ中で読み込むテンプレートのURLを定義します。ここではテンプレートとしてtemplates/header.htmlを指定しています。
実際にtemplates/header.htmlを見てみましょう。
- <div
class="header"> <a ng-click="changePage('home', false)" ng-show="isBack"><i class="fa fa-chevron-circle-left"></i></a><p ng-show="!isBack">Florist KT</p> - </div>
この上記のHTMLがindex.html中の<my-header></my-header>という記述一行で読み込まれることになります。1つのファイル中のhtmlの記述が増えてくると、ごちゃごちゃして見にくくなってくる経験があると思います。AngularJSではディレクティブという機能を提供することによって、htmlをコンポーネントごとに分割できるようになっています。コンポーネントを作る以外にもdirectiveには様々なことができますので、是非、興味のある方は調べてみてください。
ngInclude
次に、ngIncludeについて紹介します。ngIncludeを用いることで動的に読み込むテンプレートを変更することができます。まずは<my-container>で読み込んでいるtemplates/container.htmlを見てみましょう。
- <div
class="slide-animate-container" id="content"> <div id="animation" class="slide-animate" ng-include="template.url" ></div> - </div>
ngIncludeを使ったことのない方はng-includeという見慣れない属性があることが見て取れると思います。ここではng-includeでtemplate.urlを指定しています。app.jsを見てみましょう。
- function
MonaCtrl($scope) { $scope.templates = [ { name: 'navigator.html', url: 'templates/navigator.html'}, { name: 'info.html', url: 'templates/info.html'}, { name: 'map.html', url: 'templates/map.html'}, { name: 'diet.html', url: 'templates/diet.html'}, { name: 'flower.html', url: 'templates/flower.html'} ]; ... $scope.template = $scope.templates[0];
ng-include="template"は上記の最下部の$scope.templateを参照しています。上記のコードを見ると$scope.templateには$scope.templates[0]、すなわち{ name: 'navigator.html', url: 'templates/navigator.html'}が代入されています。このオブジェクトのurlの部分、すなわち'templates/navigator.html'がng-include="template.url"という記述で読み込まれます。
このng-includeで読み込まれるhtmlテンプレートであるtemplates/navigator.htmlは下記のように記述されています。
- <ul
id ="category"> <li ng-click="changePage('info', true)"><img class="listedImages" src="images/appearance2.png"><h3>お店について</h3><p>こだわりのお食事やお茶、お菓子、お酒もご提供しています。</p></li> <li ng-click="changePage('diet', true)"><img class="listedImages" src="images/diet.png"><h3>お食事</h3><p>お花屋と飲食が一緒になった東京でも珍しい形のお店です!</p></li> <li ng-click="changePage('flower', true)"><img class="listedImages" src="images/flower.png"><h3>お花</h3><p>多種多様な東京一力のある元気な花、植木を取りそろえております。 </p></li> <li ng-click="changePage('map', true)"><img class="listedImages" src="images/sign.png"><h3>アクセス</h3><p>お店の場所です。</p></li> - </ul>
ちょっと変な書き方ですが、4つの<li>タグにngClickという属性を指定しています。そのng-clickにはchangePage()という関数を定義しています。changePage()を定義しているapp.jsを見てみましょう。
- $scope.changePage
= function(page, isback){ if (typeof isback !== "undefined" && isback !== null) { $scope.isBack = isback; } switch (page) { case "home": $scope.template = $scope.templates[0]; break; case "info": $scope.template = $scope.templates[1]; break; case "map": $scope.template = $scope.templates[2]; break; case "diet": $scope.template = $scope.templates[3]; break; case "flower": $scope.template = $scope.templates[4]; break; } }
ng-click="changePage()"はapp.jsの$scope.changePage()を参照します。changePage()関数は第1引数として、遷移先のページを表す文字列を受け取ります。そして、その文字列に対応して$scope.templateの値を切り替えています。すなわち、<li ng-click="changePage()">というタグをクリック、タップした時に対応するページにtemplates/container.htmlの<div id="animation" class="slide-animate" ng-include="template.url" ></div>は切り替わります。このようにngIncludeを用いることで動的にテンプレートを切り替えることができます。
ngAnimate
<li>をクリックした際にアニメーションをしながらページが切り替わると思います。もう一度、templates/container.htmlを見てみましょう。
- <div
class="slide-animate-container" id="content"> <div id="animation" class="slide-animate" ng-include="template.url" ></div> - </div>
ng-include属性が定義してあるdivタグに"slide-animate"というCSSクラスが適用されているのが見て取れると思います。ngIncludeは自分にかけられているCSSのクラスを自動的に探して、そのCSSをページの切り替わり時に反映します。style.cssにこれらのクラスが定義してあるので見てみましょう。
- .slide-animate-container
{ position:relative; height:568px; width : 100%; overflow:hidden; - }
- .slide-animate.ng-enter,
.slide-animate.ng-leave { -webkit-transition-duration: 0.43s; position:absolute; display:block; - }
- .slide-animate.ng-enter
{ -webkit-transform: translate3d(120%,0px,0px); - }
- .slide-animate.ng-enter.ng-enter-active
{ -webkit-transform: translate3d(0px,0px,0px); - }
- .slide-animate.ng-leave
{ -webkit-transform: translate3d(0px,0px,0px); - }
- .slide-animate.ng-leave.ng-leave-active
{ -webkit-transform: translate3d(120%,0px,0px); - }
上記のCSSクラスの内
新しく入ってくるテンプレート (新しい画面) に対しては.ng-enter.ng-enter-activeがCSSとして追加されます。追加されたCSSはアニメーション完了後に削除されます。画面から消えるテンプレート (元の画面) に対しては.ng-leaveng-leave-activeがCSSとして要素に追加されます。追加されたCSSはアニメーション完了後に削除されます。ここでは新しく入ってくる画面は画面の右端からアニメーションをしながら入ってきて、元の画面は今ある位置から画面右にスライドしながら消えていくCSSを適用しています。それぞれのアニメーションの秒数は0.43秒に設定しています。考え方がちょっと特殊なので、慣れるまでは使いこなすのが大変かと思いますが (自分もまだ使いこなせていません) アニメーションをかける関数などを用意しなくて良いので、その点では便利だと思います。ngAnimateに関してはこのブログが詳しいので、興味のある方はこちらを参照してみてください。
まったくもって完全に余談なのですが、ここのお店のランチセット(餃子、カレー、ハンバーグ)などはかなりおいしいです。近所に寄った時は是非ご賞味ください!!!