はじめに
kintone Advent Calendar 2022 14日目の記事です。
さて、12月14日といえば、忠臣蔵です!
ということで、忠臣蔵にひっかけたkintoneカスタマイズをしてみました!
(※旧暦の12月14日なので、今の時代だと1月30日になるそうですが…)
デモ
こんなアプリをつくってみました!
吉良邸屋敷図の図面上に設置したボタンから、各義士の配置をセットできるカスタマイズです。
標準の入力フィールドよりも、直感的に入力をすることが可能となります。
要件と実現方法
- 隊編成アプリの入力画面に、吉良邸屋敷図を表示する。
- スペースフィールドを設置し、そこに吉良邸屋敷図の画像を表示させる。
- 吉良邸屋敷図上における隊の配置場所にボタンを設置する。
- cssによるスタイリングを施し、吉良邸屋敷図画像の上にボタンを設置する。
- ボタンを押したとき、隊に関する情報を各フィールドにセットする。
- 設置したボタンにクリックイベントを追加し、ボタン押下でフィールドに値をセットする。
- 登録済みのメンバーの配置がわかるようにする。
- 関連レコードにて対応する。
作り方
吉良邸屋敷図を作成
Wordで吉良邸屋敷図を作成し、画像で保存しました。1
kintoneアプリ作成
まず、土台となるアプリを作成します。
アプリを作成した後に、kintone REST API をたたいて、取得したフォーム設計情報を記載します。
※今回作成した隊編成アプリIDは203。名前フィールドの値は、アプリID204のマスタ用のアプリからルックアップで取得する構成です。
kintone.api(kintone.api.url('/k/v1/form.json', true), 'GET', { app: 203}, (resp) => { console.log(resp) });
resp
{
"properties": [
{
"type": "SINGLE_LINE_TEXT",
"label": "名前",
"noLabel": "false",
"code": "名前",
"required": "false",
"relatedApp": "204"
},
{
"type": "DROP_DOWN",
"label": "エリア",
"noLabel": "false",
"code": "エリア",
"required": "false",
"options": [
"表門",
"裏門"
],
"defaultValue": null
},
{
"type": "DROP_DOWN",
"label": "役割",
"noLabel": "false",
"code": "役割",
"required": "false",
"options": [
"司令",
"屋内討入",
"屋外",
"場の内"
],
"defaultValue": null
},
{
"type": "SINGLE_LINE_TEXT",
"label": "隊",
"noLabel": "false",
"code": "隊",
"required": "false",
"minLength": null,
"maxLength": null,
"expression": "",
"hideExpression": "false",
"unique": "false",
"defaultValue": ""
},
{
"type": "REFERENCE_TABLE",
"label": "司令部",
"noLabel": "false",
"code": "表門隊_司令部",
"relatedApp": "203"
},
{
"type": "REFERENCE_TABLE",
"label": "屋内討入隊",
"noLabel": "false",
"code": "表門隊_屋内討入隊",
"relatedApp": "203"
},
{
"type": "SINGLE_LINE_TEXT",
"label": "プロジェクト名",
"noLabel": "false",
"code": "プロジェクト名",
"required": "false",
"minLength": null,
"maxLength": null,
"expression": "",
"hideExpression": "false",
"unique": "false",
"defaultValue": "吉良邸討入"
},
{
"type": "REFERENCE_TABLE",
"label": "場の内隊",
"noLabel": "false",
"code": "表門隊_場の内隊",
"relatedApp": "203"
},
{
"type": "REFERENCE_TABLE",
"label": "屋外隊",
"noLabel": "false",
"code": "表門隊_屋外隊",
"relatedApp": "203"
},
{
"type": "REFERENCE_TABLE",
"label": "司令部",
"noLabel": "false",
"code": "裏門隊_司令部",
"relatedApp": "203"
},
{
"type": "REFERENCE_TABLE",
"label": "屋内討入隊",
"noLabel": "false",
"code": "裏門隊_屋内討入隊",
"relatedApp": "203"
},
{
"type": "REFERENCE_TABLE",
"label": "屋外隊",
"noLabel": "false",
"code": "裏門隊_屋外隊",
"relatedApp": "203"
},
{
"type": "NUMBER",
"label": "#",
"noLabel": "false",
"code": "ナンバー",
"required": "false",
"minValue": null,
"maxValue": null,
"digit": "false",
"unique": "false",
"defaultValue": null,
"displayScale": null,
"unit": null,
"unitPosition": "BEFORE"
},
{
"type": "SPACER",
"elementId": "sp_map"
}
]
}
コーディング
JavaScript
index.js
(() => {
'use strict';
// ================================================
// EVENT : create.show, edit.show
// ================================================
kintone.events.on(['app.record.create.show', 'app.record.edit.show'], event => {
const record = event.record;
const imgMapUrl = 'https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953166/06cfac37-4c1e-2b8b-4ee9-8c8583a6c14f.png';
const spMap = kintone.app.record.getSpaceElement('sp_map');
const divMap = document.createElement('div');
divMap.className = 'hazime__p-container__content';
spMap.appendChild(divMap);
const imgMap = document.createElement('img');
imgMap.src = imgMapUrl;
imgMap.className = 'hazime__c-media__image__map';
divMap.appendChild(imgMap);
// *********************
// 表門隊
// *********************
// 司令部
const btnFrontGateHeadquarters = document.createElement('button');
btnFrontGateHeadquarters.textContent = '司令部';
btnFrontGateHeadquarters.className = 'hazime__c-button__front-gate hazime__c-button__front-gate__headerquarters';
btnFrontGateHeadquarters.addEventListener('click', () => {
let rec = kintone.app.record.get();
rec.record.エリア.value = '表門';
rec.record.役割.value = '司令';
rec.record.隊.value = '表門隊_司令部';
kintone.app.record.set(rec);
});
divMap.appendChild(btnFrontGateHeadquarters);
// 屋内討入隊
const btnFrontGateIndoorAttack = document.createElement('button');
btnFrontGateIndoorAttack.textContent = '屋内討入隊';
btnFrontGateIndoorAttack.className = 'hazime__c-button__front-gate hazime__c-button__front-gate__indoor-attack';
btnFrontGateIndoorAttack.addEventListener('click', () => {
let rec = kintone.app.record.get();
rec.record.エリア.value = '表門';
rec.record.役割.value = '屋内討入';
rec.record.隊.value = '表門隊_屋内討入隊';
kintone.app.record.set(rec);
});
divMap.appendChild(btnFrontGateIndoorAttack);
// 場の内隊
const btnFrontGateInside = document.createElement('button');
btnFrontGateInside.textContent = '場の内隊';
btnFrontGateInside.className = 'hazime__c-button__front-gate hazime__c-button__front-gate__inside';
btnFrontGateInside.addEventListener('click', () => {
let rec = kintone.app.record.get();
rec.record.エリア.value = '表門';
rec.record.役割.value = '場の内';
rec.record.隊.value = '表門隊_場の内隊';
kintone.app.record.set(rec);
});
divMap.appendChild(btnFrontGateInside);
// 屋外隊
const btnFrontGateOutdoor = document.createElement('button');
btnFrontGateOutdoor.textContent = '屋外隊';
btnFrontGateOutdoor.className = 'hazime__c-button__front-gate hazime__c-button__front-gate__outdoor';
btnFrontGateOutdoor.addEventListener('click', () => {
let rec = kintone.app.record.get();
rec.record.エリア.value = '表門';
rec.record.役割.value = '屋外';
rec.record.隊.value = '表門隊_屋外隊';
kintone.app.record.set(rec);
});
divMap.appendChild(btnFrontGateOutdoor);
// *********************
// 裏門隊
// *********************
// 司令部
const btnBackGateHeadquarters = document.createElement('button');
btnBackGateHeadquarters.textContent = '司令部';
btnBackGateHeadquarters.className = 'hazime__c-button__back-gate hazime__c-button__back-gate__headerquarters';
btnBackGateHeadquarters.addEventListener('click', () => {
let rec = kintone.app.record.get();
rec.record.エリア.value = '裏門';
rec.record.役割.value = '司令';
rec.record.隊.value = '裏門隊_司令部';
kintone.app.record.set(rec);
});
divMap.appendChild(btnBackGateHeadquarters);
// 屋内討入隊
const btnBackGateIndoorAttack = document.createElement('button');
btnBackGateIndoorAttack.textContent = '屋内討入隊';
btnBackGateIndoorAttack.className = 'hazime__c-button__back-gate hazime__c-button__back-gate__indoor-attack';
btnBackGateIndoorAttack.addEventListener('click', () => {
let rec = kintone.app.record.get();
rec.record.エリア.value = '裏門';
rec.record.役割.value = '屋内討入';
rec.record.隊.value = '裏門隊_屋内討入隊';
kintone.app.record.set(rec);
});
divMap.appendChild(btnBackGateIndoorAttack);
// 屋外隊
const btnBackGateOutdoor = document.createElement('button');
btnBackGateOutdoor.textContent = '屋外隊';
btnBackGateOutdoor.className = 'hazime__c-button__back-gate hazime__c-button__back-gate__outdoor';
btnBackGateOutdoor.addEventListener('click', () => {
let rec = kintone.app.record.get();
rec.record.エリア.value = '裏門';
rec.record.役割.value = '屋外';
rec.record.隊.value = '裏門隊_屋外隊';
kintone.app.record.set(rec);
});
divMap.appendChild(btnBackGateOutdoor);
return event;
});
// ================================================
// EVENT : detail.show
// ================================================
kintone.events.on(['app.record.detail.show'], event => {
const record = event.record;
// 全配員表示用のレコードのときは、以下のフィールドを非表示
if (record.名前.value === '全配員表示用') {
kintone.app.record.setFieldShown('名前', false);
kintone.app.record.setFieldShown('エリア', false);
kintone.app.record.setFieldShown('役割', false);
kintone.app.record.setFieldShown('隊', false);
kintone.app.record.setFieldShown('ナンバー', false);
}
const imgMapUrl = 'https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953166/06cfac37-4c1e-2b8b-4ee9-8c8583a6c14f.png';
const spMap = kintone.app.record.getSpaceElement('sp_map');
const divMap = document.createElement('div');
divMap.className = 'hazime__p-container__content';
spMap.appendChild(divMap);
const imgMap = document.createElement('img');
imgMap.src = imgMapUrl;
imgMap.className = 'hazime__c-media__image__map';
divMap.appendChild(imgMap);
// *********************
// 表門隊
// *********************
// 司令部
const btnFrontGateHeadquarters = document.createElement('button');
btnFrontGateHeadquarters.disabled = true;
btnFrontGateHeadquarters.textContent = '司令部';
btnFrontGateHeadquarters.className = 'hazime__c-button__front-gate hazime__c-button__front-gate__headerquarters';
if (record.隊.value === '表門隊_司令部') {
btnFrontGateHeadquarters.classList.add('hazime__c-button__his-placement');
}
// btnFrontGateHeadquarters.addEventListener('click', () => {
// let rec = kintone.app.record.get();
// kintone.app.record.set(rec);
// });
divMap.appendChild(btnFrontGateHeadquarters);
// 屋内討入隊
const btnFrontGateIndoorAttack = document.createElement('button');
btnFrontGateIndoorAttack.disabled = true;
btnFrontGateIndoorAttack.textContent = '屋内討入隊';
btnFrontGateIndoorAttack.className = 'hazime__c-button__front-gate hazime__c-button__front-gate__indoor-attack';
if (record.隊.value === '表門隊_屋内討入隊') {
btnFrontGateIndoorAttack.classList.add('hazime__c-button__his-placement');
}
// btnFrontGateIndoorAttack.addEventListener('click', () => {
// let rec = kintone.app.record.get();
// kintone.app.record.set(rec);
// });
divMap.appendChild(btnFrontGateIndoorAttack);
// 場の内隊
const btnFrontGateInside = document.createElement('button');
btnFrontGateInside.disabled = true;
btnFrontGateInside.textContent = '場の内隊';
btnFrontGateInside.className = 'hazime__c-button__front-gate hazime__c-button__front-gate__inside';
if (record.隊.value === '表門隊_場の内隊') {
btnFrontGateInside.classList.add('hazime__c-button__his-placement');
}
// btnFrontGateInside.addEventListener('click', () => {
// let rec = kintone.app.record.get();
// kintone.app.record.set(rec);
// });
divMap.appendChild(btnFrontGateInside);
// 屋外隊
const btnFrontGateOutdoor = document.createElement('button');
btnFrontGateOutdoor.disabled = true;
btnFrontGateOutdoor.textContent = '屋外隊';
btnFrontGateOutdoor.className = 'hazime__c-button__front-gate hazime__c-button__front-gate__outdoor';
if (record.隊.value === '表門隊_屋外隊') {
btnFrontGateOutdoor.classList.add('hazime__c-button__his-placement');
}
// btnFrontGateOutdoor.addEventListener('click', () => {
// let rec = kintone.app.record.get();
// kintone.app.record.set(rec);
// });
divMap.appendChild(btnFrontGateOutdoor);
// *********************
// 裏門隊
// *********************
// 司令部
const btnBackGateHeadquarters = document.createElement('button');
btnBackGateHeadquarters.disabled = true;
btnBackGateHeadquarters.textContent = '司令部';
btnBackGateHeadquarters.className = 'hazime__c-button__back-gate hazime__c-button__back-gate__headerquarters';
if (record.隊.value === '裏門隊_司令部') {
btnBackGateHeadquarters.classList.add('hazime__c-button__his-placement');
}
// btnBackGateHeadquarters.addEventListener('click', () => {
// let rec = kintone.app.record.get();
// kintone.app.record.set(rec);
// });
divMap.appendChild(btnBackGateHeadquarters);
// 屋内討入隊
const btnBackGateIndoorAttack = document.createElement('button');
btnBackGateIndoorAttack.disabled = true;
btnBackGateIndoorAttack.textContent = '屋内討入隊';
btnBackGateIndoorAttack.className = 'hazime__c-button__back-gate hazime__c-button__back-gate__indoor-attack';
if (record.隊.value === '裏門隊_屋内討入隊') {
btnBackGateIndoorAttack.classList.add('hazime__c-button__his-placement');
}
// btnBackGateIndoorAttack.addEventListener('click', () => {
// let rec = kintone.app.record.get();
// kintone.app.record.set(rec);
// });
divMap.appendChild(btnBackGateIndoorAttack);
// 屋外隊
const btnBackGateOutdoor = document.createElement('button');
btnBackGateOutdoor.disabled = true;
btnBackGateOutdoor.textContent = '屋外隊';
btnBackGateOutdoor.className = 'hazime__c-button__back-gate hazime__c-button__back-gate__outdoor';
btnBackGateOutdoor.disabled === true;
if (record.隊.value === '裏門隊_屋外隊') {
btnBackGateOutdoor.classList.add('hazime__c-button__his-placement');
}
// btnBackGateOutdoor.addEventListener('click', () => {
// let rec = kintone.app.record.get();
// kintone.app.record.set(rec);
// });
divMap.appendChild(btnBackGateOutdoor);
return event;
});
})();
SCSS
今回のスタイリングについては、SassのSCSS記法を使っています。コンパイルしたCSSファイルをkintoneに適用します。(今回は、VS Codeの拡張機能であるLive Sass Compiler でコンパイルしました。)
scss/style.scss
// ==========================================================================
// Object
// ==========================================================================
// -----------------------------------------------------------------
// Component
// -----------------------------------------------------------------
@import "object/component/_button";
@import "object/component/_media";
// -----------------------------------------------------------------
// Project
// -----------------------------------------------------------------
@import "object/project/_container";
scss/object/component/_button
$cButtonColorFrontGate: rgb(241, 184, 231);
$cButtonColorBackGate: rgb(187, 189, 255);
$cButtonColorHover: rgb(161, 254, 145);
$cButtonHisPlacement: rgb(255, 0, 0);
$cButtonRadius: 10px;
.hazime__c-button {
// 表門隊
&__front-gate {
position: absolute;
border-radius: $cButtonRadius;
background-color: $cButtonColorFrontGate;
&:hover {
background-color: $cButtonColorHover;
}
&:disabled {
background-color: $cButtonColorFrontGate;
cursor: default;
}
// 司令部
&__headerquarters {
top: 245px;
left: 880px;
}
// 屋内討入隊
&__indoor-attack {
top: 280px;
left: 800px;
}
// 場の内隊
&__inside {
top: 255px;
left: 690px;
}
// 屋外隊
&__outdoor {
top: 340px;
left: 680px;
}
}
// 裏門隊
&__back-gate {
position: absolute;
border-radius: $cButtonRadius;
background-color: $cButtonColorBackGate;
&:hover {
background-color: $cButtonColorHover;
}
&:disabled {
background-color: $cButtonColorBackGate;
cursor: default;
}
// 司令部
&__headerquarters {
top: 110px;
left: 150px;
}
// 屋内討入隊
&__indoor-attack {
top: 245px;
left: 185px;
}
// 屋外隊
&__outdoor {
top: 355px;
left: 350px;
}
}
// 詳細画面で配置されている隊
&__his-placement {
background-color: $cButtonHisPlacement;
font-weight: bold;
color: rgb(255, 255, 255);
&:disabled {
background-color: $cButtonHisPlacement;
}
}
}
scss/object/component/_media
.hazime__c-media {
&__image__map {
width: 1000px;
}
}
scss/object/project/_container
.hazime__p-container{
&__content {
position: relative;
}
}