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アプリのデモを紹介します。今回はタスクの表示と、編集機能を解説します。一覧の表示、タスクの追加と削除は以下を参照してください。

[UMD版Hexabase JavaScript SDKを使ってTodoアプリを作る（タスク一覧表示・追加・削除） \#JavaScript \- Qiita](https://qiita.com/goofmint/items/eea4d8e6d950f7d68e21)

## デモ

デモはこちらの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を使って、以下のように作成しています。上半分が既存タスクの表示、下半分はタスクの追加・編集用フォームになっています。

```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を選択状態にするところで多少工夫が必要です。

```javascript
// 編集フォームの作成・項目を表示する処理
const editItem = async (e) => {
  const id = e.target.closest('tr').getAttribute('data-id');
	// 該当するアイテムを探す
	const item = params.items.find(item => item.id === id);
	// フォームの入力項目
	const { elements } = form;
  elements.id.value = id;
  elements.Title.value = item.get('Title');
  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);
  elements.Category.options[option.id].selected = true;
  elements.Button.innerText = '更新';
  elements.Button.disabled = false;
};
```

## タスクの更新

タスクの更新を行う処理です。`id` の有無によって、新規作成と更新とを分けています。どちらも新しい値を `set` メソッドで設定して、 `save` メソッドで保存する流れは同じです。

```javascript
document.querySelector('#button').onclick = async (e) => {
	e.preventDefault();

  // 新規保存と編集とを分ける
	const { id } = form.elements;
  const item = id.value === '' ?
        await params.datastore.item() :
        params.items.find(item => item.id === id.value);
  // アイテムに値をセット
  for (const input of Array.from(form.elements)) {
    if (['Button', 'id'].indexOf(input.name) > -1) continue;
    item.set(input.name, input.value);
  }
	// 保存
  await item.save();
  // 新規保存の場合は、一覧に追加
  if (!id) {
	  params.items.push(item);
  }
  // 再描画
  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/)