目次
(1) 環境構築編
(2) アプリ構築・設定編
(3) アプリ実装編 part1
(4) アプリ実装編 part2(この記事)
(5) ユニットテスト編
一連のソースは GitHub で公開しています。
前置き
第 3 回 の記事では、第 1 回 で作成した Vue.js
、TypeScript
、Pug
、SCSS
を組み込んだプロジェクトで kintone カスタマイズビューのイベント処理をきっかけに Trello のようなリスト・カード型の画面を構成するフロント側実装を行いました。
しかしながら、前回までの実装ではカードを動かしてもその情報は kintone には保存されず、あくまで見た目上カードを動かせるだけに過ぎないと言う話をしました。
今回はいよいよ動かしたカードの情報を kintone に保存する機能を実装し、アプリを完成させたいと思います。
今更ですがタイトルが長くてアレだったので短くしてナウくしました。
前提
以下の環境で作業しています。
- macOS Catalina
- Homebrew 2.1.16
- Node.js 13.1.0
- VisualStudio Code 1.40.1
(1) 環境構築編の記事で、以下をセットアップしました。
- Vue.js 4.0.5
- TypeScript 3.5.3
- vue-cli-plugin-pug 1.0.7
他、プロジェクト作成時の流れで Sass / SCSS
や ESLint
、Prettier
、Jest
などがセットアップされています。
(2) アプリ構築・設定編 の記事で、kintone 側で用意している雛形アプリ「案件管理」を使って新規アプリを構築し、型定義ファイルの生成まで行いました。
(3) アプリ実装編 part1 では画面をコンポーネントで分割し、ビジュアル面の実装を中心に進めました。
今回のゴール
前回、Vue.Draggable を組み込んだ事で、以下のようにドラッグ&ドロップでリスト間を移動できるようになりました。
今回はカードを別なリストに動かした際に自動的にレコードを保存するようにしましょう。
これにより、カードを動かすだけで確度を変更する事ができるようになります。
以下の順で説明していきます。
- ドロップ時のイベント処理を実装する
- 親コンポーネントに emit する
- kintone JS SDK でレコードを保存する
- プロジェクトをビルドしてアプリに適用する
(4) アプリ実装編 part2
ドロップ時のイベント処理を実装する
カードが別の確度のリストにドロップされたイベントを捕まえて、レコード保存する部分を実装します。
引き続き List コンポーネント に実装を加えていきます。
Vue.Draggable
のイベントについてはプロジェクトのページにきちんと解説があります。
ここでは、ドラッグ&ドロップ終了後のイベントである @end
を使います。
template
を以下のように修正しましょう。
<template lang="pug">
.list
.list-title
span.list-title-label 確度:
span.list-title-value {{listTitle}}
.list-body
Draggable.draggable(
:group="'list'"
:data-group="group"
@end="onDropEnd"
)
Card(
v-for="r in records"
:key="r.$id.value"
:record="r"
:data-record-id="r.$id.value"
)
</template>
.draggable
に @end="onDropEnd"
イベントハンドラを割り当てています。
さらに、:data-group="group"
として、どの確度のリストであるかを格納しておきます。
同様に、カードコンポーネントにも :data-record-id="r.$id.value"
として、そのカードのレコード番号を dataset
に格納しておきます。
@end
イベントの引数は、前回の記事 の @types/vuedraggable/index.d.ts
ファイルで定義した DropEvent
になります。
これには、item
としてドロップしたカードそのものの要素が、 from
にはドラッグ前に所属していた要素が、to
にはドロップされた先の要素の情報が格納されています。
そして、上の template
でそれぞれの要素に割り当てた dataset
の値を拾えば、どのレコード番号を持つカードをどのリストからどのリストにドロップしたかを捕捉できると言うわけです。
/**
* カードのドラッグ&ドロップ終了時処理
*/
onDragEnd(e: DropEvent) {
// 動かされたカードのレコード番号
const recordId: string = (e.item as HTMLElement).dataset.recordId!;
// 動かす前のリスト(確度)と動かされた先のリスト(確度)を確認し、同じだったら何もしない
const fromGroup: string = (e.from as HTMLElement).dataset.group!;
const toGroup: string = (e.to as HTMLElement).dataset.group!;
if (fromGroup === toGroup) {
return;
}
}
今回のアプリではリスト内のカードの順番までは制御しないので、リスト内でカードを(上下に)動かした場合、つまり dataset.group
の値に変化がない場合は何もせず処理を抜けるようにします。
リスト間でカードを移動した場合は、これらの情報をもとに kintone にレコード更新に行く事になります。
親コンポーネントに emit する
カードを動かした情報を kintone に保存に行くわけですが、この List
コンポーネント自身、レコードの情報はプロパティで受け取っただけでした。
そう言う観点では、このコンポーネント自身が kintone に直接接続してレコード更新に行くのはその責務を超えていると言うか、バランスを欠く実装と言えます。
外部との折衝役を受け持つ窓口は常に同じ人の方がふさわしいですよね。
と言う事で、List
コンポーネントは親コンポーネントである Board
コンポーネントに処理を依頼します。
Vue.js
では emit
と言う機構でこれを行います。
親に渡す情報としては、「どのレコード ID を」「どの確度に」動かしたか、と言う情報だけで充分です。もともとどこに居たとかなんと言う案件名だったかとかは伝えなくても支障ありません。
/**
* カードのドラッグ&ドロップ終了時処理
*/
onDragEnd(e: DropEvent) {
// 動かされたカードのレコード番号
const recordId: string = (e.item as HTMLElement).dataset.recordId!;
// 動かす前のリスト(確度)と動かされた先のリスト(確度)を確認し、同じだったら何もしない
const fromGroup: string = (e.from as HTMLElement).dataset.group!;
const toGroup: string = (e.to as HTMLElement).dataset.group!;
if (fromGroup === toGroup) {
return;
}
// 親コンポーネントのメソッドを emit
this.$emit("card-moved", { id: recordId, group: toGroup });
}
Board
コンポーネントはどう振る舞うのが良いでしょうか。
この人もまた、レコードの総体を親コンポーネントである App
から受け取っています。
そして先ほど子コンポーネントから card-moved
と言うイベントを emit されていますので、それを handleCardMoved
と言うメソッドで受け、同じように親コンポーネントに emit します。
部下から上がって来た申請をろくに見もせず上司に投げるだけのダメな中間管理職みたいな仕事ぶりですが、こと コンポーネント指向
の世界ではそのようにただただ連絡役に徹するだけのダメ上司こそ優秀であると言えます。
<template lang="pug">
.board
List.list(
v-for="g in listGroups"
:key="g"
:group="g"
:records="getGroupRecords(g)"
:data-group="g"
@card-moved="handleCardMoved"
)
</template>
<script lang="ts">
(省略)
/**
* カード移動時処理(子コンポーネントから emit)
*/
handleCardMoved(r: { id: string; group: string }) {
// 親コンポーネントのメソッドを emit
this.$emit("card-moved", r);
}
</script>
これで、大元である App.vue
まで情報が上がるようになりました。
App.vue
でも card-moved
イベントを子コンポーネントから emit できるようにします。
ハンドラである handleCardMoved()
メソッドは次の節で実装します。
<template lang="pug">
#app
Board.board(:records="records" @card-moved="handleCardMoved")
</template>
kintone JS SDK でレコードを保存する
ようやく kintone JS SDK の出番です。
まず、components/App.vue
で kintone JS SDK を使えるようにしなければいけません。
// デコレーター
import { Component, Vue } from "vue-property-decorator";
// kintone JS SDK
const kintoneJSSDK = require("@kintone/kintone-js-sdk");
(省略)
@kintone/kintone-js-sdk
を require
している行を追加しています。
import
じゃダメなんですか?と言うご意見もあるかもですが、
import * as kintoneJSSDK from "@kintone/kintone-js-sdk";
としても動くは動くのですが、 **「@kintone/kintone-js-sdk
の d.ts ファイルがありませんぜ」**と文句を言われるので、ここでは require
にしています。
そのためせっかく TypeScript で記述しているのにタイプフリーにはならないしエディタでのコード補完も機能しません。残念。
d.ts
ファイルを自作すれば良いかもですが、そこは公式に期待したいところです。issue も上がっている事ですし。
では、onDragEnd()
に SDK を通じてレコードを更新する実装を加えましょう。
/**
* カード移動時処理(子コンポーネントから emit)
*/
async handleCardMoved(r: { id: string; group: string }) {
// レコード操作オブジェクトを作成
const kintoneRecord = new kintoneJSSDK.Record();
// 更新を実行
const result = await kintoneRecord
.updateRecordByID({
app: kintone.app.getId(),
id: r.id,
record: {
確度: { value: r.group }
}
})
.catch((e: object) => {
window.alert(e);
});
}
いくつかポイントがあります。
まずメソッドを宣言する部分で、
async handleCardMoved(r: { id: string; group: string }) {
としています。
後で出て来るレコードを更新するメソッドは戻り値として Promise
オブジェクトを返却するため、async / await
で処理する事が可能です。
const kintoneRecord = new kintoneJSSDK.Record();
の部分が kintone JS SDK でレコードを操作するためのオブジェクトを作成するところです。
このように記述するとセッション認証でレコード操作ができます。
つまりログインユーザーの権限の影響を受けると言うわけです。
API トークンを使用したり、別途ユーザーアカウントの権限でレコード操作をする場合はこの前に kintone.Auth
オブジェクトや kintone.Connection
オブジェクトの準備が必要です。
この辺は公式のドキュメントに一通り記述があります。
さて、今回は既に更新対象とするレコードのレコード番号が分かっているので、 updateRecordByID()
メソッドでレコードを更新します。
// 更新を実行
const result = await kintoneRecord
.updateRecordByID({
app: kintone.app.getId(),
id: r.id,
record: {
確度: { value: r.group }
}
})
.catch((e: object) => {
window.alert(e);
});
メソッドにアプリ ID とレコード番号と変更するフィールドの値を引き渡すだけの、実に簡単なメソッドです。
これだけで簡単にレコードを更新できます。
ここではあくまでサンプルと言う事でエラー処理を window.alert()
でやっていますが、ちゃんとしたアプリにするならもっと気の利いた実装にしましょう。
このような実装を加える事で、手で移動したカードがブラウザリロード後もそのリストに並んでいるのが確認できるはずです。
参考までに、これを従来の JavaScript API から kintone REST API を呼ぶ形式で実装したコードを見てみましょう。
// 更新を実行
const result = await kintone.api(kintone.api.url("/k/v1/record", true), "PUT", {
app: kintone.app.getId(),
id: r.id,
record: { 確度: { value: r.group } }
})
.catch((e) => {
window.alert(e);
});
え、あんまり変わってないじゃないかって?
確かにそうかも知れません。
けれども、 文字列とメソッドの組み合わせで API を指定するよりもオブジェクトの関数でやりたい事を明示的に呼び出す書き方の方が可読性が高くコードとしてクリーンであると言えるのではないでしょうか。
プロジェクトをビルドしてアプリに適用する
さて、ここまででアプリの実装はひと段落です。
このシリーズでは、VS Code の拡張機能である Live Server を使用してローカルで生成されたファイルを kintone 上で表示するやり方で開発を進めていました。
この方式では当然本運用はできないので、ここまでの開発成果を ビルド してアプリに適用しましょう。
VS Code のターミナルウィンドウで以下のように実行します。
% yarn build
10〜20 秒ほどで、 dist/
フォルダの下に以下のようなファイルが作成されます。
これらのファイルは難読化及び圧縮化が施されており容易にリバースエンジニアリングできないものになっています。
また、ファイル名にはキャッシュ除けのランダムな文字列が含まれています。この辺は webpack
の設定で付けないように(常に固定名に)したりできますが、今回は説明を省きます。
出来上がった JS ファイルと CSS ファイルをアプリに適用します。
これでアプリを更新すれば、ビルド前の状態と同じ動作をする事が確認できるはずです。
まとめ
ここまででアプリの実装はひと段落です。
Vue.js
と TypeScript
、kintone JS SDK
を使い、kintone のカスタマイズを実装する一連の流れについて説明して来ました。
もちろんここまでの実装でこのアプリは実用レベルで使えるかと言うと、実際にはそんな事はありません。
例えば、
- 複数人でアプリを触っていた際、別の人がカードを動かしていたのを知らずに自分も動かしてしまってエラーが出てしまった
- あるいは他の人がレコードを編集したためエラーが出てしまった
- そもそもカードから別ウィンドウでレコード詳細に飛ぶ機能があるが、そちらでの変更結果はリロードしなければカスタマイズビューには反映されない
- 担当者以外はカードを動かせないようにしたい
- 1 つのリスト内で順番を任意に並び替えたい
- あるいは小計の降順など特定の条件でソートしたい
- リスト内のカードの小計の合計値をリストに表示したい
- 確度以外の条件でグルーピングしたい
など、ちょっと考えれば多数の問題点・改善点が見出せます。
この辺りはビジネス上の要求やアプリを使用するユーザー層によっても最適解が変わって来る部分でしょう。
次回は
というわけで、今回は Vue.js
と TypeScript
で開発するプロジェクトを kintone で運用に載せるところまでを見て来ました。
一昔前まではごくごく普通だった生の JavaScript ファイルを書いてアプリに適用して複数のブラウザで動作確認して・・・と言う開発の進め方と較べると、これらモダンなテクノロジーの採用による恩恵は計り知れないと言わざるを得ません。
しかし、こう言った進め方をすれば効率よくバグのない開発ができるかと言えば、それは言い過ぎです。
ここまでの解説では テスト に関する観点がまるっきり存在していません。
と言うわけで、最終回(予定)となる次回は、kintone カスタマイズを Jest でモダンにテストする手法について解説していこうと思います。