max-mapper / yo-yo

A tiny library for building modular UI components using DOM diffing and ES6 tagged template literals
1.33k stars 65 forks source link

controlled input on select tag requires both select value AND option selected #37

Open modernserf opened 8 years ago

modernserf commented 8 years ago

More of a documentation request than a feature request -- this is how to do a "controlled" (in the React parlance) version of a select tag:

const select = (options, value) =>
    yo`<select value="${value}">
        ${options.map((o) =>
            yo`<option value="${o.value}"
                ${value === o.value ? "selected" : ""}>
                ${o.label}
            </option>`
        )}
    </select>`

Note that you need to both set a value on the <select> tag and set the selected attribute on the selected <option>.

shama commented 8 years ago

It looks like selected wasn't included in the list of boolean attributes, that is now fixed.

Give this a try:

const select = (options, value) =>
    yo`<select>
        ${options.map((o) =>
            yo`<option value="${o.value}"
                selected="${value === o.value}">
                ${o.label}
            </option>`
        )}
    </select>`

More documentation on boolean attributes would be good!

modernserf commented 8 years ago

That's still not working for me. I'll put together a demo when I get a chance.

shama commented 8 years ago

@modernserf Try npm cache clean && rm -rf node_modules && npm i to get the latest dependency tree.

modernserf commented 8 years ago
$ npm ls bel

└─┬ yo-yo@1.2.2
  └── bel@4.4.3

this should work?

shama commented 8 years ago

Hmm yep, 4.4.3 should work. A demo of what's not working would be great then. Thanks!

timwis commented 8 years ago

FYI, this works for me


/**
 * Create a <select> element
 * @param {string[]|Object[]} items List of value strings or objects with value and label properties
 * @param {string} selected Value of selected item
 * @param {Object} attributes Object of attributes to apply to <select> element
 */
module.exports = ({ items = [], selected, attributes = {} }) => {
  return html`
    <select ${attributes}>
      ${items.map((item) => {
        const optAttributes = {
          value: item.value || item
        }
        if (selected && selected === optAttributes.value) {
          optAttributes.selected = 'selected'
        }
        const label = item.label || item
        return html`<option ${optAttributes}>${label}</option>`
      })}
    </select>`
}
modernserf commented 8 years ago

Example: https://esnextb.in/?gist=a9a9a8128e0e7ade7505e533c3854186

shama commented 8 years ago

Thanks! I'll take a look and see if I can find out why it's not working. I wonder if it's related to Babel parsing the template strings?

The main reason I prefer the syntax that supplies the attributes <option selected="${isSelected}"> over an expression <option ${attrs}> is because we can parse the HTML attributes if they're not in an expression. This allows us to make better optimizations when converting the template literal into elements.

timwis commented 8 years ago

Agreed @shama, actually the only reason I used an expression is because selected="false" still renders as selected. Though I may be thinking of checked. Basically, there's no false value technically for them in html

fjeldstad commented 8 years ago

I've also found that both select.value and option.selected is needed to be able to programmatically select an option (such as when the options are rendered first, and then an async action is selecting one of the options).

fjeldstad commented 8 years ago

I guess is has to do with yo-yo copying the value of the existing select element to the new one (https://github.com/maxogden/yo-yo/blob/master/index.js#L29).

yoshuawuyts commented 8 years ago

@Hihaj I'm not sure I follow: what issue are you experiencing?

fjeldstad commented 8 years ago

@yoshuawuyts I was just making the same observation as @modernserf in the original question; that you need to use both (select.value + option.selected) in order to control which option should be selected via some other action than the user actually picking it in the dropdown. For example if the <select> first renders with a couple of options and you (a little bit later) want to set one of the options as selected (based on some logic).

yoshuawuyts commented 8 years ago

@Hihaj thanks; that makes sense - feel we should address this indeed :sparkles: