読み込み中 %

アイコン

【サンプルあり】Vue.jsで各Input属性をコンポーネント化する

taikishiino
2020/06/29

Formの各Input属性の部品をコンポーネント化してみたので、メモとして記事にしました。

テキスト

text.vue
<template>
  <input
    type="text"
    :value="value"
    :placeholder="placeholder"
    @input="changeValue"
    :disabled="disabled"
  />
</template>

<script>
export default {
  props: {
    value: {
      type: String,
      required: true
    },
    type: {
      type: String,
      required: true
    },
    placeholder: {
      type: String,
      required: false
    },
    disabled: {
      type: Boolean,
      default: false,
      required: false
    }
  },
  methods: {
    changeValue: function(event) {
      this.$emit("input", event.target.value);
    }
  }
};
</script>
親コンポーネント
<template>
  <InputText
    v-model="text"
    placeholder="InputText"
  />
</template>

export default {
  data() {
    return {
      text: ""
    };
  }
}

チェックボックス

checkbox.vue
<template>
  <div>
    <template v-for="(option, index) in options">
      <label :key="index">
        <input
          type="checkbox"
          :name="name"
          :value="option.value"
          @change="updateValue"
          :disabled="disabled"
        />
        {{ option.label }}
      </label>
    </template>
  </div>
</template>

<script>
export default {
  props: {
    options: {
      type: Array,
      required: true
    },
    name: {
      type: String,
      required: true
    },
    disabled: {
      type: Boolean,
      default: false,
      required: false
    }
  },
  data() {
    return {
      values: []
    };
  },
  methods: {
    updateValue: function(event) {
      if (event.target.checked) {
        this.values.push(event.target.value);
      } else {
        this.values = this.values.filter(v => v !== event.target.value);
      }
      this.$emit("input", this.values);
    }
  }
};
</script>
親コンポーネント
<template>
  <InputCheckbox
    v-model="checkbox"
    name="InputCheckbox"
    :options="INPUT_CHECKBOX_OPTION"
  />
</template>

export default {
  data() {
    return {
      checkbox: []
    };
  }
}

INPUT_CHECKBOX_OPTION: [
  { label: "hoge", value: 1 },
  { label: "huga", value: 2 }
]

セレクトボックス

select.vue
<template>
  <div>
    <select :name="name" @change="updateValue" :disabled="disabled">
      <template v-for="(option, index) in options">
        <option :value="option.value" :key="index">
          {{ option.label }}
        </option>
      </template>
    </select>
  </div>
</template>

<script>
export default {
  props: {
    name: {
      type: String,
      required: true
    },
    value: {
      required: true,
      validator: prop => typeof prop === "string" || typeof prop === "number"
    },
    options: {
      type: Array,
      required: true
    },
    disabled: {
      type: Boolean,
      default: false,
      required: false
    }
  },
  methods: {
    /**
     * @return {String}
     */
    updateValue: function(event) {
      this.$emit("input", event.target.value);
    }
  },
  mounted() {
    this.$emit("input", this.options[0].value);
  }
};
</script>
親コンポーネント
<template>
  <InputSelect
    v-model="formData.select"
    name="InputSelect"
    :options="INPUT_SELECT_OPTION"
  />
</template>

export default {
  data() {
    return {
      select: 0
    };
  }
}

INPUT_SELECT_OPTION: [
  { label: "選択してください", value: 0 },
  { label: "hoge", value: 1 },
  { label: "huga", value: 2 }
]

ラジオボタン

radio.vue
<template>
  <div>
    <div 
      v-for="(option, index) in options"
      :key="index"
    >
      <input
        type="radio"
        :id="`${name}-${index}`"
        :checked="value === option.value"
        :value="option.value"
        @change="changeRadioValue"
        :disabled="disabled"
      />
      <label :for="`${name}-${index}`">
        {{option.label}}
      </label>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    name: {
      type: String,
      required: true
    },
    value: {
      type: String | null,
      required: true
    },
    options: {
      type: Array,
      required: true
    },
    disabled: {
      type: Boolean,
      default: false,
      required: false
    }
  },
  methods: {
    changeRadioValue (event) {
      const targetId = event.target.id
      const newVal = event.target.value
      this.$emit("input", newVal, targetId)
    }
  }
};
</script>
親コンポーネント
<template>
  <InputRadio
    name="hoge"
    v-model="radio"
    :options="INPUT_RADIO_OPTION"
    :disabled="isDisabled"
  />
</template>

export default {
  data() {
    return {
      radio: null
    };
  }
}

INPUT_RADIO_OPTION: [
  { label: "有り", value: 1 },
  { label: "無し", value: 0 }
]

解除可能なラジオボタン

チェック済みのボタンをもう一度選択すると、チェックが解除されます。

radioDeletable.vue
<template>
  <div>
    <div 
      v-for="(option, index) in options"
      :key="index"
    >
      <input
        type="radio"
        :id="`${name}-${index}`"
        :checked="value === option.value"
        :value="option.value"
        @click="changeRadioValue"
        :disabled="disabled"
      />
      <label :for="`${name}-${index}`">
        {{option.label}}
      </label>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    name: {
      type: String,
      required: true
    },
    value: {
      type: String | null,
      required: true
    },
    options: {
      type: Array,
      required: true
    },
    disabled: {
      type: Boolean,
      default: false,
      required: false
    }
  },
  methods: {
    /**
    * @削除できるラジオボタン
    * v-model.numberを考慮して、厳密等価演算子「===」ではなく、等価演算子「==」
    * event.target.value: String
    * newVal: number | null
    */
    changeRadioValue (event) {
      let newVal = event.target.value
      if (newVal == this.value) {
        newVal = null
      }
      this.$emit("input", newVal);
    }
  }
};
</script>
親コンポーネント
<template>
  <InputRadioDeletable
    name="hoge"
    v-model="hoge"
    :options="INPUT_RADIO_OPTION"
    :disabled="isDisabled"
  />
</template>

export default {
  data() {
    return {
      radio: null
    };
  }
}

INPUT_RADIO_OPTION: [
  { label: "有り", value: 1 },
  { label: "無し", value: 0 }
]

上記コードは、こちらのリポジトリにありますので、よかったら参考にしてみてください。

https://github.com/taikishiino/vue-input-atomic-components