Contoh ini menggunakan Vue dan InertiaJs dimana Create.vue & Edit.vue berkongsi menggunakan Fields.vue. Dengan cara ini, semua fields akan lebih seragam dan mudah untuk diselenggara.
Sebagai rujukan, Props digunakan untuk menyalurkan (pass) data ke Child Component. Emit pula digunakan untuk menyalurkan (pass) data ke Parent
<!-- resources/js/Pages/User/Create.vue --><script setup> import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout.vue" import Fields from "@/Pages/User/Fields.vue" import RoleFields from "@/Pages/User/RoleFields.vue" import PermissionFields from "@/Pages/User/PermissionFields.vue" import { Link, useForm } from "@inertiajs/inertia-vue3" import { ref } from "vue" const form = useForm({ "name": "", "email": "", "password": "", "password_confirmation": "", "roles": [], "permissions": [], "avatar": null, }) const onSubmit = () => { console.log(form.password_confirmation, form.password) form.post(route("user.store"), { onSuccess: () => { form.reset() }, }) }</script><template> <AuthenticatedLayout> <template #header> <div class="flex justify-between"> <h2 class="font-semibold text-xl text-gray-800 leading-tight">Create new user</h2> <Link :href="route('user.index')" class="inline-flex items-center px-2.5 py-1.5 border border-transparent text-xs font-medium rounded shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"> Back </Link> </div> </template> {{ form }} <form @submit.prevent="onSubmit()"> <div class="mt-10 sm:mt-0"> <div class="md:grid md:grid-cols-3 md:gap-6"> <div class="md:col-span-1"> <div class="px-4 sm:px-0"> <h3 class="text-lg font-medium leading-6 text-gray-900">Personal Information</h3> <p class="mt-1 text-sm text-gray-600">Use a permanent address where you can receive mail.</p> </div> </div> <div class="mt-5 md:col-span-2 md:mt-0"> <div class="overflow-hidden shadow sm:rounded-md"> <div class="bg-white px-4 py-5 sm:p-6"> <Fields v-model:isDirty="form.isDirty" v-model:errors="form.errors" v-model:name="form.name" v-model:email="form.email" v-model:password="form.password" v-model:password_confirmation="form.password_confirmation" v-model:avatar="form.avatar" /> </div> <div class="bg-gray-50 px-4 py-3 text-right sm:px-6 space-x-2"> <Link :href="route('user.index')" class="inline-flex items-center px-2.5 py-1.5 border border-transparent font-medium rounded text-indigo-700 bg-indigo-100 hover:bg-indigo-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">Back</Link> <button type="submit" class="inline-flex justify-center rounded-md border border-transparent bg-indigo-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">Save</button> </div> </div> </div> </div> </div> <RoleFields v-model:roles="form.roles" v-model:isDirty="form.isDirty" /> <PermissionFields v-model:permissions="form.permissions" v-model:isDirty="form.isDirty" /> </form> </AuthenticatedLayout></template>
<!-- resources/js/Pages/User/Edit.vue --><script setup> import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout.vue" import Fields from "@/Pages/User/Fields.vue" import RoleFields from "@/Pages/User/RoleFields.vue" import PermissionFields from "@/Pages/User/PermissionFields.vue" import { useForm } from "@inertiajs/inertia-vue3" import { Link } from "@inertiajs/inertia-vue3" const props = defineProps({ user: Object, // get user data (include roles & permission) roles: Object, // get all permissions from db permissions: Object, // get all permissions from db }) const form = useForm({ "name": props.user.name, // set initial value from db - get from props "email": props.user.email, // set initial value from db - get from props "password": "", "password_confirmation": "", "roles": props.roles, // set initial value from db (user roles) - get from props "permissions": props.permissions, // set initial value from db (user permissions) - get from props "avatar": null, }) const onUpdate = () => { form.put(route("user.update", props.user.id), { // TODO preserve state false, validation error not show... PS true, save notification not showing preserveState: true, // avoid save notification show only once even save multiple times // TODO preserve scroll cause scroll flick (role, permission bottom area). Suspect cause by axios call in component preserveScroll: true, }) }</script><template> <AuthenticatedLayout> <template #header> <div class="flex justify-between"> <h2 class="font-semibold text-xl text-gray-800 leading-tight">Edit user: {{ props.user.name.slice(0, 10) + "..." }}</h2> <Link :href="route('user.index')" class="inline-flex items-center px-2.5 py-1.5 border border-transparent text-xs font-medium rounded shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"> Back </Link> </div> </template> {{ form }} <form @submit.prevent="onUpdate()"> <div class="mt-10 sm:mt-0"> <div class="md:grid md:grid-cols-3 md:gap-6"> <div class="md:col-span-1"> <div class="px-4 sm:px-0"> <h3 class="text-lg font-medium leading-6 text-gray-900">Personal Information</h3> <p class="mt-1 text-sm text-gray-600">Use a permanent address where you can receive mail.</p> </div> </div> <div class="mt-5 md:col-span-2 md:mt-0"> <div class="overflow-hidden shadow sm:rounded-md"> <div class="bg-white px-4 py-5 sm:p-6"> <Fields v-model:isDirty="form.isDirty" v-model:errors="form.errors" v-model:name="form.name" v-model:email="form.email" v-model:password="form.password" v-model:password_confirmation="form.password_confirmation" v-model:avatar="form.avatar" /> </div> <div class="bg-gray-50 px-4 py-3 text-right sm:px-6 space-x-2"> <!-- TODO Float save button--> <Link :href="route('user.index')" class="inline-flex items-center px-2.5 py-1.5 border border-transparent font-medium rounded text-indigo-700 bg-indigo-100 hover:bg-indigo-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">Back</Link> <button type="submit" class="inline-flex justify-center rounded-md border border-transparent bg-indigo-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">Save</button> </div> </div> </div> </div> </div> <RoleFields v-model:roles="form.roles" v-model:isDirty="form.isDirty" /> <PermissionFields v-model:permissions="form.permissions" v-model:isDirty="form.isDirty" /> </form> </AuthenticatedLayout></template>
<!-- resources/js/Pages/User/Fields.vue --><script setup>import unsaveChangesNotification from '@/Components/unsaveChangesNotification.vue'import Avatar from '@/Pages/User/Avatar.vue'import { computed } from 'vue' const props = defineProps({ errors: Object, name: String, email: String, password: String, password_confirmation: String, avatar: File, isDirty: Boolean, }) const emit = defineEmits([ 'update:name', 'update:email', 'update:password', 'update:password_confirmation', 'update:avatar', ]) // use computed to allows use v-model on input/component element otherwise need to use // (:value="email" @input="$emit('update:email', $event.target.value)") on input element which is cumbersome and // couldnt pass v-model on nested component. ref: https://vuejs.org/guide/components/events.html#usage-with-v-model const name = computed({ get() { return props.name }, set(value) { emit('update:name', value)}}) const email = computed({ get() { return props.email }, set(value) { emit('update:email', value)}}) const password = computed({ get() { return props.password }, set(value) { emit('update:password', value)}}) const password_confirmation = computed({ get() { return props.password_confirmation }, set(value) { emit('update:password_confirmation', value)}}) const avatar = computed({ get() { return props.avatar }, set(value) { emit('update:avatar', value)}}) const isDirty = computed({ get() { return props.isDirty }, set(value) { emit('update:isDirty', value)}})</script><template> <unsaveChangesNotification v-model:isDirty="isDirty" /> <div class="grid grid-cols-6 gap-6"> <div class="col-span-6"> <label for="name" class="block text-sm font-medium text-gray-700">Name</label> <input type="text" v-model="name" name="name" id="names" autocomplete="given-name" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" /> <div v-if="errors.name" class="text-sm text-red-700 font-medium mt-1">{{ errors.name }}</div> </div> <div class="col-span-6"> <label for="email-address" class="block text-sm font-medium text-gray-700">Email address</label> <input type="text" v-model="email" name="email" id="email-address" autocomplete="email" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" /> <div v-if="errors.email" class="text-sm text-red-700 font-medium mt-1">{{ errors.email }}</div> </div> <div class="col-span-6"> <label for="password" class="block text-sm font-medium text-gray-700">Password</label> <input type="password" v-model="password" name="password" id="password" autocomplete="email" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" /> <div v-if="errors.password" class="text-sm text-red-700 font-medium mt-1">{{ errors.password }}</div> </div> <div class="col-span-6"> <label for="password_confirmation" class="block text-sm font-medium text-gray-700">Comfirm Password</label> <input type="password" v-model="password_confirmation" name="password_confirmation" id="password_confirmation" autocomplete="email" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" /> <div v-if="errors.password_confirmation" class="text-sm text-red-700 font-medium mt-1">{{ errors.password_confirmation }}</div> </div> </div> <div class="mt-4"> <label class="block text-sm font-medium text-gray-700">Photo</label> <Avatar class="h-24 w-24 rounded-full" v-model:avatar="avatar" /> </div> <div class="mt-4"> <label class="block text-sm font-medium text-gray-700">Cover photo</label> <div class="mt-1 flex justify-center rounded-md border-2 border-dashed border-gray-300 px-6 pt-5 pb-6"> <div class="space-y-1 text-center"> <svg class="mx-auto h-12 w-12 text-gray-400" stroke="currentColor" fill="none" viewBox="0 0 48 48" aria-hidden="true"> <path d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /> </svg> <div class="flex text-sm text-gray-600"> <label for="file-upload" class="relative cursor-pointer rounded-md bg-white font-medium text-indigo-600 focus-within:outline-none focus-within:ring-2 focus-within:ring-indigo-500 focus-within:ring-offset-2 hover:text-indigo-500"> <span>Upload a file</span> <input id="file-upload" name="file-upload" type="file" class="sr-only" /> </label> <p class="pl-1">or drag and drop</p> </div> <p class="text-xs text-gray-500">PNG, JPG, GIF up to 10MB</p> </div> </div> </div></template>
Penggunaan Props & Emit akan menjadi sukar jika mempunyai banyak Child component dan antara cara lain ialah dengan menggunakan kaedah Store State in an External File. Buat masa ini tiada tutorial khas mengenainya tetapi Anda boleh rujuk (search “resources\js\stores\userFormStore.js”) post ini untuk mendapatkan gambaran mengenainya.
*kod ini adalah sebahagian daripada projek sebenar bertujuan untuk memberikan rujukan kasar mengenai penggunaan sesuatu kod dan bukan bertujuan untuk panduan langkah penyelesaian secara menyeluruh kerana setiap projek mempunyai keperluan yang berlainan