Monacaを使って何かアプリを作ってみたいと思っても、いざとなるとアイデアが出てこないかもしれません。また、最初から大きなアプリを作ろうと思うと、何から手を付けて良いのか分からないことでしょう。

そこで、この記事では手順を踏んで簡単なアプリを開発してみます。最初の一歩として、ぜひチャレンジしてみてください。

今回はNCMB(ニフクラmobile backend)と組み合わせて、作業依頼アプリを開発します。記事は全部で2回に分けており、後半となるこの記事では作業依頼の登録と一覧、作業割り当て、そしてコメント投稿機能を開発します。前半の記事はこちらです。

作業依頼を作成する

作業依頼を作成する画面は pages/new.html です。この画面には pages/home.html のツールバーのプラスアイコンをタップすると遷移します (9〜11行目)。

<a href="/new/" class="link">
  <i class="f7-icons">plus</i>
</a>

作業依頼を作成する処理は pages/new.htmlsave メソッドで行います (99〜133行目)。このメソッドは「依頼を登録する」ボタンを押した際に呼ばれます。

まず入力内容をオブジェクトにします (101行目)。

// 入力内容をシリアライズ(app.jsに定義)
const params = serializeForm(this.$el.find('form#task')[0]);

次にNCMBのクラス(DBでいうテーブル相当)を用意します。名前はTaskクラスとしています (103行目)。NCMBのデータストア機能の詳細については公式ドキュメントを参照してください。

// タスククラス(DBでいうテーブル相当)を用意
const Task = ncmb.DataStore('Task');

このTaskクラスのインスタンスを作ると、それがDBでいう行相当のオブジェクト(タスクオブジェクト)になります (106行目)。

// タスクオブジェクト(DBでいう行相当)を用意
const task = new Task();

そして、タスクオブジェクトの set メソッドを使って入力値を設定します (108行目)。

// 入力値を適用
for (const key in params) {
  task.set(key, params[key]);
}

次にアクセス権限を設定します。これは申請者(ログインユーザ)は読み書き可能、adminグループに所属しているユーザも読み書き可能として設定します (111〜119行目)。ACLはAccess Control List (アクセス制御リスト) の略です。

// ACL(アクセス権限)を準備
const acl = new ncmb.Acl();
const user = ncmb.User.getCurrentUser();
// 申請者は読み書き可能
// adminグループのユーザの読み書き可能
acl
  .setUserWriteAccess(user, true)
  .setUserReadAccess(user, true)
  .setRoleWriteAccess('admin', true)
  .setRoleReadAccess('admin', true);

このACLと、申請者情報、ステータスを初期化して保存します (121〜127行目)。

// ACLと申請者、ステータスを初期化
task
  .set('acl', acl)
  .set('owner', user.displayName)
  .set('status', '申請中');
// 保存
await task.save();

保存処理が完了したら、前の画面 pages/home.html へ戻って、依頼登録完了した旨アラートを出します (128〜130行目)。

// 前の画面に戻る
$f7router.back();
// 完了のアラート
app.dialog.alert(依頼を登録しました);

依頼一覧画面の作成

次に依頼を一覧表示する画面 pages/home.html を作成します。これは画面を表示したタイミングで、作業依頼の取得と表示を行います (42〜44行目)。

// 作業依頼の一覧を取得
const tasks = await getTasks();
// 一覧を表示
showTasks.bind(page.$el)(tasks);

getTasksはNCMBから依頼を取得する関数です (77〜83行目)。 include メソッドを使って承認者のデータも読み込みます。

// NCMBから作業一覧を取得する処理
const getTasks = async () => {
  const Task = ncmb.DataStore('Task');
  return await Task
    .include('accept') // 承認者も読み込む
    .fetchAll();
}

showTasksは受け取った一覧一覧のデータを描画する処理です (86〜96行目)。HTMLのテーブルタグに渡されたデータを流し込むだけです。

// 作業一覧を表示する処理
const showTasks = function(tasks) {
  this.find('.tasks tbody').html(tasks.map(task => `
    <tr data-objectId="${task.objectId}">
      <td>${task.status}</td>
      <td>${task.accept ? task.accept.displayName : '未定'}</td>
      <td>${task.category}</td>
      <td>${task.detail}</td>
      <td>${task.priority}</td>
    </tr>
  `).join(''))
};

一覧を表示したら、各行をタップした際のイベントを設定します (47〜53行目)。タップされた作業依頼を特定し、それを編集画面に送ります。

// 表示した作業一覧にイベント設定
page.$el.find('.tasks tbody tr').on('click', e => {
  // 選択された作業を取得
  const objectId = $(e.target).parents('tr').data('objectId');
  const task = tasks.filter(task => task.objectId === objectId)[0];
  // 編集画面に移動
  $f7router.navigate({name: 'Edit'}, {props: { task }});
});

作業編集画面の作成

編集画面 pages/edit.html では、前の画面(一覧画面)から受け取った作業依頼データを描画します。Framework7ではテンプレートに文字列リテラルが利用できるので、JavaScriptをそのまま書けます (40〜45行目)。

<div class="item-content">
  <div class="item-inner">
    <div class="item-header">依頼者</div>
    ${task.owner}
  </div>
</div>

そして作業担当者を割り当てる際には、Framework7の絞り込み機能付きのドロップダウンを利用します。

<a
  class="item-link smart-select smart-select-init"
  data-open-in="popup"
  data-searchbar="true"
  data-searchbar-placeholder="絞り込み"
>
  <select name="worker" id="worker">
  </select>
  <div class="item-content">
    <div class="item-inner">
      <div class="item-title">作業担当者</div>
    </div>
  </div>
</a>

JavaScript側ではユーザの一覧を取得し、このドロップダウン #worker に対してユーザを追加していきます。

const users = await ncmb.User.fetchAll();
$('#worker').html(users.map(user => `
  <option value="${user.objectId}">${user.displayName}</option>
`).join(''));

管理者とユーザでの表示の出し分け

作業者の割り当ては管理者しか行えません。作業担当者・依頼者は作業依頼の閲覧・ステータスの変更・コメントの追加のみ行います。CSSを使って権限による表示の出し分けをしましょう (148〜158行目)。

// ログインユーザを取得
const user = ncmb.User.getCurrentUser();
// 管理者であれば
if (user.get('admin')) {
  // 作業者向けのUI項目を非表示に
  $('.only-no-admin').hide();
} else {
  // 管理者向けのUI項目を非表示に
  $('.only-admin').hide();
  // コメントを表示
  showComment.bind(page.$el)(task.comments);
}

ここで出てきた showComment はコメントを表示する関数です。単純にHTMLを表示しているだけなので、下記のコードを参照してください (220〜234行目)。

// コメントを表示する処理です
const showComment = function(comments) {
  if (!comments) return;
  this.find('#comments').html(this.find('#comments').html() + comments.map(comment => `
    <li>
      <div href="#" class="item-content">
        <div class="item-inner">
          <div class="item-title-row">
            <div class="item-title">${comment.displayName}</div>
          </div>
          <div class="item-text">${comment.comment}</div>
        </div>
      </div>
    </li>
  `).join(''))
};

情報の更新

更新ボタンを押したタイミングで save メソッドが実行されます。先程の作業依頼を登録する際とあまり変わりません (166〜198行目)。

まず入力内容をオブジェクト化します (168行目)。

// 入力内容をシリアライズ(app.jsに定義)
const params = serializeForm(this.$el.find('form#task')[0]);

作業担当者が設定したステータスをタスクオブジェクトにセットします (169行目)。

task.set('status', params.status);

次に管理者が作業担当者を割り当てた場合を想定し、ACLを作成します。この時のACLは、作業担当者に対して作業依頼データの読み書き権限を追加するものです (172〜184行目)。

// 作業者を特定
const worker = users.filter(u => u.objectId === params.worker)[0];
if (worker) {
  // 現在のアクセス権限を取得
  const acl = task.get('acl');
  // 作業者の分を追加
  acl[params.worker] = {
    write: true,
    read: true
  };
  task
    .set('acl', acl)
    .set('worker', params.worker)
    .set('workerName', worker.displayName);     // 作業者名を設定
}

そして管理者アカウントであれば、自分自身を承認者として設定します。また、作業依頼が申請中の状態であれば、作業中に変更します (186〜193行目)。

// 管理者だったら承認者として設定
const user = ncmb.User.getCurrentUser();
if (user.admin) {
  task
    .set('accept', user);
  if (task.status === '申請中') {
    task.set('status', '作業中');
  }
}

後はタスクを更新し、クラウド側のデータに反映します (195行目)。

await task.update();

更新完了したら、前の画面に戻ります (197行目)。

// 前の画面に戻る
$f7router.back();

コメントを登録する

コメントはコメント投稿ボタンを押した際に comment 関数 (201〜217行目) を呼び出します。この処理では入力されたコメント内容と入力者(ログインユーザ)情報を、作業依頼データの comments フィールドに追加します。NCMBはRDBMSではないので、データを柔軟に管理できます。また、一つのデータ(今回は作業依頼データ)の中に入れることでネットワーク利用回数の低減が実現できます。

// コメント処理
const comment = async function() {
  const comment_body = this.$el.find('#comment_body');
  // コメントを作成
  const comment = {
    comment: comment_body.val(),
    displayName: ncmb.User.getCurrentUser().displayName,
  };
  // 作業のcommentsに追加して更新
  await task
    .add('comments', comment)
    .update();

  // 入力を消す
  comment_body.val('');
  // コメントを追記
  showComment.bind(this.$el)([comment]);
}

まとめ

今回の作業依頼アプリの内容は以上です。データの追加、更新そして検索はどのようなアプリでも必要とされる機能です。今回のアプリを参考に、ぜひ実装してみてください。他にもNCMBにはファイルを保存する機能やプッシュ通知機能があります。こちらもぜひ皆さんのアプリ開発に活かしてください。

Framework7とMonacaを組み合わせることで、多彩な機能を持ったUIのアプリを開発できるようになります。ぜひトライしてみてください。