winter icon indicating copy to clipboard operation
winter copied to clipboard

Add dynamic option creation to dropdown

Open RomainMazB opened this issue 3 years ago • 23 comments

This allows the user to accept new options to be set on the dropdown field: easy-bizi

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

RomainMazB avatar May 04 '21 20:05 RomainMazB

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 avatar May 04 '21 22:05 damsfx

@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. image

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.

RomainMazB avatar May 04 '21 23:05 RomainMazB

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.

LukeTowers avatar May 05 '21 02:05 LukeTowers

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 avatar May 05 '21 06:05 RomainMazB

@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 avatar May 05 '21 08:05 bennothommo

@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

RomainMazB avatar May 05 '21 11:05 RomainMazB

@bennothommo with dropdown "templating" it's possible to add an icon.
In certain circumstances is more useful than a text.

damsfx avatar May 05 '21 12:05 damsfx

@damsfx yeah that's cool if we use an icon - I was merely suggesting we need something.

bennothommo avatar May 06 '21 02:05 bennothommo

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

RomainMazB avatar May 23 '21 21:05 RomainMazB

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?

LukeTowers avatar May 24 '21 23:05 LukeTowers

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: image image
  • 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 avatar May 25 '21 11:05 RomainMazB

@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?

bennothommo avatar May 27 '21 09:05 bennothommo

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.

RomainMazB avatar May 27 '21 18:05 RomainMazB

@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.

RomainMazB avatar May 27 '21 18:05 RomainMazB

@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 avatar Jun 30 '21 21:06 RomainMazB

@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 avatar Jun 30 '21 22:06 LukeTowers

@LukeTowers I've just improved the documentation's PR with a full basic example here https://github.com/wintercms/docs/pull/14/commits/0b358397de575c5e8f100f84e114cc83184f3e21

RomainMazB avatar Jul 01 '21 07:07 RomainMazB

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.

github-actions[bot] avatar Aug 31 '21 00:08 github-actions[bot]

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.

github-actions[bot] avatar Nov 24 '21 00:11 github-actions[bot]

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.

github-actions[bot] avatar Jan 24 '22 00:01 github-actions[bot]

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.

github-actions[bot] avatar Apr 04 '22 00:04 github-actions[bot]

@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 avatar Aug 02 '22 11:08 RomainMazB

@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).

LukeTowers avatar Aug 04 '22 20:08 LukeTowers

👀 What is the status of this? I could use this functionality

AIC-BV avatar May 16 '23 13:05 AIC-BV

@RomainMazB are you still interested in getting this merged? If not, @AIC-BV are you interested in getting it merged?

LukeTowers avatar Dec 27 '23 22:12 LukeTowers

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?

RomainMazB avatar Dec 27 '23 23:12 RomainMazB

Fair enough @RomainMazB, I appreciate all your help and support over the years!

LukeTowers avatar Dec 28 '23 15:12 LukeTowers

Still looks like a great feature to me! Don't need it straight away but can obviously test it

AIC-BV avatar Jan 03 '24 07:01 AIC-BV

Implemented in https://github.com/wintercms/winter/commit/bb79834af1690c63cc642af72a560e5f7e0d3c4a. Thanks @RomainMazB!

LukeTowers avatar Feb 06 '24 22:02 LukeTowers

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'

image
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');
}

AIC-BV avatar Mar 29 '24 14:03 AIC-BV