Vue.jsでFormの各要素をComponent化する際の覚え書き

Vue.jsでFormの各要素をComponent化する際の覚え書き:

Vue.jsでFormの各フィールドをcomponent化する際の実装例です。

コンポーネント化することで、デザイン、バリデーションなどが全Formで共通化できます。

流行りのAtomicデザインを適応する際などに良いかと思います。

以下コードは、全てこちらのリポジトリにあります。
https://github.com/kawamataryo/Vue-atomic-form


Input

まず基本のinputのcomponet実装例。

フィールドの値は、@inputで変更を監視して、$emit'input'を利用し親コンポーネントのv-modelに値を送っています。
updateValueが引数で受け取るeにはeventが入っているので、そこからtaget.valueで値を取得してemitで親コンポーネントに渡しています。

MyInput.vue
<template> 
  <input 
    :type="type" 
    :name="name" 
    :value="value" 
    :placeholder="placeholder" 
    @input="updateValue" 
  /> 
</template> 
 
<script> 
export default { 
  name: "MyInput", 
  props: { 
    value: { type: String, require: true }, 
    type: { type: String, require: true }, 
    name: { type: String, require: true }, 
    placeholder: { type: String, require: false } 
  }, 
  methods: { 
    updateValue: function(e) { 
      this.$emit("input", e.target.value); 
    } 
  } 
}; 
</script> 
呼び出し元は、、

App.vue
<MyInput 
  v-model="sampleForm.text" 
  placeholder="サンプル" 
  name="sample-input" 
  type="text" 
></MyInput> 


Textarea

基本的にinputと同様の実装です。

typeを外してrowsとcolsを追加しました。

MyTextarea.vue
<template> 
  <textarea 
    :name="name" 
    :value="value" 
    :placeholder="placeholder" 
    :rows="rows" 
    :cols="cols" 
    @input="updateValue" 
  ></textarea> 
</template> 
 
<script> 
export default { 
  name: "MyTextarea", 
  props: { 
    value: { type: String, require: true }, 
    name: { type: String, require: true }, 
    placeholder: { type: String, require: false }, 
    rows: { type: Number, require: false }, 
    cols: { type: Number, require: false } 
  }, 
  methods: { 
    updateValue: function(e) { 
      this.$emit("input", e.target.value); 
    } 
  } 
}; 
</script> 
呼び出し元は

App.vue
<MyTextarea 
  v-model="sampleForm.textarea" 
  placeholder="サンプル" 
  name="sample-textarea" 
  :rows="10" 
  :cols="50" 
></MyTextarea> 


Radio Button

radioボタンの場合は、要素をoptionsとして配列で渡すようにしています。

あとはコンポーネント内でv-foroptionsを回し、内部のvaluelabelを表示します。
updateValueは同様です。

MyRadio
<template> 
  <fieldset> 
    <template v-for="(option, index) in options"> 
      <label :key="index"> 
        <input 
          type="radio" 
          :name="name" 
          :value="option.value" 
          @change="updateValue" 
        />{{ option.label }} 
      </label> 
    </template> 
  </fieldset> 
</template> 
 
<script> 
export default { 
  name: "MyRadio", 
  props: { 
    value: { type: String, require: true }, 
    options: { type: Array, require: true }, 
    name: { type: String, require: true }, 
  }, 
  methods: { 
    updateValue: function(e) { 
      this.$emit("input", e.target.value); 
    } 
  } 
}; 
</script> 
呼び出し元はoptionsに表示したいラベルと値を持つオブジェクトの配列をoptionsとして渡します。

App.vue
// 変数optionsには[{label: "hoge", value: "1"}, {label: "fuga", value: "2"}]のようなオブジェクトの配列を設定 
<MyRadio 
  v-model="sampleForm.radio" 
  name="sample-radio" 
  :options="options" 
></MyRadio> 


Select

基本はRadioボタンと同様にoptionsで値とラベルのオブジェクトの配列を渡しv-forで表示します。

ただ、selectの場合、初期で選択されているので、何も変更しない場合@changeでのupdateValueが呼ばれず、値が空になってしまいます。

それを防ぐため、mouted()で初期選択時の値を$emitで送っています。

select.vue
<template> 
  <fieldset> 
    <select :name="name" @change="updateValue"> 
      <template v-for="(option, index) in options"> 
        <option :value="option.value" :key="index"> 
          {{ option.label }} 
        </option> 
      </template> 
    </select> 
  </fieldset> 
</template> 
 
<script> 
export default { 
  name: "MySelect", 
  props: { 
    value: { type: String, require: true }, 
    options: { type: Array, require: true }, 
    name: { type: String, require: true }, 
  }, 
  methods: { 
    updateValue: function(e) { 
      this.$emit("input", e.target.value); 
    } 
  }, 
  mounted() { 
    this.$emit("input", this.options[0].value); 
  } 
}; 
</script> 
呼び出し元はradioボタンの時と同様にoptionsで表示したい値、ラベルを渡します。

App.vue
```App.vue 
// 変数optionsには[{label: "hoge", value: "1"}, {label: "fuga", value: "2"}]のようなオブジェクトの配列を設定 
<MySelect 
  v-model="sampleForm.select" 
  name="sample-select" 
  :options="options" 
></MySelect> 


Checkbox

これが一番苦労しました。

一見ラジオボタンと同様なのですが、復数選択可能なので、配列で値を返す必要があります。
datavaluesを持ちupdateValueでは、チェック状態を判定してそのvaluesに値を追加、削除を行っています。

そしてemitで返すのはdataとして持っているvaluesです。

MyCheckbox.vue
<template> 
  <fieldset> 
    <template v-for="(option, index) in options"> 
      <label :key="index"> 
        <input 
          type="checkbox" 
          :name="name" 
          :value="option.value" 
          @change="updateValue" 
        />{{ option.label }} 
      </label> 
    </template> 
  </fieldset> 
</template> 
 
<script> 
export default { 
  name: "MyCheckbox", 
  props: { 
    options: { type: Array, require: true }, 
    name: { type: String, require: true }, 
  }, 
  data() { 
    return { 
      values: [] 
    }; 
  }, 
  methods: { 
    updateValue: function(e) { 
      if (e.target.checked) { 
        this.values.push(e.target.value); 
      } else { 
        this.values = this.values.filter(v => v !== e.target.value); 
      } 
      this.$emit("input", this.values); 
    } 
  } 
}; 
</script> 
呼び出し元はこちらです。

App.vue
// 変数optionsには[{label: "hoge", value: "1"}, {label: "fuga", value: "2"}]のようなオブジェクトの配列を設定 
<MyCheckbox 
  v-model="sampleForm.checkbox" 
  name="sample-checkbox" 
  :options="options" 
></MyCheckbox> 


Form全体

結果としてこのようなcomponentが集約されたフォームができます。

App.vue
<template> 
  <div id="app"> 
    <form action=""> 
      <MyLabel>InputText</MyLabel> 
      <MyInput 
        v-model="sampleForm.text" 
        placeholder="サンプル" 
        name="sample-input" 
        type="text" 
      ></MyInput> 
      <MyLabel>TextArea</MyLabel> 
      <MyTextarea 
        v-model="sampleForm.textarea" 
        placeholder="サンプル" 
        name="sample-textarea" 
        :rows="10" 
        :cols="50" 
      ></MyTextarea> 
      <MyLabel>RadioButton</MyLabel> 
      <MyRadio 
        v-model="sampleForm.radio" 
        name="sample-radio" 
        :options="options" 
      ></MyRadio> 
      <MyLabel>Checkbox</MyLabel> 
      <MyCheckbox 
        v-model="sampleForm.checkbox" 
        name="sample-checkbox" 
        :options="options" 
      ></MyCheckbox> 
      <MyLabel>Select</MyLabel> 
      <MySelect 
        v-model="sampleForm.select" 
        name="sample-select" 
        :options="options" 
      ></MySelect> 
      <MyBtn @click="sendForm">send</MyBtn> 
    </form> 
  </div> 
</template> 
 
<script> 
import MyInput from "./components/MyInput"; 
import MyTextarea from "./components/MyTextarea"; 
import MyRadio from "./components/MyRadio"; 
import MyCheckbox from "./components/MyCheckbox"; 
import MySelect from "./components/MySelect"; 
import MyBtn from "./components/MyBtn"; 
import MyLabel from "./components/MyLabel"; 
 
export default { 
  name: "app", 
  components: { 
    MyTextarea, 
    MyInput, 
    MyRadio, 
    MyCheckbox, 
    MySelect, 
    MyBtn, 
    MyLabel 
  }, 
  data() { 
    return { 
      sampleForm: { 
        text: "", 
        radio: "", 
        select: "", 
        textarea: "", 
        checkbox: [] 
      }, 
      options: [ 
        { 
          label: "fizz", 
          value: "3" 
        }, 
        { 
          label: "buzz", 
          value: "5" 
        }, 
        { 
          label: "fizzBuzz", 
          value: "15" 
        } 
      ] 
    }; 
  }, 
  methods: { 
    sendForm() { 
      // formデータの送信処理 
    } 
  } 
}; 
</script> 


参考

Vue.jsでForm部品をComponent化する
https://qiita.com/wakame_isono_/items/611e51ff965d698bbc7c

コメント

このブログの人気の投稿

投稿時間:2021-06-17 22:08:45 RSSフィード2021-06-17 22:00 分まとめ(2089件)

投稿時間:2021-06-20 02:06:12 RSSフィード2021-06-20 02:00 分まとめ(3871件)

投稿時間:2021-06-17 05:05:34 RSSフィード2021-06-17 05:00 分まとめ(1274件)