kintone Advent Calendar 2019 13日目の記事です。
はじめに
2019年、ついにバーコード決済デビューをしました。IC カード決済と組み合わせて、無敵な気持ちになっています
なので、スマートフォン版の kintone で QRコードを読み取るカスタマイズをしてみました!
ポイントは、ネイティブのアプリを作らずに Web ブラウザ上で QR コードを読み取るところです。
このカスタマイズは、iOS Safari 限定で動作します。
動作確認は、iPhone XR(iOS 13.2.3)で行いました。
完成イメージ
- レコード追加画面上部の[スキャン]ボタンをクリックすると、QRコードを読み取るモーダルが表示されます。
- QRコードを読み取って[OK]ボタンを押すと、ルックアップで商品マスタから商品名を取得します。
- あとは入出庫情報を入力して、レコードを保存します。
必要なもの
- Node.js 10系 環境
- kintone 環境(スタンダードコース)
※ 1年無料で利用できる開発者ライセンスがあります。
kintone アプリの作成
-
入出庫履歴アプリ
バーコード読み取りカスタマイズを行うアプリです。このアプリに JavaScript カスタマイズをします。フィールド名 フィールドタイプ フィールドコード 備考 商品コード ルックアップ itemCode 商品マスタの商品コードに関連付ける 商品名 文字列(1行) itemName 商品マスタの商品名からコピー 種別 ラジオボタン ー 「入庫」「出庫」 数量 数値 ー 初期値: 0 -
商品マスタ
入出庫履歴に関連付けるアプリです。こちらはカスタマイズしません。
あらかじめ、このアプリに商品レコードを追加しておきます。フィールド名 フィールドタイプ フィールドコード 備考 コード 文字列(1行) itemCode 「値の重複を禁止する」にチェック 商品名 文字列(1行) itemName
バーコードを読み取るライブラリの入手
2次元バーコードを読み取るライブラリには、InstaScan を利用します。
ただ、モバイルの Safari では動かないので、fork した InstaScan1を用意しました。
-
fork した InstaScan にアクセスし、リポジトリをダウンロードします。
-
リポジトリのディレクトリ以下で、次のコマンドを実行します。Node.js 10系で実行してください2
# リポジトリのディレクトリに移動する $ cd instascan-master # ビルドに必要なツール群をインストールする $ npm install # ビルド $ npx gulp release
-
dist
ディレクトリ以下に、「instascan.min.js」が生成されます。この後の「kintone カスタマイズの適用」で利用します。
kintone カスタマイズの適用
「入出庫履歴」アプリに、カスタマイズを適用します。
-
スマートフォン用の JavaScript ファイル(次の順で適用)
- https://js.cybozu.com/jquery/3.4.1/jquery.min.js
- instascan.min.js
- customize.js(カスタマイズファイル)
-
スマートフォン用の CSS ファイル
- customize.css(カスタマイズファイル)
カスタマイズファイル(customize.js
customize.css
)の詳細は次のとおりです。
Safari で動かすので、ES6+ の記法を使っています。
(($) => {
'use strict';
let camera, result;
/**
* スキャンボタンの HTML を生成する
* @return {Object} scanButtonHTML
*/
const getScanBtnHTML = () => {
return `
<div class="kpc-header">
<button type="button" class="kpc-btn js-kcp-button"><i class="fas fa-barcode"></i> スキャン</button>
</div>
`;
};
/**
* モーダルの HTML を生成する
* @return {Object} modalHTML
*/
const getModalHTML = () => {
return `
<div class="kcp-modal">
<div class="kcp-modal__cointainer js-kcp-modal__cointainer">
<div class="kpc-modal__header"><i class="fas fa-barcode"></i> QR コードをスキャン</div>
<div class="kpc-video__container js-kpc-video__container"><video id="video" class="video"></video></div>
<div class="kcp-modal__result">
<dl>
<dt>読み取り結果</dt>
<dd>
<input type="text" class="qrcode" name="itemCode" disabled>
</dd>
</dl>
</div>
<div class="js-kpc-modal__footer kpc-modal__footer">
<div class="kpc-modal__footer--left"><button name="cancel"> キャンセル</button></div>
<div class="kpc-modal__footer--right"><button name="ok" disabled> OK</button></div>
</div>
</div>
<div class="kpc-modal__bg js-kpc-modal__bg"></div>
</div>
`;
};
/**
* スキャナーオブジェクトを生成する
* @param {jQuery} $modal
* @return {Object} Scanner
*/
const getScanner = ($modal) => {
const $video = $modal.find('#video');
const scanner = new Instascan.Scanner({video: $video[0], mirror: false});
// 読み取れたときのイベントを登録
scanner.addListener('scan', (_result) => {
result = _result;
$('.js-kpc-modal__footer button[name="ok"]').prop('disabled', false);
$('input[name="itemCode"]').val(result);
});
return scanner;
};
/**
* モーダルを開く
*/
const openModal = () => {
if ($('.kcp-modal').length > 0) {
$('.kcp-modal').remove();
}
const $modal = $(getModalHTML());
$('body').append($modal);
$('input[name="itemCode"]').val('');
const scanner = getScanner($modal);
Instascan.Camera.getCameras().then((cameras) => {
if (cameras.length <= 0) {
console.error('No cameras found.');
return;
}
camera = cameras[0];
scanner.start(camera);
}).catch((err) => {
console.error(err);
});
$('.js-kpc-modal__footer button[name="ok"]').on('click', () => {
const record = kintone.mobile.app.record.get();
record.record.itemCode.value = result;
record.record.itemCode.lookup = true;
kintone.mobile.app.record.set(record);
closeModal(scanner);
});
$('.js-kpc-modal__footer button[name="cancel"]').on('click', () => {
closeModal(scanner);
});
$('.js-kpc-modal__bg,.js-kcp-modal__cointainer').fadeIn('slow');
};
/**
* モーダルを閉じる
* @param {Object} scanner スキャナー
*/
const closeModal = (scanner) => {
$('.js-kcp-modal__cointainer,.js-kpc-modal__bg').fadeOut('slow', () => {
$('.js-kpc-modal__bg').remove();
if (camera) {
scanner.stop(camera);
}
});
};
// モバイル版のレコード追加画面を開いたときに実行する
kintone.events.on('mobile.app.record.create.show', (event) => {
const $scanBtn = $(getScanBtnHTML());
$(kintone.mobile.app.getHeaderSpaceElement()).append($scanBtn);
$scanBtn.find('.js-kcp-button').on('click', () => {
openModal();
});
return event;
});
})(jQuery);
.kpc-header {
padding: 10px;
}
.kpc-btn {
padding: 8px 12px;
font-size: 1em;
font-weight: 700;
line-height: 1;
color: #fff;
background-color: #206694;
border: solid 2px #206694;
border-radius: 6px;
}
.kpc-modal__bg {
position: fixed;
top: 0;
left: 0;
z-index: 1;
display: none;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
}
.kcp-modal__cointainer {
position: fixed;
top: 2vh;
right: 5vh;
left: 5vh;
z-index: 2;
max-height: 95vh;
background-color: #fff;
border-radius: 6px 6px 6px 6px;
}
.kpc-modal__header {
padding: 16px;
font-size: 1.4rem;
font-weight: 700;
line-height: 1.4em;
border-bottom: 1px solid #d8d8d8;
}
.kpc-video__container {
position: relative;
padding: 16px;
line-height: 1.4em;
border-bottom: 1px solid #d8d8d8;
}
.video {
top: 0;
z-index: 1;
width: 100%;
}
.kcp-modal__result {
position: relative;
-webkit-box-sizing: border-box;
box-sizing: border-box;
width: 100%;
padding: 16px;
}
.kcp-modal__result dl {
width: 100%;
}
.kcp-modal__result dt {
margin-bottom: 5px;
font-weight: 700;
}
.kcp-modal__result dd {
width: 100%;
margin-bottom: 5px;
}
.kpc-modal__footer {
display: flex;
padding: 10px 16px;
}
.kpc-modal__footer--left {
display: flex;
float: left;
width: 50%;
}
.kpc-modal__footer--right {
display: flex;
float: right;
width: 50%;
text-align: right;
}
.kpc-modal__footer button {
box-sizing: border-box;
display: block;
min-width: 100px;
padding: 12px;
font-weight: 700;
line-height: 1;
border: 2px solid #206694;
border-radius: 6px;
}
.kpc-modal__footer button[name="ok"] {
margin: 0 0 0 auto;
color: #fff;
background-color: #206694;
}
.kpc-modal__footer button[disabled] {
cursor: default;
background-color: #a5a5a5;
border: 2px solid #a5a5a5;
}
.kpc-modal__footer button[name="cancel"] {
color: #206694;
background-color: #fff;
}
.kcp-modal__result input {
-webkit-box-sizing: border-box;
box-sizing: border-box;
width: 100%;
padding: 0.4em;
border-radius: 0.4em;
outline: 0;
}
.kcp-modal__result input[disabled] {
-webkit-box-sizing: border-box;
box-sizing: border-box;
color: #999;
-webkit-text-fill-color: #999;
background-color: #d5d7d9;
opacity: 1;
}
.qrcode {
padding-right: 1.7em;
margin-right: 0;
vertical-align: middle;
}
おわりに
ネイティブアプリを作らなくても、少ないコード量の JavaScipt カスタマイズで、QR コードの読み取りができました!