Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

kintone「イベントハンドラー登録の適切なタイミングについて」考えてみた

More than 1 year has passed since last update.

はじめに

書き始めて長くなり上手くまとめられないので、結論だけ知りたい方は最後の方だけお読みください。

イベントハンドラー登録の適切なタイミング

cybozu developer netwaork 内の kintone Tips > 開発ノウハウ
イベントハンドラー登録の適切なタイミングについて には下記のような記述があります。少し長いですが引用します。

https://developer.cybozu.io/hc/ja/articles/360000882123#event

引用

kintoneの画面表示イベントが発生するタイミング
ブラウザが kintoneのページを読み込む時、kintone 本体のJavaScriptや、 カスタマイズによるJavaScriptは次の順番で実行されます。

  1. ブラウザがページのHTML読み込みを開始する
  2. kintone の JavaScript API を使うための初期化が行われる
  3. カスタマイズ JavaScript として登録されたスクリプトが読み込まれる
  4. kintone 本体の JavaScriptによるページ初期化処理が開始される
  5. ブラウザがページのHTMLを読み終わり、DOMContentLoadedイベント (※ 1) を発生させる
  6. HTMLで指定されている画像などのリソース読み込みが完了した後、ブラウザはloadイベント (※ 2) を発生させる

jQueryを使った場合など、慣例的なJavaScriptの書き方の一部には、5.のDOMContentLoadedイベントや 6.のloadイベントのイベントハンドラーで処理を始めるものがあります。

kintoneでは、これらのイベントよりも早い4.のタイミングで、kintone 本体の処理を始める方法を採用しています。
したがって、DOMContentLoadedイベントよりも、kintoneの画面表示イベントが先に発生することがあります (※ 3)。

イベントハンドラーの登録よりもイベントが先に発生した場合、イベントハンドラーは呼び出されません。
そのため、DOMContentLoadedやloadイベント内で、kintoneにイベントハンドラーを登録している場合、 それらが呼び出されないことがあります。 具体的には、次のイベントが該当します。

app.record.create.show
app.record.edit.show
app.record.detail.show

これらの画面表示時のイベントを確実にハンドリングするには、3. のカスタマイズ JavaScript が読み込まれた時に 同期的に、kintone.events.on()によりイベントハンドラーを登録してください。

(*太文字部分編集)

私の中で理解が出来ていなくてずっと引っかかっていた点はこの部分。

イベントハンドラーの登録よりもイベントが先に発生した場合、イベントハンドラーは呼び出されません。

自分なりに読み解くと、

[カスタマイズ JavaScript内で行った] イベントハンドラーの登録よりも [kintoneの画面表示] イベントが先に発生した場合、 [カスタマイズ JavaScript内で行った] イベントハンドラーは呼び出されません。

(*太文字部分追記)

それでは考察していきます。

kintone カスタマイズ とは何か?

公式には下記となっています。

cybozu developer networkには下記の記載があります。

スクリーンショット 2019-10-29 12.19.08.png

今回取り上げている「イベントハンドラ〜」の話しは、

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 を書きます。

チュートリアルの記事

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を表示させるとか

実際に動く
スクリーンショット 2019-10-29 14.39.29.png

コード
スクリーンショット 2019-10-29 14.40.10.png

イベントハンドラー登録

kintoneの画面表示イベントが発生するタイミングは次の通りでした。

  1. ブラウザがページのHTML読み込みを開始する
  2. kintone の JavaScript API を使うための初期化が行われる
  3. カスタマイズ JavaScript として登録されたスクリプトが読み込まれる
  4. kintone 本体の JavaScriptによるページ初期化処理が開始される
  5. ブラウザがページのHTMLを読み終わり、DOMContentLoadedイベント (※ 1) を発生させる
  6. 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!')})
})()

スクリーンショット 2019-10-29 16.27.54.png

DOMContentLoadedイベント

  1. ブラウザがページの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に独自処理を入れるボタンを設置する

良くあるカスタマイズ事例です。

この場合のパターンは下記のようになります。

  1. kintoneアプリにボタン設置用のスペースフィールドを設置。フィールドコードを[button]にする。
  2. kintone JavaScriptカスタマイズにて、kintone JavaScript API の kintone.app.record.getSpaceElement(id) 関数で、スペースフィールド[button]の要素を取得する。
  3. ボタン要素をJavaScriptで作成する。
  4. 取得した要素に3.で作成したボタン要素を追加する。
  5. ボタンを押した時に何かする処理を入れる。

  1. kintoneアプリにボタン設置用のスペースフィールドを設置。フィールドコードを[button]にする。 スクリーンショット 2019-10-29 19.11.15.png

コード

index.js
(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);

実行結果
スクリーンショット 2019-10-29 19.16.18.png

説明

この事例の場合、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);
    });
})();

デバックしてみるとイベントハンドラの登録がよく分かります。
スクリーンショット 2019-10-30 20.00.12.png

結論

  • JavaScriptは奥が深い
  • JavaScriptの世界を知る事でkintoneをより理解できる(気がする)

関連リンク

kintone関連

JavaScript関連

sy250f
システムエンジニア。 要件定義と基本設計、プロジェクト管理が主な業務です。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away