はじめに
プレーンなJavascript(Vanilla.js)による、コード番号からバーコードを生成するためのロジック構築について、前半と後半の2回に分けて紹介します。一般消費者では、恐らく一番目にすることが多い、JAN-13 に限定し、モバイル端末等バーコードリーダーにて認識可能なバーコード表示を目的とします。
今回は後半ということで、サンプルコードと解説となります。
サンプルコード
以降、前回記事をご一読いただいていることを前提として進めていきます。
HTML
まず、OCR-B を有効にするため、<head>
へCDNへのリンクを挿入しておきます。もちろん、スタイルシートからの @import
でも問題ありません。
<link href="http://fonts.cdnfonts.com/css/ocr-b-10-bt" rel="stylesheet">
上記のCDNを利用することで、Chromeのコンソールには以下のような SameSite Cookie
に関するエラーが表示されます。
Indicate whether to send a cookie in a cross-site request by specifying its SameSite attribute
既知の問題として挙げておきますが、気になるという場合には、あえてCDNを利用する必要はないと思います。
なお、 SameSite Cookie
について詳しく知りたい場合はこちらを参照してください→ SameSite Cookie の説明
以下はUI部分のHTMLとなります。
<div class="wrapper">
<div id="inputContainer">
<input type="text" id="inputJan13"><br>
<button type="button" id="genJan13">コード生成</button>
<button type="button" id="resetElm">リセット</button>
</div>
<div id="janContainer">
<!-- ここにJAN-13コードが表示されます -->
</div>
</div>
CSS
Javascriptによって、バーコードが動的に生成されますが、その見た目部分の設定です。
フローアウトラインは以下の通り。
- 符号化したビット文字列1文字につき、動的に白(
.w01
)と黒(.b01
)のモジュールを生成 - 上記を一つずつ横に並べることで、バーコードを構成
ビット文字列は113桁ありますので、113個のモジュール表示用の要素を順次生成することになります。
これらをflexbox
により、デバイスに応じた横幅で表示させます。
なお、JANコード番号自体も同一コンテナ内(#janContainer
)に収めたかったため、地味ですが、flexboxのwrap指定時に任意の要素で改行させる という技も使っています。
技術情報としては、こちらを参考にしました。
#inputContainer{
width:50%;
min-width:113px;
height:5rem;
margin:1rem auto;
}
/* バーコードの表示 */
#janContainer{
width:50%;
min-width:113px;
min-height: calc(70px + 3vw);
background-color: white;
margin:0 auto;
padding:1vw 0 0 0;
display:flex;
flex-wrap: wrap;
justify-content:center;
}
/* モジュール */
.w01{
min-width:1px;
min-height:70px;
margin:0;
background-color:white;
flex:1 1 auto;
}
.b01{
min-width:1px;
min-height:70px;
margin:0;
background-color:black;
flex:1 1 auto;
}
/* コード番号 */
.janCodeContainer{
width: 100%;
min-width: 113px;
height: 1em;
padding: 0.15em 0 0 0.6em;
background-color: white;
font-size: 2vw;
letter-spacing: 0.2em;
font-family: 'OCR-B 10 BT', sans-serif;
}
.janCodeContainer::before{
content: '';
width: 100%;
}
Javascript
Main
以下はメイン処理になります。後述する Class stGenJan13
より後に記述するか、読み込んだ後に実行するようにしてください。
内容としては、以下のように、2つのボタンクリック時の挙動を定義しているだけです。
- コード生成ボタン :インスタンスを生成し、入力内容のチェックを行います。エラーがあればエラー表示、なければJAN-13コードを生成、表示します。
- リセットボタン :JAN-13表示エリア内を初期化します。
window.addEventListener('DOMContentLoaded', () => {
// DOM要素の取得
const btnGenJan13 = window.genJan13;
const btnResetElm = window.resetElm;
const inputJan13 = window.inputJan13;
const janContainer = window.janContainer;
// 初期化処理の定義
const resetJanContainer = () => {
janContainer.querySelectorAll('*').forEach(elm => elm.remove());
};
// コード生成ボタンクリック
btnGenJan13.addEventListener('click', () => {
// 初期化
resetJanContainer();
// インスタンス生成
const STJAN = new stGenJan13({
value : inputJan13.value,
parent : janContainer,
childTag: 'div'
});
// エラーチェック
if(!STJAN.has13Char() || !STJAN.isDigit()){
janContainer.innerHTML = '<div class="errorMsg">入力内容にエラーがあります</div>';
return;
}
// JAN-13表示
STJAN.displayJan13();
});
// リセットボタンクリック
btnResetElm.addEventListener('click', resetJanContainer);
});
Class stGenJan13
本当の意味でのメイン処理は以下のクラスファイルになります。
スタンダードに関数仕立てで構築しても良かったのですが、今後、コードを複数表示したくなったときのことを踏まえてクラス仕立てにしてみました。
なお、クラス化するにあたっては、本来ならJAN-8等への対応も考慮し、親となるクラスを構築してからJAN-13用のクラスとして継承する方が良いと思います。
今回はJAN-13限定、かつ、サンプルコードのため、直でクラス化をしました。
class stGenJan13 {
constructor(arg){
this.code = arg.value;
this.chars = this.code.split('').map(char => Number(char));
this.digit = this.chars.slice(-1)[0];
this.prefix = this.chars[0];
this.parentElm = arg.parent;
this.childTag = arg.childTag;
}
static quietZone = {left:'00000000000', right:'0000000'};
static guardBar = {left:'101', center:'01010', right:'101'};
static numbersets = [
{A:'0001101', B:'0100111', C:'1110010'},
{A:'0011001', B:'0110011', C:'1100110'},
{A:'0010011', B:'0011011', C:'1101100'},
{A:'0111101', B:'0100001', C:'1000010'},
{A:'0100011', B:'0011101', C:'1011100'},
{A:'0110001', B:'0111001', C:'1001110'},
{A:'0101111', B:'0000101', C:'1010000'},
{A:'0111011', B:'0010001', C:'1000100'},
{A:'0110111', B:'0001001', C:'1001000'},
{A:'0001011', B:'0010111', C:'1110100'}
];
static leftDataPatterns = [
'AAAAAA',
'AABABB',
'AABBAB',
'AABBBA',
'ABAABB',
'ABBAAB',
'ABBBAA',
'ABABAB',
'ABABBA',
'ABBABA'
];
has13Char(){
return /^\d{13}$/.test(this.code);
}
isDigit(){
const chars = this.chars.slice(0, -1);
return this.calcM10W3(chars) === this.digit;
}
calcM10W3(arr){
let [odd, even] = [0, 0];
arr.forEach((num, i) => {i % 2 === 1? odd += num: even += num;});
const res = 10 - (odd*3 + even) % 10;
return res === 10? 0: res;
}
encode(){
return `${stGenJan13.quietZone.left}${stGenJan13.guardBar.left}${this.genLeftData()}${stGenJan13.guardBar.center}${this.genRightData()}${stGenJan13.guardBar.right}${stGenJan13.quietZone.right}`;
}
genLeftData(){
const leftData = this.chars.slice(1, 7);
const charPattern = stGenJan13.leftDataPatterns[this.prefix].split('');
return leftData.reduce((a, c, i) => {
a += charPattern[i] === 'A'? stGenJan13.numbersets[c].A: stGenJan13.numbersets[c].B;
return a;
}, '');
}
genRightData(){
const rightData = this.chars.slice(7);
return rightData.reduce((a, c, i) => {
a += stGenJan13.numbersets[c].C;
return a;
}, '');
}
displayJan13(){
// バーコードの表示
const arr = this.encode().split('');
arr.forEach(n => {
const child = document.createElement(this.childTag);
const className = n === '0'? 'w01': 'b01';
child.classList.add(className);
this.parentElm.appendChild(child);
});
// コード番号の表示
const codeContainer = document.createElement(this.childTag);
codeContainer.innerText = this.code;
codeContainer.classList.add('janCodeContainer');
this.parentElm.appendChild(codeContainer);
}
}
スタティックメンバとして、ナンバーセットごとのモジュールパターンをオブジェクト配列にしています。
初歩的なのですが、これにより、各キャラクタの数字が配列のインデックス値となり、ビット文字列構築時に参照が容易となります。
具体的には genLeftData()
と genRightData()
で使用しています。
displayJan13()
では、 encode()
で返ってきた113桁のビット文字列を1文字ずつ配列へ分割し、順次モジュール表示用の要素を生成、追加しています。泥臭いですが、内容はシンプルでわかりやすいと思います。
その他、基本的には前回まとめた要件に従って、処理内容を構築しています。
以下、バーコード生成後の表示サンプルです。興味のある方は読み取ってみてください。
最後に
本稿の内容は、実案件にて、monaca によるプライベートアプリとして開発した際の記録をベースにしたものとなります。
本稿のサンプルコードはクライアント様の許可を得た上で、プロトタイプ完成時のコード中、コアとなる部分のみを再編集し、簡単なテストの後、掲載させていただきました。
誰かのお役に立てたなら幸いです。