基本構成と状態に応じた制御
フォーム設計において、ユーザーの操作権限や画面モードに応じて入力コンポーネントの可否を切り替えるのは一般的な要件です。Vue 3のComposition APIとcomputedプロパティを組み合わせることで、簡潔なリアクティブな無効化ロジックを構築できます。
<template>
<el-form-item label="配信チャネル:" :prop="FormKeys.CHANNEL_TYPE">
<el-select
v-model="formState.channelType"
placeholder="チャネルを選択"
class="w-300"
>
<el-option label="オンライン" :value="1" />
<el-option label="オフライン" :value="0" />
</el-select>
</el-form-item>
<el-form-item label="プロモーション適用:" :prop="FormKeys.PROMO_FLAG">
<el-select
v-model="formState.promoFlag"
placeholder="対象を選択"
class="w-300"
:disabled="isReadOnlyView"
>
<el-option label="有効" :value="1" />
<el-option label="無効" :value="0" />
</el-select>
</el-form-item>
</template>
<script setup lang="ts">
import { reactive, computed, ref } from 'vue'
import { useRoute } from 'vue-router'
enum FormKeys { CHANNEL_TYPE = 'channelType', PROMO_FLAG = 'promoFlag' }
enum ViewMode { CREATE = 'create', EDIT = 'edit', VIEW = 'view' }
const route = useRoute()
const currentView = ref<ViewMode>(route.query.mode as ViewMode || ViewMode.CREATE)
const formState = reactive({
channelType: undefined as number | undefined,
promoFlag: undefined as number | undefined
})
const isReadOnlyView = computed(() => {
return [ViewMode.EDIT, ViewMode.VIEW].includes(currentView.value)
})
</script>
UI要素のカスタマイズと配置干渉の回避
デフォルトの外観を業務要件に合わせる場合、CSSオーバーライドや内部クラスへの干渉調整が必要です。
- クリアボタンのアイコン変更:
.el-input__clearセレクターに対し、clear-iconプロパティまたはCSSの::before疑似要素で代替アイコンを指定できます。 - 複数選択時のチェックマーク非表示:
.el-select-dropdown__item.is-selected::afterのcontentプロパティをnoneに設定し、選択済みアイテムのテキストカラーを独立して調整可能です。 - ポップオーバー内の自動閉じ問題:
el-popover内にel-selectを配置すると、ドロップダウン展開時にイベントバブリングにより親コンテナがクリックアウトを検知し閉じることがあります。これを防ぐには、ポップオーバーの:teleported="false"を指定しDOM配置を変更するか、ドロップダウンの表示中に親のvisible状態を手動で維持するロジックを組み込みます。
ドロップダウンの表示状態フック
@visible-change イベントはメニューの展開・非表示を検知する標準的な手段です。これを利用し、展開時に追加アクションボタンを動的にレンダリングしたり、非表示時に状態をリセットしたりできます。
<el-select
v-model="selectedTags"
filterable
multiple
collapse-tags
:max-collapse-tags="3"
placeholder="タグ選択"
class="full-width"
ref="tagSelectRef"
@visible-change="onMenuToggle"
>
<el-option
v-for="tag in availableTags"
:key="tag.identifier"
:label="tag.displayName"
:value="tag.identifier"
/>
</el-select>
<el-button v-if="displayAddBtn" @click="openTagCreator">新規タグ作成</el-button>
<script setup lang="ts">
import { ref } from 'vue'
const displayAddBtn = ref(false)
const selectedTags = ref<string[]>([])
const onMenuToggle = (isVisible: boolean) => {
displayAddBtn.value = isVisible
}
</script>
特定のダイアログ表示後やバリデーション実行後もメニューを維持したい場合、コンポーネントインスタンス経由で内部表示フラグを操作することで強制展開が可能です。
const keepMenuOpen = () => {
const instance = tagSelectRef.value
if (instance?.dropdownRef) {
instance.dropdownRef.show()
}
}
値変更検知と検証ルールの動的クリア
@change コールバックの引数は、単一選択時はプリミティブ値、複数選択時は配列として返却されます。この特性を活用し、依存する他フィールドの検証エラーを部分的に削除できます。フォーム全体のresetFields() は入力値まで巻き戻すため、clearValidate([field1, field2]) を使用して検証状態のみをリセットするのが安全です。
const clearRelatedValidation = () => {
formInstance.value?.clearValidate(['serverPort', 'apiEndpoint'])
}
const onProtocolChange = () => {
clearRelatedValidation()
// 依存ロジックの実行
}
検索モードとデータ取得戦略
オプション数が多い場合、クライアント側でのフィルタリングではパフォーマンス劣化やUXの低下を招くため、用途に応じた検索手法の選択が重要です。
ローカルフィルタリング
filterable 属性を追加するだけで、インプット値とラベルの前方一致/部分一致が自動有効化されます。
<el-select v-model="assigneeId" filterable placeholder="担当者を検索">
<el-option
v-for="member in teamMembers"
:key="member.employeeCode"
:label="member.legalName"
:value="member.employeeCode"
/>
</el-select>
リモート非同期検索
remote および remote-method を有効化すると、入力値の変更をトリガーとして指定した関数が実行されます。サーバー側での部分一致クエリ結果をリアクティブに反映する設計が推奨されます。
<el-select
v-model="searchResultCode"
remote
:remote-method="queryBackend"
:loading="isFetching"
placeholder="キーワードで検索"
>
<el-option
v-for="record in remoteDataList"
:key="record.primaryKey"
:label="record.title"
:value="record.primaryKey"
/>
</el-select>
初期値の表示と連動ドロップダウン
v-model に既存の値をバインドするだけで、オプションリスト内に一致する項目が存在する場合、自動的にラベルが描画されます。複数の選択項目が相互に影響を与える連動パターンでは、親項目の変更を監視し、子項目のデータソースを再取得・置換するフローを構築します。この際、連続した入力でリクエストが過負荷になるのを防ぐため、debounce 処理やAbortControllerによる古い通信のキャンセルを実装すると、安定した動作が保証されます。