alpinejs / alpine

A rugged, minimal framework for composing JavaScript behavior in your markup.
https://alpinejs.dev
MIT License
28.21k stars 1.23k forks source link

Select option is not set by default #872

Closed leecolarelli closed 3 years ago

leecolarelli commented 3 years ago

Found a small issue today when trying to set up a select option on an alpine form... if the x-data for the select x-model is blank, and the form is submitted, then no data for the select form is included.

This seems obvious since the user didn't choose an option, and the x-model/x-data is blank on page load, but often a user will leave a select form if the correct option is already displayed.

Here's a codepen to highlight the issue - https://codepen.io/leecolarelli/pen/PozxLGY

I think therefore that it would be better if alpine has a way to load the displayed option if no selection is made.

I was going to try and make a pull request but don't currently have time, and probably don't have the skills :)

ryangjchandler commented 3 years ago

The option is not being selected because it is disabled. If you remove that attribute, it will be selected correctly.

Disabled options are generally used for default values when the field is required, forcing people to select and option and not letting them choose the default one. In your case though, the field isn't required by the sounds of it so there should be no harm in making that default option active (not disabled) or instead choosing a better default value.

pomartel commented 3 years ago

I think what @leecolarelli means is that it should be possible to set the x-data with the initial value of a form field. Here is an example:

<form x-data='{ name: null }'>
    <input type='text' value='John' x-model='name' />
</form>

If the name is set to null or to an empty string but the input binded to the x-model has an initial value, then this value should be used. In this case, the name variable should be initialized with "John".

I don't know about other back-end frameworks but this would play nicely with Ruby on Rails.

KevinBatdorf commented 3 years ago

How does Vue handle it?

ryangjchandler commented 3 years ago

Ah right, I'm following now. Like @KevinBatdorf said, see what Vue does and that's generally best way to go with how Alpine handles it.

pomartel commented 3 years ago

v-model no longer cares about the initial value of an inline value attribute. For predictability, it will instead always treat the Vue instance data as the source of truth. https://vuejs.org/v2/guide/migration.html#value-Attribute-with-v-model-removed

So I guess that settles it! Someone released a vue plugin to bring back the functionality. Maybe that would be possible with Alpine too as a plugin.

SimoTod commented 3 years ago

Sine you write the html, why don't you put the default value in x-data?

pomartel commented 3 years ago

@SimoTod Yes, that's how I'm handling it right now. I inject the model values from the backend in the x-data. It's just that model values are already set in the form fields by default in Rails. So it doesn't feel as DRY to set them again in the x-data.

I don't know if the same goes with other frameworks such as Laravel or Django. I just wish there was a way to "glue" this together somehow!

KevinBatdorf commented 3 years ago

Maybe be a good addition to the magic helpers library? Something like x-init="$formInit()", which could scan all input types and find any server set values... maybe? I don't feel too strongly about it.

https://github.com/alpine-collective/alpine-magic-helpers

pomartel commented 3 years ago

I wouldn't bother with it unless a big chunk of Alpine users are also Rails devs. Otherwise it's not a big deal to just inject model attributes in the x-data. For future Rails devs bumping into this ticket, this is the most elegant way I found to make this work:

<form x-data='<%= @my_model.attributes.slice('first_name', 'last_name', 'email').to_json %>'>
HugoDF commented 3 years ago

We good to close this @leecolarelli ?

You should be able to use x-init (or even x-data) to read the current value from the DOM into state.

<div 
  x-data="{ 
    selected: $el.querySelector('option[selected]').value,
    inputValue: $el.querySelector('input').value
  }"
>
  <select x-model="selected">
    <option selected value="hello">hello</option>
  </select>
  <input value="world" x-model="inputValue" />
  <p>Selected: <span x-text="selected"></span></p>
  <p>Input value: <span x-text="inputValue"></span></p>
</div>

(As a Codepen: https://codepen.io/hugodf/pen/Vwjqrzq)

leecolarelli commented 3 years ago

Yes sounds good! Thanks

lgoudriaan commented 3 years ago

I actually would like to see this functionality. This will save a lot of work. I'm working with asp.net framework.

HugoDF commented 3 years ago

I actually would like to see this functionality. This will save a lot of work. I'm working with asp.net framework.

If you're willing to create a PR go for it.

Unfortunately since there's a userland workaround and equivalent frameworks don't do this it's very unlikely to be accepted.

pomartel commented 3 years ago

If there is enough interest, I could give it a go as a simple magic helper bundled with the other magic helpers or in its own repo. Here's what I have in mind.

Does that make sense?

HugoDF commented 3 years ago

@pomartel sure, might be worth moving the discussion to https://github.com/alpine-collective/alpine-magic-helpers/discussions/33 since we're not talking about Alpine.js changes any more.

miquelbrazil commented 1 year ago

I know this is a closed issue, but I stumbled on it while I was looking for a solution to handle this same problem.

I ended up coming up with a solution that I figured I'd share here.

To use Alpine to build the options for a <select> input and set the correct default <option>, I use x-bind on the :selected attribute and then pass a constructed array of potential default values (i.e. [null, 0, '0', '']) and check this array against the actual value set in x-data or $store using the Array.includes() method. This lets you still use value as a static attribute or set it dynamically with x-bind and x-data.

The <option> tag ends up looking something like <option :selected="[null, 0, '0', ''].includes(var)" x-text=var disabled hidden value></option>

Caveats

  1. I don't include false as a value in the constructed array. You can only set these as a text value in an HTML form anyway, and type juggling would cause it to evaluate truthaly (if that's a word) – the listed options all evaluate as falsy.
  2. You'll notice I have the disabled, hidden and value attributes included with the "default" option. This allows the browser to remove it as an option from the <select> once a User clicks the input. You can remove those if you want the default also to be an option.

I put together my first pen on CodePen as a demo.