winter
winter copied to clipboard
Add dynamic option creation to dropdown
This allows the user to accept new options to be set on the dropdown field:
It's natively handled by Select2 as you can see into the documentation.
To active the option, we just need to set acceptNewOptions
to true
:
my_field:
label: my_label
type: dropdown
acceptNewOptions: true
The dropdown
expects a list of options which can take two different forms:
value only:
options:
draft
published
archived
or key/value :
options:
draft: Draft folder
published: Published folder
archived: Archived folder
how to deal with the second case when adding a new option/item?
@damsfx Whichever the way you pass the available options to the dropdown, it is initialized with values and labels. In your first case, both values and labels will be the same, in the second the labels are differents from the values that will be sent to the server, but all of that is just front-end sugar.
Using your first case is exactly the same as this with the syntax of the second:
options:
draft: draft
published: published
archived: archived
This is just repetitive so the syntax has been simplified.
When Select2 (the library used by the dropdown) adds a new key/value pair to it, it just act as your first use case, and that's all we need. Here is an example of value added dynamically.
Of course if you use your second case and dynamically add a value to the dropdown, the dropdown labels may seem "weird" like:
Draft folder
Published folder
Archived folder
custom-value
but that's how it work, and it's kind of logic: when you select one of the 1st, 2nd or 3rd label and send it to the server, the browser defines the value associated to it (draft
for Draft folder
, published
for Published folder
and so on) and you don't have to care of, but if you send a custom value, you exactly know what you send because key = value.
How is this handled with the get*Options method? Is the custom value merged into that array of options? Would this be supported for type: relation dropdowns? Also, we should probably name it allowCustom
instead.
The get*Options method works well with this behavior. For now it does not merge the value into the array though, because there is no AJAX.
There is a callback that we can override to do some stuff during the addition of the value, it is not intended to do that but it's a possibility. It's designed to modify the value or label before adding it, or change the position of the inserted label into the dropdown.
There is some additional work to be supported by the relationship dropdowns as you may guess. The good way to do (I guess) would be to open a popup including the form of the related model to create it dynamically using deferred bindings and then select it.
I could work on this feature later if we decide of how it should work.
@RomainMazB from a UI perspective, I would want to see it show something to indicate that if you type a value that doesn't exist as an existing, it will be added as an option. With your example in your OP, option "c" looks like a legitimate value since it doesn't look any different to options "a" and "b" which do exist. Perhaps it could show Create "c"
as the option in the dropdown (maybe the prefix itself can be customisable too).
@bennothommo yeah, I can easily prefix the just created value, this is why the method I may use to send the AJAX request is designed for at its basis: see here
@bennothommo with dropdown "templating" it's possible to add an icon.
In certain circumstances is more useful than a text.
@damsfx yeah that's cool if we use an icon - I was merely suggesting we need something.
Ok: I've made the modifications asked.
I made some test with default AJAX events like dependsOn, it works very well as is!
I made a video to show all the actual behavior.
https://user-images.githubusercontent.com/53976837/119276618-6932ae00-bc1b-11eb-9f57-a0d524d5ea2f.mp4
I agree with @bennothommo that we need something to indicate that custom user provided options are not part of the default set of options, but I'm not sure the plus icon is the way to go hear. Are there any examples of this UX pattern elsewhere in the world that we could pull inspiration from?
The only one that came up to my mind is the github's branch dropdown but it creates the branch and reload the page instantly so we can't get inspiration on "how should we make it clear that this tag has just been created on the fly"
https://user-images.githubusercontent.com/53976837/119489207-bf0e6f80-bd5b-11eb-858f-4dbf174d632d.mp4
What could be (easily) done:
- Prepend the tag to the list instead of append it
- Change the "plus" icon to something else, maybe the terminal icon which is used into component to set custom value to property, or the pencil outline icon:
- Increase the left padding
- Adds a title attribute which specifies that it has just been created as a custom value
I'm not the most UX-sensible guy so my suggestions are maybe not so accurate
@RomainMazB I've had another look at this. I'm finding it difficult to see the use case. You mentioned here:
There is a callback that we can override to do some stuff during the addition of the value, it is not intended to do that but it's a possibility. It's designed to modify the value or label before adding it, or change the position of the inserted label into the dropdown.
What would the intended use case be for that? Is it the case that people wanting to use this particular allowCustom
property will have to set up some sort of handler to save the new option, otherwise it disappears when loading the form again?
No this is not accurate anymore since all the existing form ajax handlers work well without any custom additional stuff.
When I talked about that, I thought we'd have to do something to make dependsOn, trigger, preset and other existing behavior to work, I don't have any use case in my mind that would need more work on this.
@bennothommo And if you talked about the feature introduced by the PR in general: I have some. My actual use case is the one you can see in the video: I pre-filled the dropdown with all the Winter's events, but I let the user add some custom event he may have introduced by installing or created a custom plugin. If a custom value is inserted, the next time it will be into the dropdown.
In general, this feature will be used when you have a column that will be filled with the same value most of the time:
You could pre-fill the dropdown with the most used values but also allow custom additional value, that would be then saved and added to the drop-down.
The get*Options
method returns the form's model value passed through a unique rule, but you can still add one more:
public function getMeasureNameOptions()
{
// Prefill the dropdown with the already used values
$measures = self::query()
->select('measure_name')
->distinct('measure_name')
->get();
// Insert the actual form's model value to avoid it to vanish
// on eventual AJAX call that would refresh it, dependsOn in my use case
if ($this->measure_name) {
$measures->add(['measure_name' => $this->measure_name]);
}
return $measures->pluck('measure_name', 'measure_name');
}
// If a custom values is inserted here, it will be pre-filled on next model creation
With that look in mind, it looks more like an extension of the input type text than dropdown: At the beginning it could even be empty, and the more you enter custom values, the more you get your dropdown pre-filled with values you may potentially choose without even type on the keyboard. You could even order the values depending on their occurrence counting on the table.
In the plugin I'm actually working on, I already found two use cases that would increase the UX where the user will use many time the same value but still need to be able to insert a custom one. Without this feature, I'll need:
- A switch to select "I want to insert custom value" or "Select an already used value"
- A dropdown with the existing values
- An input type text to insert custom value
- And because I get two fields to set a single column, I'll need to register into the beforeSave event to see on which state was the switch and pick the good field value.
I could also use a basic input type text but that would increase the risk of user's wrong input.
@LukeTowers @bennothommo Any new thoughts on the appearance that would fit here? I know you are all busy with more important stuff but I feel like this one just need a pinch to get merged so we can move on.
Let me know if I'm wrong and this feature is definitely unwanted
@RomainMazB is there a PR to the docs providing explanation of how to use this along with the gotchas of what people should do differently for handling options when user defined custom options are available?
@LukeTowers I've just improved the documentation's PR with a full basic example here https://github.com/wintercms/docs/pull/14/commits/0b358397de575c5e8f100f84e114cc83184f3e21
This pull request will be closed and archived in 3 days, as there has been no activity in the last 60 days. If this is still being worked on, please respond and we will re-open this pull request. If this pull request is critical to your business, consider joining the Premium Support Program where a Service Level Agreement is offered.
This pull request will be closed and archived in 3 days, as there has been no activity in the last 60 days. If this is still being worked on, please respond and we will re-open this pull request. If this pull request is critical to your business, consider joining the Premium Support Program where a Service Level Agreement is offered.
This pull request will be closed and archived in 3 days, as there has been no activity in the last 60 days. If this is still being worked on, please respond and we will re-open this pull request. If this pull request is critical to your business, consider joining the Premium Support Program where a Service Level Agreement is offered.
This pull request will be closed and archived in 3 days, as there has been no activity in the last 60 days. If this is still being worked on, please respond and we will re-open this pull request. If this pull request is critical to your business, consider joining the Premium Support Program where a Service Level Agreement is offered.
@LukeTowers @bennothommo Any news on that one? LGTM and is documented, not a BC as it just adds an option to an existing field type.
As a use case, in my sunlab/wn-measures-plugin,
I'm waiting for this PR to introduce a dropdown listing all the modules' native events but with the ability to create "on-the-fly" new event name for the listeners. The video of this is described in this https://github.com/wintercms/winter/pull/151#issuecomment-846624450
@RomainMazB can you merge develop back into your branch and resolve the conflicts / recompile storm? I should be able to review it after that's done and it's probably good to go (although I'll want to tweak the docs PR a bit first too).
👀 What is the status of this? I could use this functionality
@RomainMazB are you still interested in getting this merged? If not, @AIC-BV are you interested in getting it merged?
TBH, as of today I do not use winter anymore on a daily basis. I can merge develop back into my branch but I do not have a working installation of winter to test it.
@AIC-BV, if I do the job, could you just test the feature before it get merged?
Fair enough @RomainMazB, I appreciate all your help and support over the years!
Still looks like a great feature to me! Don't need it straight away but can obviously test it
Implemented in https://github.com/wintercms/winter/commit/bb79834af1690c63cc642af72a560e5f7e0d3c4a. Thanks @RomainMazB!
I added a new option 'Scharnier' but its not in the list. I was expecting it to be permanently added. Understandable ofcourse since its not in the extraOptions array. Shouldn't it, ideally, be permanently added to the list? So that you can later on select all Items where value is 'Scharnier'
item:
tab: Item
label: Item
type: dropdown
emptyOption: -
options: getItemOptions
allowCustom: true
public function getItemOptions() {
return [
'tablet' => 'Tablet',
'legplank' => 'Legplank'
];
}
-- OK I needed to add this, but now I have no predefined fields (I guess they can be manually added there). But I think they're not really needed in my case! Thanks, great PR!
This code is based on the code on Github docs repository, but it is not yet live on the website. (as I said on discord, might be live by the time you read this)
public function getItemOptions()
{
// Prefill the dropdown with the already used statuses: [['status' => 'draft'], ['status' => 'published']]
$items = self::distinct('item')->get();
// Insert the actual form's model value to avoid it to vanish
// on eventual AJAX call that would refresh the field partial like dependsOn
if ($this->item) {
// The actual form's status could be a custom like ['status' => 'need review']
$items->add(['item' => $this->item]);
}
// Return a list of statuses: [['status' => 'draft'], ['status' => 'published'], ['status' => 'need review']]
return $items->pluck('item', 'item');
}