Monaca入門:Onsen UI+AngularJSで作るハイブリッドモバイルアプリ(6)
AngularJSの方法でMonacaアプリを作ってみよう(後編)
―路線名を選択して、時刻表を表示するアプリ―
Monacaアプリ開発実践編の第3弾(連載最終回)。路線名を選択して、時刻表を表示するアプリの作成方法を説明する。
前々回と前回ではオープンデータを利用したMonacaアプリとして、路線番号データを表示するアプリとバス停の座標データを取得するアプリを開発した。最後は本連載のまとめとして、路線名の選択から時刻表を表示するアプリを作成しよう。
路線名を選択して、時刻表を表示する
このアプリは[Onsen UI Sliding Menu]テンプレートから作成し、図4-6のように動作する。
図の1はアプリの起動時の画面だ。この画面の上部にある[メニューを表示/非表示]ボタンをタップするか、画面を左から右にスワイプすると、2のメニューが左から現れる。
メニューで路線名をタップして選択すると、メニューは左にスライドして消え、右のメイン画面に、その路線で次に運行されるバスの時刻表が表示される。
3の右上にあるボタンをタップするか、画面を左から右にスワイプするとメニューが表示される(4)。
アプリのこの動作は複雑だが、これは全て[Onsen UI Sliding Menu]テンプレートに含まれるOnsen UIの<ons-sliding-menu>コンポーネントが皆さんに代わってやってくれる。
プロジェクトの作成
まずはプロジェクトの作成だ。IDEの[プロジェクトの作成]では[Onsen UI Sliding Menu]テンプレートを選択する(図4-7)。[Onsen UI最小限のテンプレート]ではないので注意してほしい。
[Onsen UI Sliding Menu]テンプレートからは、index.htmlとpage1.html、page2.htmlに加え、menu.htmlファイルが自動的に作成される。index.htmlファイルの下部、<body>
タグ内には、次の<ons-sliding-menu>
コンポーネントが記述されている。menu-page
属性に指定するHTMLファイルが、画面左から現れるメニューページになり、main-page
属性に指定するHTMLファイルが、アプリで最初に表示される画面になる。その他にも属性があるがデフォルトのままで問題ない(<ons-sliding-menu>
の属性についてはOnsen UIのコンポーネント一覧ページで読むことができる)。
<ons-sliding-menu menu-page="menu.html" main-page="page1.html" side="left" type="overlay" max-slide-distance="200px">
</ons-sliding-menu>
|
AngularJS仕様に変更
次いでこのプロジェクトをAngularJS仕様に変更する。コードエディターでindex.htmlファイルを開き、コード冒頭にある<html>
タグにng-app="myApp"
という属性と値を追加する。
<!-- <html>タグに属性と値を追加 -->
<html ng-app="myApp">
|
また<script>
タグ内に書かれている2行のJavaScriptコードを削除し、var app = angular.module('myApp', ['onsen']);
を記述する。
// この2行を削除
// ons.bootstrap();
// ons.disableAutoStatusBarFill(); // (Monaca enables StatusBar plugin by default)
// この1行を記述
var app = angular.module('myApp', ['onsen']);
|
ページ構成
[Onsen UI Sliding Menu]テンプレートのページ構成は図4-8のようになっている。
アプリの起動時にはpage1.htmlが表示される。画面のスワイプ操作またはボタンのタップにより左から現れるのはmenu.htmlだ。menu.htmlからはpage1.htmlに戻ることもpage2.htmlに行くこともできる。図4-6の「路線名と時刻表アプリ」でもこれを踏襲するが、page2.htmlでは、menu.htmlで選択された路線番号データに基づき、その路線の時刻表をダイナミックに表示する。
コントローラーに関していうと、menu.htmlで行う路線番号データの取得や選択はメニュー用コントローラー(MenuController)で行い、page2.htmlで行う時刻表の表示は時刻表用コントローラー(TimetableController)で行う。つまり1つのアプリにコントローラーが2つ存在することになる。
このようにコントローラーが2つあるときに問題になるのが、どうやって情報を伝達するかだ。今の場合でいうと、MenuControllerで選択された路線をTimetableControllerに知らせる必要があるが、これをどうやって行うかだ。このアプリでは情報共有用のサービスを作成して、それに仲介させることにする。
page1.html ― アプリの起動時に表示されるページ
まずは、一番簡単なpage1.htmlファイルからだ。
<ons-page>
<ons-toolbar>
<div class="center">路線名と時刻表</div>
</ons-toolbar>
<div style="text-align:center">
<ons-button
ng-click="ons.slidingMenu.toggleMenu()">
メニューを表示/非表示
</ons-button>
<p>メニューから路線を選択して時刻表を表示する</p>
</div>
</ons-page>
|
このページにはボタンのタップでons.slidingMenu.toggleMenu()
関数を呼び出す任務しかない。これは<ons-sliding-menu>
コンポーネントの関数で、メニューが表示されていれば隠し、表示されていなければ表示する。画面のスワイプ操作やメニューのスライドアニメーションなどは、全部この<ons-sliding-menu>
コンポーネントが取り仕切る。
なおスライディングメニュー式のアプリでは、メニューの表示/非表示を切り替える操作が、「画面のスワイプ」という少し特殊な方法なので、それに気付かないユーザーのために、このコード例のようにメニューの表示/非表示を切り替えるためのボタンの設置が望まれる。
menu.html ― コントローラー名「MenuController」の指定と、メニュー項目の表示
次は、路線番号データを取得してメニューに表示するmenu.htmlファイルだ。ここでは<ons-list>
と<ons-list-item>
を使って、page1.htmlとpage2.htmlにリンクするメニューのリスト項目を作成している。
<ons-page ng-controller="MenuController">
<ons-list>
<ons-list-item
modifier="tappable" class="list__item__line-height"
ng-click="ons.slidingMenu.setMainPage('page1.html', {closeMenu: true});">
<ons-icon icon="fa-home" size="20px" style="color: grey"></ons-icon>
トップ
</ons-list-item>
<ons-list-item>
-----------
</ons-list-item>
<ons-list-item ng-repeat="rosen in rosenID"
modifier="tappable" class="list__item__line-height"
ng-click="ons.slidingMenu.setMainPage('page2.html', {closeMenu: true}); sendID(rosen.id);">
<ons-icon icon="fa-sign-in" size="20px" style="color: grey"></ons-icon>
{{rosen.id}} {{rosen.name}}
</ons-list-item>
</ons-list>
</ons-page>
|
ons.slidingMenu.setMainPage()
も<ons-sliding-menu>
コンポーネントの関数で、指定されたHTMLページを画面右のメインページに表示する。そのときにはオプションの{closeMenu: true}
によってメニューが閉じられる。
AngularJSの出番は3つめの<ons-list-item>
にある。ここではons.slidingMenu.setMainPage()
関数によって一律にpage2.htmlがメインページとなるが、同時に呼び出されているsendID(rosen.id)
関数によって、page2.htmlの内容はダイナミックに変化する。ng-repeat
ディレクティブの使用やrosen in rosenID
、{{rosen.id}}
と{{rosen.name}}
のAngular式の扱いは前々回の「路線番号データを取得する」のときと基本的に変わりはない。
page2.html ― コントローラー名「TimetableController」の指定と、バス停データの表示
page2.htmlファイルは、TimetableControllerコントローラーの守備範囲にある。
<ons-page ng-controller="TimetableController">
<ons-toolbar>
<div class="right" ng-click="ons.slidingMenu.toggleMenu()">
<ons-icon icon="fa-bars" size="40px"></ons-icon>
</div>
<div class="center">路線名と時刻表</div>
</ons-toolbar>
<div>
<table border=4 width=250 align=center>
<caption style="color:red">{{destination}}行き</caption>
<tr bgcolor="#cccccc">
<th>停留所ID</th>
<th>発着時刻</th>
</tr>
<tr align=center ng-repeat="list in timetableList">
<td>{{list.busstopid}}</td>
<td>{{list.time}}</td>
</tr>
<table>
</div>
</ons-page>
|
<ons-toolbar>
の右にはバー・アイコンを置いて、この部分のタップでメニューの表示と非表示を切り替えられるようにしている。
また時刻表の上にはテーブルの<caption>
を使って行き先を赤字で表示している。このdestination
はTimetableControllerのスコープのプロパティだ。そしてテーブルの2つめの<tr>
はng-repeat
ディレクティブによってダイナミックに作成され、これが時刻表になる。
index.htmlのJavaScript ― 各サービスの作成と、サービスを使用する各コントローラーの作成
RosenIDServiceサービスとTimetableServiceサービスの作成
最後はJavaScriptコードだ。サービスに関していうと、路線番号データを提供するRosenIDServiceサービスは前々回の「路線番号データを取得する」で見たものと同じで、路線番号から時刻表を提供するTimetableServiceサービスも、前回の停留所データを提供するBusstopServiceサービスと呼び出し先のPHPファイルが異なるだけだ。
// 路線番号データを提供するサービス
app.factory('RosenIDService', ['$http', function($http){
var rosenIDService = {};
// 路線番号データを要求して、渡されたコールバック関数を呼び出す
rosenIDService.getRosenID = function(callback){
$http.jsonp('http://tutujibus.com/rosenidLookup.php?callback=JSON_CALLBACK')
.success(function(data){
callback(data);
});
}
return rosenIDService;
}]);
// 路線番号データから時刻表を提供するサービス
app.factory('TimetableService', ['$http', function($http){
var timetableService = {};
// 路線番号の時刻表を要求して、渡されたコールバック関数を呼び出す
timetableService.getTimetable = function(rosenid, callback){
$http.jsonp('http://tutujibus.com/timetableLookup.php?rosenid='+rosenid+'&callback=JSON_CALLBACK')
.success(function(data){
callback(data);
});
}
return timetableService;
}]);
|
※先ほどの「<script>タグの中身をAngularJS仕様に変更(index.html)」コードの下に追記する。
コントローラー間の仲介役となるSharedRosenIDサービスの作成
このアプリのポイントは2つのコントローラー間の仲介役を果たす次のSharedRosenIDサービスにある。とはいえ機能は単純で、自分のid
プロパティを持ち、それを設定する関数(set(id)
)とidを返す関数(get()
)を備えたオブジェクトにすぎない。MenuControllerではメニュー項目がタップされたときに路線番号のid値をこのサービスに登録し、TimetableControllerコントローラーではpage2.htmlが作成されるときに、このサービスからid値を得て時刻表を作成する。SharedRosenIDサービスは路線番号の一時的な預け場所だ。
// コントローラー間で路線IDを共有するサービス
app.factory('SharedRosenID', function(){
var sharedRosenIDInfo = {};
sharedRosenIDInfo.id='';
// idを設定
sharedRosenIDInfo.set = function(id){
sharedRosenIDInfo.id=id;
};
// idを返す
sharedRosenIDInfo.get = function(){
return sharedRosenIDInfo.id;
};
return sharedRosenIDInfo;
});
|
※上記の2つのサービスに関するコードの下に追記する。
MenuControllerコントローラーとTimetableControllerコントローラーの作成
MenuControllerは次のように、RosenIDServiceとSharedRosenIDを注入して作成する。このコントローラーのスコープはsendID()
という関数を持っている。この関数はmenu.htmlのリスト項目のタップでons.slidingMenu.setMainPage()
の後に呼び出され、タップされた路線のid値をSharedRosenIDサービスに登録する。
// メニューのコントローラー
app.controller('MenuController', ['$scope', 'RosenIDService', 'SharedRosenID',
function($scope, RosenIDService, SharedRosenID) {
// RosenIDServiceを使って路線番号データをrosenIDプロパティに割り当てる
RosenIDService.getRosenID(function(data){
$scope.rosenID = data.rosen;
});
// メニューのリストのタップで、路線番号データを共有サービスに登録
// ng-click='...;sendID(rosen.id);'で呼び出している
$scope.sendID = function(id){
SharedRosenID.set(id);
}
}]);
|
※さらに上記のSharedRosenIDサービスの下に追記する。
そしていよいよ最後のTimetableControllerだ。このコントローラーにもRosenIDServiceを注入してこのサービスを利用できるようにする。そしてpage2.htmlが作成されるときに毎回、RosenIDServiceに登録された路線番号のid値を取得してそれをスコープのgetTimetable()
関数に渡す。getTimetable()
はTimetableServiceサービスに路線番号のid値を渡し、その応答を待ってスコープのdestination
プロパティとtimetableList
プロパティに、応答のデータを割り当てる。これにより、この2つのプロパティが結び付けられたpage2.htmlファイルの{{destination}}
と、ng-repeat="list in timetableList"
の{{list.busstopid}}
と{{list.time}}
がレンダリングされて、page2.htmlの画面に行き先とその時刻表として表示される。
// page2のコントローラー
app.controller('TimetableController', ['$scope', 'SharedRosenID', 'TimetableService',
function($scope, SharedRosenID, TimetableService) {
// 路線番号データを渡して時刻表を取得する
$scope.getTimetable = function(id){
TimetableService.getTimetable(id, function(data){
$scope.destination = data.timetable[0].destination;
$scope.timetableList = data.timetable[0].list;
});
}
// page2.htmlが読み込まれるときに、メニューのリストのタップで登録された
// 路線番号データを読み取って、その路線番号の時刻表を取得する
$scope.getTimetable(SharedRosenID.get());
}]);
|
※上記のMenuControllerの下に追記する。
最後に
前々回~今回は本連載の総仕上げとして、オープンデータのWebサービスを使ったアプリの作成方法を見てきた。取り上げたアプリ自体は単につつじバスの情報を表示するだけのものだが、例えば前回の図4-5に示した地図アプリのように発展させることができる。今後有用な情報を持ち、扱いやすい形式で提供されるオープンデータが増えてくると、さまざまな切り口のモバイルアプリが登場してくるだろう。
本連載を読まれた皆さんもこれを機会にぜひMonacaとOnsen UI、AngularJSに習熟し、人々の暮らしに役立つモバイルアプリの作成にチャレンジしてみてはどうだろう。
※以下では、本稿の前後を合わせて5回分(第2回~第6回)のみ表示しています。
連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。
2. Monacaで作る、初めてのOnsen UIアプリ
新規作成した“初めてのOnsen UIアプリ”プロジェクトの各ファイルをAngularJS流に書き換える。そのアプリをデバッグビルドし、デバイスに実際にインストールする。
3. Onsen UIの舞台裏で働くAngularJSの世界
AngularJS流のデータ/コントローラー/表示の実装方法と、AngularJSのディレクティブによるHTML要素の操作方法、データの追加、AngularJS機能のサービスについて解説する。
4. AngularJSの方法でMonacaアプリを作ってみよう(前編)
実践的なMonacaアプリ開発の一例として、オープンデータのWebサービスを使ったアプリの作成方法を説明する。Monacaアプリ開発実践編の第1弾。