hotwired / stimulus

A modest JavaScript framework for the HTML you already have
https://stimulus.hotwired.dev/
MIT License
12.62k stars 418 forks source link

events aren't available via connect() #222

Closed turgs closed 5 years ago

turgs commented 5 years ago

Hello

I have a page that shows a form, which is mapped to a Stimulus controller, <form data-controller="form">.

Initial action on pageload, should fire a chain of events

When the page is displayed, I want to have the first field of the form focused, so the user can start typing. I do this in connect() using jQuery - old habits die hard :)

connect() {
   $('input#first').focus();
}

In the HTML, all input fields have an Stimulus action data-action="focus->form#addActiveClass blur->form#removeActiveClass".

<form data-controller="form">
  <input data-action="focus->form#addActiveClass blur->form#removeActiveClass" name="one">
  <input data-action="focus->form#addActiveClass blur->form#removeActiveClass" name="two">
  <input data-action="focus->form#addActiveClass blur->form#removeActiveClass" name="three">
</form>

Events don't fire on initial pageload

When doing an initial pageload, these events data-action don't fire when the connect() method calls triggers the focus event.

The field is focused, like the line in my connect() method specifies, but the addActionClass() method of my form controller is not called.

Events do fire if cached turbolinks page

Using turbolinks however, if I navigate away from the page with the form, and back to it, then the events do fire. The addActionClass() method of my form controller is called.

Thoughts

My guess is this is because the events are already attached to the field when turbolinks loads the page from cache, where as on initial pageload, the controller's connect() method fires before it's wired-up all the data-action linkages.

The reference docs say:

Connection

A controller is connected to the document when both of the following conditions are true:

  • its element is present in the document (i.e., a descendant of document.documentElement, the <html> element)
  • its identifier is present in the element’s data-controller attribute

When a controller becomes connected, Stimulus calls its connect() method.

Is this an issue with how I'm trying to use connect(), and what should I be doing different?

Or, should connect() be waiting until everything's wired up? (i.e. a third bullet-point is needed in the reference docs saying:

turgs commented 5 years ago

I know this is crazy, but if I wrap my call in connect() in the quickest of timeouts... it works.

connect() {
  setTimeout(function() {
    $('input#first').focus();
  }, 1);
}
sstephenson commented 5 years ago

Great catch. Swapping the order of these two lines in Application#start fixes the problem: https://github.com/stimulusjs/stimulus/blob/7f58b5597292e88423c3fa9ccd0e6f25ffc83192/packages/%40stimulus/core/src/application.ts#L29-L30

I believe this is only an issue on the initial page load because we start the dispatcher (and thus add event listeners for all actions already on the page) after we start the router (which initializes controllers for elements already on the page).