Monacaとニフクラ mobile backendを使ってタスク管理アプリを作ろう【ハンズオンスライド】

# ベースアプリについて

---

ハンズオンのベースアプリはGitHubリポジトリにあります。

[NCMBMania/task_app](https://github.com/NCMBMania/task_app)

---
Monacaのダッシュボードに行き、インポートを選びます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/e3d0c2b5-0766-11aa-40cb-2395c80ebb70.png)

---
URLを指定してインポートを選びます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/18c3b615-6970-b181-f0dd-b8eac93e16b3.png)

---
URLを `https://github.com/NCMBMania/task_app/archive/refs/heads/main.zip` と入力してインポートを実行してください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/8a8a6eac-303f-5d38-96ca-cba6922c527a.png)

---

# 1. 認証機能を実装する

---

### index.html について

index.htmlは一番初めに読み込まれるHTMLファイルです。ここではOnsen Navigatorを読み込んでいます。初期設定は list.html を読み込んでいます。
また、jQueryとdayjs（日付操作ライブラリ）を読み込んでいます。

---

```html
<body>
  <ons-navigator id="nav" page="list.html"></ons-navigator>
  <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
  <script src="https://unpkg.com/dayjs@1.8.21/dayjs.min.js"></script>
  <script src="js/app.js"></script>
</body>
```

---

### list.html

では最初に読み込まれるlist.htmlについてです。list.htmlではすでに登録されているタスクを一覧表示します。

---

```html
<ons-page>
  <ons-toolbar>
    <div class="center" id="form-title">タスク一覧</div>
    <div class="right">
      <ons-toolbar-button id="add">
        <ons-icon icon="fa-plus"></ons-icon>
      </ons-toolbar-button>
    </div>
  </ons-toolbar>
  <p>
    <h3>タスク一覧</h3>
    <ons-list id="tasks">
    </ons-list>
  </p>
</ons-page>
```

---

### add.html

list.htmlで #add をタップした際に表示されるのが add.html です。タスクの追加を行います。

---

```html
<ons-toolbar>
  <div class="left">
    <ons-back-button>一覧</ons-back-button>
  </div>
  <div class="center" id="form-title">タスクの追加</div>
  </ons-toolbar>
  <div style="text-align: center; margin-top: 60px;">
    <p>
      <ons-input id="title" modifier="underbar" placeholder="タスク名" float></ons-input>
    </p>
    <p>
      <textarea id="body" class="textarea" rows="5" placeholder="詳細を入力してください"></textarea>
    </p>
    <p>
      <ons-input id="date" type="date" modifier="underbar" float></ons-input>
    </p>
    <p style="margin-top: 30px;">
      <ons-button id="addTask">タスクを登録する</ons-button>
    </p>
  </div>
</ons-page>
```

---

### edit.html

タスクを編集するのがedit.htmlです。画面構成は add.html とほとんど変わりません。

---

```html
<ons-page>
  <ons-toolbar>
    <div class="left">
      <ons-back-button>一覧</ons-back-button>
    </div>
    <div class="center task_title_label">タスク</div>
  </ons-toolbar>
  <div style="text-align: center; margin-top: 60px;">
    <p>
      <ons-input id="title" class="task_title" modifier="underbar" placeholder="タスク名" float></ons-input>
    </p>
    <p>
      <textarea id="body" class="task_body textarea" rows="5" placeholder="詳細を入力してください"></textarea>
    </p>
    <p>
      <ons-input id="date" class="task_date" type="date" modifier="underbar" float></ons-input>
    </p>
    <p style="margin-top: 30px;">
      <ons-button id="updateTask">タスクを更新する</ons-button>
    </p>
  </div>
</ons-page>
```

---

## login.html

認証を行う画面です。新規登録とログインを同じ画面で行っています。

---

```html
<ons-page>
  <ons-toolbar>
    <div class="center" id="form-title">新規登録/ログイン</div>
  </ons-toolbar>
  <div style="text-align: center; margin-top: 60px;">
    <p>
      <ons-input id="userName" modifier="underbar" placeholder="ユーザ名" float></ons-input>
    </p>
    <p>
      <ons-input id="password" modifier="underbar" type="password" placeholder="パスワード" float></ons-input>
    </p>
    <p style="margin-top: 30px;">
      <ons-button id="login">登録/ログイン</ons-button>
    </p>
  </div>
</ons-page>
```

---

## mBaaSの設定について

ニフクラ mobile backendの設定について解説します。まずアカウントはすでに持っているという前提で進めていきます。その際、アプリケーションキーとクライアントキーが取得できるはずです。そのキーを `js/app.js` に書き込みます。

---

```javascript
// ↓ アプリケーションキーとクライアントキーをそれぞれ書き換えてください
const applicationKey = 'YOUR_APPLICATION_KEY';
const clientKey = 'YOUR_CLIENT_KEY';
window.ncmb = new NCMB(applicationKey, clientKey);
```

---

## 認証状態の判定

最初に表示されるのは list.html なので、ここで認証判定を行うのがよさそうです。この判定はログアウト処理や履歴管理表示した後も実行したいので、 `ons.getScriptPage().onShow` に実装するのがよいでしょう。この関数は画面が表示される度に呼び出されます。

---


最初、次のようになっているはずです。

```javascript
// この画面が表示される度に実行されます
ons.getScriptPage().onShow = async function() {
  // 1. ログインをチェックします。ログインしていなければ login.html に遷移します
  await getTaskList();
  renderTasks();
}
```

---

この `1. ログインをチェックします。ログインしていなければ login.html に遷移します` の下に処理を書いていきます。

---

### ログイン状態を判別する

ログイン状態の判別は、NCMBの会員管理機能を使って `ncmb.User.getCurrentUser()` を取得して行います。これが空の場合、認証されていません。認証されていない場合は `login.html` に遷移します。

---

```javascript
// ログインユーザを取得します
const user = ncmb.User.getCurrentUser();
if (!user) {
  // 未ログインの場合
  return $('#nav')[0].pushPage('login.html');
}
// この下にまだ続きます（次のスライド）
```

---

さらにセッションの有効性もチェックしなければいけません。これは適当なクラス名でデータ取得を試みるのが簡単です。取得エラー（セッションが無効）だった場合には、セッション情報が入っているlocalStorageのデータを消して、login.htmlに遷移します。

---

```js
try {
  // セッションの有効性を確認
  await ncmb.DataStore('Hello').fetch();
} catch (e) {
  // セッションが無効な場合エラーになるので、その場合はlocalStorageの認証データを削除
  localStorage.removeItem(`NCMB/${ncmb.apikey}/CurrentUser`);
  ncmb.sessionToken = null;
  // ログイン画面に遷移
  return $('#nav')[0].pushPage('login.html');
}
```

---

## 認証機能の作成

認証機能は login.html に実装します。認証は `登録/ログイン` ボタンを押した際に実行します。ボタンを押した際のイベントは `ons.getScriptPage().onInit` に実装するのが基本です（onShowに実装するとイベントが何度も呼ばれるようになってしまいます）。

---

現在は以下のようになっています。ここは修正を加える必要はありません。

```javascript
ons.getScriptPage().onInit = function() {
  this.querySelector('#login').onclick = signInOrLogin.bind(this);
}
```

---


### 登録/ログインボタンを押した際の処理

登録/ログインボタンを押した時のイベントは `signInOrLogin` になります。

---

現在は次のように書かれています。

```javascript
async function signInOrLogin() {
  const userName = this.querySelector('#userName').value;
  const password = this.querySelector('#password').value;
  try {
    // 1. まず登録処理を実行します。
    await registerUser(userName, password);
  } catch (e) {
    // 2. それが失敗する（すでにユーザがいた場合）
    // こともありますが、そのままログイン処理に移行します。
  }
  // ログイン処理
  try {
    // 3. mBaaSへのログイン処理を実行します

    // 5. 前の画面に戻ります
    $('#nav')[0].popPage();
  } catch (e) {
    // 4. ログインに失敗する場合はID/パスワードが間違っている場合です。
    ons.notification.alert('ログイン失敗しました。ユーザ名、パスワードを確認してください');
    return false;
  }
}
```

---

## registerUserの処理

registerUserではmBaaSへのユーザ登録処理を実行します。指定されたユーザ名 `userName` とパスワード `password` を利用します。

---

```js
// 新規ユーザ登録処理です
async function registerUser(userName, password) {
  const user = new ncmb.User();
  user
    .set('userName', userName)
    .set('password', password);
  await user.signUpByAccount();
}
```

---

## ログイン処理

続けてログイン処理です。 `// mBaaSへのログイン処理を実行します` という文字を探して、その下に次のように記述してください。これだけでログイン処理が実行されます。

---

```js
// 3. mBaaSへのログイン処理を実行します
await ncmb.User.login(userName, password);
```

---

## タスクの保存と表示処理の実装について

---

## add.htmlでの処理について

ここまででログインが終わりましたので、次にタスクを追加する処理を add.html に実装します。

---

まず、add.htmlを初期化する処理を作成します。これはすでに記述してありますが、追加ボタンを押した際のイベントを設定しています。今は次のスライドのように書かれているはずです。

---

```javascript
ons.getScriptPage().onInit = function() {
  $('#addTask').on('click', async e => {
    // 変数を準備する
    const title = $('#title').val();
    const date = $('#date').val();
    const body = $('#body').val();
    try {
      // NCMBにタスクを登録する
      await addTask(title, date, body);
      $('#nav')[0].popPage();
    } catch (e) {
      ons.notification.alert('タスクの作成に失敗しました');
    }
  })
}
```

---

## addTaskの実装

ではタスクを追加する処理を実装します。これは addTask 関数になります。ACLはタスクを登録したユーザ自身だけに読み込み、書き込み権限を付与しています。

---

```js
async function addTask(title, date, body) {
  // タスクのインスタンスを作成します
  const task = new (ncmb.DataStore('Task'));
  // 入力した情報をセットします
  task
    .set('title', title)
    .set('date', new Date(date))  // 日付型にします
    .set('done', false)           // デフォルトは未完了です
    .set('body', body);
  // ACLを作成します
  const user = ncmb.User.getCurrentUser();
  const acl = new ncmb.Acl();
  acl
    .setUserReadAccess(user, true)   // ユーザ自身のみ読み込み可
    .setUserWriteAccess(user, true); // ユーザ自身のみ書き込み可
  // ACLを設定して保存
  await task
    .set('acl', acl)
    .save();
}
```
 
---

## 一覧画面を作る

タスクの追加ができましたので、続いて一覧画面 list.html を作成します。一覧画面では、画面を表示したタイミングでタスクを読み込んで表示します。現在、次のように書かれています。

---

```js
ons.getScriptPage().onShow = async function() {
  // ここにはログインチェックの実装がありますが省略します
  await getTaskList();
  renderTasks();
}
```

---

## getTaskListの実装

getTaskListはNCMBからタスク一覧を取得します

---

```js
let tasks = [];
async function getTaskList() {
  // タスクのクラスを作成
  const Task = ncmb.DataStore('Task');
  // 未了なタスクを一覧で取得
  tasks = await Task
    .equalTo('done', false)
    .limit(100)
    .order('date') // 並び順は日付ごと
    .fetchAll();
}
```

---

## renderTasksの実装

タスク一覧を取得したら、後はこれを一覧表示するだけです。注意点は日付データを取り出す際には `task.get('date').iso` のように iso を付けてあげることです。

---

```js
function renderTasks() {
  // すでに実装済み
}
```

---

## タスクを完了する

タスク一覧でチェックアイコンをタップするとタスクを完了状態にします。これは `.done` をタップした際のイベントを使います。 `// タスクを完了させる` を探してください。

---

```js
ons.getScriptPage().onInit = async function() {
  // タスクを完了させる
  $(document).on('click', '.done', async e => {
    // タップした対象のobjectIdを取得
    const objectId = $(e.target).parents('ons-list-item').data('objectid');
    // タスクオブジェクトを取得する
    const task = getTask(objectId);
    // ここに記述します
    // タスクのステータスを更新する
    // タスクを一覧データから消す
    tasks = tasks.filter(t => t.get('objectId') !== objectId)
    // 再描画
    renderTasks();
  });
  // 省略
}
```

---

記述する処理は次のようになります（`ここに記述します` の下になります）。

---

```js
// タスクを完了させる
$(document).on('click', '.done', async e => {
  // タップした対象のobjectIdを取得
  const objectId = $(e.target).parents('ons-list-item').data('objectid');
  // タスクオブジェクトを取得する
  const task = getTask(objectId);
  // ここに記述します
  // タスクのステータスを更新する
  await task.set('done', true).update();
  // タスクを一覧データから消す
  tasks = tasks.filter(t => t.get('objectId') !== objectId)
  // 再描画
  renderTasks();
});
```

---

## タスクの編集画面に遷移する

タスク一覧で編集アイコンをタップするとタスクの編集画面に遷移します。これは `.edit` をタップした際のイベントを使います。ここで行っているのはタスクを完了にする処理と同じようにタスクを取得し、それをedit.htmlに送っているだけです。

---

```javascript
// すでに実装済み
$(document).on('click', '.edit', async e => {
  const objectId = $(e.target).parents('ons-list-item').data('objectid');
  const task = getTask(objectId);
  $('#nav')[0].pushPage('edit.html', {data: {task}})
});
```

---

## タスク編集画面を作る

タスク編集画面は edit.html になります。ここでは前画面から送られてきたタスクデータを表示します。taskはapp.jsで定義した変数になります。

---

```js
// すでに実装済み
ons.getScriptPage().onShow = async function() {
  task = this.data.task;
  for (const key in task) {
    let value = task.get(key);
    if (key === 'date') {
      value = dayjs(task.get(key).iso).format('YYYY-MM-DD');
    }
    $(`.task_${key}_label`).html(value);
    $(`.task_${key}`).val(value);
  }
}
```

---

## タスクの更新は `#updateTask` をタップした際のイベントで実行します。

```javascript
// タスクを更新する（すでに実装済み）
$('#updateTask').on('click', async e => {
  const title = $('#title').val();
  const date = $('#date').val();
  const body = $('#body').val();
  try {
    await updateTask(title, date, body);
    $('#nav')[0].popPage();
  } catch (e) {
    ons.notification.alert('タスクの更新に失敗しました');
  }
})
```

---

## updateTask の実装

`updateTask` は次のようになります。新規作成時の処理と変わりません。違うのは save ではなく、updateになっていることです。

---

```js
async function updateTask(title, date, body) {
  task
    .set('title', title)
    .set('date', new Date(date))
    .set('body', body);
  await task.update();
}
```

---

## まとめ

---

今回のハンズオンでは次の3つの機能を利用しました。

1. 会員管理
    - 会員登録（ID/パスワード）
    - 会員認証（ID/パスワード）
2. データストア登録/更新
3. データストア検索