Elm備忘録 ~CustomElementつかっていこうぜ[Popover編]~


# Elm備忘録 ~CustomElementつかっていこうぜ[Popover編]~

## 問題
- Elmでポップオーバを使おうとおもうとModelにPopoverの状態を保持するためのフィールドがめっちゃ増えんねん。
  - https://getbootstrap.com/docs/4.1/components/popovers/
- 俺は嫌やねん。
  - CSSでやるという手段もあるけどまあ置いときましょう。今はBootstrapのやつがいいんです。

```elm
type alias Model = {
  titlePopoverState: Popover.State
  , columnDescribePopoverState1: Popover.State
  , columnDescribePopoverState2: Popover.State
  , columnDescribePopoverState3: Popover.State
  , columnDescribePopoverState4: Popover.State
  , columnDescribePopoverState5: Popover.State
}
```

## 解
- PopoverをCustomElementにしてElmから呼び出すぞ！

### PopoverをCustomElementにラップする

- bootstrapの本家jsを使うとjqueryとお付き合いが発生するので`https://thednp.github.io/bootstrap.native`を使っていくスタイルです。
- CustomElementのお作法にそいながら`connectedCallback`でPopoverを表示するロジックを書いて`disconnectedCallback`で後片付けロジックを書きます。
- `customElements.define('x-popover', PopoverElement);`のx-popoverが要素名になり
- `this.getAttribute(アトリビュート名);`で指定しているアトリビュートが要素のアトリビュートとして利用できます
  - *`data-placement`がポップオーバーがどこに出るかを指定できるアトリビュートです。 `top`, `left`, `right`, `bottom`が指定できます。*
- いろいろ端折ってますがWebpackでバンドルやで。とか脳内補完してください

```javascript

const Popover = require("bootstrap.native").Popover;

/**
 * TODO クォート統一無法地帯
 * https://thednp.github.io/bootstrap.native/
 */
class PopoverElement extends HTMLElement {
  attributeChangedCallback(attr, oldValue, newValue) {

  }

  disconnectedCallback() {
    if (this.removeCloseHandler) {
      this.removeCloseHandler();
    }
  }

  isSetUped() {
    return !!this.instance;
  }

  setUpBootstrapPopover(popoverparent) {
    popoverparent.className = "fas fa-info-circle color-blue ml-5px";
    const messageAttr = this.getAttribute('data-message');
    const titleAttr = this.getAttribute('data-title');
    const placementAttr = this.getAttribute('data-placement');
    popoverparent.setAttribute("data-content", messageAttr);
    popoverparent.setAttribute("data-title", titleAttr);

    this.instance = new Popover(popoverparent, {
      template:  `<div class="popover" role="tooltip">
        <div class="arrow"></div>
        <h3 class="popover-header">${titleAttr}</h3>
        <div class="popover-body">${messageAttr}</div>
        </div>`,
      placement: placementAttr
    });

    this.removeCloseHandler = () => {
      // HOGEHOGE
    }
  }

  connectedCallback() {
    const popoverparent = document.createElement('span');
    if (this.isSetUped()) {
      return;
    }
    this.setUpBootstrapPopover(popoverparent);
    this.appendChild(popoverparent);
  }
}

customElements.define('x-popover', PopoverElement);

```


### Elmで呼び出しを定義する

- `Html.node "x-popover" [] []`でCustomElementを呼び出すようにしてやる
- `Attr.attribute "attr" "value"`でアトリビュートを指定する
- PlacementをCustomElementで宣言するのはElmって感じでいいですね。

```elm

module Popover exposing (view, Config, Placement(..))

import Html
import Html.Attributes as Attr

type Placement
    = Top
    | Bottom
    | Right
    | Left


placementToString : Placement -> String
placementToString pl =
    case pl of
        Top ->
            "top"

        Bottom ->
            "bottom"

        Right ->
            "right"

        Left ->
            "left"


type alias Config =
    { title : String
    , message : String
    , placement : Placement
    }



view : Config -> Html.Html msg
view { title, message, placement } =
    Html.node "x-popover" -- ここがミソ
        [ Attr.attribute "data-message" message
         , Attr.attribute "data-title" title
         , Attr.attribute "data-placement" (placementToString placement)
         ]
        []
```

### 呼び出す側

- 下のような感じで呼び出せる

```elm
import Popover

view [] [
  Html.span [] 
    [ 
      Html.text "むずい用語や"
      , Popover.view { 
          message = "ウンタラカンタラ"
          , title = "この用語はな..."
          , placement = Popover.Top } 
    ]
]
```


## 〆
- CustomElementによってPopoverの状態をCustomElementに閉じ込めてElmから知らん顔できるようになった
- Popoverに限らずElmで状態を管理する価値はないぜ！ってなったものをCustomElementにしてしまえばElmで管理する状態は減る
- 当方DatePickerとかCustomElementにしたりしました。
  - 今回でなかったCustomElement -> Elmへのイベント通知とかDatePickerを題材に紹介しちゃいたい
- ElmのModelで状態を持つ価値がないなぁというときにはこういうのもありないんじゃない？