Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Losing input events #214

Closed
purcell opened this issue Aug 19, 2014 · 25 comments
Closed

Losing input events #214

purcell opened this issue Aug 19, 2014 · 25 comments
Labels
Type: Bug For bugs and any other unexpected breakage

Comments

@purcell
Copy link
Contributor

purcell commented Aug 19, 2014

I have a mithril component with a big list of stuff which gets filtered onkeyup as the user types into a search field.

The filtering is pretty slow, but what concerns me is that some key events get dropped as the user types. It seems like events disappear while the view is getting recomputed. Is this behaviour expected, and if so, is there an idiomatic workaround I'm missing?

You can try it out live here.

The relevant snippet is this:

m("input.form-control[type=search]", {placeholder: "Enter filter terms", autofocus: true,
                                 value: ctrl.filterTerms(), onkeyup: m.withAttr("value", ctrl.filterTerms)}),

and the full code for the component is here.

Cheers, and thanks again for mithril!

@lhorie
Copy link
Member

lhorie commented Aug 19, 2014

From a glance it seems that what is happening is that when a redraw happens, there's a lot of code running synchronously to recompute the visibility of all the rows, during which time the browser is becoming unresponsive.

You can do a few things to try and improve performance:

1 - when filtering out elements, remove them from the DOM instead of hiding them, and use keys on the tr's, i.e. change this

ctrl.sortedPackages().map(function(p) {
            return m("tr", { "class": visible[p.name] ? '' : 'filtered'},

to this

ctrl.sortedPackages().filter(function(p) {return visible[p.name]}).map(function(p) {
            return m("tr", { "class": visible[p.name] ? '' : 'filtered', key: p.name}, //assuming p.name is unique among tr's

This decreases the amount of virtual elements that need to be diffed, and reuses DOM elements.

2 - Consider using the occlusion culling technique from this article: http://lhorie.github.io/mithril-blog/an-exercise-in-awesomeness.html

The recurring idea here is to not render things into the document if you can avoid it. I counted around 24 thousand DOM elements on screen on page load. If you can decrease that number, things should get snappier.

@purcell
Copy link
Contributor Author

purcell commented Aug 19, 2014

Thanks. I'll take a look at that and get back to you. I know I've tried a couple of tricks to minimize the rendering overhead.

What's curious to me, though, is the lost events, because I'm pretty sure that until a very recent version of mithril, I didn't ever lose keypresses: they would just get queued up and then trigger a subsequent redraw cycle.

@lhorie
Copy link
Member

lhorie commented Aug 19, 2014

Re: event queuing/loss: there was a recent change that makes redraw happen asynchronously (basically so that near-simultaneous events like keypress/input don't trigger double redraws. The updates to the model data should still happen synchronous to the event handlers though, so it seems weird that you'd lose the values from those events. It might be a race condition between what the UI is trying queue up and the render trying to update the input with the model value at the same time.

I'd have to look into it more

@purcell
Copy link
Contributor Author

purcell commented Aug 19, 2014

there was a recent change that makes redraw happen asynchronously (basically so that near-simultaneous events like keypress/input don't trigger double redraws.

Sounds like it could be a candidate.

I can easily zip up the application code if it would help: it's just a few static files, including JSON data which isn't available in the repo I linked to.

@lhorie
Copy link
Member

lhorie commented Aug 19, 2014

That'd be helpful. Thanks

@purcell
Copy link
Contributor Author

purcell commented Aug 19, 2014

On its way... :-)

@purcell
Copy link
Contributor Author

purcell commented Aug 20, 2014

Re. the optimisations you suggested: "key" was the trick I was missing. I'd previously been conditionally creating the elements when filtering, and noticed that mithril was recreating the elements as the user backspaced characters out of the filter term. So I switched to toggling a class attribute. "key" is much better, thanks.

I'll try to survive without the extra mental overhead of occlusion culling for as long as I can. :-)

@taylorhakes
Copy link

I am also losing input events. Basically, the async redraw will overwrite an input value when typing quickly.

I implemented the TODOMVC in Mithril and ran into this when running the browser Selenium tests. I created a branch to show it. https://github.com/taylorhakes/todomvc/tree/mithril-async-bug . If you run the browser tests with -framework=mithril you can see the issue. The only workaround right now is to use oninput, but occasionally this fails as well. It is much more reliable though.

I would submit a pull request, but I am not sure the best fix. My thought process would be to not trigger a redraw when the value property changes. The change in value is already updating the model. I don't think the model should then update the DOM again.

@lhorie lhorie added the Type: Bug For bugs and any other unexpected breakage label Sep 15, 2014
@lhorie
Copy link
Member

lhorie commented Sep 15, 2014

Hmm yeah, this one is tricky. One of the problems is that diffing isn't aware of what triggered the redraw (i.e. it could be an event from an input, but it could also be a number of other things). I'm going to need to try out different things to figure out a reasonable way to fix this.

@lhorie
Copy link
Member

lhorie commented Sep 15, 2014

@taylorhakes on a completely separate note, there's a todomvc implementation here that @pygy and I were working on for a perf benchmark: http://lhorie.github.io/todomvc-perf-comparison/todomvc-benchmark/todomvc/mithril/#/

I just found out yesterday that there's a discussion over at the todomvc repo, as well that is related ( tastejs/todomvc#936 (comment) )

Maybe we can coordinate to get it submitted there

@taylorhakes
Copy link

@lhorie Sounds good. My TodoMVC is passing all functional tests (when using oninput). Here is a link to mine https://github.com/taylorhakes/todomvc/tree/mithril/architecture-examples/mithril .

I can help out on yours if you feel it is better. Up to you.

@lhorie
Copy link
Member

lhorie commented Sep 17, 2014

@taylorhakes Oh does that mean your implementation is already submittable? If so, your code looks readable and everything, so I'd say go ahead and submit yours.

@eddyystop
Copy link

In reference to the @lhorie post above, github.com/tastejs/todomvc has agreed to add an implementation in mithril. Nice. It wouldn't have happened without a bunch of +1 being posted.

@lhorie
Copy link
Member

lhorie commented Sep 25, 2014

I've added logic based on @taylorhakes first attempt, combined with an activeElement sniff

This will be in v0.1.22

@lhorie lhorie closed this as completed Sep 25, 2014
@purcell
Copy link
Contributor Author

purcell commented Sep 25, 2014

Nice. I'll give it a whirl!

@lhorie
Copy link
Member

lhorie commented Sep 25, 2014

Cool. Let me know how it goes

bsuh added a commit to bsuh/mithril.js that referenced this issue Oct 1, 2014
lhorie pushed a commit that referenced this issue Oct 2, 2014
…olve double-redraws in onkeypress+oninput
lhorie added a commit that referenced this issue Oct 2, 2014
@bsuh
Copy link
Contributor

bsuh commented Oct 3, 2014

@lhorie The fix for #214 caused #288 but the fix for #151 did not cause #214. This is just an inherent issue with asynchronous rendering and still exists.

To actually test the issue, you have to run grunt connect:server:keepalive and visit http://localhost:8000/tests/e2e/test.html

Unfortunately PhantomJS doesn't support requestAnimationFrame and the issue does not show up running grunt teste2e. You need an actual browser.

I see three options:

  1. Adopt something like in Simplify redraw logic to always be async #270 where you keep deferring updating the node.value until cachedAttr matches node.value. I'm playing around with it right now
  2. Encouraging the use of oninput/onchange for bidirectional bindings instead of onkeydown/onkeyup/onkeypress, and discouraging any complex filtering on those events. As @taylorhakes said input events still can be lost if the rendering takes too long.
  3. Synchronous rendering for certain events.

@bsuh
Copy link
Contributor

bsuh commented Oct 3, 2014

3 doesn't seem like a good option. It makes the #214 regression test fail sometimes.
keyup 'a' -> synchronous render 'a'
keyup 'b' -> synchronous render 'ab'
keyup 'c' -> synchronous render 'abc'
keyup 'd' -> synchronous render 'abcd'
submit event -> asynchronous render ''
keyup 'enter' -> synchronous render to either 'abcd' or '' depending on the timing.

Same thing with option 1, where the submit event and keyup enter fight to set input value.

@bsuh
Copy link
Contributor

bsuh commented Oct 3, 2014

I checked out MELPA and using the latest commit version on next there's no key input loss. The regression tests might be too strict and this might not be an issue for real world scenarios where humans are typing at sane speeds.

@bsuh
Copy link
Contributor

bsuh commented Oct 3, 2014

Nevermind this issue is ok and should remain closed. Sorry for the confusion.

@kvanbere
Copy link

kvanbere commented Oct 3, 2014

@bsuh This is certainly an issue with selenium testing though. We would want to test without delays ...

lhorie added a commit that referenced this issue Nov 13, 2014
brandonPurvis added a commit to brandonPurvis/osf.io that referenced this issue Feb 26, 2016
"onkeypress" would cause some keypresses to be missed. Potentially related to a mithril issue. MithrilJS/mithril.js#214
@naveennagan
Copy link

naveennagan commented Jun 12, 2017

let m = require("mithril");

let keymap = {
  "13": "enter"
};

let keys = {
  onKey: function (keyVal, callback) {
    return function (e) {
      e.redraw = false;
      var key = e.keyCode + "";
      var keyInput = keymap[key];
      if (keyInput && keyInput === keyVal) {
        setTimeout(function () {
          m.redraw();
          callback();
        }, 0);
      }
    };
  }
};

module.exports = keys;

@dead-claudia
Copy link
Member

@naveennagan Could you file a new issue explaining what went wrong?

@naveennagan
Copy link

naveennagan commented Jun 12, 2017

@isiahmeadows i was just giving a solution for losing input events.
My approach was to pause redraw for sometime and then upon capturing enter key, push the callback to event loop(in timeout ) and then trigger the callback preceded by a redraw.

@dead-claudia
Copy link
Member

Oh okay. Note that this bug was filed for 0.2.x, and we're on v1.1 now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Type: Bug For bugs and any other unexpected breakage
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants