この記事は、Monaca Advent Calendar 2019の10日目の記事です。
間に合いませんでした。折を見て完成させたいです……。
あらまし
- 普段は、ニフクラ mobile backendの企画 兼 営業として働いていて、なかなかコードを書く機会がない
- けど、専門外なりにコードを書けるようにしておきたい
- アドベントカレンダーの募集を見たので、いい機会だと思い書いてみることに
作ったもの
使うもの
- Monaca
- Cordovaベースのハイブリッドアプリ開発環境。HTML / JSで簡単にスマホアプリが作れるすごいやつ
- Onsen UI
- 普通にHTML / JSを使ってアプリを作るとWebサイトっぽくなってしまって、アプリらしさがなくなっちゃうけど、これを使うとネイティブアプリのように見える便利なやつ。リファレンスのサイトが見やすい
- ShangriLa Anime API
- 年/クールごとにアニメの情報を引っ張ってこれるAPI。無償で使える
- https://qiita.com/AKB428/items/64938febfd4dcf6ea698
使いたかったもの(使えなかったもの)
- ニフクラ mobile backend
- Monacaと非常に相性のいいmBaaSサービス。3行のコードでデータをクラウドに保存できる
- 間に合わず、自社の製品を組み込めないという失態……
構成
以下のようなファイル構成になりました。
www
L index.html
L main.html(最初の一覧画面)
L quarter_list.html(クォーターごとのアニメリスト)
L detail.html(アニメの詳細画面)
L script.js(jsの処理が詰め込まれています)
index.html
以下のような感じです。
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
<meta http-equiv="Content-Security-Policy" content="default-src * data: gap: https://ssl.gstatic.com; style-src * 'unsafe-inline'; script-src * 'unsafe-inline' 'unsafe-eval'">
<script src="components/loader.js"></script>
<script src="lib/onsenui/js/onsenui.min.js"></script>
<link rel="stylesheet" href="components/loader.css">
<link rel="stylesheet" href="lib/onsenui/css/onsenui.css">
<link rel="stylesheet" href="lib/onsenui/css/onsen-css-components.css">
<link rel="stylesheet" href="css/style.css">
<script>
ons.ready(function() {
console.log("Onsen UI is ready!");
});
if (ons.platform.isIPhoneX()) {
document.documentElement.setAttribute('onsflag-iphonex-portrait', '');
document.documentElement.setAttribute('onsflag-iphonex-landscape', '');
}
</script>
</head>
<body>
<ons-navigator id="navi" page="main.html"></ons-navigator>
<script src="script.js"></script>
</body>
</html>
ons-navigatorを使って画面遷移を実現しているので、タグを書いています。
IDはnavi、初期ページはmain.htmlです。
また、script.jsをさいごに読み込んでいます。
main.html
以下の様な感じです。
<ons-page>
<ons-toolbar>
<div class="center">
アニメメモ
</div>
</ons-toolbar>
<ons-list>
<ons-list-header>2019年</ons-list-header>
<ons-list-item tappable onclick="document.querySelector('#navi').pushPage('quarter_list.html', {data:{year_and_q: '2019/1'}})">1Q</ons-list-item>
<ons-list-item tappable onclick="document.querySelector('#navi').pushPage('quarter_list.html', {data:{year_and_q: '2019/2'}})">2Q</ons-list-item>
<ons-list-item tappable onclick="document.querySelector('#navi').pushPage('quarter_list.html', {data:{year_and_q: '2019/3'}})">3Q</ons-list-item>
<ons-list-item tappable onclick="document.querySelector('#navi').pushPage('quarter_list.html', {data:{year_and_q: '2019/4'}})">4Q</ons-list-item>
<ons-list-header>2018年</ons-list-header>
<ons-list-item tappable onclick="document.querySelector('#navi').pushPage('quarter_list.html', {data:{year_and_q: '2018/1'}})">1Q</ons-list-item>
<ons-list-item tappable onclick="document.querySelector('#navi').pushPage('quarter_list.html', {data:{year_and_q: '2018/2'}})">2Q</ons-list-item>
<ons-list-item tappable onclick="document.querySelector('#navi').pushPage('quarter_list.html', {data:{year_and_q: '2018/3'}})">3Q</ons-list-item>
<ons-list-item tappable onclick="document.querySelector('#navi').pushPage('quarter_list.html', {data:{year_and_q: '2018/4'}})">4Q</ons-list-item>
<ons-list-header>2017年</ons-list-header>
<ons-list-item tappable onclick="document.querySelector('#navi').pushPage('quarter_list.html', {data:{year_and_q: '2017/1'}})">1Q</ons-list-item>
<ons-list-item tappable onclick="document.querySelector('#navi').pushPage('quarter_list.html', {data:{year_and_q: '2017/2'}})">2Q</ons-list-item>
<ons-list-item tappable onclick="document.querySelector('#navi').pushPage('quarter_list.html', {data:{year_and_q: '2017/3'}})">3Q</ons-list-item>
<ons-list-item tappable onclick="document.querySelector('#navi').pushPage('quarter_list.html', {data:{year_and_q: '2017/4'}})">4Q</ons-list-item>
</ons-list>
</ons-page>
時間がなかったので完全にハードコーディングしています。
ShangriLa Anime APIは、以下形式でそのクォーターに放送されたアニメを一覧で取得できます。
http://api.moemoe.tokyo/anime/v1/master/yyyy(西暦)/q(クォーター)
そこで、最後のyyyy/qの部分をyear_and_qという変数で渡しつつ、quarter_list.htmlへ遷移しています。
quarter_list.html/detail.html
イベントリスナーでキャッチしてDOMに情報を流し込むので、HTMLファイルそのものは短いです。
quarter_list.html
<ons-page id="quarter_list">
<ons-toolbar>
<div class="center" id="title">
</div>
<div class="left">
<ons-back-button></ons-back-button>
</div>
</ons-toolbar>
<div id="quarter_list_html"></div>
</ons-page>
detail.html
<ons-page id="detail">
<ons-toolbar>
<div class="center" id="detail_title"></div>
<div class="left"><ons-back-button></ons-back-button></div>
</ons-toolbar>
<h1 id="anime_title"></h1>
<h2 id="hashtag"></h2>
<h2 id="twitter"></h2>
<p id="production"></p>
</ons-page>
script.js
ここにjsの処理を集約しています。
document.addEventListener('init', function(event) {
var page = event.target;
// quarter_list.htmlが読み込まれた時に発火
if (page.id == 'quarter_list') {
document.getElementById('title').innerHTML = page.data.year_and_q;
print_list(page.data.year_and_q);
}
// detail.htmlが読み込まれた時に発火
if(page.id == 'detail') {
print_detail(page.data.year_and_q, page.data.id);
}
});
// 指定されたクォーターの作品一覧を表示
function print_list(year_q){
var xmlHttpRequest = new XMLHttpRequest();
xmlHttpRequest.onreadystatechange = function(){
if( this.readyState == 4 && this.status == 200 ){
if( this.response )
{
var result = '';
for(var i in this.response){
result += '<ons-list-item tappable onclick="document.querySelector(\'#navi\').pushPage(\'detail.html\', {data: {id: \'' + i + '\', year_and_q: \'' + year_q + '\'}})">' + this.response[i]['title'] + '</ons-list-item>';
}
document.getElementById('quarter_list_html').innerHTML = '<ons-list>' + result + '</ons-list>';
}
}
}
xmlHttpRequest.open( 'GET', 'https://api.moemoe.tokyo/anime/v1/master/' + year_q, true );
xmlHttpRequest.responseType = 'json';
xmlHttpRequest.send( null );
}
// 指定された作品の情報を表示
function print_detail(year_q, id){
var xmlHttpRequest = new XMLHttpRequest();
xmlHttpRequest.onreadystatechange = function(){
if( this.readyState == 4 && this.status == 200 ){
if( this.response )
{
console.log(this.response[id]);
document.getElementById('detail_title').innerHTML = this.response[id]['title'];
document.getElementById('anime_title').innerHTML = this.response[id]['title'];
document.getElementById('hashtag').innerHTML = 'ハッシュタグ: #'+ this.response[id]['twitter_hash_tag'];
document.getElementById('twitter').innerHTML = 'Twitterアカウント: ' + this.response[id]['twitter_account'];
document.getElementById('production').innerHTML = '制作会社: ' + this.response[id]['product_companies'];
}
}
}
xmlHttpRequest.open( 'GET', 'https://api.moemoe.tokyo/anime/v1/master/' + year_q, true );
xmlHttpRequest.responseType = 'json';
xmlHttpRequest.send( null );
}
まず、document.addEventListenerで、ページ遷移をキャッチします。
Onsen UIでは、ページが表示された際のイベントが'init'らしく、それをキャッチしています。
そして発火したページごとにprint_list()/print_detail()を実行するイメージです。
print_listでは、取得した作品一覧情報を、ons-listを使って整え、それをDOMに流し込んでいます。
また、詳細ページに遷移できるよう、tappableで、かつonclickを仕込んで、タップされたタイミングでpushPageを発火させるようになっています。
print_detailでは、クォーター情報とJSONの添字を受け取り、該当する作品の情報を表示します。
以上です。
よかったこと
- 普段企画を担当している僕でも、拙い知識とリファレンスさえあれば、さほど詰まることなく開発を進められた点
完成させられなかったけど- Onsen UIのリファレンスは本当にわかりやすい
- いいJSの復習になったこと
- 開発者向けのサービスを提供していることもあり、やはり自分もたまには開発者の目線で色々なサービスを触ってみる必要があると痛感
いまいちだったこと
- そもそも完成させられなかった点
- 年末年始のお休みにでも完成させたいです
- 画面を遷移するたびにAPIから情報を取得し直している点
- 引数として取得結果のJSONを引き渡すなどして、API呼び出し回数を削減したい
まとめ
完成させられなかったのは残念でしたが、なんとかアプリとして動作するものは作ることができた。
ShangriLa Anime APIのようなAPIが無償で提供されているのは素晴らしいと思った。
ご覧頂きありがとうございました!