はじめに
書き始めて長くなり上手くまとめられないので、結論だけ知りたい方は最後の方だけお読みください。
イベントハンドラー登録の適切なタイミング
cybozu developer netwaork 内の kintone Tips > 開発ノウハウ の
イベントハンドラー登録の適切なタイミングについて には下記のような記述があります。少し長いですが引用します。
引用
(*太文字部分編集)
私の中で理解が出来ていなくてずっと引っかかっていた点はこの部分。
イベントハンドラーの登録よりもイベントが先に発生した場合、イベントハンドラーは呼び出されません。
自分なりに読み解くと、
[カスタマイズ JavaScript内で行った] イベントハンドラーの登録よりも [kintoneの画面表示] イベントが先に発生した場合、 [カスタマイズ JavaScript内で行った] イベントハンドラーは呼び出されません。
(*太文字部分追記)
それでは考察していきます。
kintone カスタマイズ とは何か?
公式には下記となっています。
cybozu developer networkには下記の記載があります。
今回取り上げている「イベントハンドラ〜」の話しは、
kintoneの画面をカスタマイズ
JavaScript API で画面をカスタマイズできます
の部分。Garoonは別として、REST APIの話しも今回関係ありません。つまり、「kintoneの画面の見た目(や動き)をkintoneで提供されるJavaScript API を通して、ある程度ユーザー側で変更出来ますよ」ということ。
カスタマイズ全般だと幅広いので今回取り上げるのは app.record.detail.show イベントが発生した時の話しに限って考えてみます。
kintone JavaScript の世界へようこそ
まずは下記サンプルコードをご覧ください。
レコード詳細画面が表示された時にメッセージを表示します。
kintone.events.on('app.record.detail.show', function(event) {
alert("レコード詳細画面を開きました");
});
kintone.events.on て何よ?
上記コード、実際はkintoneカスタマイズのお作法により下記のようになります。
// 始まりはIIFE (即時実行関数式)にします
// https://developer.mozilla.org/ja/docs/Glossary/IIFE
// 関数書き出しの
function(){} // これを
// さらに (ここにfunction)+() でラップする
(function(){})() // こんな感じ
// こうなる
(function(){
kintone.events.on('app.record.detail.show', function(event) {
alert("レコード詳細画面を開きました");
});
})()
区別するとこんな感じ
(function(){ // 通常のJavaScriptの世界
kintone.events.on( // ここは kintone JavaScript API の世界
'app.record.detail.show', // ここも kintone JavaScript API の世界
function(event) { // 通常のJavaScriptの世界
alert("レコード詳細画面を開きました"); // 通常のJavaScriptの世界
});
})()
kintone.events.on
kintoneカスタマイズでは嫌という程 kintone.events.on を書きます。
チュートリアルの記事
-
はじめようJavaScript 第12回 kintone JavaScript カスタマイズでkintoneのデータを見てみる
- レコード詳細ページでkintoneのrecordデータ取得と表示 あたりを参照
-
第1回 kintone JavaScript APIのイジりかた
- kintoneのイベント処理に触れてみる あたりを参照
kintone.events.on は kintoneに用意された、イベントハンドラー登録用関数です。
[kintone.events.on]===kintoneに用意されたイベントハンドラー登録用関数
イベントハンドラー
イベントハンドラーって何?の方に向けて少しまとめておきます。
ちょっと混乱してきたので、言い換えると
[kintone.events.on]===kintoneに用意されたイベントリスナー
イベントハンドラーはただのJavaScriptで書かれた関数
再度[kintone.events.on]を考えてみます。
kintone.events.on 再び
kintone.events.on() // kintoneから提供されたただの関数(APIとも言う)
kintone.events.on(引数1, 引数2) // 引数を2つ取る
kintone.events.on('app.record.detail.show', 引数2) // 引数1にはkintoneで提供するイベント名が入る
kintone.events.on('app.record.detail.show', function(){}) // 引数2はただのJavaScriptで書いた関数。ユーザーが実装する
kintone.events.on('app.record.detail.show', function(){alert('Hi!')}) // 例えばalertを表示させるとか
イベントハンドラー登録
kintoneの画面表示イベントが発生するタイミングは次の通りでした。
- ブラウザがページのHTML読み込みを開始する
- kintone の JavaScript API を使うための初期化が行われる
- カスタマイズ JavaScript として登録されたスクリプトが読み込まれる
- kintone 本体の JavaScriptによるページ初期化処理が開始される
- ブラウザがページのHTMLを読み終わり、DOMContentLoadedイベント (※ 1) を発生させる
- HTMLで指定されている画像などのリソース読み込みが完了した後、ブラウザはloadイベント (※ 2) を発生させる
最初、次が引っかかっていました。
イベントハンドラーの登録よりもイベントが先に発生した場合、イベントハンドラーは呼び出されません。
[カスタマイズ JavaScript内で行った] イベントハンドラーの登録よりも [kintoneの画面表示] イベントが先に発生した場合、 [カスタマイズ JavaScript内で行った] イベントハンドラーは呼び出されません。
ではどこにユーザー処理の イベントハンドラー を登録すれば良いのか? と言うと、
これらの画面表示時のイベントを確実にハンドリングするには、3. のカスタマイズ JavaScript が読み込まれた時に 同期的に、kintone.events.on()によりイベントハンドラーを登録してください。
つまり、
app.record.detail.show のイベント発生時に何か処理を入れたい時は、ユーザー作成のカスタマイズJavaScript内に同期的に(意図した順番で処理されること) kintone.events.on()を使ってイベントハンドラーを登録する と言うことになります。
最初のコードを少し変更して、ブラウザの開発者ツールのコンソールにログを出力するように変更します。
kintoneアプリのフィールド要素を、kintone JavaScript APIの kintone.app.record.getFieldElement(fieldCode) で取得してみます。
(function(){
kintone.events.on('app.record.detail.show', function(){console.log('Hi!')})
})()
DOMContentLoadedイベント
- ブラウザがページのHTMLを読み終わり、DOMContentLoadedイベント (※ 1) を発生させる
5.のタイミングではkintoneに登録したイベントハンドラーは呼び出されないと言うことでした。
実際に試してみます。
ブラウザのDocumentオブジェクトのDOMContentLoadedイベント発生時のハンドラに登録してみます。
(function(){
// ここでは画面の描画が終わっていないので、kintone APIでフィールド要素を取得できない。
window.addEventListener('DOMContentLoaded', function(){console.log('1', kintone.app.record.getFieldElement('datetime'))}); // 1 null
})();
(function(){
// ここでは画面の描画が終わっていないので、kintone APIでフィールド要素を取得できない。
window.addEventListener('DOMContentLoaded', function(){console.log('2', kintone.app.record.getFieldElement('datetime'))}); // 2 null
// kintone.events.on()関数内でイベントハンドラーを登録する。
kintone.events.on('app.record.detail.show', function(){
console.log('Hi!'); // Hi!
// DOMContentLoadedイベントのタイミングではイベントハンドラーは呼び出されない。
window.addEventListener('DOMContentLoaded', function(){console.log('3', kintone.app.record.getFieldElement('datetime'))}); // 何も表示されない
// Loadイベントのタイミングではイベントハンドラーは呼び出されない。
window.addEventListener('load', function(){console.log('4', kintone.app.record.getFieldElement('datetime'))}); // 何も表示されない又は状況により表示されるが同期的では無い
// kintone.events.on内に登録したイベントハンドラは呼び出される。
console.log('5', kintone.app.record.getFieldElement('datetime')); // 5 <div class="control-value-gaia value-5544356">...</div>
// 即時関数の外で定義しているイベントハンドラも呼び出される。
datetime(); // 6 <div class="control-value-gaia value-5544356">...</div>
});
})();
var datetime = function(){console.log('6', kintone.app.record.getFieldElement('datetime'))};
開発者コンソール
1 null
2 null
Hi!
5 <div class="control-value-gaia value-5544356">…</div>
6 <div class="control-value-gaia value-5544356">…</div>
4 <div class="control-value-gaia value-5544356">…</div>
う〜ん。4.は表示されますね。
事例 kintoneに独自処理を入れるボタンを設置する
良くあるカスタマイズ事例です。
この場合のパターンは下記のようになります。
- kintoneアプリにボタン設置用のスペースフィールドを設置。フィールドコードを[button]にする。
- kintone JavaScriptカスタマイズにて、kintone JavaScript API の kintone.app.record.getSpaceElement(id) 関数で、スペースフィールド[button]の要素を取得する。
- ボタン要素をJavaScriptで作成する。
- 取得した要素に3.で作成したボタン要素を追加する。
- ボタンを押した時に何かする処理を入れる。
コード
(function($){
// 3. ボタン要素をJavaScriptで作成する。
var objButton = function(elm){$(elm).append('<button id="kintone-event-test-button">ボタン</button>')};
kintone.events.on('app.record.detail.show', function(){
// 2. kintone JavaScriptカスタマイズにて、kintone JavaScript API の [kintone.app.record.getSpaceElement(id)](https://developer.cybozu.io/hc/ja/articles/201942014#step6) 関数で、スペースフィールド[button]の要素を取得する。
var elmButton = kintone.app.record.getSpaceElement('button');
// 4. 取得した要素に3.で作成したボタン要素を追加する。
objButton(elmButton);
// 5. ボタンを押した時に何かする処理を入れる。
$('#kintone-event-test-button').on('click', function(){alert('Hi!')});
});
})(jQuery);
説明
この事例の場合、kintone.events.on()のイベントハンドラ内で処理するのは、2.と4.と5.。
2.はkintone.events.on内のイベントハンドラでなければ、kintoneがHTMLを描画できていないのでここで取得するのが正解ですね。
kintone.events.on をちょっと覗いてみる
ここにあるように、kintone.events.on は書き換えが出来るようです。
(おすすめはしませんが)
ちょっとどうなっているか覗いてみたいと思います。
var refKintoneEventsOn;
(function(){
var referenceKintoneEventsOn = kintone.events.on; // オリジナル kintone.events.on の参照を変数にセット
refKintoneEventsOn = kintone.events.on; // クロージャー
var objCallbackEvents = {}; // ユーザーのcallback関数を保管
// kintone.events.on の書き換え
kintone.events.on = function(_events, _callback) {
if(typeof _events === "string") {
_events = [_events];
}
if(!(_events instanceof Array) || (typeof _callback !== "function")) {
return false;
}
_events.forEach(function(event){
if (objCallbackEvents[event]) {
objCallbackEvents[event].push(_callback);
} else {
objCallbackEvents[event] = [_callback];
}
});
console.log(objCallbackEvents);
};
})();
(function($){
var objButton = function(elm){$(elm).append('<button id="kintone-event-test-button">ボタン</button>')};
// kintone.events.on('app.record.detail.show', function(myCheckEvents){
refKintoneEventsOn('app.record.detail.show', function(myCheckEvents){ // オリジナルのkintone.events.on を使う
console.log('function(myCheckEvents)=>', myCheckEvents);
var elmButton = kintone.app.record.getSpaceElement('button');
objButton(elmButton);
$('#kintone-event-test-button').on('click', function(){alert('Hi!')});
});
})(jQuery);
(function(){
kintone.events.on('app.record.detail.show', function(myCheckEvents2){
console.log('function(myCheckEvents2)=>', myCheckEvents2);
var datetime = function(){console.log('2', kintone.app.record.getFieldElement('datetime'))};
datetime;
});
})();
(function(){
kintone.events.on('app.record.index.show', function(event){
console.log('function(event)=>', event);
});
refKintoneEventsOn('app.record.detail.show', function(event){
console.log('クロージャーを使って外部からオリジナルのkintone.events.onを参照する');
console.log(event);
});
})();
結論
- JavaScriptは奥が深い
- JavaScriptの世界を知る事でkintoneをより理解できる(気がする)