Vue.js 3.x で作る LabeledForm
要求
- ラベル、入力コントロールを再利用可能な形でレイアウトしたい.
- モバイル向けでないレイアウトの場合、ラベルと入力コントロールのラッパーの高さを同じにしたい.
- 使用する CSS フレームワークに合わせて指定クラスやスタイルを調整可能にしたい.
- カスタム CSS クラス、カスタムスタイルを props 経由で設定可能にする.
- 入力内容にエラーが存在する場合、自動的にエラー用のスタイルを反映したい.
前提・制約
- 主に Bootstrap3, Bootstrap4, Bootstrap5 を対象として想定している.
- 検証結果のフィードバックは複雑になるため、実装しない.
課題
- ラベル部分もアイコンやタグなどを自由にデザインできるよう、スロットにするか?
仕様
- ラベル、コントロール、エラーメッセージを保持するラッパーに対して、外部からクラス、スタイルを指定できること.
- 入力内容にエラーが発生している場合は、エラーメッセージを設定することにより、エラーを視認性が高い状態で表示できること.
コード
- LabeledForm.js
import { computed } from 'https://cdnjs.cloudflare.com/ajax/libs/vue/3.0.2/vue.esm-browser.js'; import { getWrapClass } from 'https://wws.jp/_export/code/vuejs/vue3/form_utils?codeblock=0'; const LabeledForm = { template: ` <div class="row" :class="mergedFormWrapperClasses" :style="formWrapperStyles"> <div :class="computedLabelWrapperClasses" :style="labelWrapperStyles"> <slot name="label"><label>{{ labelText }}</label></slot> </div> <slot></slot> </div> `, props: { errors: { type: Array, default: [] }, formWrapperClasses: { type: Array, default: [] }, formWrapperStyles: { type: Object, default: {} }, labelWrapperClasses: { type: Array, default: [] }, labelWrapperStyles: { type: Object, default: { paddingTop: '7px' } }, labelText: { type: String }, formErrorClass: { type: String, default: 'has-error' }, labelErrorClass: { type: String, default: 'text-danger' }, }, components: {}, setup(props, context) { const mergedFormWrapperClasses = computed(() => { return getWrapClass( props.errors, props.formWrapperClasses, props.formErrorClass ) }) const computedLabelWrapperClasses = computed(() => { return getWrapClass( props.errors, props.labelWrapperClasses, props.labelErrorClass ) }) return { mergedFormWrapperClasses, computedLabelWrapperClasses, } } } export default LabeledForm ;
Props
formWrapperClasses:
- type: Array
- default: []
labelText:
- type: String
labelWrapperClasses:
- type: Array
- default: []
labelWrapperStyles:
- type: Object
- default: { paddingTop: '7px' }
errors:
- type: Array
- default: []
formErrorClass:
- type: String
- default: 'has-error' }
labelErrorClass:
- type: String
- default: 'text-danger'
Demo
シンプルなレイアウト
<html>
<labeled-form
label-text="Test Label from props"
:form-wrapper-classes="['mb-5']"
:label-wrapper-classes="['col-sm-4', 'sm-text-right']"
:label-wrapper-styles="{ paddingTop: '7px' }"
:errors="errorMessages"
form-error-class=""
label-error-class="text-danger"
control-error-class="has-error"
>
<template #label>
<span class="label label-info">Test</span> <label>Test Label</label>
</template>
<input-form type="text"
:control-wrapper-classes="['col-sm-8']"
:errors="errorMessages"
>
</input-form>
</labeled-form>
<script type=“module”>
import { createApp } from 'https://cdnjs.cloudflare.com/ajax/libs/vue/3.0.2/vue.esm-browser.js';
import LabeledForm from '/_export/code/vuejs/vue3/labeled_form?codeblock=0';
import InputForm from '/_export/code/vuejs/vue3/input_form?codeblock=0';
const App = {
components: {
InputForm: InputForm,
labeledForm: LabeledForm
},
data() {
return {
errorMessages: ['error message A', 'error message B']
}
}
}
createApp(App).mount('#app')
</script>
<link rel=“stylesheet” href=“/_export/code/vuejs/vue3/form_styles?codeblock=0”>
</html>
<html><style>
h2.page-header {
font-weight: 600; font-size: 2.4rem; position: relative; padding: 10px 0 10px 16px; margin-top: 3rem !important; margin-bottom: 2rem !important;
}
h2:before {
content: ""; position: absolute; top: 0; left: 0; width: 6px; height: 100%; background: #ccc;
}
div#disqus__thread {
width: 100% !important;
}
</style></html>