Monaca + Movable Type で記事投稿スマホアプリを作ってみる

[前回の続き](http://qiita.com/TakeshiNickOsanai/items/69f2d5aa87bbaca785ad)です。
今回は、Movable Type のData APIから認証機能を呼び出し

* Monaca で作成したスマホアプリから Movable Type へサインインする
* スマホアプリから投稿する

までを作ってみます。

UIには、前回同様 Onsen UIを使います。


## 新しいプロジェクトの作成
[前回の「Monaca プロジェクト作成」](http://qiita.com/TakeshiNickOsanai/items/69f2d5aa87bbaca785ad)と同様に、記事投稿アプリ用のプロジェクトを作成します。

![monaca_post_1.png](https://qiita-image-store.s3.amazonaws.com/0/83767/73881ec7-db73-0492-3f15-b4aef74195f9.png)


## 投稿用インデックスファイルの作成

プロジェクトの作成が終わったら、inde.htmlを以下のファイルに差し替えます。
[こちらの記事で作成した投稿用のスクリプト](http://qiita.com/TakeshiNickOsanai/items/f4b1335d87a2f5525964)を、一部 Monaca 用にアレンジして書き換えました。

```html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>autheticationを使った投稿サンプル</title>
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
  <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">
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
</head>

<body>

<ons-page>
  <ons-toolbar>
    <div class="center">MT投稿アプリ</div>
  </ons-toolbar>

  <div style="text-align: center; margin-top: 30px;" id="content">
    <p>
      <ons-input id="username" modifier="underbar" placeholder="Username" float></ons-input>
    </p>
    <p>
      <ons-input id="password" modifier="underbar" type="password" placeholder="Password" float></ons-input>
    </p>
    <p style="margin-top: 30px;">
      <ons-button id="signin">サインイン</ons-button>
    </p>
  </div>
</ons-page>

<script> 

$(function() {
  const clientId = 'smartphone_post_sample';
  const currentSession = 'mt_session_' + clientId;
  const currentToken = 'mt_token_' + clientId;
  const nowTime = Math.floor(new Date().getTime() / 1000);

  const apiUrl = 'http://your-mt/mt-data-api.cgi';
      const siteId = 'ブログIDの数字';


  if (sessionStorage.getItem(currentSession)) {
    haveSignedIn();
  }

  $('#signin').on('click', function() {
    signIn();
  });

  $('#submit').on('click', function() {
    var promise = Promise.resolve(checkToken());
    promise.then(function() {
      PostEntry();
    });
  });

  $('#signout').on('click', function() {
    revokeSession();
  });

  function haveSignedIn() {
    const displayData = `
        <h3>投稿する</h3>
        <form class="col-sm-8">
        <div class="form-group">
          <input id="title" name="title" placeholder="タイトル" type="text"  class="form-control">
        </div>
        <div class="form-group">
          <textarea id="body" maxlength="140" placeholder="本文" rows="7"  class="form-control"></textarea>
        </div>
        <div class="form-group">
          <ons-button id="submit" name="submit" type="button">投稿する</ons-button>
        </div>
        <div class="form-group">
          <ons-button id="signout" name="signout" type="button">サインアウト</ons-button>
        </div>
        </form>
        `
    $("#content").empty();
    $("#content").append(displayData);
  }

  function signIn() {
    const username = $('#username').val();
    const password = $('#password').val();
    $.ajax({
      url: apiUrl + '/v3/authentication',
      type: 'POST',
      dataType: 'json',
      data: {
        'username': username,
        'password': password,
        'clientId': clientId,
      }
    }).done(function(data) {
      sessionStorage.setItem(currentSession, data.sessionId);
      let TokenData = {
        'accessToken': data.accessToken,
        'expiresIn': data.expiresIn,
        'gotTokenTime': nowTime,
      }
      sessionStorage.setItem(currentToken, JSON.stringify(TokenData));
      alert("サインインしました。");
      location.reload();
    }).fail(function(data) {
      alert("サインイン時にエラーが発生しました。再度サインインをお試しください。");
    });
  }

  function PostEntry() {
    const entry = {};
    entry.title = $('#title').val();
    entry.body = $('#body').val();
    $.ajax({
      url: apiUrl + '/v3/sites/' + siteId + '/entries',
      type: 'POST',
      dataType: 'json',
      headers: {
        'X-MT-Authorization': 'MTAuth accessToken=' + JSON.parse(sessionStorage.getItem(currentToken)).accessToken,
      },
      data: {
        'entry': JSON.stringify(entry)
      },
    }).done(function() {
      alert("データを投稿しました。");
    }).fail(function(data) {
      alert("投稿時にエラーが発生しました。");
    });
  }

  function revokeSession() {
    $.ajax({
      url: apiUrl + '/v3/token',
      type: "DELETE",
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
        'X-MT-Authorization': 'MTAuth accessToken=' + JSON.parse(sessionStorage.getItem(currentToken)).accessToken,
      }
    }).done(function() {
      $.ajax({
        url: apiUrl + "/v3/authentication",
        type: "DELETE",
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
          'X-MT-Authorization': 'MTAuth sessionId=' + sessionStorage.getItem(currentSession),
        }
      })
    }).done(function() {
      sessionStorage.removeItem(currentSession);
      sessionStorage.removeItem(currentToken);
      alert("サインアウトしました。");
      location.reload();
    }).fail(function(data) {
      alert("サインアウト中にエラーが発生しました。もう一度サインインし直してください。");
      sessionStorage.removeItem(currentSession);
      sessionStorage.removeItem(currentToken);
      location.reload();
    });
  }

  function checkToken() {
    const expiresIn = JSON.parse(sessionStorage.getItem(currentToken)).expiresIn;
    const gotTokenTime = JSON.parse(sessionStorage.getItem(currentToken)).gotTokenTime;
    if (nowTime >= expiresIn + gotTokenTime) {
      refreshToken();
    }
  }

  function refreshToken() {
    $.ajax({
      url: apiUrl + '/v3/token',
      type: "DELETE",
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
        'X-MT-Authorization': 'MTAuth accessToken=' + JSON.parse(sessionStorage.getItem(currentToken)).accessToken,
      }
    }).done(function() {
      $.ajax({
        url: apiUrl + '/v3/token',
        type: "POST",
        dataType: "json",
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
          'X-MT-Authorization': 'MTAuth sessionId=' + sessionStorage.getItem(currentSession),
        }
      }).done(function(data) {
        let TokenData = {
          'accessToken': data.accessToken,
          'expiresIn': data.expiresIn,
          'gotTokenTime': nowTime,
        }
        sessionStorage.setItem(currentToken, JSON.stringify(TokenData));
      }).fail(function(data) {
        alert("アクセストークンのリフレッシュ中にエラーが発生しました。サインし直してください。");
        sessionStorage.removeItem(currentSession);
        sessionStorage.removeItem(currentToken);
        location.reload();

      });
    });
  }
});


</script>
</body>
</html>
```

このアプリでは、以下の機能を実装しています。

* サインイン画面を表示
* ユーザー名、パスワードを入力後、「サインイン」ボタンを押すと、Movable Type のData APIを利用した認証を行う
* ユーザー情報が正しい場合、サインイン画面を消去して記事投稿用のフォームを表示する。
* 今回は「タイトル」と「本文」を入力できるようにする。
* データ入力後「投稿する」ボタンを押すと、Movable Type にデータを投稿する。
* 「サインアウト」ボタンを押すと、サインアウト処理を行い、初期画面に戻る

詳細は[元記事を参照ください。](http://qiita.com/TakeshiNickOsanai/items/f4b1335d87a2f5525964)

Onsen UI を使うために、header内でOnsen UI の各ライブラリを読み込んでいます。
サインイン用の初期画面、および投稿用画面のボタンを、Onsen UI のタグに書き換え、スマホアプリっぽい見栄えに変えてみました。

保存したら、Monaca デバッガーで確認します。

![monaca_post_2.png](https://qiita-image-store.s3.amazonaws.com/0/83767/3b353ab6-c935-18fd-454b-f1d73bf30d8b.png)

![monaca_post_3.png](https://qiita-image-store.s3.amazonaws.com/0/83767/11c7bd7d-aeec-bd92-6265-ddbef81bc08e.png)


## 免責

今回のサンプルは、十分なテストを行っていないため、実際に利用する際は、作成したコードのテスト・チェックを十分に行った上で実装ください。

※何かお気づきの点などありましたら、どうぞお知らせください。