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

Cross-Controller Event callback lacks access to intuitive this #666

Closed
drewlustro opened this issue Mar 9, 2023 · 7 comments
Closed

Cross-Controller Event callback lacks access to intuitive this #666

drewlustro opened this issue Mar 9, 2023 · 7 comments

Comments

@drewlustro
Copy link

drewlustro commented Mar 9, 2023

Capture 2023-03-08 - 000813

https://stimulus.hotwired.dev/reference/controllers

To my dismay, the above example yields an execution scope without ref to EffectsController. No access to values, targets, etc.

For example, I define a value on EffectsController like so:

<div data-controller="clipboard" data-action="clipboard:copy->effects#flash>
  <div data-controller="effects" data-effects-foo-value="123" />
</div>
class EffectsController extends Controller {
 static values = {
   foo: Number
  }

  flash() {
    console.log(this.fooValue); // ❌ does not work
  }

 // using an arrow function has no effect
 // flash = (event) => { ... }
}

this.fooValue is undefined.

I now realize this is the Clipboard controller that triggered the event, but what I expect sounds like a common workflow... is it?

What is the Stimulus way to get a reference to EffectsController in #flash?

I could perform querySelector and read/write by hand, but that defeats the purpose of the framework.

@NakajimaTakuya
Copy link
Contributor

The value data attribute seems to be attached incorrectly.
The controller name is missing in your example.

❌ <div data-controller="effects" data-foo-value="123">
⭕️ <div data-controller="effects" data-effects-foo-value="123">

https://stimulus.hotwired.dev/reference/values

@drewlustro
Copy link
Author

drewlustro commented Mar 9, 2023

@NakajimaTakuya – Thanks for the reply. I made a mistake when transcribing the sample code into the issue description (obfuscating project code)–apologies for that! My attribute is formed as noted, and I'm experiencing the issue.

I updated the example to be more accurate.

@NakajimaTakuya
Copy link
Contributor

NakajimaTakuya commented Mar 9, 2023

@drewlustro

Thanks for providing the additional information. 😄
I wrote a similar code and it appears to be working fine in my environment.
https://codepen.io/nazomikan/pen/bGxYwXj?editors=1010

I would ask for the complete code if it is possible to provide it.

By the way, looking at the HTML you provided, there is still a problem with calling the effects controller function (flash) on the outside of the element specified in the "effects" controller.
This can only be referenced inside the controller at all times.

@seanpdoyle
Copy link
Contributor

In the sample code you've provided, the clipboard:copy->effects#flash value is defined in the [data-action] attribute on the element with the [data-controller~="clipboard"] declaration outside the scope of the element with the [data-controller~="effects"] element:

<div data-controller="clipboard" data-action="clipboard:copy->effects#flash>
  <div data-controller="effects" data-effects-foo-value="123" />
</div>

Since the effects portion of the clipboard:copy->effects#flash Action Descriptor refers to the effects Controller Identifier, that Action Descriptor must be within the scope of a controller with that identifier.

I'm surprised that the event is being routed at all.

In the sample code you've linked to, both the clipboard and effects identifiers are declared in the same [data-controller] token list:

<div data-controller="clipboard effects" data-action="clipboard:copy->effects#flash">
  PIN: <input data-clipboard-target="source" type="text" value="1234" readonly>
  <button data-action="clipboard#copy">Copy to Clipboard</button>
</div>

Since they're both at the same depth in the document, the [data-action="clipboard:copy->effects#flash"] is in scope of the controller with the effects identifier.

@drewlustro
Copy link
Author

@seanpdoyle – Thanks for the reply.

Is it possible to send an event from one controller to another when their corresponding DOM elements are parent/child?

@seanpdoyle
Copy link
Contributor

Is it possible to send an event from one controller to another when their corresponding DOM elements are parent/child?

@drewlustro dispatching an event from a parent will never reach a child. If you're able to do so, sharing your real code and your desired behavior might make some of these abstract concepts more concrete.

If you're unable to share your code, I've created a JSFiddle to demonstrate how you might control a controller from a parent controller using the recently added Outlets interface:

<script type="module">
  import { Application, Controller } from "https://cdn.skypack.dev/@hotwired/[email protected]"
  
  const application = Application.start()
  
  application.register("clipboard", class extends Controller {
    static outlets = [ "effects" ]
    static targets = [ "source" ]

    copy() {
      this.effectsOutlet.flash()
      navigator.clipboard.writeText(this.sourceTarget.value)
    }
  })
  
  application.register("effects", class extends Controller {
    static values = {
     foo: Number
    }
    
    flash() {
      console.log(this.fooValue) // 1234
    }
  })
</script>

<div data-controller="clipboard" data-clipboard-effects-outlet="#effects_element">
  PIN: <input data-clipboard-target="source" type="text" value="1234" readonly>
  <button data-action="clipboard#copy">Copy to Clipboard</button>
  
  <div id="effects_element" data-controller="effects" data-effects-foo-value="123" />
</div>

When a controller uses an Outlet, it can control any element anywhere on the page. It's certainly a powerful tool, but it comes at the cost of directly coupling one controller to another.

Without knowing the exact circumstances you're facing, it's difficult to provide helpful guidance. Some ideas to consider:

  • can the behavior be managed by a single controller extracted from combining both existing controller's code?
  • can you re-structure the HTML so that your code more closely resembles the documentation example that declares [data-controller~="clipboard effects"] on the same element?
  • can you push more of the rendering logic to the server, and act upon already rendered HTML made available through a <template> element? (for example: turbo_stream_button)

@drewlustro
Copy link
Author

@seanpdoyle – Thank you for the response! I took your advice and reconsidered how the controllers are designed. It is possible to colocate both controllers on one DOM element, so I refactored my code in that structure with success. Awesome!

In the original implementation, I'm not sure how the child DOM element was receiving the event dispatch, but it surely produced a rough learning experience.

Before resorting to filing this issue, I had also tried Outlets without success.

Off-topic, but some quick notes on that experience:

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