Stimulus Discourse

Running inner controller's functions from inside outer controllers

I’m trying to use getControllerForElementAndIdentifier (I read 1, 2, 3, 4, 5, 6, etc. where it’s mentioned) because I would like to run a inner controller’s function from inside an outer controller.

I think I’m in a similar situation as described here:

On the same page I’ve individually working controllers, the outer ones named say ‘aaa--bbb--cc-c’ and the inner ones named ‘ddd--ee-e’. They’re stated as follows:

// File: aaa/bbb/cc_c_controller.js
export default class extends Controller {
  static targets = ['one'];
  // ...
}

// File: ddd/ee_e_controller.js
export default class extends Controller {
  static targets = ['two'];
  // ...

  functionToRunFromInsideOuterController(){
    // ...
  }
}

The HTML where those controller are used may have “casual” child-parent tags and “under” an outer controller may be present more than one inner controller. To give you an idea:

<div data-controller="aaa--bbb--cc-c">
  <!-- some child-parent HTML tags -->
    <!-- some child-parent HTML tags -->
      <!-- ... -->
        <div data-controller="ddd--ee-e">
          <input data-target="ddd--ee-e.two" type="checkbox">
          <button data-action="click->ddd--ee-e#ya">Ya!</button>
        </div>
      <!-- ... -->
    <!-- some child-parent HTML tags -->
  <!-- some child-parent HTML tags -->
  <input data-target="aaa--bbb--cc-c.one" type="text">
  <button data-action="click->aaa--bbb--cc-c#yo">Yo!</button>
</div>

<div data-controller="aaa--bbb--cc-c">
  <!-- some child-parent HTML tags -->
    <div data-controller="ddd--ee-e">
      <input data-target="ddd--ee-e.two" type="checkbox">
      <button data-action="click->ddd--ee-e#ya">Ya!</button>
    </div>
  <!-- ... -->
  <!-- some child-parent HTML tags -->
    <!-- ... -->
      <div data-controller="ddd--ee-e">
        <input data-target="ddd--ee-e.two" type="checkbox">
        <button data-action="click->ddd--ee-e#ya">Ya!</button>
      </div>
    <!-- ... -->
  <!-- some child-parent HTML tags -->
  <input data-target="aaa--bbb--cc-c.one" type="text">
  <button data-action="click->aaa--bbb--cc-c#yo">Yo!</button>
</div>

My intent is to run the ‘ddd--ee-e’ controller’s functionToRunFromInsideOuterController() from inside the ‘aaa--bbb--cc-c’ controller, something as follows:

// File: aaa/bbb/cc_c_controller.js
export default class extends Controller {
  static targets = ['one'];
  
  initialize() { // or connect() {
    // This doesn't work
    this.application.getControllerForElementAndIdentifier(this.element, 'ddd--ee-e').functionToRunFromInsideOuterController();
  }
}

The issue is…

Only the combination of this.application.getControllerForElementAndIdentifier(this.element, 'aaa--bbb--cc-c') seems to work, which just gets me the controller I’m already in. Other combinations return always null. What exact two parameters do I need to pass to get to the inner controllers named ‘ddd--ee-e’ and then run the functionToRunFromInsideOuterController()?
Quotation (a bit edited to fit my case)

P.S. I’m using Rails 5.2.2 with Turbolinks and Stimulus installed via Webpack, if it could be useful.

Hello

I wrote a small package that could maybe help you

For now it is only based on a plural convention outer controller items is the plural of the inner item

2 Likes

Thanks @adrienpoly, it looks promising.
Please, show me an example of using it for my case: run the ‘ ddd--ee-e ’ controller’s functionToRunFromInsideOuterController() from inside the ‘ aaa--bbb--cc-c ’ controller.

BTW - Why opinionated? What JS is at the core of stimulus-conductor?

I solved it this way:

export default class extends Controller {
  static targets = ['one'];
      
  initialize() { // or connect() {
    var self = this;
    setTimeout(function() {
      var element = self.element.querySelector("[data-controller='ddd--ee-e']");
      const inner_controller = self.application.getControllerForElementAndIdentifier(element, 'ddd--ee-e');

      inner_controller.functionToRunFromInsideOuterController();
    }, 50);
  }
}

:grinning:

Any further addition is appreciated.

One more approach to the problem:
Inner

class InnerController extends Controller {
  connect() {
    this.element.innerController = this
  }

  log() {
    console.log('Here')
  }
}

HTML

<ul data-controller="outer">
  <li data-controller="inner" data-target="outer.inner"></li>
  <li data-controller="inner" data-target="outer.inner"></li>
  <li data-controller="inner" data-target="outer.inner"></li>
</ul>

Outer

class InnerController extends Controller {
  static targets = [ "inner" ]

  logAll() {
    this.ineerTargets.forEach(el => el.innerController.log())
  }
}

2 Likes

@Alexandr_K, in your approach, should you still use the getControllerForElementAndIdentifier() in order to run the ‘ddd--ee-e’ inner controller’s functionToRunFromInsideOuterController() from inside the outer ‘aaa--bbb--cc-c’ controller?

Nope, because I store a reference to the controller itself in an element property (this.element.innerController = this), which is a target in the outer controller.

BTW There are some typos in your outer controller statements. It should be (note: use of OuterController instead of InnerController and innerTargets instead of ineerTargets):

Outer

class OuterController extends Controller {
  static targets = [ "inner" ]

  logAll() {
    this.innerTargets.forEach(el => el.innerController.log())
  }
}