はじめに
kintone連携サービスのkrewSheet便利ですよね!
何よりJavaScript APIがあるのがとても良い!
krewSheetの一覧画面で編集した結果をうまく使ってJSの独自処理を追加できるので、重宝してます。
kintone JavaScript APIの仕様にうまいこと似せて、
こんな感じのイベントハンドリングができます。楽しいね!
https://docs.krew.grapecity.com/krewsheet/#event_recordlist.html
こんなプログラム作ってました
Webpackでビルドする前提です。
import swal from 'sweetalert'
const CANCEL_MESSAGE = 'キャンセルしました。'
kintone.events.on('app.record.index.show', event => {
krewsheet.events.on('app.record.index.edit.submit', async event => {
const confirmed = await swal({
text: '他のアプリにAPI発行とか色々します。よろしいですか?',
buttons: {
// OKするとアラートを消さずにspinner状態になって待機
confirm: { closeModal: false },
cancel: true,
},
})
if (!confirmed) {
event.error = CANCEL_MESSAGE
}
return event
})
krewsheet.events.on('app.record.index.edit.submit.success', async event => {
try {
// 更新したレコードを使って他のアプリにAPI発行とか色々
// 成功しても失敗しても、spinner状態になってたアラートはここで消える
await swal('成功しました')
} catch (e) {
await swal('失敗しました')
}
})
return event
})
4行でまとめるとこんな感じ。
- krewSheetの一覧画面で何かしらレコード更新
- SweetAlertのこんな感じのダイアログを出して、OK押したらSpinner状態になって待機
- krewSheetがレコード更新完了したら、更新したレコードを使って他のアプリにAPI発行とか色々
- 成功しても失敗しても新しいアラートを出して、2のSpinnerはそこで消える
submit.failureハンドラが欲しい!
ここで一つ問題があるんです。
kintoneの「レコードアクセス権」を設定していて「編集権限がない」場合、krewSheetがレコード保存に失敗しやがるんですよ!
その場合、submit.success
ハンドラが発火しないので、submit
ハンドラで表示したアラートのSpinnerがずーっと消えずに残っちゃうんです。
画面左下にこっそりこんなエラーが出たり、console.errorにもエラー表示が出るけど、詳しくない人だったら、気づかずにずーっと待ってしまうはず。
なので、app.record.index.edit.submit.failure
みたいなイベントハンドラが本当は欲しいんですよね。そうすれば「krewSheetがレコード保存に失敗した時はこうリカバリ」ってロジックが自前で書ける。
無いから自作しました!
unhandledなErrorがthrowでもされていれば、こんな方法で無理やりハンドリングしてやることもできます。
ユーザのブラウザで起きた JavaScript のエラーを収集する
でも今回はただ「エラー出力がされているだけ」なので、その方法は無理。
ぢゃあどうするか?コンソールだけじゃなくて画面上にエラーが出てるのなら、DOM監視でエラーハンドリングしてやることにしました。
この記事がとても参考になりました。感謝!
JavaScriptのMutationObserverでDOMの変化を監視する方法
そして、どうせならkrewsheet.events.on()
みたいなイベントハンドリングをしてやりたいってことで、EventEmitter2ライブラリを使いました。
完成版がこちらになります!
import swal from 'sweetalert'
import EventEmitter2 from 'eventemitter2'
import emitHandler from './krewErrorHandler'
const CANCEL_MESSAGE = 'キャンセルしました。'
kintone.events.on('app.record.index.show', event => {
krewsheet.events.on('app.record.index.edit.submit', async event => {
const confirmed = await swal({
text: '他のアプリにAPI発行とか色々します。よろしいですか?',
buttons: {
cancel: true,
// OKするとアラートを消さずにspinner状態になって待機
confirm: { closeModal: false },
},
})
if (!confirmed) {
event.error = CANCEL_MESSAGE
}
return event
})
krewsheet.events.on('app.record.index.edit.submit.success', async event => {
try {
// 更新したレコードを使って他のアプリにAPI発行とか色々
// 成功しても失敗しても、spinner状態になってたアラートはここで消える
await swal('成功しました')
} catch (e) {
await swal('失敗しました')
}
})
// EventEmitter2を新しいグローバルオブジェクトに割り当てる
window.krewsheetNeo = { events: new EventEmitter2() }
// 保存失敗時のハンドリング処理(Neoがつきます)
krewsheetNeo.events.on('app.record.index.edit.submit.failure', async event => {
await swal('エラー:' + event.error)
})
// 通常のkrewsheet一覧表示イベント
krewsheet.events.on('app.record.index.show', event => {
// failureイベントハンドラの発火タイミングをこの中で設定
emitHandler(CANCEL_MESSAGE)
})
return event
})
export default (...excludeErrors) => {
// 監視ターゲットの取得(krewSheetのステータスバー)
const target = document.querySelector('.GCSK_statusbar')
// オブザーバーの作成
const observer = new MutationObserver(records => {
// エラーメッセージ表示時だけが対象
if (records.every(r => r.target.textContent !== 'エラー')) {
return
}
// ステータスバー内のエラーメッセージを取得
const error = document.querySelector('#GCSK-statusMessage').textContent
if (excludeErrors.includes(error)) {
// 特定のエラーメッセージはハンドリングから除外
return
}
// イベント発火
krewsheetNeo.events.emit('app.record.index.edit.submit.failure', { error })
})
// 監視オプションの作成
const options = {
childList: true, // 子要素リストの変化を監視
subtree: true, // 子孫ノードを監視対象に含める
}
// 監視の開始
observer.observe(target, options)
}
どうですか!めっちゃそれっぽいのが出来ましたよ!
これでレコードアクセス権の問題で保存に失敗した時も、綺麗にアラートを消してあげることができます。
ポイントは、confirmダイアログをキャンセルした時も便宜上エラー扱いになるので、その場合だけはメッセージを判定してハンドリングの対象外にしてあげることですね。
終わりに
ってわけで、頑張って作りはしましたが、グレープシティさん、どうか公式でapp.record.index.edit.submit.failure
ハンドラをお願いします
あとkintoneのように、submitハンドラ内でreturn false
するとキャンセルできるようにもして欲しいなぁ〜。
追記)kintone REST APIの evaluate.json
使った方が良さそう…
去年の夏に、こんな/k/v1/records/acl/evaluate.json
と言うAPIコマンドが追加されたんでした!
https://developer.cybozu.io/hc/ja/articles/360000869566
今回failure
ハンドラを作った唯一の目的が「編集権限がないレコードを更新試みて失敗した場合」だったので、それを回避するだけならsubmit
ハンドラ内でevaluate.json
を使って事前チェックしてやれば十分じゃないか・・・
import swal from 'sweetalert'
const CANCEL_MESSAGE = 'キャンセルしました。'
kintone.events.on('app.record.index.show', event => {
krewsheet.events.on('app.record.index.edit.submit', async event => {
// 追記ここから
// REST APIで更新対象レコードのアクセス権を調べる
const ids = event.records.map(record => record.$id.value)
const { rights } = await kintone.api('/k/v1/records/acl/evaluate', 'GET', {
app: kintone.app.getId(),
ids,
})
// アクセス権がないレコードが1つでもあったら以降の処理はキャンセル
if (rights.some(acl => !acl.record.editable)) {
return event
}
// 追記ここまで
const confirmed = await swal({
text: '他のアプリにAPI発行とか色々します。よろしいですか?',
buttons: {
cancel: true,
// OKするとアラートを消さずにspinner状態になって待機
confirm: { closeModal: false },
},
})
if (!confirmed) {
event.error = CANCEL_MESSAGE
}
return event
})
krewsheet.events.on('app.record.index.edit.submit.success', async event => {
try {
// 更新したレコードを使って他のアプリにAPI発行とか色々
// 成功しても失敗しても、spinner状態になってたアラートはここで消える
await swal('成功しました')
} catch (e) {
await swal('失敗しました')
}
})
return event
})
krewSheet側に改善してもらいたいのは、ぜひ一覧表示時にevaluate.json
を考慮して「アクセス権のないレコードを編集状態にできない」って仕様にしてもらいたいですね!そうすればsubmit
ハンドラが発火すらしないので、より楽にカスタマイズできる。
(結局failure
ハンドラは要らなかったのか・・・・orz)