Actions
Actions are provided to your pages via method attributes. Call the actions from vue via wrappers around @tanstack/vue-query
useMutation hooks.
<template> <button @click="mutate({ first: 10, second: 20 })" :disabled="isPending"> Add </button> <p v-if="data">{{ data }}</p></template>
<script setup lang="ts">import { usePage } from '@ozmos/viper-vue';
const page = usePage<ViperGen.Example>();
const { mutate, data, isPending } = page.useMutation('add');</script>
<php>use \App\Dto\UserDto;use \App\Dto\CreateUserDto;
return new class { #[\Ozmos\Viper\Attrs\Action] public function add(): int { $data = request()->validate([ 'first' => ['required', 'integer'], 'second' => ['required', 'integer'], ]);
return $data['first'] + $data['second']; }};</php>
useForm Helper
Section titled “useForm Helper”A useForm
helper is provided to make working with forms a breeze. This gives you an extra state
ref for your form and errors
for any 422 errors returned by laravel.
<template> <form method="post" @submit.prevent="mutate()"> <input v-model="state.email" /> <p v-if="errors.email">{{ errors.email }}</p> <button type="submit" :disabled="isPending">Login</button> </form></template>
<script setup lang="ts">import { usePage } from '@ozmos/viper-vue';import { router } from '@/pages/routes';
const page = usePage<ViperGen.Example>();
const { state, mutate, errors, isPending } = page.useForm('login', { state: { email: "", }, onSuccess() { router.push('/home'); },});</script>
<php>use \App\Dto\UserDto;
return new class { #[\Ozmos\Viper\Attrs\Action] public function login() { // you would hopefully add password validation $data = request()->validate([ 'email' => ['required', 'string', 'email'], ]);
auth()->login(\App\Models\User::whereEmail($data['email'])->first()); }};</php>
Form Data / File Uploads
Section titled “Form Data / File Uploads”Files can be uploaded alongside other form state using the useFormData
hook.
This feature currently ONLY WORKS when injecting a spatie/laravel-data
DTO into your action to be validated.
<template> <form method="post" @submit.prevent="mutate()"> <input v-model="state.email" /> <input @change="state.photo = $event.target.files[0]" /> <button type="submit" :disabled="isPending">Update Profile</button> </form></template>
<script setup lang="ts">import { usePage } from '@ozmos/viper-vue';import { router } from '@/pages/routes';
const page = usePage<ViperGen.Example>();
const { state, mutate, errors, isPending } = page.useFormData('submit', { state: { email: "", photo: null, }, files: ['photo'], onSuccess() { router.push('/home'); },});</script>
<php>use \App\Dto\CreateUserDto;
return new class { #[\Ozmos\Viper\Attrs\Action] public function submit(CreateUserDto $data) { request()->user()->update($data); }};
// create user DTO would look like
#[TypeScript]class CreateUserDto extends Data{ use \Ozmos\Viper\Traits\ValidatesFormData;
public function __construct( // non-file fields MUST be annotated #[\Ozmos\Viper\Attrs\FormDataValue] public string $email, public ?UploadedFile $photo = null ) {}}</php>
We require the attributes/DTO because when the request is made we submit multipart/form-data
that has a single key called state
with the json stringify of your state object, and then items for each file.
--- boundary name="state""{ email: \"test@test.com\" }"
--- boundary name="photo"...binary data
When the request comes in we can json_decode($request->state, true)
and then validate it as if it was the root request body in the first place.
The benefit of doing it this way is your state can contain complex json (just like normal actions) instead of only flat string values accepted by the typical FormData
class.
Middleware
Section titled “Middleware”Any route middleware applied to the page or previous layouts will apply to the action.
Container Injection
Section titled “Container Injection”Action functions are proxied through app()->call(...)
and can take advantage of Laravel’s service container.
<template> <button @click="form.mutate()">Perform Action</button></template>
<script setup lang="ts">import { usePage } from '@ozmos/viper-vue';
const page = usePage<ViperGen.Example>();
const form = page.useMutation('name');</script>
<php>return new class { #[\Ozmos\Viper\Attrs\Action] public function name(\Illuminate\Http\Request $request): string { // ... use $request }};</php>
TypeScript
Section titled “TypeScript”Requests can be typed in php and autocompleted within vue’s mutate function (request) and data ref (response).
Only DTO’s are supported at the moment. Laravel’s built in form requests cannot be typed at this time.
<template> <form method="post" @submit.prevent="mutate()"> <input v-model="state.email" /> <p v-if="errors.email">{{ errors.email }}</p> <button type="submit" :disabled="isPending">Register</button> </form></template>
<script setup lang="ts">import { usePage } from '@ozmos/viper-vue';import { router } from '@/pages/routes';
const page = usePage<ViperGen.Example>();
const { state, errors, isPending, mutate } = page.useForm('register', { state: { email: "", }, onSuccess(data) { // data typed to App.Dto.UserDto router.push('/home'); },});</script>
<php>use \App\Dto\UserDto;use \App\Dto\CreateUserDto;
return new class { #[\Ozmos\Viper\Attrs\Action] public function register(CreateUserDto $data): UserDto { // you would hopefully add password validation $user = \App\Models\User::create($data);
auth()->login($user);
return UserDto::from($user); }};</php>
See the generating types page for more info.