Hyperhtml: Conditional attributes

Created on 16 Aug 2017  ·  30Comments  ·  Source: WebReflection/hyperHTML

Is there a way to apply attributes to an element based on a condition? The following does not work:

const maxlength = state.maxlength ? `maxlength="${state.maxlength}"` : '';
const fileElement = hyperHTML.wire(state)`<input type="file" ${maxlength}`>;

I prefer not to apply maxlength property if state.maxlenght is falsy.
Thanks!

help wanted invalid question wontfix

Most helpful comment

As always, great work @WebReflection! 👌

All 30 comments

In this case the only real way to achieve the result you want it so interface with the input directly.

Because wire returns DOM, you'd achieve the desired outcome as follows:

const fileElement = hyperHTML.wire(state)`<input type="file" />`;
if (state.maxlength) {
  fileElement.setAttribute('maxlength', state.maxlength)
} else {
  fileElement.removeAttribute('maxlength')
}

Thanks @joshgillies, I did not know that wire returns DOM, great solution, we'll go with this. 👍
Is there a plan to support this without using setAttribute/removeAttribute directly on the element?

This is a violation of the logic behind the engine

hyperHTML.wire(state)<input type="file" ${maxlength}>

hyperHTML does not handle strings, it handles DOM, and attributes always need to be assigned.

If you can write that in two lines, you can also do the following:

const fileElement = hyperHTML.wire(state)`<input type="file">`;
if (state.maxlength) fileElement.maxlength = state.maxlength;

which in turns, means you can create conditionally the right element right away.

const fileElement = state.maxlength ?
  hyperHTML.wire(state, ':with-ml')`<input type="file" maxlength=${state.maxlength}>` :
  hyperHTML.wire(state, ':no-ml')`<input type="file">`;

But what bothers me is this sentence:

I prefer not to apply maxlength property if state.maxlenght is falsy.

What's wrong with the following ?

hyperHTML.wire(state)`<input type="file" maxlength=${state.maxlength || -1}>`

It should just work out of the box in every browser and being the layout generated dynamically anyway, having maxlength there should not affect performance or even size, because indeed you use less code to obtain same result.

Is there a plan to support this without using setAttribute/removeAttribute directly on the element?

It's basically impossible to address attributes in that way with an engine that doesn't parse strings and specifications that don't let you know the variable name used for the interpolation.

Your eyes only can tell you passed a maxlength variable as interpolation there, JS has no idea, it just receives a string so I'd say to make it work there is a lot of bloat and over-engineered tricks to do that's just not worth it since you have easier and better ways to achieve the same.

@WebReflection

hyperHTML.wire(state)`<input type="file" maxlength=${state.maxlength || -1}>`

This was going to be my other suggestion! 👍 Of course, ignoring the fact maxlength doesn't apply to type="file" in any case.

P.S. about this, as heads up

I did not know that wire returns DOM

wires returns either a single DOM node, if these represent a container,
but these could also return an Array of elements

hyperHTML.wire()`
<p>1</p>
<p>2</p>`;

Arrays are automatically handled in bound context but if you need to modify wired elements at runtime, just remember multi elements mean Array, one element means DOM node.

Thanks guys!

I prefer not to apply maxlength property if state.maxlenght is falsy.

We create a custom element, it has the same attributes as the file input element (maxlength, accept, name etc.), thats why we want to avoid adding unnecessary attributes if the component does not have it so we don't have to deal with the defaults.

Arrays are automatically handled in bound context but if you need to modify wired elements at runtime, just remember multi elements mean Array, one element means DOM node.

👍

we don't have to deal with the defaults

remember boolean attributes will be automatically added/removed:
https://viperhtml.js.org/hyperhtml/documentation/#essentials-5

by the way ... now that I think about it ... there is a little enhancement I could easily bring in to the engine.

If the attribute content is either null or undefined, this will always be removed until its value has a meaning.

This should make it possible to just write the following:

hyperHTML.wire(state)`<input type="file" maxlength=${state.maxlength}>`

and be sure that if state.maxlength is null or undefined, it won't be there as attribute.

This looks like a nice feature to have for cases like yours.

What do you think?

Yes that would cover our case and I think it makes sense. This would be nice!

To be honest I had to check whether that wasn't the existing behavior, and I would consider passing null or undefined as an attribute value a valid hint that we're not interested in having that attribute make its way into the DOM.

That's already the case for events. Boolean attributes are handled differently, null or undefined as hint makes sense to me too, specially because on the DOM world undefined doesn't exist as a concept.

document.body.getAttribute('whatever') === null true even if whatever is not there.

So, it's a go?

P.S. now that I've checked the code, for special attributes that's already the case so the maxlength should be covered. however, other attributes might be dropped when the value is null or undefined.

I'm definitely +1 on this! 👍

v1.1.0 is out with this behavior. Please give it a try but again, this specific maxlength case was already covered because maxlength is a special attribute and as such, is alwas removed from the DOM but it's applied behind the scene.

Thanks for the quick and efficient brainstorming, now I also have to implement setAttributeNode and removeAttributeNode in basicHTML :smile:

done :sunglasses:

As always, great work @WebReflection! 👌

I just tried it with a name attribute. It works great until I try to remove the attribute from custom element, I get a name="null".

const fileElement = hyperHTML.wire(state)`<input type="file" name="${state.name}">`;

My custom element sets state.name on attribute change callback. If the attribute gets removed, value will be null.
Any idea?

watch out, you're dealing with special properties, you should not use setAttribute or getAttribute there, but you also should never remove a name from an input anyway.

If you specify in the first place, what's the reason you'd remove it ?

In any case, this is not a regression, this has always been the case for special attributes.

All I can do there, is fallback to an empty string because the behavior is native, not something hyperHTML is responsible for.

Example

var input = document.createElement('input');
input.outerHTML; // <input>
input.name = 'test';
input.outerHTML; // <input name="test">
input.name = null;
input.outerHTML; // <input name="null">

Same goes for value. These are special attributes with a special meaning.

Like you would never do input.value = null on every day DOM but input.value = '' instead,
I suggest when you want to deal with special attributes, you just pass value=${state.value || ''}

I gotta think about what should be the expected behavior, truth is i don't wan't to over complicate due edge cases

P.S. actually maxlength doesn't suffer the name issue because it's handled as non special
the property name is maxLength so it works like a non-special attribute. Yaiiii quirks!

What would you expect from <input disabled=${null}> ?

  • the disabled attribute is there but it's false
  • the attribute is removed

For consistency sake, and after some test, it looks like it's safe to removeAttrubute when this is a special one, because setting it later on would just recreate it, as it is for the name.

Sorry, in case it wasn't clear, I'm planning to make null or undefined with attributes an explicit intent to always remove the attribute (or try, at least)

I hope that's fine. Yay?

You are right, we should not remove the name attribute. We use these component in several frameworks, and for example Angular (1) handles attributes pretty weird way if you bind something to an attribute: <x-component name="{{ model.name }}"><x-component>

The fallback method to empty string will work for us perfectly.
Thanks a lot, hyperHTML is awesome! 👏

jQuery "attr(name,value)" behaves like this:
http://api.jquery.com/attr/#attr-attributeName-value

null : removes the attribute
undefined : the current value will not change.

if the "undefined" behavoir makes no sense, i prefer to throw a exeption if
"undefined". To be armed for a future api / behavoir

2017-08-16 14:16 GMT+02:00 Andrea Giammarchi notifications@github.com:

Sorry, in case it wasn't clear, I'm planning to make null or undefined
with attributes an explicit intent to always remove the attribute (or try,
at least)

I hope that's fine. Yay?


You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/WebReflection/hyperHTML/issues/87#issuecomment-322752018,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAA_xvgT7BFd504LgQ3IVj6fm2kdKu6aks5sYt2dgaJpZM4O4coo
.

--
Freundliche Grüsse
Tobias Buschor

shwups GmbH
Unterlindenberg 206
9427 Wolfhalden

+41 76 321 23 21
shwups.ch

The fallback method to empty string will work

but it wouldn't work for .disabled or other edge cases.

The current v1.1.1 I've just published tries to remove the attribute, even the name, if you pass null.

As summary, passing null or undefined as attribute value:

  • if it's an event, it removes the previous event hanlder
  • if it's a regular attribute, it removes it
  • if it's a special attribute, it sets the value as is, granting native behavior, plus it tries to remove it regardless (i.e. input.value that's never reflected through the getAttribute('value'))

I hope this now works well and for everyone.

It looks like the best, consistent, behavior to implement. It's been tested down to IE9 and it's just fine.

@nuxodin thanks for chiming in.

Glad jQuery drops the attribute if you pass null, but I don't like ignoring undefined.

var input = document.createElement('input');
input.value = 123;
input.value; // "123" as string, always
input.value = null;
input.value; // "" empty string
input.value = undefined;
input.value; // "undefined" 

I believe the latter case is never a desired behavior and the current status is cleaner and easier to reason about. You pass a value, if you don't have it, that won't be there.

As simple as that.

makes sense

2017-08-16 14:31 GMT+02:00 Andrea Giammarchi notifications@github.com:

@nuxodin https://github.com/nuxodin thanks for chiming in.

Glad jQuery drops the attribute if you pass null, but I don't like
ignoring undefined.

var input = document.createElement('input');input.value = 123;input.value; // "123" as string, alwaysinput.value = null;input.value; // "" empty stringinput.value = undefined;input.value; // "undefined"

I believe the latter case is never a desired behavior and the current
status is cleaner and easier to reason about. You pass a value, if you
don't have it, that won't be there.

As simple as that.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/WebReflection/hyperHTML/issues/87#issuecomment-322755663,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAA_xgY5BCSaj_ygAKAW-KU4NY55AKm2ks5sYuEhgaJpZM4O4coo
.

--
Freundliche Grüsse
Tobias Buschor

shwups GmbH
Unterlindenberg 206
9427 Wolfhalden

+41 76 321 23 21
shwups.ch

So thanks @nuxodin 'cause I had to also fix the input.value returning "undefined" and v1.1.2 it is.

Now there is a proper behavior I'm going to write in the documentation.

Happy hyperHTML :tada:

Just tested 1.1.1 with name="" and I also tried input's accept="" attribute, works perfectly, thank you!

please use 1.1.2 or undefined might bit you :wink:

thanks for reporting back everything is fine

Was this page helpful?
0 / 5 - 0 ratings

Related issues

diodac picture diodac  ·  3Comments

davidmerrique picture davidmerrique  ·  7Comments

BentleyDavis picture BentleyDavis  ·  8Comments

jaschaio picture jaschaio  ·  3Comments

dfleury picture dfleury  ·  7Comments