kintoneカスタマイズのつまづきポイント

Featured Image(Photo) by Nik Shuliahin on Unsplash

更新履歴

  • 2020/5/15 「インライン要素にするとモバイル画面で背景色がつかない」を追加しました。
  • 2020/5/12 「特に理由がなければインライン要素にしよう」を追加しました。
  • 2020/4/28 「画面遷移は発生しないけれどイベントは発火される場合がある」を追加しました。

特に理由がなければインライン要素にしよう

これは弊社も人のことは言えず、少しずつ改修しているところなのですが・・・

ヘッダーなどにHTML要素を表示する場合はインライン要素にしましょう。自分のカスタマイズのみを利用している場合はブロック要素でも問題ないのですが、他のカスタマイズやプラグインを併用する場合に問題になることがあります。

ブロック要素にしてしまうとこのように要素が縦に並んでしまいます。

(($) => {
  "use strict";

  kintone.events.on(["app.record.index.show"], (event) => {
    const element = kintone.app.getHeaderMenuSpaceElement();
    $(element).append("<div><button>1つ目</button></div>");
    return event;
  });

  kintone.events.on(["app.record.index.show"], (event) => {
    const element = kintone.app.getHeaderMenuSpaceElement();
    $(element).append("<div><button>2つ目</button></div>");
    return event;
  });
})(jQuery);

特に困るのは自分のカスタマイズではインライン要素にしていても、実行される順番が後だと先のブロック要素の影響を受けます。

(($) => {
  "use strict";

  kintone.events.on(["app.record.index.show"], (event) => {
    const element = kintone.app.getHeaderMenuSpaceElement();
    $(element).append("<div><button>1つ目</button></div>");
    return event;
  });

  kintone.events.on(["app.record.index.show"], (event) => {
    const element = kintone.app.getHeaderMenuSpaceElement();
    $(element).append("<span><button>2つ目(インライン)</button></span>"); // spanにしたのに…
    return event;
  });
})(jQuery);

利用者もプラグイン開発者もとても困ります。せめてお作法としてcybozu developer networkに掲載してほしいです。

インライン要素にするとモバイル画面で背景色がつかない

ということでインライン要素にしたらモバイル画面では背景色が灰色に…

ブロック要素だったらbackground-color: whiteで白色にできるのですが…

ゲストスペース内アプリの扱い

ゲストスペース内アプリはREST APIのエンドポイントが異なります。レコード取得を例にすると以下のとおりです。

アプリの場所エンドポイント
通常アプリhttps://xxx.cybozu.com/k/v1/record.json
ゲストスペース内アプリhttps://xxx.cybozu.com/k/guest/スペースID/v1/record.json

kintone.api.url()を利用すればある程度のパターンは対応できます。対応できないパターンはこちらのページ下部でまとめられています。

が、そもそもkintone.api.url()にtrueやらfalseやら指定せずとも勝手に判断してほしいです。

特定の環境でカスタマイズする場合は現在の仕様で問題ないと思います。しかし、カスタマイズを配布するような場合はアプリの場所がどこにあるかわからないので問題になります。

このあたりサイボウズさんが提供されているJavaScriptライブラリで吸収してくれるとありがたいのですがゲストスペースIDを指定する設計になっているので注意が必要です。

画面遷移は発生しないけれどイベントは発火される場合がある

詳細画面から編集画面への移動、編集画面でキャンセルして詳細画面への移動、では画面遷移が発生しません。そのため、編集、キャンセルを繰り返すとapp.record.edit.showイベントが複数回呼ばれることになります。ロジックによっては処理が重複する場合があるので注意が必要です。例えば以下のようなコードです。

(($) => {
  "use strict";

  console.log("load file");

  const button =
    '<button type="button" id="potara-button">click(jQuery)</button>';

  kintone.events.on("app.record.edit.show", (event) => {
    const spaceElement = kintone.app.record.getHeaderMenuSpaceElement();
    $(spaceElement).append(button);

    console.log("add EventListener");
    $("body").on("click", "#potara-button", () =>
      console.log("clicked(jQuery)")
    );
  });
})(jQuery);

このような動作になります。

私はかなりハマりました。。。

変更履歴でも画面遷移が発生せずにapp.record.detail.showが複数回呼ばれます。

(($) => {
  "use strict";

  console.log("load file");

  const button =
    '<button type="button" id="potara-button">click(jQuery)</button>';

  kintone.events.on("app.record.detail.show", (event) => {
    const spaceElement = kintone.app.record.getHeaderMenuSpaceElement();
    $(spaceElement).append(button);

    console.log("add EventListener");
    $("body").on("click", "#potara-button", () =>
      console.log("clicked(jQuery)")
    );
  });
})(jQuery);

app.record.index.showの項目クリックによるソートも同様です。

app.record.index.edit.showも同様です。

app.record.detail.process.proceedも同様です。

return Promise;では問題になる場合がある

複数のカスタマイズ(プラグイン)を利用する場合、return Promise;では他のカスタマイズでエラーが発生する可能性があります。例えば以下のコードを実行すると2つ目のevent変数の内容が異なることがわかります。

kintone.events.on("app.record.create.submit.success", event => {
  console.log("1:", Object.keys(event));

  return kintone.api("/k/v1/record", "POST", {
    app: kintone.app.getId(),
    record: {}
  });
});

kintone.events.on("app.record.create.submit.success", event => {
  console.log("2:", Object.keys(event));
});

1: (4) [“type”, “appId”, “recordId”, “record”]
2: (2) [“id”, “revision”]

そのため、例えばrecordプロパティを参照しているとエラーが発生します。

kintone.events.on("app.record.create.submit.success", event => {
  console.log("1:", event.record.$id.value);

  return kintone.api("/k/v1/record", "POST", {
    app: kintone.app.getId(),
    record: {}
  });
});

kintone.events.on("app.record.create.submit.success", event => {
  console.log("2:", event.record.$id.value);
});

Uncaught TypeError: Cannot read property ‘$id’ of undefined

この状況を避けるには以下の対応が必要です。

あなたのカスタマイズの実行順が前の場合

  1. return Promise;しない
  2. 変数を作る
  3. 変数にプロパティ(”type”, “appId”, “recordId”, “record”)をセットする
  4. 変数をreturnする

あなたのカスタマイズの実行順が後の場合

  1. REST APIでレコードを取得する
  2. その取得した値を利用する

そして自分のカスタマイズが先に実行されるのか、後に実行されるのかはわかりません。

これサイボウズさん側でどうにかならないですかね・・・。利用者もプラグイン開発者もとても困ります。せめてお作法としてcybozu developer networkに掲載してほしいです。

カスタマイズの実行順序に関する公式見解はない

さて、上の問題に対応するためにはカスタマイズの実行順序が重要ですが、これに関する公式見解はありません(私は見つけられませんでした)。cybozu developer networkのコミュニティに「kintoneの全体JS、アプリJS、プラグインJSの読み込み順を調べてみた」という内容が投稿されていますが現状こういう仕様というだけで保証はできないのでしょう。おそらく。

上記の投稿では全体JS、アプリのJS、プラグインの実行順序だけで、それぞれが複数登録されている場合の順序はわからなかったので確認しました。確認に利用したのは以下のコードです。

(() => {
  "use strict";

  kintone.events.on("app.record.detail.show", (event) => {
    console.log("1つ目の全体JS"); // ここはスクリプトごとに変更
  });
})();

設定はこの通りです。

全体JS

アプリのJS

プラグイン

結果はこのようになりました。

ちなみにプラグインの読み込み順序を変更するには「kintoneシステム管理 > プラグイン」でプラグインの再インストールが必要です。「アプリの設定 > プラグイン」では変更できません。

なので気軽に自分のプラグインを先に読み込まれるように・・・とは言えないのでなおさらreturn event;の取り扱いが重要になります。

日時フィールドは秒を指定できるけど00秒固定になる

APIのレコード登録で日時フィールドは秒まで指定できますが、登録される値は00秒になります。日時フィールドでソートする場合は注意が必要です。

kintone
  .api("/k/v1/record", "POST", {
    app: kintone.app.getId(),
    record: { 日時: { value: "2012-01-11T12:13:14Z" } },
  })
  .then((res) => {
    console.log(res);
    kintone
      .api("/k/v1/record", "GET", {
        app: kintone.app.getId(),
        id: res.id,
      })
      .then((res2) => {
        console.log(res2);
        console.log(res2.record["日時"].value);
      });
  });

Promise {}
{id: “1”, revision: “1”}
{record: {…}}
2012-01-11T12:13:00Z

kintone.api()のmethodは大文字のみ

methodを小文字にするとエラーになります。

GETの例

const params = { app: 1, id: 1 };
try {
  kintone.api(kintone.api.url("/k/v1/record", true), "get", params);
} catch (error) {
  console.error(error);
}

Error: USAGE: kintone.api(pathOrUrl, method, params, opt_callback, opt_errback)

POSTの例

const params = { app: 1, record: {} };
try {
  kintone.api(kintone.api.url("/k/v1/record", true), "post", params);
} catch (error) {
  console.error(error);
}

Error: USAGE: kintone.api(pathOrUrl, method, params, opt_callback, opt_errback)

fileKeyは大文字小文字が区別される

fileKey(kのみ大文字)にする必要があります。

const method = "GET";
const url = kintone.api.url("/k/v1/file", true);
const headers = {
  "X-Cybozu-API-Token": "xxx",
};
const params = {
  filekey: "xxx",
};

try {
  const response = await axios({
    method,
    url,
    headers,
    params,
    responseType: "blob",
  });
  const blob = response.data;
} catch (error) {
  console.error(error);
}

Error: Request failed with status code 404

この記事を書いた人