UMD版Hexabase JavaScript SDKを使ってTodoアプリを作る（タスクのステータス変更）

[Hexabase（ヘキサベース）](https://www.hexabase.com/)は企業においても安心して利用できるBaaS（Backend as a Service）を提供しています。多くのBaaSがそうであるように、主にフロントエンド開発者に利用してもらいたいと考えています。そこで現在、TypeScript SDKの開発が進められています。

元々TypeScriptで作られていることもあって、Next.jsやVue.jsなどでimportして利用することができます。しかし、もっと普通のWebアプリケーションなどで使う際には、CDN経由で利用したいと考える方もいるでしょう。そこで、UMD版Hexabase JavaScript SDKを作成しました。

今回は、そのUMD版Hexabase JavaScript SDKを使ったTodoアプリのデモを紹介します。今回はタスクの表示と、編集機能を解説します。一覧の表示、タスクの追加と削除および編集は以下を参照してください。

- [タスク一覧表示・追加・削除](https://qiita.com/goofmint/items/eea4d8e6d950f7d68e21)
- [タスクの表示・編集](https://qiita.com/goofmint/items/647b6ad5521712701bd0)

## デモ

デモはこちらのURLで体験できます。

[![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/348149e7-3f13-e621-bd64-cffd38cb8a1e.png)](https://jsfiddle.net/moongift/ohy3zwv1/144/)

## SDKの読み込み

SDKは下記URLで読み込めます。

```html
<script src="https://cdn.jsdelivr.net/npm/@hexabase/hexabase-js@latest/dist/umd/hexabase.min.js"></script>
```

バージョン指定する場合は、以下のようになります。メジャーバージョンが上がらない限りはlatest指定をお勧めします。

```html
<script src="https://cdn.jsdelivr.net/npm/@hexabase/hexabase-js@2.0.6/dist/umd/hexabase.min.js"></script>
```

モジュールとして使う場合には、以下のようになります。

```html
<script type="module">
import hexabase from 'https://cdn.jsdelivr.net/npm/@hexabase/hexabase-js@latest/+esm'
</script>
```

## SDKの初期化

SDKは以下のようコードで初期化します。これさえ終われば、後はTypeScriptと同じように使えます。

```javascript
const { HexabaseClient } = hexabase;
const client = new HexabaseClient();
```

## HTMLについて

HTMLはBootstrapを使って、以下のように作成しています。上半分が既存タスクの表示、下半分はタスクの追加・編集用フォームになっています。 `#Status` にステータス変更の表示を行います。

```html
<div class="container" style="padding-top: 1em;">
  <div class="row justify-content-md-center">
    <div class="col-10">
      <div class="row justify-content-end">
        <div class="col-5">
          <button type="button" id="add" class="btn btn-primary">➕ 新しいタスク</button>
        </div>
      </div>
      <table class="table">
        <thead>
          <tr>
            <th scope="col">タスク</th>
            <th scope="col">担当者名</th>
            <th scope="col">カテゴリ</th>
            <th scope="col">ステータス</th>
            <th scope="col">アクション</th>
          </tr>
        </thead>
        <tbody>
          
        </tbody>
      </table>
      <form>
        <div class="row g-3">
          <input type="hidden" name="id" />
          <div class="col">
            <input type="text" class="form-control" name="Title" aria-describedby="emailHelp" placeholder="新しいタスク">
          </div>
          <div class="col">
            <input type="text" class="form-control" name="Assignee" placeholder="担当者名">
          </div>
          <div class="col">
            <select name="Category" class="form-control">
            
            </select>
          </div>
          <div class="col">
            <button type="submit" name="Button" id="button" class="btn btn-primary" disabled>追加</button>
          </div>
        </div>
      </form>
      <div class="row g-3" style="padding-top: 1em;">
        <div class="col">
          <div id="Status">
          </div>
        </div>
      </div>
    </div>
  </div>
</div>
```

### 初期設定

まずコード内で使う変数、DOMを定義します。

```javascript
// 認証用のメールアドレスとパスワード
const email = 'demo5@moongift.jp';
const password = 'K4c%j%vzR7sQKS$u&Uo%';

// Hexabaseのワークスペース、プロジェクト、ワークスペースのID
const workspaceId = '644f6e5ab30d853869ec919f';
const projectId = '650a30501222568b1ae7a2c2';
const datastoreId = '65895ba97b4300d37757d562';

// 複数の処理で使う変数用
const params = {
	datastore: null, // データストア
  items: [], // 既存タスク一覧
};

// DOM
const table = document.querySelector('.table tbody');
const form = document.querySelector('form');
const addButton = document.querySelector('#add');
```

## 初期表示処理

初期表示の処理は以下のようになります。初期設定、データの読み込み、そしてフォームへの値設定となります。

```js
// 画面が読み込まれたときの処理
(async () => {
  await init();
  load();
  await makeForm();
})();
```

### 初期設定

初期設定用の `init` 関数ではログインやワークスペース・プロジェクト・データストアの取得を行っています。

```javascript
// 初期設定
const init = async () => {
	// ログイン
	const res = await client.login({email, password});
  // ワークスペース・プロジェクト・データストアの取得
  await client.setWorkspace(workspaceId);
  const project = await client.currentWorkspace.project(projectId);
  params.datastore = await project.datastore(datastoreId);  
  // 追加ボタンを押せるようにする
  addButton.disabled = false;
};
```

### データの読み込み

`load` 関数は既存のタスク一覧を読み込み、テーブルに表示します。

```javascript
// 既存のアイテム一覧を取得する処理
const load = async () => {
	const query = client.query(projectId);
  const items = await query
      .from(datastoreId)
      .select('*');
  params.items = items;
  // 表示更新
  showTable();
};
```

`showTable` 関数は、取得したアイテム一覧をテーブルに表示します。また、削除ボタンのイベントリスナーも設定しています。編集ボタンは `.edit` で、クリックすると `editItem` 関数を呼び出します。

```javascript
// タスク一覧の更新と、タスクへのアクション設定
const showTable = () => {
	// params.itemsに沿って、表示を更新
	table.innerHTML = params.items.map(item => `
  <tr data-id="${item.id}">
  	<td>${item.get('Title')}</td>
    <td>${item.get('Assignee')}</td>
    <td>${item.get('Category').ja}</td>
    <td>${item._status ? item._status.name : item.statusLabel}</td>
    <td>
    	<span class="edit">📝</span>
    	<span class="delete">🗑️</span>
    </td>
  </tr>`).join('');
  // 表示リセット
  form.reset();
  Status.innerHTML = '';
  
  // 削除のイベントリスナー
  table.querySelectorAll('.delete').forEach(dom => {
  	dom.addEventListener('click', deleteItem);
  });
  
  table.querySelectorAll('.edit').forEach(dom => {
  	dom.addEventListener('click', editItem);
  });
};
```

### フォームの作成

`makeForm` 関数は、フォームの `select` タグに対して、Hexabaseで設定しているオプションを取得し、描画します。

`ja` は日本語で、 `en` に英語が設定されています。

```javascript
// フォームの作成処理
const makeForm = async () => {
  // ドロップダウンの項目を取得
  const field = await params.datastore.field('Category');
  // オプション項目を取得
  const options = await field.options();
  // HTMLに反映
  const html = options.map(option => `
  	<option id="${option.id}">${option.value.ja}</option>
  `);
  document.querySelector('form [name="Category"]').innerHTML = html;
}
```

## タスクの編集

既存タスクの編集アイコンをクリックすると `editItem` 関数が呼ばれます。これは、既存データをフォームに表示する処理です。ドロップダウンのoptionを選択状態にするところで多少工夫が必要です。

`item.statusActions` を実行すると、次のステータスに変更するためのアクションが取得できます。これをボタンにして表示しています。ボタンをクリックすると `updateStatus` 関数が呼ばれます。

```javascript
// 編集フォームの作成・項目を表示する処理
const editItem = async (e) => {
  const id = e.target.closest('tr').getAttribute('data-id');
	// 該当するアイテムを探す
	const item = params.items.find(item => item.id === id);
  form.elements.id.value = id;
  form.elements.Title.value = item.get('Title');
  form.elements.Assignee.value = item.get('Assignee');
  const category = item.get('Category');
  const field = await params.datastore.field('Category');
  // オプション項目を取得
  const options = await field.options();
  const option = options.find(option => option.value.ja === category.ja);
  form.elements.Category.options[option.id].selected = true;
  form.elements.Button.innerText = '更新';
  form.elements.Button.disabled = false;
  
  // アイテムの詳細情報取得（ステータス用）
  await item.fetch(); 
  // 次のステータスをボタンで表示
  Status.innerHTML = item.statusActions.map(action => `
  	<button class="btn btn-success" data-id="${action.id}">${action.name}</button>
  `).join(' ');
  // 各ボタンにイベント設定
  Status.querySelectorAll('button').forEach(dom => {
  	dom.addEventListener('click', (e) => updateStatus(item, e));
  });
};
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/963bf858-f5ca-39bf-5ebf-3a1cbf948cac.png)

上記の画像の場合、 `作業中に戻す` や `完了する` がステータス変更のアクションになります。

## ステータスとは

Hexabaseのデータストア（RDBMSでいうテーブル相当）ではステータスという特別なフィールドが用意されています。

ステータスは、データの状態を表すためのフィールドです。例えば、案件のデータを管理する場合、ステータスには「新規」「進行中」「完了」などの値を設定します。そして、ステータスは直接変更はできず、ステータス変更のアクションを実行して変更します。

たとえば以下のようになります。

| データの状態     | ステータス |
| ---------------- | ---------- |
| 新規保存         | 起票       |
| プロジェクト開始 | 開始       |
| 納品完了         | 検収中     |
| プロジェクト終了 | 請求前     |
| 請求書発行       | 入金前     |
| 入金確認         | 完了       |

そして、このステータスの中には差し戻しが発生することもあるでしょう。検収中のステータスから開始に戻すこともあるでしょう。このような場合も、ステータスの変更はアクションを実行することで行います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/81032847-12f3-5478-1d1e-f52a0cc3d524.png)


Hexabaseでは、特定のステータスから別なステータスに変更するアクションは、あらかじめ定義したものだけが指定できます。

## ステータスの作成

ステータスは管理画面で作成します。ここで各ステータスが別なステータスに変更できるアクションを定義します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/85d0d1bd-faa0-1966-cd42-db8240a7308a.png)


## ステータスの更新

ステータスの更新は `updateStatus` 関数で行います。ステータス変更を行うアクションIDを取得して、 `item.execute` メソッドで実行します。

```javascript
// ステータスの更新処理
const updateStatus = async (item, e) => {
  e.preventDefault();
  // 次のステータスに移動するアクション
  const actionId = e.target.getAttribute('data-id');
	await item.execute(actionId);
  // 再描画
  showTable();
}
```

## まとめ

今回はUMD版Hexabase JavaScript SDKを使って、Todoアプリを作成するデモを紹介しました。UMD版Hexabase JavaScript SDKは、CDN経由で利用できるため、jQueryなど普通のWebアプリケーションでも利用できます。また、TypeScript SDKと同じように使えるため、TypeScript SDKを使っている方でも移行が容易です。

ぜひHexabaseを使ってWebアプリケーション開発を行ってください。

[Hexabase \| 新規事業向け開発・競争領域でのDX実現をサポート](https://www.hexabase.com/)