「Monacaを使ってアニメの感想を記録できるアプリを爆速で開発しました」って言いたかった

この記事は、[Monaca Advent Calendar 2019](https://adventar.org/calendars/4135)の10日目の記事です。

間に合いませんでした。折を見て完成させたいです……。

## あらまし

- 普段は、ニフクラ mobile backendの企画 兼 営業として働いていて、なかなかコードを書く機会がない
- けど、専門外なりにコードを書けるようにしておきたい
- アドベントカレンダーの募集を見たので、いい機会だと思い書いてみることに

## 作ったもの

- 年／クールごとにアニメの情報がズラッと並んでいて、クリックしたら詳細が見えるというもの
- 本当は、感想やメモをクラウドに保存する仕組みを作りたかったが、間に合わず断念
![2019-12-10_17h02_27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280892/a1605e09-0715-fbf0-a905-766641ce08c8.png)
![2019-12-10_17h02_42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280892/ad6f4bdb-62f7-3133-3494-9b6a5667e57e.png)
![2019-12-10_17h02_53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280892/cb9996b6-f045-0596-44cc-d7324219cdaa.png)


## 使うもの
- 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

以下のような感じです。

```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を使って画面遷移を実現しているので、<ons-navigator>タグを書いています。
IDはnavi、初期ページはmain.htmlです。
また、script.jsをさいごに読み込んでいます。

## main.html

以下の様な感じです。

```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

```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

```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の処理を集約しています。

```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が無償で提供されているのは素晴らしいと思った。

ご覧頂きありがとうございました！