フォームブリッジのJavascriptカスタマイズでハマったこと、工夫したこと

初めてフォームブリッジをカスタマイズすることになり、色々とハマったので備忘録として。

 ### サブテーブルフィールドが、スマホ用フィールドに設定されているかどうかで、fb.getTableElementsByCode関数の返り値が変わる

スマホ用フィールドに設定されていないサブテーブルの場合は、
スマホ表示でfb.getTableElementsByCode関数を使用すると全行分の値を取得できます。
しかし、スマホ用フィールドに設定されていて且つスマホ表示した場合、1行目の値しか取得できません。

対策としては、組み込み関数の使用は諦めて、突然の仕様変更のリスクはありますがclassNameから取得することにしました。
（data-vv-さんがいなくなると詰みます。）
```JavaScript
export function getTableElementsBySelector(tableCode, fieldCode) {
  const tbodyElement = document.querySelector(`[data-vv-name="${tableCode}"]`).getElementsByTagName("tbody")[0];
  if (!tbodyElement) {
    throw new Error("サブテーブル要素が見つかりません。フィールドコードに誤りがあるか、FormBridgeの仕様変更が発生した可能性があります。");
  }

  const rowLength =  tbodyElement.getElementsByTagName("tr").length;
  if (!rowLength) {
    throw new Error("サブテーブル内の行要素が見つかりません。フィールドコードに誤りがあるか、FormBridgeの仕様変更が発生した可能性があります。");
  }

  let tds = [];
  
  for (let i = 0; i < rowLength; i++) {
    const div = document.querySelector(`[data-vv-name="${tableCode}-${i}-${fieldCode}"]`);
    const td = div.parentNode;
    if (!td) {
      throw new Error("サブテーブル内のセル要素が見つかりません。フィールドコードに誤りがあるか、FormBridgeの仕様変更が発生した可能性があります。");
    }
    tds.push(td);
  }

  return tds;
}
```
（kintoneと違ってHTML内にフィールドコードが存在していたので、まだやりやすかった）

### イベント外でカスタマイズによってフィールドに値をセットしたい場合、保存時に一工夫必要
オリジナルのボタンを設置し、クリックすることによってフィールドに値をセットしようとしました。
[ドキュメント](https://formbridge.kintoneapp.com/help/customize)のfb.getElementByCode関数の例に倣って実装してみると、
フィールドには値がセットされているように見えますが、**保存してみるとkintoneには値が入りません。**
（確認画面を挟んでいる場合は、確認画面で値が空に）

例の実装だけではDOM上に値が表示されているだけなので、**保存時にstate.recordに入れてあげる必要がある**ようです。
fb.events.form.submitやfb.events.form.confirmといったイベントでDOMからstate.recordに値を移すようにしました。
（ここではサブテーブル版のみ紹介します）

```JavaScript
// function.js
export function subtableDomToRecord(state) {
  const config = {
    subtables: [
      {
        tableCode: "<tableCode1>",
        fields: ["field1", "field2"]
      }
    ]
  };

  config.forEach(subtableSetting => {
    const tableCode = subtableSetting.tableCode;

    if (!state.record[tableCode]) {
      console.error("存在しないサブテーブル", tableCode);
    } else {
      const rowsInRecordObject = state.record[tableCode].value;
      rowsInRecordObject.forEach((row, rowIndex) => {
        subtableSetting.fields.forEach(fieldCode => {
          const elementValue = fb.getTableElementsByCode(tableCode, fieldCode)[rowIndex].getElementsByTagName("input")[0].value;

          if (!row.value[fieldCode]) {
            console.error("存在しないテーブル内フィールドコード", fieldCode);
          } else {
            row.value[fieldCode].value = elementValue;
          }
        });
      });
    }
  });
}

// main.js
fb.events.form.submit = [function(state) {
  subtableDomToRecord(state)
  return state
}];
```

### カスタマイズで日時フィールドにISO8601形式で値をセットした時、他のフィールドで保存時にバリデーションエラーが発生するとYYYY-MM-DD HH:mm形式に変換される
先に書いた保存時の一工夫の話と関連します。
変換された後、そのまま先の工夫で紹介した転記処理を実行すると、
YYYY-MM-DD HH:mm形式の値を日時フィールドにセットして、ISO8601で入れろとエラーが出ます。

対策としてはボタンクリック後に回答ボタンを押しているかどうかを判定し、転記処理を行うかどうか制御しました。

### CSSでフィールドDOMを非表示にすると、fb.getElementByCode関数で要素の取得やセットができなくなる

利用者にはフィールドを見せたくないが、裏でカスタマイズによって値をセットしたいといった場合に、
kintoneと同じ感覚でやろうとするとうまくいかずにハマります。
フォームブリッジの設定でフィールドを非表示にした時も同様です。

対策としては、苦肉の策ですがグローバルな場所にセットしたい値を置いておき、保存時にstate.recordへ転記するような形を取りました。
```JavaScript
//TempRecord.js
export default class TempRecord {
  constructor() {
    this.record = {
      [fieldCode1]: {
        value: ""
      },
      [fieldCode2]: {
        value: ""
      },
     //....
  }

  get() {
    return this.record;
  }

  getFieldValue(fieldCode) {
    return this.record[fieldCode].value;
  }

  update(newRecord) {
    Object.keys(newRecord).forEach(field => {
      this.record[field] = {value: newRecord[field].value};
    });
  }

  delete() {
    Object.keys(this.record).forEach(field => {
      this.record[field] = {value: ""};
    });
  }
}

//function.js
export function tempToStateRecord(newRecord, state) {
  Object.keys(newRecord).forEach(field => {
    state.record[field] = {value: newRecord[field].value};
  });
}

//main.js
fb.events.form.submit = [function(state) {
  const temp = tempRecord.get()
  tempToStateRecord(temp, state)

  return state
}]
```

**しかし、サブテーブル内フィールドは設定やCSSで非表示にしても、fb.getTableElementsByCode関数で取得や値のセットが可能です。**
サブテーブルフィールドの値を登録・更新する際には必ず全フィールドをパラメータに含める必要があるというkintoneの仕様の為かなと思っています。

###  テーブル行の追加イベント（fb.events.fields.{fieldCode}.add）は、発火時点では追加行が存在しない
まだDOMが生成されていないようです。
苦肉の策ですがとりあえず**settimeoutで遅延させる**ことで対処しています。

```JavaScript
// main.js
fb.events.fields[tableCode].add = [function(state) {
  setTimeout(function() {
  // やりたいこと
  }, 100);
  
  return state;
}];

```

---------------------------------------
以上苦肉の策特集でした。
※ソースコードは記事用に一部抜粋・編集している為動作の確認をしていません。ご了承ください。