bvfi-dev liked a comment+100 XP
5mos ago
One clean approach is to store a snapshot of the form state after preFillEntry() and compare against it later. Instead of manually coding this in every FormObject, you can abstract it once in a base Form class:
public array $original = [];
public function snapshot(): void { $this->original = $this->toArray(); }
public function isDirty(array $only = []): bool { $current = $this->toArray();
if ($only) {
$current = Arr::only($current, $only);
$original = Arr::only($this->original, $only);
} else {
$original = $this->original;
}
return $current !== $original;
}
Then call $this->form->snapshot() after preFillEntry() in mount().
Now every FormObject automatically gets a reusable isDirty() without repeating logic and you can even check only specific fields.
bvfi-dev wrote a reply+100 XP
5mos ago
Yea, that actually makes sense, I can just abstract and extend that, then use it instead of the form with a snapshot. Thank you for the idea
bvfi-dev started a new conversation+100 XP
5mos ago
Im trying to use FormObjects for all my livewire components to reduce duplicate code, but I keep running into an issue where I need to check if the model was changed, like when in an edit form and checking if the initial state and save state are different, so that I can save on my API calls. In my Livewire Form, I have:
public function preFillEntry(User $entry):void
{
$entry->loadMissing('contact');
$this->email = $entry->email;
$this->first_name = $entry->first_name;
$this->last_name = $entry->name;
which I call in my mount() method in my Component like so:
$this->form->preFillEntry($entry);
And then in my save() method, Id like to check if a couple of properties have changed, but of course I cant use isDirty(), so Im looking into alternatives and whats the best and optimal way around this issue?
I know i can just make an array in the FormObject, with the original state and then compare this in a custom isDirty() function that I call in the save method, but Im not sure if this is the most efficient way of doing it, since this will get very repetitive if i start introducing it in all my FormObjects
bvfi-dev wrote a reply+100 XP
5mos ago
The thing with flash messages is you have to consider 2 scenarios when using Livewire:
- One where you want instant notifications as soon as the button is clicked
- One where there is a redirect in the session
I like making my stuff custom so that I always have 100% control over it, and also from my experience you need separate handles for both. I have for example an instant flash notification that appears on a click of a button that I call via Livewire's dispatch().
However If I redirect it would disappear so I need a session flash for after the session has been redirected and depending on your scenario you need to use one of these options.
Lets say I have a Livewire Index component and within it, I have create modal. What I would do on submitting the form is show a flash notification and make the modal not show -> User clicks "Submit" modal closes, notification is shown with results success/fail. This creates a smooth UI experience. However If I were to want to redirect to the edit page after submitting the create form, I would need a session flash notification.
The instant notification banner I make like so:
- In my default app layout, I insert the livewire component:
@stack('modals')
@livewire('components.notification-manager')
Then my component is like:
class NotificationManager extends Component
{
public $banners = [];
public function render()
{
return view('livewire.components.notification-manager');
}
/**
* Appends a banner to banners[]
* @param string|null $message
* @param int $type Types:<br>0 - Error/Fail<br>1 - Success<br>2 - Info (Default)
* @param int $timeout Defaults to 3 (seconds)
* @return void
*/
#[On('show-banner')]
public function addBanner(?string $message, int $type = 2, int $timeout = 3): void
{
if(is_null($message) || empty(trim($message))) { $message = 'Failed to generate a message!'; $type = 0; }
$key = uniqid();
$timeout = $timeout *1000;
$this->banners[] = ['key' => $key, 'message' => $message, 'type' => $type, 'timeout' => $timeout];
}
public function removeBanner($key): void
{
$this->banners = array_filter($this->banners, function($banner) use ($key) {
return $banner['key'] !== $key;
});
}
}
This way I can have multiple notifications stack on each other, I can give custom timeouts, I set the type, etc
Now from anywhere in the Livewire Component I can call:
$this->dispatch('show-banner', message: 'Success message' , type: 1);
And this will make the notification appear The frontend:
<div class="w-full fixed mx-auto top-2 right-4 flex flex-col space-y-2" style="z-index: 9999 !important;">
@foreach ($banners as $banner)
<div wire:key="banner-{{ $banner['key'] }}" x-data="{ show: true }" x-init="setTimeout(() => show = false, {{ $banner['timeout'] }})" x-show="show"
x-on:click="show = false"
...
You can use alpineJs to make it appear from top with smooth transitions and animations and you also need to display the $banner['message']
And depending on type I give it a different background color.
With session banner just make a blade component: , put it in your app layout, or the main html file you want it to be used, and have the html like so:
@props(['style' => session('flash.bannerStyle', 'success'), 'message' => session('flash.banner')])
<div x-data="{{ json_encode(['show' => true, 'style' => $style, 'message' => $message]) }}"
:class="{ 'bg-green-500': style == 'success', 'bg-red-700': style == 'danger', 'bg-yellow-500': style == 'warning', 'bg-gray-500': style != 'success' && style != 'danger' && style != 'warning'}"
style="display: none;"
x-show="show && message"
x-on:banner-message.window="
style = event.detail.style;
message = event.detail.message;
show = true;
">
...
Remember to use x-text="message" to display the message in a <p> or something, and don't forget the x-on:click="show = false" so that the notification can be closed.
Now anywhere in your livewire components you can do:
return redirect()->route('route.name')
->with('flash.banner', $message)
->with('flash.bannerStyle', 'success');
And when you return, the banner will pop out. I dont have full code for you, because mine has a lot more code in it and It took me a long time to figure it out. Im giving you an idea and more than a start to learn how the notifications should work
bvfi-dev was awarded Best Answer+1000 XP
6mos ago
bvfi-dev started a new conversation+100 XP
6mos ago
I replied to my own post about a significant update and I am unable to see what I have written, eventhough its mentioned that I have done it on the "Forums" frontpage. The related post is: Post
When I am in the post it says "2 Replies", however on the frontpage it says 3, which is correct if my Reply is included.
bvfi-dev wrote a reply+100 XP
6mos ago
bvfi-dev wrote a reply+100 XP
6mos ago