kintone UI Component v1で作った要素にsetAttributeする

# はじめに

[kintone UI Component](https://kintone-ui-component.netlify.app/)がベータ版から正式版(v1)になって、とてもシンプルにいい感じになりました。
便利に使わせてもらっております！

```js
import { Text } from 'kintone-ui-component'

kintone.events.on(['app.record.edit.show', 'app.record.create.show'], (event) => {
  const text = new Text({ value: '2021-04-20' })
  kintone.app.record.getHeaderMenuSpaceElement().appendChild(text)
})
```

ただ、やっぱり使っていると「ここは`<input type="date">`にしたいなぁ」とか、UI Componentで対応してない属性を使いたい場合とかあるんですよ。
そのやり方がちょっぴり複雑だったのでメモしておきます。

# TL;DR

```js
kintone.events.on(['app.record.edit.show', 'app.record.create.show'], (event) => {
  const text = new Text({ value: '2021-04-20' })
  kintone.app.record.getHeaderMenuSpaceElement().appendChild(text) // appendしないと配下はnull
  setTimeout(() => {
    // レンダリング終わるまで待ってからsetAttribute
    text.querySelector('input').setAttribute('type', 'date')
  }, 1000)
})
```

# JSで普通にHTML要素を作成する場合
[setAttribute](https://developer.mozilla.org/ja/docs/Web/API/Element/setAttribute)を使って色々と属性を設定できます。

```js
kintone.events.on(['app.record.edit.show', 'app.record.create.show'], (event) => {
  const text = document.createElement('input')
  text.value = '2021-04-20'
  text.setAttribute('type', 'date')
  kintone.app.record.getHeaderMenuSpaceElement().appendChild(text)
})
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24333/96d53365-310a-1436-11c4-97ef051edc57.png)


# kintone UI Componentの場合

## 失敗例1

こうすれば行けそうな気がしますが、まったくダメです。

```js
kintone.events.on(['app.record.edit.show', 'app.record.create.show'], (event) => {
  const text = new Text({ value: '2021-04-20' })
  text.setAttribute('type', 'date')
  kintone.app.record.getHeaderMenuSpaceElement().appendChild(text)
})
```

UI Componentで作ったtext要素は、実はけっこう複雑なHTML構造をしてて、
こんな感じで`<div>` `<span>`がたくさんネストされてます。
一番外側のタグは`<kuc-text>`みたいな[Web Components](https://developer.mozilla.org/ja/docs/Web/Web_Components)のカスタムタグでした！

```html
<kuc-text>
  <style>
    (略)
  </style>

  <div class="kuc-text__text" style="width: 192.5px;">
    <label class="kuc-text__text__label" for="xxxxxxxx-xxxx-4xxx-xxxx-xxxxxxxxxxxx-label" hidden="">
      <span class="kuc-text__text__label__text"></span
      ><span class="kuc-text__text__label__required-icon" hidden="">*</span>
    </label>
    <div class="kuc-text__text__input-form">
      <div class="kuc-text__text__input-form__prefix-outer">
        <span class="kuc-text__text__input-form__prefix-outer__prefix" hidden=""></span>
      </div>
      <div class="kuc-text__text__input-form__input-outer" style="width: 100%;">
        <input
          class="kuc-text__text__input-form__input-outer__input"
          type="text"
          id="xxxxxxxx-xxxx-4xxx-xxxx-xxxxxxxxxxxx-label"
          placeholder=""
          textalign="left"
          aria-required="false"
          aria-invalid="false"
          aria-describedby="xxxxxxxx-xxxx-4xxx-xxxx-xxxxxxxxxxxx-error"
        />
      </div>
      <div class="kuc-text__text__input-form__suffix-outer">
        <span class="kuc-text__text__input-form__suffix-outer__suffix" hidden=""></span>
      </div>
    </div>
    <div class="kuc-text__text__error" role="alert" id="xxxxxxxx-xxxx-4xxx-xxxx-xxxxxxxxxxxx-error" hidden=""></div>
  </div>
</kuc-text>
```

developer Networkの記事[kintoneライクなスタイルシートの利用](https://developer.cybozu.io/hc/ja/articles/203302214)にある構造と似てます。これを意識せずにパッと使えるようにしてくれているライブラリということですね。

上に書いたJSだと、`<kuc-text type="date">`になるだけで、何の意味もないわけですね。
子要素の中心部`<input>`を狙って`setAttribute`してやらないといけません。

## 失敗例2

`<input>`要素はコンポーネント配下に1つしかないので、これで行けそうな気がしますが、やっぱダメです。

```js
kintone.events.on(['app.record.edit.show', 'app.record.create.show'], (event) => {
  const text = new Text({ value: '2021-04-20' })
  text.querySelector('input').setAttribute('type', 'date')
  kintone.app.record.getHeaderMenuSpaceElement().appendChild(text)
})
```

この場合、`text.querySelector('input')`の結果が`null`になっちゃうんですね。
一方、ブラウザのコンソールで`text.querySelector('input')`を直接走らせると、ちゃんと動く。
レンダリングの時間が少しかかるので、それを待たねばならぬのです。

## 失敗例3

1秒ほど待機してみることにしましょう。

```js
kintone.events.on(['app.record.edit.show', 'app.record.create.show'], (event) => {
  const text = new Text({ value: '2021-04-20' })
  setTimeout(() => {
    text.querySelector('input').setAttribute('type', 'date')
    kintone.app.record.getHeaderMenuSpaceElement().appendChild(text)
  }, 1000)
})
```

これも、結果はダメ。
実は`new Text()`しただけでは、いくら待っても配下の要素は`null`のままで、**`appendChild()`で表示中のページに追加して初めて、配下のレンダリングが始まる**ようです。このあたりの背景は詳しくないんですが、パフォーマンス上の理由かと思われます。

## 成功例

`appendChild()`してから待機することで、うまく動きました！

```js
kintone.events.on(['app.record.edit.show', 'app.record.create.show'], (event) => {
  const text = new Text({ value: '2021-04-20' })
  kintone.app.record.getHeaderMenuSpaceElement().appendChild(text) // appendしないと配下はnull
  setTimeout(() => {
    // レンダリング終わるまで待ってからsetAttribute
    text.querySelector('input').setAttribute('type', 'date')
  }, 1000)
})
```

この「1秒待ち」というのはあてずっぽうなので、PCのスペックなどによってはもっと待たなければいけなかったり、もっと短くて十分な場合もあります。

本当にちゃんとやるなら、[MutationObserver](https://developer.mozilla.org/ja/docs/Web/API/MutationObserver)使ってしっかりと監視することになるのでしょうが、そこまではオーバーキルだと思うので、まぁ「ちょっと待機」で十分かなーと個人的には思います。

# サイボウズさんへ

こんな風に、カスタムアトリビュートを自由に設定できるようになると嬉しいっす！
循環参照ですが、[Issue送ってみました。](https://github.com/kintone-labs/kintone-ui-component/issues/518)

```js
new Text({ value: '2021-04-20', attribute: { type: 'date' } })
```

でも「ラベルのattribute」とか「途中のdivのattribute」とか
言い出すと切りがないから難しいかなぁ。。。

追記）
やはり[カスタムAttributeは無理](https://github.com/kintone-labs/kintone-ui-component/issues/518#issuecomment-846669700)ってことでした。
Date系のコンポーネントはそのうち出るらしいので、
そのあたりが充実したら、setAttributeする機会も減るでしょうね。


では今日はこの辺で～。またお会いしましょう :sunglasses: