Monaca + NCMBでカメラメモアプリを作ろう

# ハンズオンのベースプロジェクト

[Monacaのダイレクトインポート](https://monaca.mobi/en/directimport?pid=607f75bae78885721d014ff5)からプロジェクトを取り込めます。

---

# 仕様と画面の説明

---

## 仕様について

---

### 仕様1

- 認証する
  - ID/パスワード認証を利用します
- 写真を撮影する
  - スマートフォンの標準機能を使います
- メモを書く

---

### 仕様2

- 写真とメモを一緒に保存する
  - 写真はファイルストアに、メモはデータストアに保存します
- 写真一覧を表示する
  - メモからデータを取得します
- 写真とメモを表示する

---


## 利用している技術

今回はOnsen UIを利用しています。JavaScriptは素のJavaScriptで、jQuery/Vue/React/Angularなどは利用していません。

---

## 画面

カメラメモアプリの画面は全部で5つです。

- ログイン/新規登録画面
- カメラ画面
- メモ画面
- 写真一覧画面
- 写真/メモ画面

---

### ログイン/新規登録画面

ログインと新規登録は処理が似ているので一つの画面にまとめてしまいました。ID/パスワードで認証していますが、追加で表示名も登録できます。

---

### カメラ画面

カメラアイコンが表示されています。アイコンのクリックでファイル選択（カメラ）が立ち上がります。

---

### メモ画面

撮影した写真のプレビューと、説明欄が表示されます。メモを残しておきます。

---

### 写真一覧画面

データストアに保存されているメモ情報を取得し、そこに紐付いている写真を一覧表示します。

---

### 写真/メモ画面

写真一覧画面で写真をタップすると、写真の拡大表示とメモを表示します。

---

# 認証を作ろう

---

## アプリ全体の処理

まず `www/js/app.js` の中で、NCMBを初期化します。

---

```javascript
const applicationKey = 'YOUR_APPLICATION_KEY';
const clientKey = 'YOUR_CLIENT_KEY';
const ncmb = new NCMB(applicationKey, clientKey);
```

---

## 認証の判定

まず大事なのは認証の判定です。今回は次のように実装しています。これは `www/camera.html` に実装しています。現状は次のようになっています。

---

```javascript
ons.getScriptPage().onShow = async function() {
  const user = null; // ← 書き換えます
  if (!user) {
    // ログインしていない場合はログイン画面に遷移
    return document.querySelector('#nav').pushPage('login.html');
  }
  try {
    // セッションの有効性チェック
    
  } catch (e) {
    // エラー = セッションが無効の場合
    localStorage.removeItem(`NCMB/${ncmb.apikey}/CurrentUser`); // 強制的にlocalStorageを削除
    ncmb.sessionToken = null; // セッショントークンも削除
    // ログイン画面に遷移
    document.querySelector('#nav').pushPage('login.html');
  }
  // ログインしている場合はここに来る（特に処理なし）
}
```

---

## ログインユーザ情報を得る

ログインユーザ情報は `ncmb.User.getCurrentUser()` で取得できます。ログインしていない場合は null が返ってきます。 `camera.html` の内容を次のように書き換えてください。

```js
// 元
const user = null; // ← 書き換えます

// 変更後
const user = ncmb.User.getCurrentUser();
```

---

## セッションの有効性チェック

ユーザ情報は取得できても、セッションが無効になっている可能性があります。そこで、適当なデータストアにアクセスして、セッションの有効性を確認します。セッションが無効な場合、下記コードはエラーを起こします。

```js
// セッションの有効性チェック
await ncmb.DataStore('Hello').fetch();
```

---

## ログイン/新規登録画面

ログイン/新規登録画面（login.html）ではユーザ名とパスワードを入力しています。今回は処理を簡略化するため、新規登録とログイン処理を一つにまとめています。

---


### 認証処理

認証を実行する処理は `#login` をクリックした際のイベントで作成していきます。

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

async function signInOrLogin() {
  // 必要な変数を揃えます
  const userName = this.querySelector('#userName').value;
  const password = this.querySelector('#password').value;
  try {
    // ユーザ登録処理を実行
    // すでに同名でユーザ登録済みの場合、エラーになります
    await registerUser(userName, password);
  } catch (e) {
    // すでに同名でユーザ登録済みの場合、こちらにきます（エラーは無視します）
  }
  try {
    // ログイン実行
    
    // カメラ画面に戻ります
    document.querySelector('#nav').resetToPage('camera.html');
  } catch (e) {
    // ログイン失敗した場合
    ons.notification.alert('ログイン失敗しました。ユーザ名、パスワードを確認してください');
    return false;
  }
}
```

---

### 新規ユーザ登録処理 registerUser について

新規ユーザ登録処理の registerUser は次のようになります。

---

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

---

### ログイン処理について

ログイン処理は次のように書きます。

```javascript
// ログイン実行
await ncmb.User.login(userName, password);
```

---

# 写真アップロード/一覧表示/詳細表示を作る

---

## カメラの処理について

カメラの起動、および写真の選択はcamera.htmlで行います。この処理はNCMBを使っていませんので説明することは多くありません。

---

## 写真の表示とメモ書き

写真が選択された後の画面 `photo.html` について紹介します（ここは口頭で説明）。

---

### 保存ボタンを押した時の処理

メモ書きを追加して保存ボタンを押したら、NCMBへの保存処理を実行します。

```js
async function saveMemo() {
  // まずボタンを多重で押されないようにします
  this.querySelector('#save').style.display = 'none';
  this.querySelector('#spin').style.display = 'block';

  // 必要な変数を準備します
  const body = this.querySelector('#memo').value;
  const { photo } = this.data;
  // 投稿するユーザ情報
  const user = ncmb.User.getCurrentUser();
  const fileName = `${user.get('userName')}-${(new Date).getTime()}-${photo.name}`;

  try {
    // 写真のアップロード処理
    await uploadPhoto(fileName, photo);
    
    // メモをデータストアに保存
    await addMemo(fileName, body);
    // メモを保存したらタブを一覧に変更する
    document.querySelector('#tabbar').setActiveTab(1);
    // アラートでアップロード完了を通知
    ons.notification.alert('アップロード完了しました');
    // ボタンを再度押せるように
    this.querySelector('#save').style.display = 'block';
    // スピナーを消す
    this.querySelector('#spin').style.display = 'none';
  } catch (e) {
    ons.notification.alert('エラーが発生しました');
  }
}
```

---

## 写真のアップロード処理

写真のアップロード処理は次のように記述します。 `ncmb.File.upload` メソッドでアップロードできます。

```js
// ファイルアップロード処理
async function uploadPhoto(fileName, photo) {
  await ncmb.File.upload(fileName, photo, getAcl());
}
```

---

同じACLをデータストアでも使うので getAcl として関数にしています。ACL（アクセス制限）はアップロードしたユーザに限りデータの閲覧、更新を可能としています。

```js
// ACLを返すのみ
function getAcl() {
  const user = ncmb.User.getCurrentUser();
  const acl = new ncmb.Acl();
  acl
    .setUserReadAccess(user, true)   // 特定ユーザに読み込み権限付与
    .setUserWriteAccess(user, true); // 特定ユーザに書き込み権限付与
  return acl;
}
```

---

次にデータストアに保存します。ここではメモ書きとファイル名を保存します。


```js
async function addMemo(fileName, body) {
  // データストアのクラス（DBでいうテーブル相当）を用意
  const Memo = ncmb.DataStore('Memo');
  // データストアのインスタンス（DBでいう行相当）を用意
  const memo = new Memo();
  // 値をセットして保存
  await memo
    .set('photo', fileName)  // 写真のファイル名
    .set('body', body)       // メモ
    .set('acl', getAcl())    // アクセス制限（ACL）
    .save();
}
```

---

## 写真の一覧表示

写真が保存できたら、一覧画面（list.html）に遷移します。ここでは、まず写真を取得します。

```js
ons.getScriptPage().onShow = async function() {
  // 写真を表示する処理
  showPhotos.bind(this)();
}
```

---
showPhotosでは、メモ一覧を取得して、その内容を表示します。

```js
async function showPhotos() {
  // 現在の内容をクリア
  this.querySelector('#photos').innerHTML = '';
  // メモ一覧を取得
  const memos = await loadMemos();
  // メモを表示
  showMemo(memos);
}
```

---

メモ一覧の表示はデータストアの機能を使います。作成日の逆順として、最新のものが上に出るようにします。

```js
async function loadMemos() {
  const Memo = ncmb.DataStore('Memo');
  return await Memo
    .order('createDate', true) // 新しいものが上（降順）
    .limit(100)                // 100件取得
    .fetchAll();
}
```

---

## まとめ

今回はNCMBの以下の機能を利用しました。

- 会員管理
  - ID/パスワードによる新規登録とログイン
- データストア
  - メモとファイル名の保存
- ファイルストア
  - 写真のアップロード
  - 写真のダウンロード