Stimulus Discourse

Running inner controller's functions from inside outer controllers

#1

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.

0 Likes

#2

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

#3

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?

0 Likes

#4

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.

0 Likes

#5

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())
  }
}

1 Like

#6

@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?

0 Likes

#7

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.

0 Likes

#8

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())
  }
}
0 Likes