Skip to content

いいねプラグインのjQuery削除#229

Open
juridon wants to merge 3 commits into
kintone-samples:masterfrom
juridon:like-plugin-remove-jquery
Open

いいねプラグインのjQuery削除#229
juridon wants to merge 3 commits into
kintone-samples:masterfrom
juridon:like-plugin-remove-jquery

Conversation

@juridon
Copy link
Copy Markdown
Contributor

@juridon juridon commented May 15, 2026

いいねプラグインのjQueryはがしてバニラ化しています。

Comment thread examples/vote/js/vote.js
Copy link
Copy Markdown
Contributor

@akika akika May 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

いくつかきになるので、ご確認のうえ、修正ください。

修正方法

  1. vote.js:95 — Object.values → Object.entries
-  for (const [fieldCode, value] of Object.values(fields)) {
+ for (const [fieldCode, value] of Object.entries(fields)) {

理由: Object.values() はフィールドオブジェクト自体の配列を返す。プレーンオブジェクトを [key, value] で destructuring しようとすると TypeError: object is not iterable が発生する。Object.entries() なら [fieldCode, fieldObject] のペア配列になり、元の $.each と同じ挙動になる。

  1. vote.js:174 & mobile-vote.js:185 — update() の Promise が reject されない
    vote.js と mobile-vote.js の両方で同じ修正を適用:
  update: function() {
-   return new Promise((resolve) => {
+   return new Promise((resolve, reject) => {
      const newRecord = {};
      newRecord[VOTE_FIELD] = {'value': voteUsers};
      newRecord[VOTE_COUNT_FIELD] = {'value': voteUsers.length};
      kintone.api(kintone.api.url('/k/v1/record', true), 'PUT', {
        'app': APPID,
        'id': recordId,
        'record': newRecord,
        'revision': revision
      }, resolve, (e) => {
        NotifyPopup.showPopup(createErrorMessage(e));
+       reject(e);
      });
    });
  }

理由: エラーコールバックで reject() を呼ばないと Promise が永久 pending になり、呼び出し元の clickable が false のままリセットされず、ボタンが操作不能になる。

  1. vote.js:326 — !== null が undefined をすり抜ける
- if (voteModel !== null) {
+ if (voteModel !== undefined) {

または、より明示的に:

- if (voteModel !== null) {
+ if (voteModel) {

理由: filter()[0] はマッチなしで undefined を返す。undefined !== null は true なので条件をすり抜け、new VoteView(undefined) が呼ばれてクラッシュする。

  1. config.js:302/356
    スクリプトは DOM が構築された後に読み込まれることが多く、DOMContentLoaded が既に発火済みの場合はコールバックが一切呼ばれません。
    9行目の console.log(document.readyState) はおそらくこれを調査したためのものでしょうか。
    ただし、単純にコメントアウトを解除すると、kintone 環境では動かない可能性があります。
    次のような書き方が安全ではないかと思います。
function init() {
  const loginInfo = kintone.getLoginUser();
  const lang = getLanguage(loginInfo.language);
  renderConfigUI(lang);
  // ... 以下の実行コード
}

if (document.readyState === 'loading') {
  document.addEventListener('DOMContentLoaded', init);
} else {
  init();  // 既にDOMが準備済みなら即実行
}
  1. vote.js:319 — fetchVoteModels() に lang を渡す
- fetchVoteModels().then((voteModels) => {
+ fetchVoteModels(lang).then((voteModels) => {

理由: language が undefined のまま VoteModel コンストラクタに渡され、API エラー時に Msg[undefined].updatedWhileClicking で TypeError が発生する。

  1. config.js:148/160 — arrItem[0] へのガード追加
  this.data.value = data;
- this.data.name = arrItem[0].textContent;
+ if (arrItem[0]) {
+   this.data.name = arrItem[0].textContent;
+ }
  ...
  } else {
-   arrItem[0].classList.add('kintoneplugin-dropdown-list-item-selected');
+   if (arrItem[0]) {
+     arrItem[0].classList.add('kintoneplugin-dropdown-list-item-selected');
+   }
  }

理由: 保存済みのフィールドがアプリから削除された場合、arrItem が空配列になり arrItem[0].textContent で TypeError がスローされる。

  1. config.js:9 — デバッグログ削除
- console.log(document.readyState);

@akika
Copy link
Copy Markdown
Contributor

akika commented May 28, 2026

今回の依頼内容ではないですが、もし可能であれば、
kintone.promiseを使った書き方をawait/asyncに書き換えて欲しいです…すみません。
じゅるさんは、確かにClaude codeアカウントを持っていますか?
AIにお願いしたら、やってくれそうな気がします。

Devnet API ドキュメントおよびTipsの内容をベクタリングしてMCPサーバーを作ったので、
ベータ版ですが、よかったら使ってみてください。

  1. MCPサーバーを登録する
claude mcp add --transport http -s user "kintone-documentation-mcp" \
  "https://mcp.cybozu.dev/mcp"
  1. 接続を確認する
    Claude Codeを起動し、/mcp コマンドを実行
    kintone-documentation-mcp が connected と表示されていればOKです

設定した後に、次のようなプロンプトで試してください。

examples/vote/プラグインでは、非同期処理はkintone Promiseが使っているように見せます。推薦の書き方でリファクタリングして

AIが提案した変更

非同期処理の問題点と推奨の書き方

問題1: kintone.api をコールバックで包んだ Promise アンチパターン
kintone.api はコールバックなしで呼び出すとネイティブの Promise を返します。new Promise で包む必要はありません。

現在の書き方(アンチパターン) — vote.js:161-170

fetch: function() {
  return new Promise((resolve) => {
    kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {
      'app': APPID, 'id': recordId
    }, (evt) => {           // ← コールバック形式
      voteUsers = evt.record[VOTE_FIELD].value;
      revision = evt.record.$revision.value;
      resolve();
    });
  });
},

推奨の書き方(async/await)

fetch: async function() {
  const resp = await kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {
    'app': APPID, 'id': recordId
  });
  voteUsers = resp.record[VOTE_FIELD].value;
  revision = resp.record.$revision.value;
},

問題2: update() でエラー時に reject が呼ばれない
vote.js:173-187 — エラーコールバックで reject していないため、エラー発生時に Promise が解決されずハングします。

推奨の書き方

update: async function() {
  const newRecord = {};
  newRecord[VOTE_FIELD] = {'value': voteUsers};
  newRecord[VOTE_COUNT_FIELD] = {'value': voteUsers.length};
  try {
    await kintone.api(kintone.api.url('/k/v1/record', true), 'PUT', {
      'app': APPID, 'id': recordId, 'record': newRecord, 'revision': revision
    });
  } catch (e) {
    NotifyPopup.showPopup(createErrorMessage(e));
  }
}

問題3: toggleLoginUser() の .then().then() チェーン
vote.js:144-159 — async/await で素直に書けます。

推奨の書き方

toggleLoginUser: async function() {
  await this.fetch();
  if (this.isLoginUserVoted()) {
    voteUsers = voteUsers.filter((user) => user.code !== kintone.getLoginUser().code);
  } else {
    voteUsers.push({'code': kintone.getLoginUser().code});
  }
  await this.update();
},

問題4: fetchVoteModel / fetchVoteModels の Promise ラップ
vote.js:191-225

推奨の書き方

async function fetchVoteModel(language) {
  const id = kintone.app.record.getId();
  const resp = await kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {
    'app': APPID, 'id': id
  });
  const record = {'$id': {'value': id}, '$revision': resp.record.$revision};
  record[VOTE_FIELD] = resp.record[VOTE_FIELD];
  return new VoteModel(record, language);
}

async function fetchVoteModels(language) {
  const resp = await kintone.api(kintone.api.url('/k/v1/records', true), 'GET', {
    'app': APPID,
    'query': kintone.app.getQuery(),
    'fields': ['$id', VOTE_FIELD, '$revision']
  });
  return resp.records.map((rec) => new VoteModel(rec, language));
}

問題5: イベントハンドラーで Promise を返していない(index.show)
vote.js:308-347 — fetchVoteModels().then(...) の結果を return していないため、kintone がイベントの完了を待機できません。

推奨の書き方

kintone.events.on('app.record.index.show', async (event) => {
  if (event.records.length === 0) return event;

  const loginInfo = kintone.getLoginUser();
  const lang = getLanguage(loginInfo.language);
  NotifyPopup.createPopup();

  const RECORD_FIELD = getRecordNumberFieldCode(event.records[0]);
  try {
    const voteModels = await fetchVoteModels(lang);
    // ... ボタン配置処理 ...
  } catch (e) {
    NotifyPopup.showPopup(Msg[lang].errorOccurred);
  }
  return event;
});

問題6(バグ): イベントハンドラーの引数が間違い
vote.js:349 — kintone イベントハンドラーの引数は常に (event) 一つです。
最後にreturnをつける必要があります。

// ❌ 間違い

kintone.events.on('app.record.detail.show', (appId, record, recordId) => {

// ✅ 正しい

kintone.events.on('app.record.detail.show', async (event) => {

問題7: config.js のコールバック形式

[config.js:311-344](vscode-webview://12li8ggg5a06q36s7458kt7uu0t946f4lu93t1e5da92vtl5hptd/examples/vote/js/config.js#L311-L344) — 同様に async IIFE でラップするのが推奨です。


(async () => {
  try {
    const resp = await kintone.api(kintone.api.url('/k/v1/preview/app/form/fields', true), 'GET', {
      'app': kintone.app.getId()
    });
    // ... フィールド処理 ...
    Loading.hide();
  } catch (error) {
    console.error(error);
    Loading.hide();
  }
})();

"homepage_url": {
"ja":"https://cybozu.dev/ja/kintone/tips/development/plugins/sample-plugin/like-plugin/",
"en":"https://cybozu.dev/ja/kintone/tips/development/plugins/sample-plugin/like-plugin/",
"zh":"https://cybozudev.kf5.com/hc/kb/article/1007993/"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants