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

None of the Turbo events are not triggered on 422 #520

Closed
james-em opened this issue Jan 24, 2022 · 8 comments
Closed

None of the Turbo events are not triggered on 422 #520

james-em opened this issue Jan 24, 2022 · 8 comments

Comments

@james-em
Copy link

james-em commented Jan 24, 2022

Hi,

Kind of reopening #85

When you have form errors and render it using turbo streams

# Ruby
render :edit, status: :unprocessable_entity

# JS
document.addEventListener('turbo:load', () => queryDocumentForTomSelect());
document.addEventListener('turbo:render', () => queryDocumentForTomSelect());
document.addEventListener('turbo:before-render', () => queryDocumentForTomSelect());
document.addEventListener('turbo:frame-load', () => queryDocumentForTomSelect());

None of the Javascript events are triggered making impossible to load libraries such as Tom-Select or Bootstrap custom calendars.

Some people recommends to add data-controller attribute and use stimulus controller, but none of them show how to properly do it when you have many input you want to trigger or that maybe you already call another controller on the same input.

What are the proper workaround ?

Thank you

Edit

A dirty workaround would be

import { Controller } from "@hotwired/stimulus"

// Connects to data-controller="page-load"
export default class extends Controller {
  connect() {
    const event = new Event('page:load');
    document.dispatchEvent(event);
  }
}

...

<body data-controller="page-load">

...

document.addEventListerner('page:load', () => { ... });

but keep in mind that javascript inside turbo-frame will still not reload on frame update

@seanpdoyle
Copy link
Contributor

Would listening for a turbo:before-stream-render event suit your use case?

@seanpdoyle
Copy link
Contributor

Some people recommends to add data-controller attribute and use stimulus controller, but none of them show how to properly do it when you have many input you want to trigger or that maybe you already call another controller on the same input.

If you're on Stimulus v3, you have access to target connection callbacks, which could help you avoid event listening entirely.

If your Stimulus controller is high-enough in the page's hierarchy, you could annotate elements with [data-$IDENTIFIER-target~="tomSelect"], then declare a tomSelectTargetConnected(target) callback and tomSelectTargetDisconnected(target) to manage installing and uninstalling whatever plugin you're using.

This avoids event listeners, page visits vs. Turbo Streams, etc because Stimulus manages the plugin entirely based on whether or not the element is present or absent in the document.

@james-em
Copy link
Author

james-em commented Jan 25, 2022

@seanpdoyle

Thanks for your reply. It's very appreciated !

The event turbo:before-stream-render is indeed called but loading plugins from there doesn't work. Probably if I use a setTimeout(() => { ... }, 1000) that would work but I find it ugly.

I went ahead and gave a try to your method:

import {Controller} from "@hotwired/stimulus"
import {loadTomSelect} from "../libs/tom-select";

// Connects to data-controller="tomselect"
export default class extends Controller {
  static targets = ["ts"]

  tsTargetConnected(selectInput) {
    if (selectInput.classList.contains('tomselected')) return;
    if (selectInput.classList.contains('no-ts')) return;

    if (selectInput.classList.contains('tomselect-ajax')) {
      loadTomSelect(selectInput, true);
    } else {
      loadTomSelect(selectInput, false);
    }
  }
}

Append to all my select data: { "tomselect-target": "inputs" } and it does indeed work flawlessly.

I would be happy with this solution, but there is one thing that bothers me and is doing this to my body (It's HAML)

%body{data: { controller: 'tomselect' }}

If I have multiple libraries that would get ugly, I would then need to do this:

<body data-controller="library1">
 <div data-controller="library2">
    <div data-controller="library3">
      content...
    </div>
</div>
</body>

Isn't there a better way?

@seanpdoyle
Copy link
Contributor

The data- attributes that power Stimulus accept token lists, so you can add controller identifiers or action descriptors as lists separated by spaces.

@james-em
Copy link
Author

The data- attributes that power Stimulus accept token lists, so you can add controller identifiers or action descriptors as lists separated by spaces.

You mean that

<body data-controller="library1 library2 library3">

would be valid?

@seanpdoyle
Copy link
Contributor

That's right! The same is true for data-action="click->library1#doSomething click->library2#doSomethingElse".

@james-em
Copy link
Author

That's right! The same is true for data-action="click->library1#doSomething click->library2#doSomethingElse".

Thank you I didn't know! Thanks for all the quick replies. Very appreciated. I'm closing the issue

@jdsampayo
Copy link

jdsampayo commented Jun 26, 2022

The solution of TargetConnected / TargetDisconnected is great because it works with any plugin (tomselect, flatpickr, etc). Basically the ones that use addEventListener("turbo:load") can be written like that.

Additionally, I found this wrapper that works great particularly for tom-select:
https://github.com/bbonamin/stimulus-tom-select

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

3 participants