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

Featured Image(Photo) by Nik Shuliahin on Unsplash

こちらも合わせてお読みください。

更新履歴

  • 2021/8/26 記事を分割しました。
  • 2021/8/10 「APIトークン利用時のREST APIのエラーメッセージは多言語対応が難しい」を追加しました。
  • 2021/3/16 「一部のフィールドコードはアプリ作成者の言語設定に依存する」を追加しました。
  • 2021/2/5 「REST APIのテーブルの行指定更新では全行必要」を追加しました。
  • 2021/1/8 「フィールド情報はfields.jsonとlayout.jsonを併用する必要があるかも」を追加しました。

APIトークン利用時のREST APIのエラーメッセージは多言語対応が難しい

REST APIでエラーが発生した場合、利用者にはエラーの内容と適切な対応を案内したい、と考えました。しかし、kintoneのエラーは公開されていないので、やむなくREST APIが返すエラーメッセージを補足情報として案内することになりました。

そこでエラーメッセージの言語がどのように選択されるのかを調査したので共有します。

セッション認証の場合はユーザーの言語設定が優先されるようです。開発者コンソールでこのコードを実行すると

const method = 'POST';
const url = '/k/v1/record.json';
const body = JSON.stringify({ app: 5, id: 1 });
const headers = {
  'Content-Type': 'application/json',
  'X-Requested-With': 'XMLHttpRequest',
  'X-HTTP-Method-Override': 'GET',
};
const res = await fetch(url, { method, headers, body });
const json = await res.json();
console.log(json.message);

ユーザーの言語設定が日本語の場合は日本語、英語の場合は英語のエラーメッセージが返ってきました。

指定したレコード(id: 1)が見つかりません。

The specified record (ID: 1) is not found.

参考までに以下のケースでも同じ結果になりました。

  • APIトークンを利用した場合(上記コードのヘッダーにX-Cybozu-API-Tokenを追加)
  • クライアント端末からユーザー認証(X-Cybozu-Authorizationを利用)

今回問題となったのはREST APIでAPIトークン利用時です。以下のようなコードをクライアント端末から実行すると

const { request } = require('gaxios');
const qs = require('qs');

const main = async () => {
  const method = 'GET';
  const url = 'https://xxxxx.cybozu.com/k/v1/record.json';
  const body = { app: 5, id: 1 };
  const headers = {
    'X-Cybozu-API-Token': 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', 
  };
  try {
    const res = await request({
      method,
      url: `${url}?${qs.stringify(body)}`,
      headers,
    });
  } catch (error) {
    console.error(error.response.data.message);
  }
};

main();

ユーザーの言語設定は関係なく(当たり前ですが)、日本語のエラーメッセージが返ってきました。もう少し詳しく調べてみるとAdministratorユーザーの言語設定に依存しているようでした。

言語設定が英語の利用者に日本語のエラーを表示するわけにはいかないので何らかの対応が必要になりそうです。エラー一覧公開してほしいですね。

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

これらのフィールドコードはアプリ作成者の言語設定に依存します。さらに、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も同様です。

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

この記事を書いた人