こんにちは。サイボウズ公認 kintoneエバンジェリスト プロジェクト・アスノートの松田です。
Qiitaデビュー一発目は、kintoneをもっと便利にサポートしてくれる、ブックマークレットのアイデアを紹介したいと思います。
※この記事のコンテンツは随時追加・修正をしていきます。記事をストックしていただけると、更新・追加時に通知を受けることができます。
悩めるkintone管理者のための便利ツール、ぜひご活用ください。
ブックマークレットとは
ブラウザーのブックマーク機能を活用して、ブラウザー上でJavaScriptプログラムを動作させるためのプログラムです。
ブラウザー上で利用するkintoneにおいてもブックマークレットが活用できることに気づいて、その活用の可能性を検証してみました。
その中から、広く活用できそうなものをいくつか紹介します。
- アプリ一覧
- アプリ検索
- 印刷レイアウト変更
- 印刷幅変更
- レコード内容コピー
- フィールド一覧の表示
- ステータスの一括更新(2021/03/18追加)
- 一覧の設定リスト出力(2022/02/23追加)
- プロセス管理の設定表示(2022/03/14追加)
- ユーザー情報検索表示(2022/04/11追加)
利用方法と参考情報:
- 参考:ブックマークレットの登録方法 - Qiita
- 登録用となっているソースコードを全文コピーし、ブックマークのURL欄に張り付けて使用ください。
- よく使用するブックマークレットは、ブックマークバーに表示しておくと便利です。
- コードの内容に書いてあるJavaScriptコードをコピーして、コンソールで実行することもできます。
この場合はコードの頭の「javascript:」は削除してから実行してください。
既存のkintoneブックマークレット記事
kintoneでブックマークレットを活用するメリット
通常のJavaScriptカスタマイズやプラグインは、利用するアプリ固有の機能追加に向いています。
今回提供しているような管理のための機能は、kintone全体で使用したり、個別のアプリでも常に使用するものではありません。プラグインを設定していなくても、使いたいときにブラウザからすぐ利用できるというブックマークレットによる機能追加のメリットは、このようなところにあります。
利用者自身のブラウザーで動くブックマークレットは、アプリ固有ではなく、もっと汎用的に活用できる機能をサポートするのに向いていると思います。
ブックマークレットの処理内で、kintoneのJavaScript APIやREST APIを使うものは、kintoneスタンダードコースでのみ動作します。
APIを使わない画面上のカスタマイズであれば、通常カスタマイズを行うことができない、kintoneライトコースでも使うことができます。
ちょっとデメリット
アプリカスタマイズであれば管理者が作成して利用者に使ってもらうことができますが、ブックマークレットは個別の利用者が自身のブラウザーに設定する必要があります。利用者のスキルやリテラシーによっては、ここがネックになるかもしれません。
効率的でメンテナンスが容易なブックマークレット配布方法をご存知の方は、ぜひノウハウを共有お願いします。
動作確認環境
- PC: Chrome(Mac/Win), Firefox(Win), Safari(Mac), Edge(Win)
※Internet Explorer 11では正常に動作しません。 - スマートフォン: Chrome(iOS), Safari(iOS)
注意書き
- 画面レイアウトを変更しているプログラムはkintoneのDOM要素を使用しています。将来のアップデート等で正常に作動しなくなる可能性があります。とはいえ、kintone内のカスタマイズではないため、kintoneの操作性やデータに影響を与える心配はありません。
- 悪意のあるプログラムが仕込まれたブックマークレットをkintone画面で動作させた場合、kintone内のデータを操作したり、外部に送信したりすることが技術上可能になります。ブックマークレットやカスタマイズを使用する時は処理内容を確認して信頼できるものを適用するようにしましょう。
- JavaScriptやCSSについてはまだまだ勉強中なので、もっといい書き方や間違いなどがあれば、コメントいただけると嬉しいです!
①アプリ一覧
【kintoneスタンダードコース】
利用するアプリの数が増えてくると、kintoneの中でアプリを探し出すのが結構たいへんになりませんか?
このブックマークレットは、そのユーザーにアクセス権のあるアプリが一発で一覧表示され、そこからアプリ画面へ直接リンクで移動できるというものです。
スマートフォンのブラウザー上のモバイルビューでも使えますので、現状(2019年1月)アプリを探す機能がないモバイルシーンにおいては非常に重宝しています。
- 利用するアプリが最大数十個程度のユーザー向けです。
- 表示順はアプリ設定の更新日時の新しいもの順となります。
※(2023/05/13アップデート):JavaScriptコードをES6対応に修正。同時に画面表示で使っていた'document.open'をやめて'innerHTML'を使う処理に修正。結果表示をリスト形式からテーブル形式に変更。
機能・利用イメージ
ブックマークレット(登録用)
※このコードをブックマークのURL欄に貼り付けてください。
javascript:(async()=>{if(!location.href.includes("cybozu.com/k")){window.alert("kintoneの画面から操作してください");return;}const n=`${location.origin}/k/`;const e=async(t=0,o=[])=>{try{const n=await kintone.api(kintone.api.url("/k/v1/apps",!0),"GET",{offset:t});o.push(...n.apps);return 100===n.apps.length?e(t+100,o):o;}catch(t){console.error(t);}};const t=await e();t.sort((t,n)=>new Date(n.modifiedAt)-new Date(t.modifiedAt));const o=`<body style="font-family: sans-serif;"><h3>アプリ一覧</h3><p>◇:アプリ設定画面</p>${0===t.length?"検索結果がゼロ件でした":""}<p>(検索結果:${t.length}件)</p><table><tr><th>App ID</th><th>App Name</th><th>Settings</th><th>Space ID</th></tr>${t.map(t=>`<tr><td>${t.appId}</td><td><a href="${n}${t.appId}/">${t.name}</a></td><td><a href="${location.origin}/k/admin/app/flow?app=${t.appId}">□</a></td><td>${null!==t.spaceId?`<a href="${location.origin}/k/#/space/${t.spaceId}" target="_blank">${t.spaceId}</a>`:"portal"}</td></tr>`).join("")}</table><p><a href="."> << 前の画面に戻る<a></p></body>`;document.body.innerHTML=o;})();
コードの内容
javascript: (async () => {
// kintoneの画面でのみ動作させる
if (!location.href.includes('cybozu.com/k')) {
window.alert("kintoneの画面から操作してください");
return;
}
const domain = `${location.origin}/k/`;
const fetchApps = async (offset = 0, apps = []) => {
try {
const response = await kintone.api(kintone.api.url('/k/v1/apps', true), 'GET', { offset });
apps.push(...response.apps);
return response.apps.length === 100 ? fetchApps(offset + 100, apps) : apps;
} catch (error) {
console.error(error);
}
};
const apps = await fetchApps();
apps.sort((a, b) => new Date(b.modifiedAt) - new Date(a.modifiedAt));
const result = `
<body style="font-family: sans-serif;">
<h3>アプリ一覧</h3>
<p>◇:アプリ設定画面</p>
${apps.length === 0 ? '検索結果がゼロ件でした' : ''}
<p>(検索結果:${apps.length}件)</p>
<table>
<tr>
<th>App ID</th>
<th>App Name</th>
<th>Settings</th>
<th>Space ID</th>
</tr>
${apps.map(app => `
<tr>
<td>${app.appId}</td>
<td><a href="${domain}${app.appId}/">${app.name}</a></td>
<td><a href="${location.origin}/k/admin/app/flow?app=${app.appId}">□</a></td>
<td>${app.spaceId !== null ? `<a href="${location.origin}/k/#/space/${app.spaceId}" target="_blank">${app.spaceId}</a>` : 'portal'}</td>
</tr>
`).join('')}
</table>
<p><a href="."> << 前の画面に戻る<a></p>
</body>
`;
document.body.innerHTML = result;
})();
②アプリ検索
【kintoneスタンダードコース】
アプリ一覧ブックマークレットに検索機能を追加しました。
キーワードを1つ設定して、アプリ名で検索し、一覧表示します。
利用するアプリの数が非常に多いユーザーや、kintoneの管理者の方向きの機能で、大量のアプリの中からキーワードで絞り込んで目的のアプリを見つけることができます。
また、管理者向けに、各アプリの設定画面への直リンク(□)も表示するようにしています。
表示順はアプリ設定の更新日時の新しいもの順となります。
キーワードを入れずにOKを押すと、アクセス権のあるアプリが全件表示されます(最大100件)。
※(2021/8/20アップデート):検索結果リストに所属しているスペースのIDおよびスペースへのリンクを表示するようにしました。スペース外アプリの場合は[Portal]表示。
※(2023/05/13アップデート):JavaScriptコードをES6対応に修正。同時に画面表示で使っていた'document.open'をやめて'innerHTML'を使う処理に修正。また、アプリ数が100を超える場合も全件取得する処理に変更。キーワード入力ダイアログに何も入力せずにOK(エンター)すると、アプリリスト全件表示。表示形式をリスト形式からテーブル形式に変更。
機能・利用イメージ
ブックマークレット(登録用)
※このコードをブックマークのURL欄に貼り付けてください。
javascript:(async()=>{if(!location.href.includes("cybozu.com/k")){alert("kintoneの画面から操作してください");return}let t=prompt("検索キーワード:"),a=0,e=[],p=async()=>{let i=await kintone.api(kintone.api.url("/k/v1/apps",!0),"GET",{name:t,offset:a,limit:100});i.apps.length>0&&(e=[...e,...i.apps],a+=100,await p())};await p(),e.sort((t,a)=>t.modifiedAt<a.modifiedAt?1:t.modifiedAt>a.modifiedAt?-1:0);let i=`${location.origin}/k/`,d=e.map(t=>`<tr><td>${t.appId}</td><td><a href="${i}${t.appId}/">${t.name}</a></td><td><a href="${location.origin}/k/admin/app/flow?app=${t.appId}">設定</a></td><td>${null!==t.spaceId?`<a href="${location.origin}/k/#/space/${t.spaceId}" target="_blank">${t.spaceId}</a>`:"portal"}</td></tr>`).join(""),r=`<body style="font-family: sans-serif; background-color: white;"><h3>検索結果</h3><p>□:アプリ設定画面</p>${0===e.length?"検索結果がゼロ件でした":`<p>(キーワード:${t}, 検索結果:${e.length}件)</p>`}<table><tr><th>App ID</th><th>App Name</th><th>Settings</th><th>Space ID</th></tr>${d}</table><p><a href="."> << 前の画面に戻る<a></p></body>`;if(0===e.length){alert("検索結果がゼロ件でした");return}document.body.innerHTML=r})().catch(t=>console.error(t));
コードの内容
javascript: (async () => {
if (!location.href.includes('cybozu.com/k')) {
alert("kintoneの画面から操作してください");
return;
}
const keyword = prompt("検索キーワード:");
let offset = 0;
const limit = 100;
let apps = [];
const getApps = async () => {
const resp = await kintone.api(kintone.api.url('/k/v1/apps', true), 'GET', {
"name": keyword,
"offset": offset,
"limit": limit
});
if (resp.apps.length > 0) {
apps = [...apps, ...resp.apps];
offset += limit;
await getApps();
}
}
await getApps();
apps.sort((a, b) => a.modifiedAt < b.modifiedAt ? 1 : a.modifiedAt > b.modifiedAt ? -1 : 0);
const domain = `${location.origin}/k/`;
const appList = apps.map(app => `
<tr>
<td>${app.appId}</td>
<td><a href="${domain}${app.appId}/">${app.name}</a></td>
<td><a href="${location.origin}/k/admin/app/flow?app=${app.appId}">設定</a></td>
<td>${app.spaceId !== null ?
`<a href="${location.origin}/k/#/space/${app.spaceId}" target="_blank">${app.spaceId}</a>`
: 'portal'}</td>
</tr>
`).join('');
const result = `
<body style="font-family: sans-serif; background-color: white;">
<h3>検索結果</h3>
<p>□:アプリ設定画面</p>
${apps.length === 0 ?
'検索結果がゼロ件でした'
: `<p>(キーワード:${keyword}, 検索結果:${apps.length}件)</p>`}
<table>
<tr>
<th>App ID</th>
<th>App Name</th>
<th>Settings</th>
<th>Space ID</th>
</tr>
${appList}
</table>
<p><a href="."> << 前の画面に戻る<a></p>
</body>
`;
if (apps.length === 0) {
alert("検索結果がゼロ件でした");
return;
}
document.body.innerHTML = result;
})().catch((error) => console.error(error));
③印刷レイアウト変更
【kintoneライトコース/スタンダードコース】
kintone標準のレコード印刷画面、ちょっとイケてないです。そもそも印刷などしないためにクラウドを使うんだ!と言ってしまえばそれまでです。しかし実際の活用シーンにおいては、紙に印刷したり、またはレコードの内容をPDFに出力するニーズもまだまだあるのが現実です。
このブックマークレットはkintoneの印刷用画面のレイアウトや装飾を変更します。APIは利用していないため、通常のカスタマイズや連携サービスを使うことができない、kintoneライトコースでも利用できます。
※(2023/05/13アップデート):JavaScriptコードをES6対応に修正。
機能・利用イメージ
ブックマークレット(登録用)
※このコードをブックマークのURL欄に貼り付けてください。
javascript:(()=>{if(!location.href.includes('cybozu.com/k')){alert("kintoneの画面から操作してください");return;}const s=prompt("文字のサイズ(pt):","14");const f=1.1;const st=`@media print{.body-record-print .subtable-label-gaia,.body-record-print .show-subtable-gaia th:first-child{-webkit-print-color-adjust:exact}}#record-gaia,div.control-value-gaia{font-size:${s}px}.body-record-print .showlayout-gaia .row-gaia .control-value-gaia,.body-record-print .control-group-gaia{border-style:none;background-color:#f5f5f5}.control-label-text-gaia{border-left:4px solid #777;font-size:${s*f}px;font-weight:bold;padding-left:5px}.control-label-gaia{color:#777}.body-record-print .subtable-label-gaia,.body-record-print .show-subtable-gaia th:first-child{border-width:1px;background-color:#e0e0e0!important;font-size:${s}px;text-align:center}`;const n=document.createElement('link');n.rel='stylesheet';n.href='data:text/css,'+escape(st);document.getElementsByTagName("head")[0].appendChild(n);})();
コードの内容
javascript: (() => {
if (!location.href.includes('cybozu.com/k')) {
alert("kintoneの画面から操作してください");
return;
}
const size = prompt("文字のサイズ(pt):", "14");
const f=1.1;
const styles = `@media print {
.body-record-print .subtable-label-gaia,.body-record-print .show-subtable-gaia th:first-child {
-webkit-print-color-adjust: exact;
}
}
#record-gaia,div.control-value-gaia {
font-size: ${size}px;
}
.body-record-print .showlayout-gaia .row-gaia .control-value-gaia,.body-record-print .control-group-gaia {
border-style: none;
background-color: #f5f5f5;
}
.control-label-text-gaia {
border-left: 4px solid #777;
font-size: ${size * f}px;
font-weight:bold;
padding-left: 5px;
}
.control-label-gaia {
color:#777;
}
.body-record-print .subtable-label-gaia,.body-record-print .show-subtable-gaia th:first-child {
border-width: 1px;
background-color: #e0e0e0 !important;
font-size: ${size}px;
text-align: center;
}`;
const newSS = document.createElement('link');
newSS.rel = 'stylesheet';
newSS.href = 'data:text/css,' + escape(styles);
document.getElementsByTagName("head")[0].appendChild(newSS);
})();
④印刷幅調整
【kintoneライトコース/スタンダードコース】
kintoneのレコードを印刷するときに悲しいのが、アプリフォームのレイアウトによっては、印刷時に右端が切れてしまうこと。
全体を縮小するといいんですが、そうすると今度は文字が小さくなってしまう。
このブックマークレットは、印刷レイアウト画面の幅を強制的に調整します。③のレイアウト変更と一緒に使ってもいいし、標準の印刷用画面でも単独で利用できます。
標準の印刷用画面でも使えますし、上記の印刷レイアウト変更ブックマークレットと合わせて用いてもOKです。
kintoneライトコースでも利用できます
※(2023/05/13アップデート):JavaScriptコードをES6対応に修正。
利用方法
- ブックマークレットを起動し、レイアウト幅をピクセルで設定します。
- Chromeの場合は、1034px ぐらいでちょうどおさまります(私の環境での数値なので微調整してください)
- そのままOKを押すと1034px, 手動で入力する場合は数値を入力します。
ブックマークレット(登録用)
※このコードをブックマークのURL欄に貼り付けてください。
javascript:(function () { const w1 = document.getElementsByClassName("layout-gaia")[0].style.width; const w2 = window.prompt("幅を指定してください(現状:"+w1+"px => )", 1034); document.getElementsByClassName("layout-gaia")[0].style.width=w2+"px"; })();
コードの内容
javascript: (function () {
const w1 = document.getElementsByClassName("layout-gaia")[0].style.width;
const w2 = window.prompt("幅を指定してください(現状:" + w1 + "px => )", 1034);
document.getElementsByClassName("layout-gaia")[0].style.width = w2 + "px";
})();
⑤レコード内容をコピー
【kintoneライトコース/スタンダードコース】
kintoneのレコードを印刷ではなく、文字で取り出して使いたい場合がたまにあります。
このブックマークレットは、レコード詳細画面で、表示されているレコードデータをまるっとクリップボードにコピーします。その後はメール等の本文に貼り付けて加工したり、メモ帳などに貼り付けたり、いろいろできます。
現状、PCビューでのみ作動します。kintoneライトコースでも利用できます
※(2023/05/13アップデート):JavaScriptコードをES6対応に修正。
ブックマークレット(登録用)
※このコードをブックマークのURL欄に貼り付けてください。
javascript:(()=>{if(!location.href.includes('cybozu.com/k')){alert("kintoneの画面から操作してください");return}const c=document.getElementsByClassName("layout-gaia")[0].innerText;const f=t=>{const e=document.createElement("textarea");e.textContent=t;const b=document.getElementsByTagName("body")[0];b.appendChild(e);e.select();const r=document.execCommand('copy');b.removeChild(e);return r};f(c);alert("クリップボードにコピーしました!")})();
コードの内容
クリップボードコピー関数は以下のサイトを参考にしました。
JavaScript でテキストをクリップボードへコピーする方法
javascript: (() => {
if (!location.href.includes('cybozu.com/k')) {
alert("kintoneの画面から操作してください");
return;
}
const contents = document.getElementsByClassName("layout-gaia")[0].innerText;
copyTextToClipboard(contents);
alert("クリップボードにコピーしました!");
const copyTextToClipboard = (textVal) => {
const copyFrom = document.createElement("textarea");
copyFrom.textContent = textVal;
const bodyElm = document.getElementsByTagName("body")[0];
bodyElm.appendChild(copyFrom);
copyFrom.select();
const retVal = document.execCommand('copy');
bodyElm.removeChild(copyFrom);
return retVal;
}
})();
⑥フィールド一覧を表示
【kintoneスタンダードコース】
kintoneのカスタマイズを行ったり、自動計算の計算式を作ったりするとき、アプリの各フィールドのフィールドコードが必要になります。
このブックマークレットは、今開いているアプリのフィールド一覧を画面表示するというものです。
※2021/2/19 出力項目に「重複禁止」「必須設定」を追加しました。
※(2023/05/13アップデート):JavaScriptコードをES6対応に修正。同時に画面表示で使っていた'document.open'をやめて'innerHTML'を使うDOM処理に修正。
ブックマークレット(登録用)
※このコードをブックマークのURL欄に貼り付けてください。
javascript:(()=>{if(!location.href.includes('cybozu.com/k')){alert("kintoneの画面から操作してください");return;}kintone.api(kintone.api.url('/k/v1/form',true),'GET',{"app":kintone.app.getId()},(r)=>{const f=r.properties;console.log(f);let a='<h3>フィールド一覧</h3>';if(f.length===0){alert("フィールドがありません");return;}a+='<table rules="rows"><thead><tr><th>SUBTABLE</th><th>フィールド名</th><th>フィールドコード</th><th>フィールドタイプ</th><th>重複禁止</th><th>必須</th></tr></thead><tbody>';f.forEach((e)=>{if(e.type==='SUBTABLE'){e.fields.forEach((b)=>{a+=`<tr><td>${e.code}</td><td>${b.label}</td><td>${b.code}</td><td>${b.type}</td><td>${b.unique?b.unique:''}</td><td>${b.required?b.required:''}</td></tr>`;});}else{a+=`<tr><td></td><td>${e.label}</td><td>${e.code}</td><td>${e.type}</td><td>${e.unique?e.unique:''}</td><td>${e.required?e.required:''}</td></tr>`;}});a+='</tbody></table><p><a href="javascript:location.reload();"> << 前の画面に戻る<a></p></br>';const b=document.getElementsByTagName('body')[0];b.innerHTML=a;},(e)=>{console.log(e);});})();
コードの内容
javascript: (() => {
// kintone画面でのみ作動させる
if (!location.href.includes('cybozu.com/k')) {
window.alert("kintoneの画面から操作してください");
return;
}
const domain = location.origin + '/k/';
// アプリ情報一括取得処理
kintone.api(kintone.api.url('/k/v1/form', true), 'GET', { "app": kintone.app.getId() }, (resp) => {
const fields = resp.properties;
console.log(fields);
let result = '<h3>フィールド一覧</h3>';
if (fields.length === 0) {
window.alert("フィールドがありません");
return;
}
result += '<table rules="rows">';
result += '<thead><tr><th>SUBTABLE</th><th>フィールド名</th><th>フィールドコード </th><th>フィールドタイプ </th><th>重複禁止 </th><th>必須</th></tr></thead><tbody>';
fields.forEach((field) => {
if (field.type === 'SUBTABLE') {
field.fields.forEach((row) => {
result += `<tr><td>${field.code}</td><td>${row.label}</td><td>${row.code}</td><td>${row.type}</td><td>${row.unique ? row.unique : ''}</td><td>${row.required ? row.required : ''}</td></tr>`;
});
} else {
result += `<tr><td></td><td>${field.label}</td><td>${field.code}</td><td>${field.type}</td><td>${field.unique ? field.unique : ''}</td><td>${field.required ? field.required : ''}</td></tr>`;
}
});
result += '</tbody></table><p><a href="javascript:location.reload();"> << 前の画面に戻る<a></p></br>';
const body = document.getElementsByTagName('body')[0];
body.innerHTML = result;
}, (error) => {
console.log(error);
});
})();
⑦ステータスの一括更新
【kintoneスタンダードコース】
「kintoneのアプリを新しく作ってデータを引越ししたいんですが」
私「あーできますよ。CSVに出力してですね・・・」
「おおーいいですね。じゃぁステータスも前と同じ状態にしておいてください」
私「・・・え!?あの・・・」
みなさんも経験ありませんか?アプリのフォーム内のデータであればCSVやコマンドラインツールを使って引越しできますが、引越しできないものもあります(レコード履歴、ステータス、ステータス履歴など)。
プロセス管理を設定したアプリを作って、そこに別システムから過去データを取り込むときも同じですね。
こういうときによくやる手段として、通称「神の手プロセス」を一時的に設定して、未処理→完了のようなバイパスプロセスを作り、レコードを開いてプロセス管理のアクションボタンをポチポチ押していく。
数件~20件ぐらいのレコード数であれば、手作業でもいいんですが、レコードが多いと死ねます(笑)
そこで一覧画面から絞り込んだレコードを対象として、指定ステータスから指定アクションを一括で実行することができるブックマークレットを作成しました。
操作方法
- アプリ一覧画面でブックマークレット実行
2. このとき一覧の絞り込み条件が設定されている場合は絞り込まれたレコードに対して更新処理が行われます
3. ステータス以外で更新対象レコードを絞り込んでから処理を実行する、という使い方ができます - 更新対象ステータスを入力
- 実行するアクション名(プロセス管理のボタン名)を入力
- 確認ダイアログが表示され、OKを押すと一括更新処理実行。キャンセルを押すと処理中断
- 処理には時間がかかりますが、画面の更新等は行わないでください
- 処理完了後にもダイアログが表示されます
制限事項等
- 一度に更新できるレコード数は100件です。対象レコードが多い場合は、一括更新処理を何度か繰り返すことで対処可能です。腕に覚えのある人は大量レコード対応してみてください。
- 使用したAPI
※(2023/05/13アップデート):JavaScriptコードをES6対応に修正。コールバックをasync/awaitに変更。
ブックマークレット(登録用)
※このコードをブックマークのURL欄に貼り付けてください。
javascript:(async()=>{const t=window.prompt("更新対象ステータス:"),n=window.prompt("実行アクション名(ボタン名)"),e={fromStatus:t,doAction:n},a=kintone.api.url("/k/v1/records",!0),o=kintone.api.url("/k/v1/records/status",!0),r=(c=kintone.app.getQueryCondition())?c+" and ":"",s={app:kintone.app.getId(),fields:["$id"],query:`${r}ステータス = "${e.fromStatus}" limit 100`};try{const c=await kintone.api(a,"GET",s),i=c.records.map((t=>t.$id.value)),u={app:kintone.app.getId(),records:i.map((t=>({id:t,action:e.doAction})))};if(!window.confirm(`${i.length}件のレコードのステータス更新をします。\n対象ステータス:${e.fromStatus}\n実行アクション:${e.doAction}`))throw new Error("処理中断しました");console.log(u);const d=await kintone.api(o,"PUT",u);console.log(d),alert("ステータスの一括更新が完了しました"),window.location.reload()}catch(t){console.log(t),alert("処理中断しました")}})();
コードの内容
javascript:(async () => {
const fromStatus = window.prompt('更新対象ステータス:');
const doAction = window.prompt('実行アクション名(ボタン名)');
const condition = {
fromStatus,
doAction
};
const getUrl = kintone.api.url('/k/v1/records', true);
const putUrl = kintone.api.url('/k/v1/records/status', true);
let currentQuery = kintone.app.getQueryCondition();
if (currentQuery != '') {
currentQuery += ' and ';
}
const getBody = {
app: kintone.app.getId(),
fields: ['$id'],
query: `${currentQuery}ステータス = "${condition.fromStatus}" limit 100`
};
try {
const resp = await kintone.api(getUrl, 'GET', getBody);
const ids = resp.records.map(record => record.$id.value);
const putBody = {
"app": kintone.app.getId(),
"records": ids.map(id => ({
id,
"action": condition.doAction
}))
};
console.log(putBody);
const message = `${ids.length}件のレコードのステータス更新をします。
対象ステータス:${condition.fromStatus}
実行アクション:${condition.doAction}`;
const result = window.confirm(message);
if (!result) {
throw new Error('処理中断しました');
}
const putResp = await kintone.api(putUrl, 'PUT', putBody);
console.log(putResp);
alert('ステータスの一括更新が完了しました');
window.location.reload();
} catch (error) {
console.log(error);
alert('処理中断しました');
}
})();
⑧一覧の設定リスト出力
【kintoneスタンダードコース】
長くkintoneのアプリを使っていると、一覧の設定数が増えてくることがよくあります。
特に、ユーザーにアプリ管理権限を渡して、自由に一覧設定をできるようにしていると、増殖傾向になります。
そして多くの場合、一覧の名前の付け方も微妙な感じで、同じような名前が氾濫することもあったりしますね。
そこで、kintone SIGNPOSTの「6-42 定期的な棚卸し」にも書いてあるように、一覧の棚卸しをやろう!ということになります。
しかし、ここで30数個の一覧が登録されたアプリを眺めてハッと気づくのです。
「どの一覧がどんな設定内容になっているのかが把握できない!」
そんな迷える kintone管理者のためのブックマークレットを作成しました。
操作方法
- アプリ画面(一覧画面、詳細画面どちらでもOK)でブックマークレット実行。
- 一覧の設定内容のリストが表示されます。
- 「ダウンロード」ボタンをクリックすると、CSVファイルをダウンロードすることができます。
- 文字コードがUnicodeになっているため、Excelで普通に開くと文字化けします。スプレッドシートやエディタで開くか、Excelの外部データの読み込み機能を使って、文字コードを変更して読み込んでください。
- 画面のリロードまたは、「アプリ画面に戻る」をクリックすると kintoneに戻ります。
- REST APIによる一覧設定の更新を行いたい場合は、コンソールに一覧設定のオブジェクトが出力されていますので、これをコピーして編集し、更新することで可能となります。参考:一覧の設定の変更(Cybozu developer network)
制限事項等
- kintoneのREST APIの仕様により、同じ名称の一覧が複数個登録されている場合には、データ取得がエラーとなります。メッセージに従って一覧名を修正してから再度試してください。
- 使用したAPI
※(2023/05/13アップデート):JavaScriptコードをES6対応に修正。同時に画面表示で使っていた'document.open'をやめて'innerHTML'を使うDOM処理に修正。
ブックマークレット(登録用)
※このコードをブックマークのURL欄に貼り付けてください。
javascript:(async()=>{const t=kintone.api.url('/k/v1/app/views.json',!0),n={'app':kintone.app.getId(),'lang':'user'};try{const e=await kintone.api(t,'GET',n),i=Object.values(e.views);if(i.sort((t,n)=>t.index-n.index),0===i.length)return void window.alert("検索結果がゼロ件でした");const a=r(i),o=i.map((t,n)=>`<tr style="${n%2==0?'background-color: #f0f0f0;':''}"><td>${t.index}</td><td>${t.id}</td><td>${t.name}</td><td>${t.filterCond}</td><td>${'LIST'===t.type?t.fields.join(', '):''}</td></tr>`).join(''),s=`<body style="font-family: sans-serif;"><h3>一覧設定リスト(アプリID: ${kintone.app.getId()})</h3><p>(一覧件数:${i.length}件) <button id="b" class="dlcsv" type="button"> ↓ download CSV file</button></br></p><table border="1" style="border-collapse: collapse; width: 100%;"><tr style="background-color: #444444; color: white;"><th style="padding: 15px;">Index</th><th style="padding: 15px;">id</th><th style="padding: 15px;">一覧名</th><th style="padding: 15px;">絞込条件</th><th style="padding: 15px;">フィールド構成</th></tr>${o}</table><br><a href="."> << アプリ画面に戻る<a><br></body>`;document.body.innerHTML=s,document.getElementById('b').addEventListener('click',()=>{const t=new Blob([a],{type:"text/csv"}),n=document.createElement('a');n.href=URL.createObjectURL(t),n.download=`appViews_${kintone.app.getId()}.csv`,n.click()});function r(t){let n='Index,一覧名称,絞込条件,フィールド構成\n';return t.forEach(t=>{'LIST'===t.type&&(n+=`${t.index},${t.name},${t.filterCond},${t.fields.join(',')}\n`)}),n}}catch(t){'GAIA_DU01'===t.code&&window.alert(t.message),console.log(t)}})();
コードの内容
(async () => {
const url = kintone.api.url('/k/v1/app/views.json', true);
const body = {
'app': kintone.app.getId(),
'lang': 'user'
};
try {
const resp = await kintone.api(url, 'GET', body);
const lists = Object.values(resp.views);
lists.sort((a, b) => a.index - b.index);
if (lists.length === 0) {
window.alert("検索結果がゼロ件でした");
return;
}
const data = generateCsvFile(lists);
const listItems = lists.map((list, index) => `
<tr style="${index % 2 === 0 ? 'background-color: #f0f0f0;' : ''}">
<td>${list.index}</td>
<td>${list.id}</td>
<td>${list.name}</td>
<td>${list.filterCond}</td>
<td>${list.type === 'LIST' ? list.fields.join(', ') : ''}</td>
</tr>
`).join('');
const result = `
<body style="font-family: sans-serif;">
<h3>一覧設定リスト(アプリID: ${kintone.app.getId()})</h3>
<p>(一覧件数:${lists.length}件) <button id="btn" class="dlcsv" type="button"> ↓ download CSV file</button></br></p>
<table border="1" style="border-collapse: collapse; width: 100%;">
<tr style="background-color: #444444; color: white;">
<th style="padding: 15px;">Index</th>
<th style="padding: 15px;">id</th>
<th style="padding: 15px;">一覧名</th>
<th style="padding: 15px;">絞込条件</th>
<th style="padding: 15px;">フィールド構成</th>
</tr>
${listItems}
</table>
<br><a href="."> << アプリ画面に戻る<a><br>
</body>
`;
document.body.innerHTML = result;
document.getElementById('btn').addEventListener('click', () => {
const blob = new Blob([data], { type: "text/csv" });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = `appViews_${kintone.app.getId()}.csv`;
link.click();
});
function generateCsvFile(views) {
let result = 'Index,一覧名称,絞込条件,フィールド構成\n';
views.forEach(list => {
if (list.type === 'LIST') {
result += `${list.index},${list.name},${list.filterCond},${list.fields.join(',')}\n`;
}
});
return result;
}
} catch (error) {
if (error.code === 'GAIA_DU01') {
window.alert(error.message);
}
console.log(error);
}
})();
⑨プロセス管理の設定表示
【kintoneスタンダードコース】
kintoneをワークフロー的な使い方をするときに活用するのが、プロセス管理です。
既存業務をkintone化するときのステップとしては概ね次のように考えます。
- kintone化をしたい業務の現状の業務を棚卸しし、業務フローとして見える化する
- 現状業務の無駄なところ、改善できるところを見直し、kintone化に適した業務フローを作る
- kintoneのプロセス管理の設定を行い、テストを経て運用開始
ところが、ステップ2の「現状業務の見直し」が不十分なことが非常に多いです。
これには理由もあると思います。机上で考えただけの業務プロセスだけでは見えてこないこともあります。やはり実際に運用をして、かつデータを見ないと把握できない無駄もあります。
「kintoneアプリは使い始めたときが、業務改善のスタート」とかねてからお話していますが、ここでプロセス管理設定の見直しが必要になります。kintone SIGNPOSTの「6-42 定期的な棚卸し」にもあるように、使ってみた結果をフィードバックして、業務を改善していくことは、非常にkintoneっぽいと思います。
先日、あるお客さんのアプリでこのケースがあり、さっそくプロセス管理設定画面を開きました。
しかし、そこには
- ステータス:14個
- 全アクション数:77個(条件分岐含む)
という、膨大な設定がありました。
当初設定したのは自分だったので、仕方ないのですが、これを見直すためには、現状設定を見える化する必要があります。
スクリーンショットを頑張って撮りましたが、これだけではkintoneの設定画面に慣れていない業務メンバーの理解が進みません。
そんな迷える業務改善職のためのブックマークレットを作成しました。
機能および出力結果は下の図を参照してください。
プロセス管理設定を、テキストおよびテーブルデータとして画面出力します。
これをベースに検討資料を作成し、業務プロセスの見直し、アプリのプロセス管理の設定見直しに使うことを想定しています。
77アクションの設定は? いや、長すぎて載せられませんでしたw
操作方法
- アプリ画面(一覧画面、詳細画面どちらでもOK)でブックマークレット実行。
- プロセス管理の設定内容のリストが表示されます。
- 画面のリロードまたは、「アプリ画面に戻る」をクリックすると kintoneに戻ります。
- 表示されたデータを全選択(Ctrl+A)し、コピー&ペーストでExcelやGoogleドキュメント等に貼り付けて、検討資料として利用することができます。(これがやりたかった)
制限事項等
- プロセス管理が有効化されていないアプリではメッセージが表示されます。
- 使用したAPI
ブックマークレット(登録用)
※このコードをブックマークのURL欄に貼り付けてください。
javascript:!function(){"use strict";const t=kintone.app.getId(),e=kintone.api.url("/k/v1/app/status.json",!0),n={app:t,lang:"user"};kintone.api(e,"GET",n,function(e){if(0==e.enable)return void window.alert("プロセス管理が設定されていません!");const n={};Object.entries(e.states).forEach(function(t){n[t[0]]=t[1].assignee.entities.map(function(t){return t.entity.type+":"+t.entity.code}).join("</br>")});const o=e.actions;o.forEach(function(t){t.assignee=n[t.from]});const a=Object.entries(e.states);a.forEach(function(t){t[1].assignee=n[t[0]]});const i=a.map(function(t){return{name:t[1].name,index:t[1].index,assignee:t[1].assignee}});i.sort(function(t,e){return t.index-e.index});location.origin;let s="<title>プロセス管理設定</title>";s+='<body style="font-family: sans-serif;"><h3>プロセス管理設定(アプリID:'+t+")</h3>",s+='<p><a href="'+location.origin+"/k/admin/app/status?app="+t+'" target="_blank">プロセス管理設定画面</a></p>',s+='<h4>ステータス</h4><table border="1" style="border-collapse: collapse">',s+="<tr><th>Index</th><th>ステータス名</th><th>作業者</th></tr>",i.forEach(function(t){s+="<tr>",s+="<td>"+t.index+"</td>",s+="<td>"+t.name+"</td>",s+="<td>"+t.assignee+"</td>",s+="</tr>"}),s+="</table></br>",s+='<h4>プロセス設定</h4><table border="1" style="border-collapse: collapse">',s+="<p>アクション数:"+o.length+"</p>",s+="<tr><th>実行前ステータス</th><th>作業者</th><th>アクション実行条件</th><th>アクション名</th><th>実行後ステータス</th></tr>",o.forEach(function(t){s+="<tr>",s+="<td>"+t.from+"</td>",s+="<td>"+t.assignee+"</td>",s+="<td>"+t.filterCond+"</td>",s+="<td>"+t.name+"</td>",s+="<td>"+t.to+"</td>",s+="</tr>"}),s+='</table></br><a href="."> << 前の画面に戻る<a></p></br></body>',console.log(i),console.log(o),document.open(),document.write(s)},function(t){console.log(t.message)})}();
コードの内容
/**
* プロセス管理の設定を抽出する処理
* 2022/03/14 Shotaro Matsuda
*/
(function () {
'use strict';
const appId = kintone.app.getId();
const url = kintone.api.url('/k/v1/app/status.json', true);
const body = {
'app': appId,
'lang': 'user'
};
kintone.api(url, 'GET', body, function (resp) {
// プロセス管理の設定チェック
if (resp.enable == false) {
window.alert('プロセス管理が設定されていません!');
return;
}
// 作業者オブジェクト抽出処理
const assignees = {};
Object.entries(resp.states).forEach(function (status) {
assignees[status[0]] = status[1].assignee.entities.map(function (entities) {
return entities.entity.type + ":" + entities.entity.code
}).join('</br>')
});
// プロセス設定一覧の抽出&作業者挿入
const actions = resp.actions;
actions.forEach(function (action) {
action.assignee = assignees[action.from];
});
// ステータス抽出処理
const statesObj = Object.entries(resp.states);
// ステータスの作業者紐付け
statesObj.forEach(function (status) {
status[1].assignee = assignees[status[0]];
})
const states = statesObj.map(function (state) {
return { name: state[1].name, index: state[1].index, assignee: state[1].assignee };
});
// indexによる並び替え処理
states.sort(function compareFunc(a, b) {
const dt1 = a.index;
const dt2 = b.index;
return dt1 - dt2;
});
// 結果出力処理
const domain = location.origin + '/k/';
let result = '<title>プロセス管理設定</title>';
result += '<body style="font-family: sans-serif;"><h3>プロセス管理設定(アプリID:' + appId + ')</h3>';
result += '<p><a href="' + location.origin + '/k/admin/app/status?app=' + appId + '" target="_blank">プロセス管理設定画面</a></p>';
result += '<h4>ステータス</h4><table border="1" style="border-collapse: collapse">';
result += '<tr><th>Index</th><th>ステータス名</th><th>作業者</th></tr>';
states.forEach(function (status) {
result += '<tr>'
result += '<td>' + status.index + '</td>';
result += '<td>' + status.name + '</td>';
result += '<td>' + status.assignee + '</td>';
result += '</tr>';
});
result += '</table></br>';
result += '<h4>プロセス設定</h4><table border="1" style="border-collapse: collapse">';
result += '<p>アクション数:' + actions.length + '</p>';
result += '<tr><th>実行前ステータス</th><th>作業者</th><th>アクション実行条件</th><th>アクション名</th><th>実行後ステータス</th></tr>';
actions.forEach(function (action) {
result += '<tr>'
result += '<td>' + action.from + '</td>';
result += '<td>' + action.assignee + '</td>';
result += '<td>' + action.filterCond + '</td>';
result += '<td>' + action.name + '</td>';
result += '<td>' + action.to + '</td>';
result += '</tr>';
});
result += '</table></br><a href="."> << 前の画面に戻る<a></p></br></body>';
// 出力
console.log(states); // ステータス
console.log(actions); // プロセス設定
document.open();
document.write(result);
}, function (error) {
console.log(error.message);
});
})();
⑩ユーザー情報検索表示
cybozu.comに登録されたユーザー情報を簡易的に検索・参照する機能を持つブックマークレットです。
通常、ユーザー情報(名前、ログイン名、メールアドレス、ユーザーID)を参照するためには、cybozu.com共通管理画面から、参照したいユーザーを探して管理画面を探すという操作が必要です。しかもcybozu.com共通管理画面にアクセスできるのは、cybozu.com共通管理者の権限が必要となります。
cybozu.com共通管理者の権限はシステム設定を行う権限と同等のため、kintoneを管理する立場の一部の人にしか付与しないことが通常です。
このため、通常のユーザーが(アプリ管理者であっても)他のユーザーの情報を参照したい場合は、個別にピープルから参照するしか方法がないという状況です。
そこで、ユーザー情報を簡単に検索・表示できる機能をブックマークレットで作成してみました。
cybozu.comのサービス利用中の画面であれば、どこからでもブックマークレットを選択することで利用することができます。
また、ユーザーを条件とした一覧絞り込みのURLを生成する際には、ユーザーID(ログイン名ではない)を必要としますが、これは管理画面、ピープルにおいても参照することはできません。(ユーザー管理画面のURLから確認は可能)
このブックマークレットによるユーザー情報表示には、ユーザーIDも表示しますので、URL芸を使う時にも強い味方となります。
操作方法
- cybozu.comのいずれかの画面でブックマークレット実行。
- 検索キーワードを入れるダイアログが表示されます。
- 表示名(姓名、よみがな)、ログイン名、メールアドレスの一部をキーワードとして検索絞り込み表示が可能です。
- 何もキーワードを入れずにOKをクリックまたはエンターキーを押すと、検索結果が表示されます。
- 画面のリロードまたは、「アプリ画面に戻る」をクリックすると kintoneに戻ります。
- 表示名のリンクを開くと、cybozu.comのユーザー管理画面が開きます(cybozu.com共通管理者権限が必要)。
制限事項等
- cybozu.com全ユーザーが利用可能です。
- ユーザー管理画面へのリンクを開くには、cybozu.com共通管理者権限が必要です。
- kintone等のcybozu.comの画面で、ログインした状態で利用してください。
- 使用したAPI
※(2023/05/13アップデート):JavaScriptコードをES6対応に修正。同時に画面表示で使っていた'document.open'をやめて'innerHTML'を使うDOM処理に修正。また、検索対象として姓・名が漏れていたため追加修正。
ブックマークレット(登録用)
※このコードをブックマークのURL欄に貼り付けてください。
javascript:(async()=>{if(!location.href.includes('cybozu.com')){alert("cybozu.comの画面から操作してください");return;}const k=prompt("検索キーワード:"),a=kintone.api.url('/v1/users',true),b=[],c=async()=>{for(let d=0;;d+=100){const e=await kintone.api(a,'GET',{offset:d,size:100});if(e.users.length===0)break;b.push(...e.users);}};await c();const f=b.filter(g=>g.name.includes(k)||g.surName.includes(k)||g.givenName.includes(k)||g.code.includes(k)||g.givenNameReading.includes(k)||g.surNameReading.includes(k)||g.email.includes(k)),h=f.map(i=>`<li>ID: ${i.id}: <a href="${location.origin}/admin/directory/editUser?id=${i.id}">${i.name}</a> (Code: ${i.code}), Mail: ${i.email}</li>`).join('');document.body.innerHTML=`<body style="font-family: sans-serif;"><h3>検索結果</h3>${f.length===0?'<p>検索結果がゼロ件でした</p>':`<p>(キーワード:${k}, 検索結果:${f.length}件)</p>`}<ul>${h}</ul><p><a href="."> << 前の画面に戻る<a></p></body>`;})().catch(j=>console.error(j));
コードの内容
※コンソールから実行することもできます。
(async () => {
// cybozu.com画面でのみ作動させる
if (!location.href.includes('cybozu.com')) {
window.alert("cybozu.comの画面から操作してください");
return;
}
// 検索キーワード取得
const keyword = window.prompt("検索キーワード:");
// cubozu.comユーザー情報一括取得処理
let results = [];
let offset = 0;
const size = 100;
const url = kintone.api.url('/v1/users', true);
const getUser = async () => {
try {
while (true) {
const param = { offset, size };
const resp = await kintone.api(url, 'GET', param);
if (resp.users.length === 0) {
break;
}
results = results.concat(resp.users);
offset += size;
}
} catch (error) {
console.error(error);
}
};
await getUser();
// キーワード絞り込み処理
const users = results.filter(user =>
user.name.includes(keyword)
|| user.surName.includes(keyword)
|| user.givenName.includes(keyword)
|| user.code.includes(keyword)
|| user.givenNameReading.includes(keyword)
|| user.surNameReading.includes(keyword)
|| user.email.includes(keyword)
);
// ユーザー情報一覧作成
const domain = location.origin + '/admin/directory/editUser?id=';
const userList = users.map(user =>
`<li>ID: ${user.id}: <a href="${domain}${user.id}">${user.name}</a> (Code: ${user.code}), Mail: ${user.email}</li>`
).join('');
const result = `
<body style="font-family: sans-serif;">
<h3>検索結果</h3>
${users.length === 0 ? '<p>検索結果がゼロ件でした</p>' : `<p>(キーワード:${keyword}, 検索結果:${users.length}件)</p>`}
<ul>
${userList}
</ul>
<p><a href="."> << 前の画面に戻る<a></p>
</body>
`;
document.body.innerHTML = result;
})().catch(error => console.error(error));