Updating Currency Format Dynamically

I have a opening_balance field that is supposed to dynamically update as soon as the state of another field is updated, but I can't get it to update quickly enough. I can only get it to update after the Form is created or edited but not while editing. It is kind of hard to set the opening balance field in this context so I am not sure what I could do. I need help please. If nothing I can do it is okay because it still works after creation, etc. But would still be nice.
53 Replies
Kenneth Sese
Kenneth Sese2y ago
Can you clarify some please? The snippet you sent has a currency_code field that seems unrelated to your question about your opening_balance. What field affects the opening_balance? Maybe I'm not seeing the entire code block?
Andrew Wallo
Andrew WalloOP2y ago
Yes, I will clarify. The TextInput Mask's pattern block options for opening_balance are dynamically set based on the chosen value of the currency_code field (it's changed state). For example, a user selects the option "USD" for the currency_code field. Now that "USD" is selected, the pattern block configuration should change based on the "USD" value. The configuration values come from the akaunting/laravel-money package.
Kenneth Sese
Kenneth Sese2y ago
Ahh...I see that now...sorry
Andrew Wallo
Andrew WalloOP2y ago
All good, everything works great but only after the form is reloaded (e.g. after creation, or editing) Having the mask's values update automatically after changing the currency_code value/state would be nice... I actually thought about making something like this a plugin, but unless I can figure this out I am not sure
Kenneth Sese
Kenneth Sese2y ago
...checking... I couldn't get it to work. I think ultimately it's because alpine isn't receiving the updated configuration. I think you'd have to entangle the configuration for it to react to your changes. Ok just thought of and kind of tested a REALLY hacky work around (assuming I'm fully understanding your requirements). Create two duplicate input fields, and then toggle their visibility between each select update. This will force the new one to be added to the DOM with the new configuration. You can even maintain whatever value is in opening_balance using afterStateUpdated() on your select. You need to name each input differently, but you can handle the different inputs in the handleRecordCreation method. I mean talk about a hack! (Please let there be a better way! 🤣)
Andrew Wallo
Andrew WalloOP2y ago
Could you show me what you specifically mean by the two input fields? Or like an example would be cool
Kenneth Sese
Kenneth Sese2y ago
Here's a demo video...is this what you mean:
Kenneth Sese
Kenneth Sese2y ago
Andrew Wallo
Andrew WalloOP2y ago
That is exactly what I mean
Kenneth Sese
Kenneth Sese2y ago
Ok hold on
Select::make('currency_code')
->options([
'euro' => 'euro',
'dollar' => 'dollar',
'yen' => 'yen',
])
->default('euro')
->reactive()
->afterStateUpdated(function (Closure $get, Closure $set) {
Config::set('filament-filter-sets.thousands', ',');
Config::set('filament-filter-sets.decimal', '.');
Config::set('filament-filter-sets.money', '$money');
$set('balance_2', $get('balance'));
$set('balance', $get('balance_2'));
}),

TextInput::make('balance')
->mask(fn (TextInput\Mask $mask) => $mask
->patternBlocks([
'money' => fn (Mask $mask) => $mask
->numeric()
->thousandsSeparator(config('filament-filter-sets.thousands'))
->decimalSeparator(config('filament-filter-sets.decimal')),
])
->pattern(config('filament-filter-sets.money'))
->lazyPlaceholder(false)
)
->visible(fn (Closure $get) => $get('currency_code') === 'euro'),

TextInput::make('balance_2')
->mask(fn (TextInput\Mask $mask) => $mask
->patternBlocks([
'money' => fn (Mask $mask) => $mask
->numeric()
->thousandsSeparator(config('filament-filter-sets.thousands'))
->decimalSeparator(config('filament-filter-sets.decimal')),
])
->pattern(config('filament-filter-sets.money'))
->lazyPlaceholder(false)
)
->visible(fn (Closure $get) => $get('currency_code') === 'dollar'),
Select::make('currency_code')
->options([
'euro' => 'euro',
'dollar' => 'dollar',
'yen' => 'yen',
])
->default('euro')
->reactive()
->afterStateUpdated(function (Closure $get, Closure $set) {
Config::set('filament-filter-sets.thousands', ',');
Config::set('filament-filter-sets.decimal', '.');
Config::set('filament-filter-sets.money', '$money');
$set('balance_2', $get('balance'));
$set('balance', $get('balance_2'));
}),

TextInput::make('balance')
->mask(fn (TextInput\Mask $mask) => $mask
->patternBlocks([
'money' => fn (Mask $mask) => $mask
->numeric()
->thousandsSeparator(config('filament-filter-sets.thousands'))
->decimalSeparator(config('filament-filter-sets.decimal')),
])
->pattern(config('filament-filter-sets.money'))
->lazyPlaceholder(false)
)
->visible(fn (Closure $get) => $get('currency_code') === 'euro'),

TextInput::make('balance_2')
->mask(fn (TextInput\Mask $mask) => $mask
->patternBlocks([
'money' => fn (Mask $mask) => $mask
->numeric()
->thousandsSeparator(config('filament-filter-sets.thousands'))
->decimalSeparator(config('filament-filter-sets.decimal')),
])
->pattern(config('filament-filter-sets.money'))
->lazyPlaceholder(false)
)
->visible(fn (Closure $get) => $get('currency_code') === 'dollar'),
Ok so a few things: I'm setting visible based on the currency code just for the demo BUT you wouldn't want to do that. You'd want to set some type of on/off flag and then just switch between the two You could probably set a flag somewhere, and then in the afterStateUpdated() method just toggle that on each change, And then if you need the balance you need to swap between getting and setting the balances depending on what flag is set The above code was really just to get a quick demo working
Andrew Wallo
Andrew WalloOP2y ago
Okay I will try with what you gave me, I really appreciate your help! This will be great if it works
Kenneth Sese
Kenneth Sese2y ago
The nice thing is you really don't visually see the inputs being swapped out. It's hacky behind the scenes but to the end user I don't know if they'd notice Updated the notes above a bit for clarity
Andrew Wallo
Andrew WalloOP2y ago
Okay so this works but only when there are 2 options, which for you would be dollar and euro. There are essentially hundreds a user can choose from so I can't use for example:
->visible(fn (Closure $get, $state) => $get('currency_code') === 'USD'),
->visible(fn (Closure $get, $state) => $get('currency_code') === 'USD'),
For one text input, and:
->visible(fn (Closure $get, $state) => $get('currency_code') !== 'USD'),
->visible(fn (Closure $get, $state) => $get('currency_code') !== 'USD'),
How else could I handle this do you think?
Kenneth Sese
Kenneth Sese2y ago
Yes, that's why you need to use a flag and then by toggling the flag it'll just swap the inputs.
Andrew Wallo
Andrew WalloOP2y ago
Hmm maybe I am not sure I have used such flags before
Kenneth Sese
Kenneth Sese2y ago
What we're trying to accomplish is just swapping the fields so that it'll load the new configuration, whatever that configuration could be. Hold on.
Andrew Wallo
Andrew WalloOP2y ago
I think I got it This seems to work: For one of them:
->visible(fn (Closure $get, $record) => $get('currency_code') === $record->currency_code),
->visible(fn (Closure $get, $record) => $get('currency_code') === $record->currency_code),
And the other:
->visible(fn (Closure $get, $record) => $get('currency_code') !== $record->currency_code),
->visible(fn (Closure $get, $record) => $get('currency_code') !== $record->currency_code),
I'm sure there is a better way though
Kenneth Sese
Kenneth Sese2y ago
That seems like a viable option. I like it too since it won't unnecessarily swap out the balance if the same code is already selected.
Andrew Wallo
Andrew WalloOP2y ago
I guess this is really the only way for right now.
Kenneth Sese
Kenneth Sese2y ago
Now watch Dan come in here and give an awesome one liner!!!!!!!! 🤣
Andrew Wallo
Andrew WalloOP2y ago
I hope so I wonder if its something to do with the imask library but idk
Kenneth Sese
Kenneth Sese2y ago
But I think this is the only way currently since the real problem is that the mask in alpine doesn't get reloaded. We need to force the reload
Andrew Wallo
Andrew WalloOP2y ago
I'm testing it out right now and it seems like the max amount of times I can switch the option is like 3 times. After that it doesn't reload anymore. Better than nothing though!
Kenneth Sese
Kenneth Sese2y ago
Then that's probably related to referencing the currency code. Try setting a hidden input: hidden::make('flag')->default(true) and then in afterStateUpdated() toggle it ($set('flag', ! $get('flag')) and then reference that in your visible ->visible(fn (Closure $get) => $get('flag')) and ->visible(fn (Closure $get) => ! $get('flag')) You might need to be specific about (bool) $get('flag') and ! (bool) $get('flag') since I think the inputs are strings. You know what we really need is a refresh() method on inputs that would reload them for us
Andrew Wallo
Andrew WalloOP2y ago
Just verified that, for example: If it was "USD", changing it to "EUR" would make it reload, but then I have to switch back to "USD" again for a different option to be reloaded again before switching. So I can't do: Switch from USD to EUR, then EUR to AUD, I have to do a Switch from USD to EUR, then back to USD then to AUD. Agreed
Kenneth Sese
Kenneth Sese2y ago
Are you sure...I got it working for me to switch however i wanted it to
Kenneth Sese
Kenneth Sese2y ago
Kenneth Sese
Kenneth Sese2y ago
I'm just using and array of 0,1,2,3,4,5, but the concept is the same. It switches however it needs too The number is the currency code that is set in ->pattern()
Andrew Wallo
Andrew WalloOP2y ago
Hmm can I see your updated version?
Kenneth Sese
Kenneth Sese2y ago
Select::make('currency_code')
->options([
'0' => '0',
'1' => '1',
'2' => '2',
'3' => '3',
'4' => '4',
'5' => '5',
])
->default('euro')
->reactive()
->afterStateUpdated(function (Closure $get, Closure $set, $state) {
$set('flag', ! $get('flag'));
Config::set('filament-filter-sets.thousands', ',');
Config::set('filament-filter-sets.decimal', '.');
Config::set('filament-filter-sets.money', $state . 'money');
}),

Hidden::make('flag')
->default(true),

TextInput::make('balance')
->mask(fn (TextInput\Mask $mask) => $mask
->patternBlocks([
'money' => fn (Mask $mask) => $mask
->numeric()
->thousandsSeparator(config('filament-filter-sets.thousands'))
->decimalSeparator(config('filament-filter-sets.decimal')),
])
->pattern(config('filament-filter-sets.money'))
->lazyPlaceholder(false)
)
->visible(fn (Closure $get) => (bool) $get('flag')),

TextInput::make('balance_2')
->mask(fn (TextInput\Mask $mask) => $mask
->patternBlocks([
'money' => fn (Mask $mask) => $mask
->numeric()
->thousandsSeparator(config('filament-filter-sets.thousands'))
->decimalSeparator(config('filament-filter-sets.decimal')),
])
->pattern(config('filament-filter-sets.money'))
->lazyPlaceholder(false)
)
->visible(fn (Closure $get) => ! (bool) $get('flag')),
Select::make('currency_code')
->options([
'0' => '0',
'1' => '1',
'2' => '2',
'3' => '3',
'4' => '4',
'5' => '5',
])
->default('euro')
->reactive()
->afterStateUpdated(function (Closure $get, Closure $set, $state) {
$set('flag', ! $get('flag'));
Config::set('filament-filter-sets.thousands', ',');
Config::set('filament-filter-sets.decimal', '.');
Config::set('filament-filter-sets.money', $state . 'money');
}),

Hidden::make('flag')
->default(true),

TextInput::make('balance')
->mask(fn (TextInput\Mask $mask) => $mask
->patternBlocks([
'money' => fn (Mask $mask) => $mask
->numeric()
->thousandsSeparator(config('filament-filter-sets.thousands'))
->decimalSeparator(config('filament-filter-sets.decimal')),
])
->pattern(config('filament-filter-sets.money'))
->lazyPlaceholder(false)
)
->visible(fn (Closure $get) => (bool) $get('flag')),

TextInput::make('balance_2')
->mask(fn (TextInput\Mask $mask) => $mask
->patternBlocks([
'money' => fn (Mask $mask) => $mask
->numeric()
->thousandsSeparator(config('filament-filter-sets.thousands'))
->decimalSeparator(config('filament-filter-sets.decimal')),
])
->pattern(config('filament-filter-sets.money'))
->lazyPlaceholder(false)
)
->visible(fn (Closure $get) => ! (bool) $get('flag')),
Andrew Wallo
Andrew WalloOP2y ago
Hmm this worked but removed the value in the field on some currencies. Weird.
Kenneth Sese
Kenneth Sese2y ago
You mean the amount in the balance?
Andrew Wallo
Andrew WalloOP2y ago
Yeah
Kenneth Sese
Kenneth Sese2y ago
Yeah, that's what I was referencing earlier that I think you have to swap the get/set balance. So something like:
if ((bool) $get('flag') {
$set('balance_2', $get('balance'));
} else {
$set('balance', $get('balance_2'));
}
if ((bool) $get('flag') {
$set('balance_2', $get('balance'));
} else {
$set('balance', $get('balance_2'));
}
(or maybe the other way around). If not then it'll override with the incoming input field that is blank
Andrew Wallo
Andrew WalloOP2y ago
Do you think this is a reliable way to do this though? 😂 I mean yeah some of these work, but idk if its something I would commit to github for my application.
Kenneth Sese
Kenneth Sese2y ago
It was the other way around for me:
if ((bool) $get('flag')) {
$set('balance', $get('balance_2'));
} else {
$set('balance_2', $get('balance'));
}
if ((bool) $get('flag')) {
$set('balance', $get('balance_2'));
} else {
$set('balance_2', $get('balance'));
}
Andrew Wallo
Andrew WalloOP2y ago
Damn sure as hell hack tho
Kenneth Sese
Kenneth Sese2y ago
haha! I don't blame you! Huge hack!
Andrew Wallo
Andrew WalloOP2y ago
Changing Filament forever haha I appreciate your help man, hopefully Dan can come in here and show an easy fix. If not then I might just have to go with the hack 👍
Kenneth Sese
Kenneth Sese2y ago
But hey...I found this comment in the Filament docs today:
/**
* Please do not judge this code. There's no error handling, retries,
* knowledge of rate limiting, or anything else. It's a non-critical
* service to fetch optional data for website content. If something
* bad happens, it *doesn't really matter*. This code gets run
* every 15 minutes, and we randomise the order of the query
* results to ensure that all records get a fair chance at
* getting successfully updated.
*
* That being said, if you're looking for something to do, you
* can clean this all up and handle the errors properly. But
* it really isn't vital to this app servicing its users :)
*/
/**
* Please do not judge this code. There's no error handling, retries,
* knowledge of rate limiting, or anything else. It's a non-critical
* service to fetch optional data for website content. If something
* bad happens, it *doesn't really matter*. This code gets run
* every 15 minutes, and we randomise the order of the query
* results to ensure that all records get a fair chance at
* getting successfully updated.
*
* That being said, if you're looking for something to do, you
* can clean this all up and handle the errors properly. But
* it really isn't vital to this app servicing its users :)
*/
I think we all have code like this! Just add a comment so at least people know that YOU KNOW its a hack!!!!
Andrew Wallo
Andrew WalloOP2y ago
True true
Kenneth Sese
Kenneth Sese2y ago
And the idea of the hidden input actually came from my work on the ToggleColumn that was causing problems. There was a hidden input they were using there to store state...so it's not too far out there. (thankfully we were able to get rid of all of that)
Andrew Wallo
Andrew WalloOP2y ago
I think its something to do with the Imask library though, I saw related issues on the filament github thread.
Kenneth Sese
Kenneth Sese2y ago
At the least this is a cool record of some killer collaboration on solving a problem!
Andrew Wallo
Andrew WalloOP2y ago
Yeah I was thinking the same thing.
Kenneth Sese
Kenneth Sese2y ago
Come save us Super Dan! Night
Andrew Wallo
Andrew WalloOP2y ago
You too
Dan Harrin
Dan Harrin2y ago
yeah i dont know, i would stay away from the masking feature completely and just use prefix() for the currency symbol
Andrew Wallo
Andrew WalloOP2y ago
Hmm alright. I was wondering, I guess will the same library be used for V3? I wonder if there is something like the akaunting/laravel-money package (Filament's TextColumn option money()) but for the TextInput field instead...
Dan Harrin
Dan Harrin2y ago
if it works better in livewire 3, we will keep it. if not, we will replace it.
Andrew Wallo
Andrew WalloOP2y ago
Okay thanks
Kenneth Sese
Kenneth Sese2y ago
Please tell me you used our hack! 🤣🤣🤣🤣
Andrew Wallo
Andrew WalloOP2y ago
Haha still deciding
Want results from more Discord servers?
Add your server