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

Communicating between controllers #35

Closed
vojtad opened this Issue Jan 4, 2018 · 11 comments

Comments

Projects
None yet
7 participants
@vojtad
Copy link

vojtad commented Jan 4, 2018

I know this isn't exactly an issue, but I think it is a good question. I think of your controllers as controllers for components on a page.
How would you suggest to communicate between more controllers?

I have two components List (data rendered in a list) and ListFilter (bunch of selects and text inputs). List is loaded using AJAX. It should be reloaded every time ListFilter changes. I would like to use separate controllers for List and ListFilter to keep files small and organized.

So, how to trigger a List reload when ListFilter changes?

@georgeclaghorn

This comment has been minimized.

Copy link
Contributor

georgeclaghorn commented Jan 4, 2018

From a Controller instance, you can call this.application.getControllerForElementAndIdentifier:

import { Controller } from "stimulus"

export default class ListFilterController extends Controller {
  // ...

  reloadList() {
    this.listController.reload()
  }

  get listController() {
    return this.application.getControllerForElementAndIdentifier(this.element, "list")
  }
}
@vojtad

This comment has been minimized.

Copy link

vojtad commented Jan 4, 2018

Thanks! I think this is exactly what I was looking for.

I suppose I should pass the element List component is on as first argument for getControllerForElementAndIdentifier, right?

So when a List is next sibling to a ListFilter in the DOM, I should call getControllerForElementAndIdentifier(this.element.nextElementSibling, "list") to get the ListController instance, right? Or would you suggest different way to find the List's element?

@georgeclaghorn

This comment has been minimized.

Copy link
Contributor

georgeclaghorn commented Jan 4, 2018

I wouldn’t suggest relying on the relative orders of HTML elements.

Prefer targets to access particular elements. Can you move the data-controller="list-filter" annotation to a shared parent element and make the list element a target?

@vojtad

This comment has been minimized.

Copy link

vojtad commented Jan 4, 2018

Yeah, I can do that. This look like a better way to go, thanks.

If it wouldn't be possible the only way I can think of is to create a parent Controller with targets on both List and ListFilter. Then I would override the connect function of the parent Controller to pass ListController instance to ListFilterController instance.

However, I am not sure whether ListController and ListFilterController are already connected when the connect function for the parent Controller is called. Do you have any idea about this, please?

@domchristie

This comment has been minimized.

Copy link

domchristie commented Jan 4, 2018

FWIW T3 (another JS framework for the HTML you already have ;) has a simple messaging system for communicating between modules (or controllers), keeping them loosely coupled. I wonder if this is something that Stimulus might consider?

The T3 equivalent would look something like:

Box.Application.addModule('list-filter', function (context) {
  return {
    onchange: function () {
      context.broadcast('listfilterchanged', { filterParam1: value1, … })
    }
  }
})

Box.Application.addModule('list', function (context) {
  function reload (params) {
    // reload list with AJAX
  }
  
  return {
    onmessage: {
      listfilterchanged: reload
    }
  }
})

That way other controllers could respond to the listfilterchanged event

@sstephenson

This comment has been minimized.

Copy link
Contributor

sstephenson commented Jan 4, 2018

We don’t have anything built in for this right now. The easiest thing to do is probably to emit a DOM event from the inner controller and observe it with an action from the outer controller.

What I would like to see eventually is a data-outlet attribute that connects delegate properties on a child controller directly to a containing element’s controller. Something like Interface Builder outlets in Cocoa.

@sstephenson

This comment has been minimized.

Copy link
Contributor

sstephenson commented Jan 8, 2018

Closing this issue for now, but feel free to continue discussing it here.

@sstephenson sstephenson closed this Jan 8, 2018

@abulka

This comment has been minimized.

Copy link

abulka commented Jan 9, 2018

Could someone please explain exactly what the parameters to getContextForElementAndIdentifier are, what they mean and why I need both an element and a string as parameters? Why can't I find a controller simply by its string name e.g. "hello"?

If I have two controllers, 'hello' and 'out' and I want to get to the 'out' controller from inside the 'hello' controller. It seems that I need to pass both parameters, otherwise I get null. And only the combination of this.application.getControllerForElementAndIdentifier(this.element, "hello") work - which just gets me the controller I'm already in. What exact two parameters do I need to pass to get to the other controller named "out" - and why can't I just use the string name of that controller?

A bit of background: I too am trying to talk from one controller to another. My first controller wants to output some information in an abstract way, so wants to talk to another controller's write() method, which will do something cool with it. I'm hoping this is the sort of use case stimulus is good for?

@vojtad

This comment has been minimized.

Copy link

vojtad commented Jan 9, 2018

First parameter is element the controller is connected to and the second parameter is name of the controller you want to get instance of.

You need first parameter because controller of the same name can be connected to more elements on the page. So the function needs to know where to look for the controller instance you want to get.

And you need second parameter to specify controller's name because there can be more than one controller connected to one element.

So, one way to do this is to nest components and then handle this in the parent controller as @georgeclaghorn suggested. Second clean way I can think of without relying on access to particular elements would be to add a listener to document for custom event in one component and fire this event in the second one.

@incompletude

This comment has been minimized.

Copy link

incompletude commented Nov 2, 2018

We don’t have anything built in for this right now. The easiest thing to do is probably to emit a DOM event from the inner controller and observe it with an action from the outer controller.

How should one do that?

@alinnert

This comment has been minimized.

Copy link

alinnert commented Nov 15, 2018

We don’t have anything built in for this right now. The easiest thing to do is probably to emit a DOM event from the inner controller and observe it with an action from the outer controller.

How should one do that?

Here's an example: #200 (comment)

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