Hotwire Discussion

Relationship between controllers

Already a fan of your new concept.
I’m wondering is there way to call an another controller action from a controller:

Let’s say we have a list of todos, each todo have a controller: todo_controller with actions like toggleState, Rename or set estimate.
We have a project controller, it wraps these items and renders them, when change one todo’s estimate value to something else I want to recalculate the total estimate of the todos, so is there a way to call the project controller’s calculateEstimate action somehow from todo controller?

1 Like

Can I ask a related question from my toy application.

I have a project controller, inside it are a bunch of to do items that have an up and a down button (to reorder the items). Clicking the up or down triggers a state change, and calls the server to get the new list for display.

I’ve made each individual to do item an instance of a todo controller, and the project controller around it is an instance of a project controller.

When the todo up controller is clicked, is there a stimulus idiomatic way to get the parent project controller’s element to handle the refresh.

I tried making that element a target of the todo controller, but it’s not finding it, presumably because the target needs to be inside the scope of the controller element. I can find the correct element using the DOM, but that seems like I’m duplicating functionality. Is there a way to communicate between controllers?

1 Like

i ended up with this:

get projectController() {
  return this.application.getControllerForElementAndIdentifier(document.getElementById("projects-index"), "project")
}

so i can call this. projectController.refresh() whenever I want.

although it seems a bit hackish, i’m pretty sure there is a better way.

1 Like

See https://github.com/stimulusjs/stimulus/issues/35 for a related discussion.

2 Likes

Hi,

I use this method:

  getControllerByIdentifier(identifier) {
    return this.application.controllers.find(controller => {
      return controller.context.identifier === identifier;
    });
  }
2 Likes

I can’t make this suggested method work for me:

var controlled_element = $("*[data-controller='edit-quotation']").first()
if (controlled_element != undefined) {
    if (this.application.getControllerForElementAndIdentifier(controlled_element, "edit-quotation") == undefined) {
        console.log("FAILED: could not find the controller");
    }
}

The controller file name is edit_quotation_controller.js.
I tried alternative spellings to no avail (edit_quotation and editQuotation)

In that discussion, @sam suggested: (emphasis mine)

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 does one do that?

1 Like

Follow

  1. https://github.com/stimulusjs/stimulus/issues/35#issuecomment-438980883
  2. https://github.com/stimulusjs/stimulus/issues/200#issuecomment-434731830
1 Like

Just to add to this. I’ve had much success with a similar approach of @nowhereman but instead I extend upon the Main controller class so grabbing controller instances are applicable from any controller. For example:

import { Controller as BaseController } from 'stimulus'

export class Controller extends BaseController {

  get isPreview () {

    return this.application.element.hasAttribute('data-turbolinks-preview')

  }

  controller ($identifier, $id = false) {

    return this.application.controllers.find(({
      context: {
        identifier,
        element: { id }
      }
    }) => $id ? (
      identifier === $identifier && id === $id
    ) : (
      identifier === $identifier
    ))

  }

}

Then from within your controllers, you’d extend that base class and you’d just do the following:

this.controller('identifier')

If you have more than 1 controller, you can easily query that by passing in an optional id parameter which will reference the element id associated to the controller. Just be wary and don’t repeat element ids, so for example:

<div controller="something" id="some-id-1"></div>
<div controller="something" id="some-id-2"></div>
<div controller="something" id="some-id-3"></div>

Here you’ve got multiple something controllers in the dom, lets say you want the controller with an id of some-id-2 you can just pass this.controller('something', 'some-id-2').

Personally, it’s a code smell when you’re wrapping outer controller within inner controllers (as per the linked github discussion). Keeping logic separate is a much cleaner approach.

hello friends!

Does anyone know how you can get a value that is actually located in another nested controller?

  • Does anyone know how to get the indexValue that is in the list_controller while within the list_filter_controller?

To use the example noted here: Communicating between controllers · Issue #35 · hotwired/stimulus · GitHub

<div data-controller="list-filter">
    <div data-controller="list" data-list-index-value="0">
    </div>
</div>


import { Controller } from "stimulus"

export default class ListFilterController extends Controller {
  connect(){
      // how do I get the indexValue that is located in the list_controller?
     this.listController.indexValue // doesn't work!? - I want to get the value 0
  }

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

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


// list_controller.js
export default class ListController extends Controller {
    static values = {"index": Number} 
}

any pointers would be much appreciated! Thank you :slight_smile:

Can you read the from the DOM? Assuming the controller is assigned to the same element, something like:

parseInt(this.element.datalist.listIndexValue);
1 Like

You’ll need to pass the the element with data-controller="list" instead of this.element. By making it a target of the list-filter controller, for example:

-   return this.application.getControllerForElementAndIdentifier(this.element, "list")
+   return this.application.getControllerForElementAndIdentifier(this.listTarget, "list")
1 Like