tildeio / htmlbars

A variant of Handlebars that emits DOM and allows you to write helpers that manipulate live DOM nodes
MIT License
1.61k stars 193 forks source link

[BUGFIX] Fix text input cursor behavior #447

Closed mmun closed 8 years ago

mmun commented 8 years ago

People are using bound <input>s. For the most part everything works great.

<input type="checkbox"
       checked={{user.isCool}}
       oninput={{action 'saveCoolness' user}} >

<textarea value={{user.description}}
          oninput={{action 'saveDescription' user}} >
  actions: {
    saveCoolness(user, event) {
      user.set('isCool', event.target.checked);
      Ember.run.throttle(user, 'save', 500);
    },

    saveDescription(user, event) {
      user.set('description', event.target.value);
      Ember.run.throttle(user, 'save', 500);
    }
  }

But not everything is rainbows and butterflies. Text inputs have an annoying wart. Whenever you update their value, the cursor is moved to the end of the input. This becomes a problem when you are trying to edit text in the middle of a text input. Curiously, this issue doesn't affect <textarea> (confirmed on Firefox/Chrome/Safari).

Here's an ember-twiddle that show cases the current state of affairs.

Why does this happen?

A little background first. When you edit a text input through the browser GUI the oninput event is triggered. When you change the input element's value property, the oninput event is not fired, but the cursor is set to the end of the text field.

Now let's say you write

<input value={{user.name}} oninput={{action 'updateName'}}>

and

actions: {
  updateName(event) {
    this.set('user.name', event.target.value);
  }
}

If you edit the text input the browser will internally update the value property on the input element and then fire the oninput event. Our handler mutates the value of user.name. This triggers the value={{user.name}} binding to update, or in other words calls

input.value = user.name

Proposed fix

In order to fix this we can avoid setting .value when we don't need to. This will skirt the issue of resetting the cursor when value is mutated. Specifically, we add a guard to PropertyAttrMorph so that we avoid calling input.value = newValue if the follow conditions are met:

Here is an ember-twiddle with the patch applied. Note how editing the Name text input works correctly now. Even dead keys work just fine! (Try entering Option-e + e in the text input. It should generate a "é" character.)

Trade-offs

Making this change has a small trade off. It alters DOM semantics so that, when the conditions above are met, we treat the mutation as a no-op rather than a set-the-value-to-what-it-already-was. We believe that this is a very small price to pay for a massive improvement in developer ergonomics.

cibernox commented 8 years ago

This makes me sooo happy. No more input components.

stefanpenner commented 8 years ago

Lgtm

cibernox commented 8 years ago

As a side note, even if you're probably aware, using native inputs is more than on order of magnitude faster and memory is at least 1/3 smaller.

cibernox commented 8 years ago

Quick benchmark (without this fix): https://benchmark-inputs.pagefrontapp.com/input-helper

Check console.debugs