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

Featured Image(Photo) by Nik Shuliahin on Unsplash

更新履歴

  • 2021/3/16 「一部のフィールドコードはアプリ作成者の言語設定に依存する」を追加しました。
  • 2021/2/5 「REST APIのテーブルの行指定更新では全行必要」を追加しました。
  • 2021/1/8 「フィールド情報はfields.jsonとlayout.jsonを併用する必要があるかも」を追加しました。

REST APIのテーブルの行指定更新では全行必要

REST APIでレコードを更新する場合、通常のフィールドの場合は指定したフィールドのみ更新され、指定していないフィールドはそのまま維持されます。

このように文字列1行は更新されますが、数値は維持されます。

テーブルを行指定で更新した場合は指定したフィールドは更新されます。指定したフィールドと同じ行は維持されます。指定していない行は削除されます。

このように1行目と3行目は削除されます。

そのため、テーブルの行更新が必要な場合は全ての行を渡す必要があります。テーブル行のidを渡したとしても全行必要で、テーブル行のidは履歴との紐付けで利用されているようです。(idありの場合は更新、idなしの場合は追加として扱われる)

細かい仕様やREST APIとJS APIでも仕様が異なるので詳細はフィールド形式を確認してください。

フィールド情報はfields.jsonとlayout.jsonを併用する必要があるかも

kintone-config-helperを利用すればアプリのフィールド情報を取得できますが、一部制限があります。今まではプルリクを投げたり、forkして対応していましたが、@kintone/rest-api-clientがアプリ系のAPIにも対応したのでrest-api-clientへの移行を進めています。

記憶が曖昧なのですがkintone-config-helperはGitHubのリポジトリがkintoneからkintone-labsに移動しています。なので現時点では実験的なプロジェクトでのみ利用するのが良いのかもしれません。

このとき網羅的に情報を取得したい場合はfields.jsonだけでなく、layout.jsonも利用する必要があります。それぞれのAPIで取得できる情報が異なるので整理しました。

まずフィールド名(ラベル)やルックアップの判定、その他のフィールド設定はfields.jsonからしか取得できません。

/k/v1/app/form/fields.json/k/v1/app/form/layout.json
フィールド名(ラベル)×
フィールドタイプ
フィールドコード
テーブルのフィールド判定
ルックアップフィールドの判定×
その他のフィールド設定×

fields.jsonだけで良さそう!思うのは大きな罠です。

なんと、スペース、罫線、ラベルフィールドはlayout.jsonからしか取得できません。プラグイン作成ではスペースフィールドも必要なことが多いと思います。

/k/v1/app/form/fields.json/k/v1/app/form/layout.json
スペース×
罫線×
ラベル×

スペースフィールドもfields.jsonから取得できればいいのに・・・と思った人は多いはず。

ここからは補足情報です。

アプリ作成時に既に存在する下記のフィールドは機能の有効、無効に関わらずfields.jsonからしか取得できません。

/k/v1/app/form/fields.json/k/v1/app/form/layout.json
カテゴリー×
ステータス×
作業者×

アプリ作成時に既に存在する下記のフィールドはフォームに設置後のみlayout.jsonからも取得できます。

/k/v1/app/form/fields.json/k/v1/app/form/layout.json
レコード番号△ ※フォームに設置後
作成日時△ ※フォームに設置後
作成者△ ※フォームに設置後
更新日時△ ※フォームに設置後
更新者△ ※フォームに設置後

一部のフィールドコードはアプリ作成者の言語設定に依存する

アプリ作成時に既に存在するフィールドがあります。

  • RECORD_NUMBER
  • CREATED_TIME
  • CREATOR
  • UPDATED_TIME
  • MODIFIER
  • CATEGORY
  • STATUS
  • STATUS_ASSIGNEE

これらのフィールドコードはアプリ作成者の言語設定に依存し、これを後から変更することはできません。どのようになるかをまとめておきました。

日本語英語中国語(簡体字)中国語(繁体字)
RECORD_NUMBERレコード番号Record_number记录编号记录编号
CREATED_TIME作成日時Created_datetime创建时间创建时间
CREATOR作成者Created_by创建人创建人
UPDATED_TIME更新日時Updated_datetime更新时间更新时间
MODIFIER更新者Updated_by更新人更新人
CATEGORYカテゴリーCategories类别类别
STATUSステータスStatus状态状态
STATUS_ASSIGNEE作業者Assignee执行者执行者

プラグインの場合はフィールドタイプから判定することが多いと思うので問題になることは少なそうです。海外展開するアプリを作成している人は気をつけたほうが良いと思います。

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

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

ヘッダーなどに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も同様です。

複数カスタマイズで同一イベントハンドラーを利用する場合イベントハンドラーの引数が仕様と異なる場合がある

複数のカスタマイズ(プラグイン)で同一イベントハンドラーを利用する場合、後に実行されるカスタマイズでエラーが発生する可能性があります。例えば以下のコードを実行すると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

どうやら同一のイベントハンドラーが複数設定されている場合、先に実行されたイベントハンドラーのreturn値が後に実行されるイベントハンドラーの引数にそのまま?渡されるという仕様のようです。参考までにイベントをキャンセルできるreturn false;の場合はfalseが渡されてそのまま処理は継続します。

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

    return false;
  });

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

1: (3) [“type”, “appId”, “record”]
2: false

この状況に対応できる場合もあります。自社のカスタマイズの実行順が先の場合は対応しやすいです。前述のapp.record.create.submit.successの場合はreturn Promise;せずに適切なオブジェクトを用意してreturnすれば動作します。具体的な手順は以下のとおりです。

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

自社のカスタマイズの実行順が後の場合や先でreturn false;したい場合は自社側だけで対応するのは難しいことが多いです。

実行順が後の場合、先に動作したイベントハンドラーがreturn Promise;を実行したら適切なオブジェクトを作れない可能性が高いからです(Promiseの内容による)。

return false;を実行されたらreturn false;するのが良いはずなのでreturn false;には対応できる

実行順が先でreturn false;したい場合は後に動作するカスタマイズでもreturn false;してもらう必要があります。

そして自社のカスタマイズが先に実行されるのか、後に実行されるのかはわかりません。ドメインAでは自社のカスタマイズが先に動作し、ドメインBでは後に動作することもあります。

これサイボウズさん側でどうにかならないですかね・・・。利用者もプラグイン開発者もとても困ります。せめてお作法として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

プラグイン

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

全体JSとアプリJSは並び替えれば実行順序が変わります。

プラグインの実行順序を変更するには「kintoneシステム管理 > プラグイン」でプラグインファイルの再読み込みが必要です。再読み込みをすると他のアプリでもプラグインの実行順所が変わります。この作業が別の問題を引き起こす可能性があるので注意が必要です。

日時フィールドは秒を指定できるけど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)

クエリーの条件でフィールドコードは左辺のみ

レコードの取得(GET)kintone APIのクエリの書き方の基本にも記載されているのですがクエリーの形式は「<フィールドコード> <演算子> <値 または 関数>」にする必要があります。

例)日付 = TODAY()

逆にするとエラーになります。

例)TODAY() = 日付

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

この記事を書いた人