Vue.jsでRadioButtonGroupコンポーネントを自作する

#前提

## なにがしたいの？

Vue.jsを利用していて、単品RadioButtonでことが済んでいたんだけれど(サーバーサイドテンプレートエンジンに対して、UIパーツを作る形でVue.jsを使っているプロジェクトで、formのpostとかjsでやってないし)
どうしても、RadioButtonGroupが欲しくなった。

## なんで？

- RadioButtonパーツのHTMLの構成を変えたくなかった

```HTML:こんなカンジ
<label class="radio">
 <input type="radio">
 <span class="radio-label">ラベル表示</span>
 <span class="radio-icon">まるポチ的なやつ表示用</span>
</label>
```


- まるポチではない見た目でUIを作ろうとしたら、labelに状態classをつけるために、あるモードが選択された後、他のモードを選択されることによって、別の子がオンになったよ～な通知が必要になった。

- とはいえ、その通知はラジオボタンの話なので、マウント元にその切替用のことをいろいろ書きたくなかった。

## 他に言いたいことは

- でも、ラジオボタン自体はマウント元に書きたい。

つまり、こんなカンジにしたい。

```HTML
<div hoge="わたしはマウント元">
   <radio-button-group>
      <v-radio-button props="props" />
      <v-radio-button props="props" />
      <v-radio-button props="props" />
   </radio-button-group>
</div>
```

。。。なんか、文章がへたくそね。

# で、やったこと。

## いろいろ検索する

- Vue.jsでラジオボタン [radio button]
https://noumenon-th.net/programming/2019/05/31/radio/

- Vue.jsのScoped Slotを使って脱初心者なラジオボタンコンポーネントを作るまで
https://qiita.com/simezi9/items/702e2bd066eec352ef72

- 上記でも紹介されてますが、MaterialUIやVuetifyの実装もちょっと見てみました。
…さらっとだけでよくわかってないけど。

## でもね
なんか、思ったようなものにならず。（そんな難しいことできないし。）
自分なりの実装をしてみました。


# ここからが、本当にやったこと

## 作成前提

そもそも、こんなRadioButtonコンポーネントがあります。

```vue:RadioButton
<template>
  <label class="radio">
    <input type="radio"
      :name="name"
      :value="value"
      :required="is_required"
      v-model="selectedItem"
    />
    <span class="radio-icon"></span>
    <span class="radio-button-label">{{ label }}</span>
  </label>
</template>
<script>
export default {
  name: 'ElRadioButton',
  props: {
    name: { type: String },      //name属性値
    value: { type: String },      //value属性値
    is_required: { type: Boolean },      //必須項目か
    is_defSelected: { type: Boolean },      //初期状態でチェックされているか
    label: { type: String }      //表示項目名
  },
  computed: {
    selectedItem: {
      get: function() {
        if ( this.is_defSelected) {
          return this.value
        }
      },
      set: function(val) {
        this.$emit('change', val)
      }
    },
  }
}
</script>
```

で、シンプルにこれをマウントするには

```vue:parent
<div>
  <el-radio-button
     v-for="(menu, index) in menus"
     :key="index"
     name = "menutype"
     :value = "menu.name"
     :is_def-selected = "最初に選択されているものの条件式でboolean値"
     :is_required = false
     :label = "menu.label"
     @change = "changeRadio"
  ></el-radio-button>
</div>

<script>
import ElRadioButtonGroup from '@/components/element/RadioButtonGroup'
import ElRadioButton from '@/components/element/RadioButton'

export default {
  name: 'TestPage',
  components: {ElRadioButtonGroup, ElRadioButton},
  data() {
    return {
      menus: ラジオボタンの中身が記載されたJSON,
    }
  },
  methods: {
    changeRadio(d) {
      // d: 現在選択されているradioButtonのvalue
    }
  }
}
</script>

```

## で、なにをもっても、RadioButtonGroupの作成

```vue:RadioButtonGroup
<template>
  <div>
    <slot></slot>
  </div>
</template>

<script>
export default {
  name: 'ElRadioButtonGroup'
}
</script>

```

実際のRadioButtonはマウント元で記載する(propsとして、データを引き継がない)ので、`<slot>`を準備します。

で、親コンポーネントにマウント

```vue:parent
<template>
  <div>
    <el-radio-button-group>
      <el-radio-button
        v-for="(menu, index) in menus"
        :key="index"
        name = "menutype"
        :value = "menu.name"
        :is_def-selected = "最初に選択されているものの条件式でboolean値"
        :is_required = false
        :label = "menu.label"
        @change = "changeRadio"
      ></el-radio-button>
    </el-radio-button-group>
  </div>
</template>

<script>
import ElRadioButtonGroup from '@/components/element/RadioButtonGroup'
import ElRadioButton from '@/components/element/RadioButton'

export default {
  name: 'Parent',
  components: {ElRadioButtonGroup, ElRadioButton},
  data() {
    return {
      menus: ラジオボタンの中身が記載されたJSON,
    }
  },
  methods: {
    changeRadio(d) {
      // d: 現在選択されているradioButtonのvalue
    }
  }
}
</script>

```

# RadioButtonGroupにも、今なにが選択されているのか教える

RadioButtonGroup⇒それぞれのRadioButtonにそれぞれどの状態になっていて欲しいか通知するため、RadioButtonGroupに、現状を教える

```vue:parent
<template>
  <div>
    <el-radio-button-group :value="menu_now">
      <el-radio-button
        v-for="(menu, index) in menus"
        ~省略~
        @change = "changeRadio"
      ></el-radio-button>
    </el-radio-button-group>
  </div>
</template>

<script>
~省略~
  data() {
    return {
      menus: ラジオボタンの中身が記載されたJSON,
      menu_now: デフォルトのメニューのvalueが入るようにする
    }
  },
  methods: {
    changeRadio(d) {
      this.menu_now = d
    }
  }
}
</script>

```

RadioButtonGroupからRadioButtonへ通達

```vue:RadioButtonGroup
<template>
  ~省略~
</template>

<script>
export default {
  props: ['value'],
  watch: {
    value(nv) {
      this.$children.forEach(item => {
        if (item.value == nv) {
          item.is_selected = true
        } else {
          item.is_selected = false
        }
      })
    }
  },
}
</script>

```

RadioButtonで受け取って、class名追加

```vue:RadioButton
<template>
  <label class="radio" :class="{ 'is_selected' : is_selected}">
~省略~
  </label>
</template>

<script>
~省略~
  data() {
    return {
      is_selected: this.is_defSelected　//最初は初期値の設定を受け取る
    }
  },
  computed: {
    selectedItem: {
      get: function() {
        if ( this.is_selected ) {　//デフォルト設定ではなく、変更通知の受け取りフラグをもとにする
          return this.value
        }
      },
~省略~

</script>
```

#まとめ

RadioButtonのチェック状態(is_defSelected)をBoolean値で持たせたことが、不幸のはじまりのような気がしました。