🙌
Nuxt.js+Vuetifyでダイアログで使えるフォームを作るまで
Nuxt.js+Vuetifyでダイアログで値を編集して更新できる感じのフォームを実装する。
完成イメージ
プロフィールを表示・編集ができる画面を実装する。
- 表示部分:取得したプロフィールを表示。編集ボタンを押下した時に編集フォームを開設、
- 編集フォーム:更した値を「変更する」を押下した時点で表示部分に反映させてダイアログを閉じる。
表示部分
編集フォーム
構成
.
├── domain
│ └── components
│ └── profile
│ ├── ProfileCard.vue - プロフィール表示部分
│ ├── ProfileEditDialog.vue - プロフィール編集ダイアログ
│ └── ProfileEditForm.vue - プロフィール編集フォーム
├── domain
│ └── profile
│ └── model
│ ├── Address.ts - 住所
│ ├── Contact.ts - 連絡先
│ └── Profile.ts - プロフィール
└── pages
└── profile
└── index.vue - プロフィール画面
コンポーネントの親子関係
- 親から順にindex.vue > ProfileCard.vue > ProfileEditDialog.vue > ProfileEditForm.vueの関係になる。
props・emit
親子間でデータの受け渡しをするので、props・emitを意識する。
props:親→子に値を渡す。
emit:子→親に値を渡す。
データ受け渡しのイメージ
- プロフィールの表示:index.vue -(props)→ ProfileCard.vue
- プロフィール編集画面の表示:ProfileCard.vue -(props)→ ProfileEditDialog.vue -(props)→ ProfileEditForm.vue
- プロフィールの更新結果を表示に反映:ProfileEditForm.vue -(emit)→ ProfileEditDialog.vue -(emit)→ ProfileCard.vue
ProfileEditDialog→ProfileEditForm.vue、ProfileEditDialog.vue→ProfileCard.vueの間では、値をディープコピーして渡す。
理由は以下。
ソース
domain/components/profile
ProfileCard.vue
<template>
<div>
<v-card class="ma-auto pa-4" max-width="400" tile>
<v-card-title
>Profile
<profile-edit-dialog
:initial-data="state.profile"
@submitted="submitted"
/>
</v-card-title>
<v-divider></v-divider>
<v-card-subtitle class="pb-1 font-weight-bold">名前</v-card-subtitle>
<v-card-text class="pt-0 pb-1">
{{ state.profile.lastName + ' ' + state.profile.firstName }}
</v-card-text>
<v-card-subtitle class="pt-0 pb-1 font-weight-bold"
>電話番号</v-card-subtitle
>
<v-card-text class="pt-0 pb-1">
{{ state.profile.contact.phoneNumber }}
</v-card-text>
<v-card-subtitle class="pt-0 pb-1 font-weight-bold"
>メールアドレス</v-card-subtitle
>
<v-card-text class="pt-0 pb-1">
{{ state.profile.contact.mail }}
</v-card-text>
<template v-for="(address, index) in state.profile.addresses">
<v-card-subtitle class="pt-0 pb-1 font-weight-bold" :key="index + '_1'"
>住所{{ index + 1 }}</v-card-subtitle
>
<v-card-text class="pt-0 pb-1" :key="index + '_2'">
{{
address.prefacture +
address.municpality +
address.blockNumber +
address.building
}}
</v-card-text>
</template>
</v-card>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType, reactive } from '@nuxtjs/composition-api'
import Profile from '~/domain/profile/model/Profile'
type Props = {
initialData: Profile
}
export default defineComponent({
props: {
initialData: {
type: Object as PropType<Profile>,
},
},
setup(props: Props) {
const state = reactive({
profile: props.initialData,
})
const submitted = (p: Profile) => {
state.profile = p
}
return { state, submitted }
},
})
</script>
- submittedで表示部分を更新する。
ProfileEditDialog.vue
<template>
<div>
<v-dialog v-model="editDialog" max-width="500">
<template v-slot:activator="{ on, attrs }">
<v-btn text rounded v-bind="attrs" v-on="on">
<v-icon>{{ mdiPencil }}</v-icon>
編集
</v-btn>
</template>
<v-card class="ma-auto pa-4">
<profile-edit-form v-model="state.profile" />
<v-card-actions>
<v-btn color="primary" @click="submit">変更する</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
<script lang="ts">
import {
defineComponent,
PropType,
reactive,
ref,
SetupContext,
} from '@nuxtjs/composition-api'
import Profile from '~/domain/profile/model/Profile'
import { mdiPencil } from '@mdi/js'
import clone from 'just-clone'
type Props = {
initialData: Profile
}
export default defineComponent({
props: {
initialData: {
type: Object as PropType<Profile>,
},
},
setup(props: Props, context: SetupContext) {
const editDialog = ref(false)
const close = () => (editDialog.value = false)
const state = reactive({
profile: clone(props.initialData),
})
const submit = () => {
context.emit('submitted', clone(state.profile))
close()
}
return { state, editDialog, mdiPencil, submit }
},
})
</script>
- ProfileEditForm.vueにはstate.profileをディープコピーした値を渡す。
- submitが発火した時にsubmittedにstate.profileをディープコピーした値を引数にして渡す。
- ディープコピーにはjust-cloneを使用する。[3]
ProfileEditForm.vue
<template>
<div v-if="form">
<v-card-title>Profile</v-card-title>
<v-card-text>
<v-form>
<v-container>
<div class="mb-2 text-subtitle-1 font-weight-bold">名前</div>
<v-row no-gutters>
<v-col class="mr-3">
<v-text-field label="姓" v-model="form.lastName"></v-text-field>
</v-col>
<v-col class="ml-3">
<v-text-field label="名" v-model="form.firstName"></v-text-field>
</v-col>
</v-row>
<div class="mb-2 text-subtitle-1 font-weight-bold">連絡先</div>
<v-text-field
label="電話番号"
v-model="form.contact.phoneNumber"
></v-text-field>
<v-text-field
label="メールアドレス"
v-model="form.contact.mail"
></v-text-field>
<div v-for="(address, index) in form.addresses" :key="address.id">
<div class="mb-2 text-subtitle-1 font-weight-bold">
住所 {{ index + 1 }}
</div>
<v-row no-gutters>
<v-col class="mr-3">
<v-text-field
label="都道府県"
v-model="address.prefacture"
></v-text-field>
</v-col>
<v-col class="ml-3">
<v-text-field
label="市区町村"
v-model="address.municpality"
></v-text-field>
</v-col>
</v-row>
<v-row no-gutters>
<v-col class="mr-3">
<v-text-field
label="番地"
v-model="address.blockNumber"
></v-text-field>
</v-col>
<v-col class="ml-3">
<v-text-field
label="建物"
v-model="address.building"
></v-text-field>
</v-col>
</v-row>
</div>
</v-container>
</v-form>
</v-card-text>
</div>
</template>
<script lang="ts">
import {
defineComponent,
PropType,
reactive,
ref,
SetupContext,
} from '@nuxtjs/composition-api'
import Profile from '~/domain/profile/model/Profile'
import { mdiPencil } from '@mdi/js'
type Props = {
value: Profile
}
export default defineComponent({
props: {
value: {
type: Object as PropType<Profile>,
},
},
setup(props: Props) {
const form = reactive(props.value)
const editDialog = ref(false)
return { form, editDialog, mdiPencil }
},
})
</script>
domain/profile/model
Address.ts
type Address = {
id: number
prefacture: string
municpality: string
blockNumber: string
building: string
}
export default Address
Contact.ts
type Contact = {
phoneNumber: string
mail: string
}
export default Contact
Profile.ts
import Address from "./Address"
import Contact from "./Contact"
type Profile = {
id: number
firstName: string
lastName: string
contact: Contact
addresses: Address[]
}
export default Profile
pages/profile
index.vue
<template>
<div>
<profile-card :initial-data="profile" />
</div>
</template>
<script lang="ts">
import { defineComponent, reactive } from '@nuxtjs/composition-api'
import Profile from '~/domain/profile/model/Profile'
export default defineComponent({
setup() {
const profile = reactive<Profile>({
id: 1,
firstName: '太郎',
lastName: '山田',
contact: {
phoneNumber: '08011112222',
mail: 'xxx@example.com',
},
addresses: [
{
id: 1,
prefacture: '東京都',
municpality: '新宿区',
blockNumber: '新宿1-1-1',
building: '新宿ビル1階',
},
{
id: 2,
prefacture: '東京都',
municpality: '渋谷区',
blockNumber: '渋谷2-2-2',
building: '渋谷ビル2階',
},
],
})
return { profile }
},
})
</script>
- ここにAPIからの取得処理を記載する。
Discussion