FJCT_ニフクラ mobile backend(mBaaS)お役立ちブログ

スマホアプリ開発にニフクラ mobile backend(mBaaS)。アプリ開発に役立つ情報をおとどけ!

Monaca + NCMBでカメラメモアプリを作る【その3:写真アップロード/一覧表示/詳細表示を作る】

f:id:mbaasdevrel:20201204215026p:plain

MonacaとNCMBで簡単なアプリを作ってみるハンズオンの資料です。今回はカメラメモアプリを作ってみます。カメラで写真を撮影し、そこにメモ書きを追加して保存しておくというアプリです。この記事ではカメラの処理とメモ書き、そして一覧画面周りを解説します。なおコードはNCMBMania/camera_appにて公開しています。

カメラの処理について

カメラの起動、および写真の選択はcamera.htmlで行います。この処理はNCMBを使っていませんので説明することは多くありません。全体を下記に掲載しますので、コメントを参考にしてください。

<ons-page>
  <ons-navigator id="nav">
    <ons-toolbar>
      <div class="center" id="toolbar-title">カメラ</div>
    </ons-toolbar>
    <div style="text-align: center; margin-top: 150px;">
      <ons-card style="text-align: center;">
        <ons-icon icon="fa-camera" size="180px" id="camera"></ons-icon>
      </ons-card>
      <div style="display: none;">
        <input type="file" id="photo" accept="image/*">
      </div>
    </div>
  </ons-navigator>
  <script>
    // 初期化時に実行される関数
    // イベント処理を追加します
    ons.getScriptPage().onInit = async function() {
      // カメラアイコンをクリックした時のイベント
      this.querySelector('#camera').onclick = showDialog.bind(this);
      // 写真が選択された時のイベント
      this.querySelector('#photo').onchange = showMemo.bind(this);
    }

    // カメラアイコンをクリックした時のイベント
    // 表示を隠してあるinput type="file"をクリックします
    async function showDialog() {
      this.querySelector('#photo').click();
    }

    // 写真を選択した時のイベント
    // 次の画面(photo.html)に選択されている写真を送信します
    async function showMemo(e) {
      document.querySelector('#nav').pushPage('photo.html', {data: {
        photo: e.target.files[0]}
      });
    }
  </script>
</ons-page>

f:id:mbaasdevrel:20201204214817p:plain

写真の表示とメモ書き

では次に写真が選択された後の画面(photo.html)について紹介します。まず画面は次のようなHTMLになっています。選択された写真とテキストエリアを表示します。

<ons-page>
  <ons-toolbar>
    <div class="left"><ons-back-button>カメラ</ons-back-button></div>
    <div class="center" id="toolbar-title">メモを書く</div>
  </ons-toolbar>
  <div style="text-align: center; margin-top: 20px;">
    <ons-card style="text-align: center;">
      <img src="http://placehold.jp/300x400.png" id="photo" width="300px" height="400px" />
    </ons-card>
    <p>
      <textarea class="textarea" rows="5" cols="45" placeholder="メモを書いてください" id="memo"></textarea>
    </p>
    <ons-icon size="30px" id="spin" spin icon="md-spinner"></ons-icon>
    <ons-button modifier="large" id="save">保存する</ons-button>
  </div>
</ons-page>

初期化時のイベント設定

画面の初期化時には #save を押した際のイベントを設定します。

ons.getScriptPage().onInit = async function() {
  this.querySelector('#save').onclick = saveMemo.bind(this);
}

画面表示時のイベント設定

画面を表示した際には選択されている写真をimgタグに表示する処理を行います。

ons.getScriptPage().onShow = async function() {
  showPhoto.bind(this)(this.data.photo);
}

f:id:mbaasdevrel:20201204214837p:plain

写真の読み込み

showPhotoの解説です。ここではFileオブジェクトをFileReaderで読み込みます。そして、それをimgタグに反映します。

async function showPhoto(photo) {
  const data = await loadPhoto(photo);
  this.querySelector('#photo').src = data;
}

loadPhoto関数は、この後別な画面でも使うので www/js/app.js に定義してあります。指定されたFileオブジェクトの内容をDataURL形式で返却します。

async function loadPhoto(photo) {
  return new Promise(res => {
    const reader = new FileReader();
    reader.onload = function(e) {
      res(e.target.result);
    }
    reader.readAsDataURL(photo);
  })
}

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

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

async function saveMemo() {
  // この中に記述します
}

まずボタンを多重で押されないようにします。

this.querySelector('#save').style.display = 'none';
this.querySelector('#spin').style.display = 'block';

次に必要な変数を準備します。ファイル名はほかのユーザと被らないように、ユーザ名とユニークなID(マイクロ秒)を付けています。

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

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

try {
  await uploadPhoto(fileName, photo);
  // 略
}

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

// ACLを返すのみ
function getAcl() {
  const user = ncmb.User.getCurrentUser();
  const acl = new ncmb.Acl();
  acl
    .setUserReadAccess(user, true)
    .setUserWriteAccess(user, true);
  return acl;
}

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

await addMemo(fileName, body);

// 略

async function addMemo(fileName, body) {
  const Memo = ncmb.DataStore('Memo');
  const memo = new Memo();
  await memo
    .set('photo', fileName)
    .set('body', body)
    .set('acl', getAcl())
    .save();
}

処理がうまくいったら、タブバーを一覧表示に切り替えます。さらにボタンを再表示します。

document.querySelector('#tabbar').setActiveTab(1);
ons.notification.alert('アップロード完了しました');
this.querySelector('#save').style.display = 'block';
this.querySelector('#spin').style.display = 'none';

写真の一覧表示

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

<ons-page>
  <ons-navigator id="photoNav" page="list-page.html">
  </ons-navigator>
  <template id="list-page.html">
    <ons-page>
      <ons-toolbar>
        <div class="center" id="toolbar-title">写真</div>
      </ons-toolbar>
      <div style="text-align: center; padding-top: 50px;">
        <ons-row id="photos">
        </ons-row>
      </div>
    </ons-page>
  </template>
</ons-page>

JavaScriptは画面表示時のイベントで実行します。

ons.getScriptPage().onShow = async function() {
  showPhotos.bind(this)();
}

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

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

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

async function loadMemos() {
  const Memo = ncmb.DataStore('Memo');
  return await Memo
    .order('createDate', true)
    .limit(100)
    .fetchAll();
}

メモ一覧を受け取ったら、後はHTMLに書き出すだけです。ここでは <ons-card /> で囲んで画像を表示しています。

function showMemo(memos, photos) {
  memos.forEach(memo => {
    const dom = document.createElement('ons-col');
    dom.innerHTML = `
      <ons-card>
        <img class="photo" src="http://placehold.jp/150x150.png" width="150px" />
      </ons-card>
    `;
    photos.appendChild(dom);
    ncmb.File.download(memo.get('photo'), 'blob')
      .then(async (photo) => {
        toView.bind(this)(dom, memo, photo);
      });
  });
}

f:id:mbaasdevrel:20201204215026p:plain

カードをクリックした際のイベントは toView となっています。ここは写真とメモを表示するために viewer.html へ遷移しています。

async function toView(dom, memo, photo) {
  const img = dom.querySelector('img');
  img.src = await loadPhoto(photo);
  dom.onclick = () => {
    this.querySelector('#photoNav').pushPage('viewer.html', {
      data: {
        memo,
        photo
      }
    });
  }
}

写真とメモの詳細表示

viewer.html は次のように写真とメモを表示するHTMLとなっています。

<ons-toolbar>
  <div class="left"><ons-back-button>一覧</ons-back-button></div>
  <div class="center" id="toolbar-title">メモ</div>    
</ons-toolbar>
<div style="text-align: center; padding-top: 50px;">
  <ons-card style="text-align: center;">
    <img src="http://placehold.jp/350x350.png" id="photo" width="350px" height="350px" />
  </ons-card>
  <p id="body">
  </p>
</div>

ここでは画面表示の際に前画面から受け取った情報を表示します。

ons.getScriptPage().onShow = async function() {
  showPhoto.bind(this)();
}

async function showPhoto() {
  const img = this.querySelector('#photo');
  img.src = await loadPhoto(this.data.photo);
  this.querySelector('#body').innerText = this.data.memo.get('body');
}

f:id:mbaasdevrel:20201204215042p:plain

これで画像とメモのアップロード、そして一覧表示、詳細表示までの処理ができあがりました。

まとめ

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

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

mBaaSには他にもたくさんの機能があります。ぜひほかのハンズオンで体験してください。

Monaca + NCMBでカメラメモアプリを作る シリーズリンク

中津川 篤司

中津川 篤司

NCMBエヴァンジェリスト。プログラマ、エンジニアとしていくつかの企業で働き、28歳のときに独立。 2004年、まだ情報が少なかったオープンソースソフトの技術ブログ「MOONGIFT」を開設し、毎日情報を発信している。2013年に法人化、ビジネスとエンジニアを結ぶDXエージェンシー「DevRel」活動をスタート。