(Table) Repeater relationship options
When using the repeater (or the Table Repeater plugin), it will show the same BelongsToMany relationship options even if they've already been selected. π’
How does one (easily) avoid this...? π€
I have tried to inject
$get
or $livewire
, but I'm not having good results. Wherever I try to inject the latter, I'm told it's expecting a different type than the Livewire\Livewire
class. And $get
seems to work, but I can't seem to actually get all of the other items to exclude them from the list of available options. π
Thx in advance.... π€Solution:Jump to solution
Cool, it got merged. So @travis.elkins you can now use the disableOptionWhen() code I posted a few msgs back, and it'll work for either style of Select.
51 Replies
Not clear what you are asking. Do you mean you have a Select inside a repeater, and you want that Select to exclude any options which are selected in that field on other instances of the repeater?
Yes....sorry. It's exactly that: a select that, at the moment, simply provides all of the specific model's names/IDs.
I want it to exclude the already-selected options that are selected on the other repeater items.
This has been asked an answered a number of times. Try searching for something like "repeater select"
Thx. I have searched for "repeater" along with other key words, but not with "select", I don't think. (Discord's search capabilities aren't all that great, fwiw.)
Anyway, I found a lot of people with a lot of similar solutions for getting the current list of options to be pared down based on what was already selected.
But...none that I've found seem to address updating the other repeater item select components to make their own adjustments based on what's just been selected.
I'll have to keep looking.... π΅οΈ
It's coming up 5am, so too late to do it now, but tomorrow I'll write an example and post a gist. This gets asked often enough it needs a concrete example.
@travis.elkins OK, this is how I do it. If you're using multiple(), you'd have to also flatten() the collected ID's.
I'm still not sure what you mean by your "but". If your Select is reactive(), when you select an option on any of them, all the Selects are going to get rebuilt, and their options() code re-run, so you can do what you want in the closure.
https://github.com/cheesegrits/demo/blob/repeater-select/app/Filament/Resources/Shop/OrderResource.php#L225
GitHub
demo/app/Filament/Resources/Shop/OrderResource.php at repeater-sele...
Source code for the demo.filamentphp.com website. Contribute to cheesegrits/demo development by creating an account on GitHub.
Thanks for this. π€ It's similar to what I have seen/done already, but it's a bit cleaner. And, the "but" was, indeed, referring to what you mentioned....and making the component reactive was the solution.
Seems to be some bug somewhere, though. When I add one more item to the repeater, previous entries are cleared out. This happens when I use the code you shared (or code like it). When I simply return a list of options like this,
return MyModel::all()->pluck('name', 'id');
, it doesn't behave this way.
But, when I do this (or similar):
Then previous entries are cleared out.
Now I've got to figure out if there's an issue because of something I've done/forgotten....or if it's a bug. π³
It's the call to $get()
that seems to be related to the odd clearing of the other items' entries.
When my options()
closure does this:
It's fine.
When it does this, however:
Then the previous items' entries are cleared.
Weird....but I'll try to dig a bit....
Hold on....looks like I was doing something off somewhere. It's looking like something on my end at the moment....Maybe post your exact code, so we can help spot the error.
It looks like there's a legit error...or a misunderstanding. I'll post the exact code in a bit once I get things cleaned up. I'm trying to test to figure out how/when it happens.
In my last test, though, I cleared the repeater items and started over. I updated the options to give me the id/name pairs sorted by ID, ascending.
I added 1, then 2, then 3...and so on...and no problems.
Then, I removed the first and added it to the end....going from 1, 2, 3, 4, 5, 6, 7, 8, 9 to 2, 3, 4, 5, 6, 7, 8, 9, 1. Once I added 1, though, all of the previous entries changed to 10.
OK. Here's the repeater field along with the three repeated fields (only the first one matters):
(I can't edit for some reason, but I removed two other repeated fields, both of which were simple enum select components because discord said the message was too long....so that's what "only the first one matters" refers to.)
It seems that any new item that is selected, if its ID is less than any of the IDs that are already selected, then those IDs will "shift" (increment) one.
It also seems that it only changes what is displayed. If I save it, even though it shows the wrong values, it seems to maintain the correct state...in the livewire/filament component as well as in the database.
I can't replicate those issues at all with the code in the demo app, or the places I use it in my deployed apps.
One thing I'm confused by is:
->transform(fn ($id): int => (int) $id) // forces values to be integers, as sometimes the logged result would be in json form instead of array formThat makes no sense to me. We're collecting a foreign key, which is always going to be either an int, or null (if no option is selected). So I don't understand why you'd be seeing either an array or JSON for those values. I suggest you install the demo app, and apply the change from my fork of it, so you have a working example you can look at. Then reduce your code to the simplest form, so it directly resembles the example code. Then work from there.
I may do that with the demo app...or distill my own app down and upload it as an example.
One difference between your example and mine is that I am using the repeater for a many to many relationship. I have a
HasMany
relationship to the pivot model...per the documentation: https://filamentphp.com/docs/3.x/forms/fields/repeater#integrating-with-a-belongstomany-eloquent-relationshipI started a brand new L10/F3 repo....added a simple roles model with a many-to-many relationship: users-roles.
I then set up the form for users in the same way that I have for my current project and can easily reproduce the error.
I will tidy it up a bit and then get it on github for people to look at.
I was also able to demonstrate how the output of
collect($get('../../roles'))->pluck('role_id')->diff([$state])
changes from what appears to be JSON to an array.Here's the output from
\Log::debug(collect($get('../../roles'))->pluck('role_id')->diff([$state]));
in Laravel Debugbar when I have several roles in my repeater and then add one. You can see for yourself when I upload the repo.And, you can see that the IDs are strings, not integers.
There may be things I'm doing wrong or forgetting somewhere...but I'm hoping another set of eyes will see if that's the case. π€
FYI...the "array" format seems to always be for the last item in the list. Strange.
Here it is: https://github.com/telkins/repeater-issue
GitHub
GitHub - telkins/repeater-issue
Contribute to telkins/repeater-issue development by creating an account on GitHub.
I encountered a similar problem, which specifically occurred with native Select. Try adding ->native(false) to your Select, it resolved the issue for me.
Thx. I will give that a try.
Here's my issue: https://github.com/filamentphp/filament/issues/7808
GitHub
Repeater shows wrong values Β· Issue #7808 Β· filamentphp/filament
Package filament/filament Package Version v3.0.23 Laravel Version v10.19.0 Livewire Version v3.0.0-beta.9 PHP Version PHP 8.2.8 Problem description I have a repeater, using it to reflect a many-to-...
That seems to work....but I believe there's still a bug. I'll add this info to the issue.
If Dan and company don't get to it first, I'll take a look at your issue / repro. It may just be a case of flattening the returned id's, but there may be something weirder going on.
@travis.elkins yeah, it seems to be to do with whether it's using the native select, or Choices.
I pretty much always set my selects to searchable() so I have a consistent UI on selects, which forces the use of Choices, which is why this technique works in the demo app and all my uses of it.
I think you'll find if you set searchable() (and preload() so you don't get an empty dropdown), it'll work. I installed your repro, and changing it to searchable(), and everything works.
I'm looking at the code atm, see if I can figure out why it gets its undies in a bundle with native select. It may be something to do with caching options, not sure.
(it's conceivable it may also be a livewire dom diffing issue)
Ohhhh! I see why. Yeah, I don't think this is a Filament or Livewire issue, it's more an HTML issue.
When using native select, it's using the built in selectedIndex to determine which option is selected. We're then trying to change out the options from underneath the select. So (for example) the index for "Role 10" is 2, and that's the selectedIndex. Then we change the options, which removes one of the options before that, and suddenly index 2 is a different option.
When using Choices, everything is built on the fly using JS, so this isn't a problem.
I guess the only solution there is to disable unavailable options, rather that remove them. Can't remember offhand how to do that, I'll take a look.
Be cautious. Last I saw some browsers donβt support disabling select options.
Yeah, I remember running in to this with Fabrik (my Filament-like application builder in Joomla) when doing very similar stuff. Although I think disabled option support has improved dramatically in broswers since then.
Iβm sure it has. Just saying to not depend on it and call it a day. Definitely still have validation in place if going down this route.
Personally I'm perfectly happy with removing options entirely just working with Choices, as that's all I need. I do this a lot in my apps, as it's the most frictionless way to prevent users select options that should be unavailable. But I also refuse to let a computer beat me, so now imma have to find a way to make it work native. π
And this is just one of the reasons I respect you so much. π€
LOL. You can't show weakness or fear when dealing with computers. Have to stand your ground, whip them into shape, otherwise they smell blood in the water and start thinking they can beat us.
(This is going to become an issue once our AI Overlords start their ascendency)
Well, interestingly, disableOptionWhen() doesn't get used if Choices is being used (so if searchable() or multiple()).
When the native select is used, this works fine for @travis.elkins 's test case:
But add ->searchable() so it becomes a Choices select, and disableOptionWhen() never gets called.
So ... without figuring out why Choices doesn't use disableOptionWhen(), you'd have to use a different approach depending on whether you are using native or not. For native, do the disable thing. For Choices, do the "removing selected items from the options" thing.
Weβll just ignore that string ordering of ints. Lol.
One thing at a time.
He had that in his test case, I'd just stripped everything down to basics while testing, rule out as much as I could. But just to ease your OCD ...
Iβll stop talking now. Lol.
sigh
Now of course I feel compelled to figure out supporting disabled options in select.js.
This is a sickness.
Well, your sickness benefits us all. And weβre sick too.
Think I fixed it. Doing a little more testing before PR.
Testing, schmesting, push it. Lol.
PR'ed.
If this PR is merged, then the optionDisabledWhen() method will work for both types of select.
https://github.com/filamentphp/filament/pull/7828
GitHub
Support disabled option in Choices by cheesegrits Β· Pull Request #7...
Changes have been thoroughly tested to not break existing functionality.
New functionality has been documented or existing documentation has been updated to reflect changes.
Visual changes are ex...
Solution
Cool, it got merged. So @travis.elkins you can now use the disableOptionWhen() code I posted a few msgs back, and it'll work for either style of Select.
And you can close your issue.
OK...so it looks like we keep it simple with the options in that they're all selected, but then we disable specific options...those that have been selected elsewhere in the repeater...? π€
I like that approach...but will play with it to see how it feels.
Thx. π€
Initial thoughts are that it's nice. I may introduce searchable, as the list I have is likely to grow quite large.
Downside: Looks like the
Role::query()->pluck('name', 'id')
is run for each occurrence of a repeater item/row....so, if I have 10 rows in my repeater, the query is executed 10 times.
I might look for a nice way to "cache" the results, as they're not likely to change very often.
Hmm....now I know why I don't like the searchable select....there's weird behavior sometimes....Select the item to get into "search mode"....you will see the list expand.
Then press escape...and see the list disappear.
Then, press the down arrow, escape, up arrow, escape, left arrow, escape, and so on.
Each time you do this, that text will appear...adding on to whatever was there prior. "meta" shows up when I press the mac "command" key.
Besides these five keys, I don't know what else does this...but it's weird.
It's a known bug in Choices. Several Filament issues reference it, and there's a PR open on Choices which seems like it might fix it, which @Zep Fietje bumped earlier this year - see his comment on the PR, and the first Filament issue he references.
It's a "first press" issue, if the character isn't printable. You get the same thing if you tab into a searchable select, then hit down arrow, without first typing a printable character.
https://github.com/Choices-js/Choices/pull/1104
GitHub
Fix various "first press" bugs on single select dropdowns by krisre...
I'm not able to fully test this PR against international languages, and there's a minor behavioral change worth explaining:
Previously, with a single select dropdown that has the focus, it ...
Apologies if I'm preaching to the choir, but this is one of my pet peeves. Obviously depends on what you're selecting, and how busy you expect your server to be, but beware of "premature optimization". My advice is always to wait until performance is actually objectively suffering before starting to do things like caching queries, which can often lead to unintended edge case, or simply don't yield any meaningful performance benefit.
But if you do want to cache it, just using Laravel's cache with something like this will work.
Yeah Choices.js seems to be pretty dead unfortunately... no release since November last year. Not sure what we can do here.
And mtriff, the only person I've seen making any commits in the last 3 years on Choices, has literally zero github commits this year.
So yeah, I'd say it's dead. Time to find something else. Or fork Choices.
I've discussed this with @Dan Harrin a couple of times already. I feel like maintaining a fork of Choices.js would be a nightmare. Building a custom Alpine.js select (IIRC that's what v2 had initially) would also be a nightmare... But yeah let's discuss this and see what the options are
Custom Alpine Select is probably better than Choices Fork π
At least it uses Alpine then.
i think some of you underestimate how much the Select actually does, and how much pain it caused me when I maintained my own Alpine one. dynamic options, dynamic search results, preloaded search results, being able to fetch labels for options that don't exist, reacting to backend state changes, it's very very complex. plus, Zep has invested an enormous amount of time into styling it so it looks nothing like Choices
also, how many hours I spent looking for a Choices replacement already
as far as I am aware, the Choices we currently have works very well apart from that weird issue with pressing arrow keys in search
so I am not intersested in replacing it with another library that (as far as I am aware) doesn't exist yet
@Dan Harrin Oh, I totally get it, I went thru the same with Select2 in Fabrik. But Choices has seemingly been abandoned, with only one round of a dozen commits in November of last year over the last 18 months ((by a guy who hasn't committed anything in Github this year), 190+ open issues and open PR's going back 4 years. So at some point, it's going to break. Without a replacement in the pipeline, forking Choices may become the only option.
If I could add, as a bit of an outside observer, if Choices is used by so many people and there's still a lot of activity that is simply blocked by absent/busy maintainers, then perhaps a fork is the way to go. If informed/aware, I'm sure many of the people with open PRs and issues who otherwise like/use Choices would strongly consider shifting to the forked Choices. π€