[Javascript + CSS] JAN 13のコード番号からバーコードを生成し表示する（２）～サンプルコードと解説

# はじめに
プレーンなJavascript（Vanilla.js）による、コード番号からバーコードを生成するためのロジック構築について、前半と後半の２回に分けて紹介します。一般消費者では、恐らく一番目にすることが多い、**JAN-13** に限定し、モバイル端末等バーコードリーダーにて認識可能なバーコード表示を目的とします。

今回は後半ということで、**サンプルコードと解説**となります。

# サンプルコード
以降、前回記事をご一読いただいていることを前提として進めていきます。

https://qiita.com/STSHISHO/items/50803be3c4b6042dd2fd

## HTML
まず、**OCR-B** を有効にするため、`<head>` へCDNへのリンクを挿入しておきます。もちろん、スタイルシートからの `@import` でも問題ありません。

```HTML
<link href="http://fonts.cdnfonts.com/css/ocr-b-10-bt" rel="stylesheet">
```

::: note warn
上記の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 の説明](https://web.dev/samesite-cookies-explained/?utm_source=devtools)
:::

以下はＵＩ部分のHTMLとなります。

```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`）のモジュールを生成
2. 上記を一つずつ横に並べることで、バーコードを構成

ビット文字列は１１３桁ありますので、１１３個のモジュール表示用の要素を順次生成することになります。

これらを`flexbox` により、デバイスに応じた横幅で表示させます。

なお、JANコード番号自体も同一コンテナ内（`#janContainer`）に収めたかったため、地味ですが、**flexboxのwrap指定時に任意の要素で改行させる** という技も使っています。

技術情報としては、[こちら](https://dezanari.com/css-flexbox-intended-wrap/)を参考にしました。

```CSS
#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` より後に記述するか、読み込んだ後に実行するようにしてください。

内容としては、以下のように、２つのボタンクリック時の挙動を定義しているだけです。

- **コード生成ボタン** ：インスタンスを生成し、入力内容のチェックを行います。エラーがあればエラー表示、なければJAN-13コードを生成、表示します。
- **リセットボタン** ：JAN-13表示エリア内を初期化します。

```Javascript
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限定、かつ、サンプルコードのため、直でクラス化をしました。

```Javascript
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()` で返ってきた１１３桁のビット文字列を１文字ずつ配列へ分割し、順次モジュール表示用の要素を生成、追加しています。泥臭いですが、内容はシンプルでわかりやすいと思います。

その他、基本的には[前回まとめた要件](https://qiita.com/STSHISHO/items/50803be3c4b6042dd2fd)に従って、処理内容を構築しています。

以下、バーコード生成後の表示サンプルです。興味のある方は読み取ってみてください。

![バーコード表示サンプル](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1111613/f828435e-8cae-e8df-f718-5bfb067e764b.png '図01：バーコード表示サンプル')

# 最後に
本稿の内容は、実案件にて、[monaca](https://ja.monaca.io/) によるプライベートアプリとして開発した際の記録をベースにしたものとなります。

本稿のサンプルコードはクライアント様の許可を得た上で、プロトタイプ完成時のコード中、コアとなる部分のみを再編集し、簡単なテストの後、掲載させていただきました。

誰かのお役に立てたなら幸いです。

# [参考・引用]
- [[Javascript + CSS] JAN-13のコード番号からバーコードを生成し表示する（１）～仕様の確認からロジックアウトラインの構築](https://qiita.com/STSHISHO/items/50803be3c4b6042dd2fd)
- [JISX0507:2004 バーコードシンボル－ＥＡＮ／ＵＰＣ－基本仕様](https://kikakurui.com/x0/X0507-2004-01.html)
- [JANバーコードの仕様](https://qiita.com/Dolphin_0809/items/ac087aadcc43d15a01e2)
- [SameSite Cookie の説明](https://web.dev/samesite-cookies-explained/?utm_source=devtools)
- [【CSS】flexboxの要素を任意の位置で改行する方法](https://dezanari.com/css-flexbox-intended-wrap/)