I am looking for a simple and elegant way (a Stimulus-way) to select a specific element from the targets elements form my Stimulus controller.
For example: I have a list of 4 links and 4 paragraphs (both inside the scope of the controller). When clicking the link, I want something to happen (e.g. adding a class) with the paragraph, which has the same data-id as the link. The paragraphs have been defined as Stimulus-Targets.
Please see this JS-Fiddle for a code-example: /
Stimulus is giving me the following accesses to the target elements:
event.currentTarget
-> gives the clicked element (the link) - where its possible to access the dataset and therefore the data-id of the clicked element (event.currentTarget.dataset.linkId
)this.elementTarget
-> returns the first element target (paragraph) - but I cannot select a specific one.this.elementTargets
-> returns an array of target-elements (paragraphs) - but also here, I cannot filter for a specific one.
For the latest one (this.elementTargets
), I would hope, there is a JS method to somehow select a specific element from this array of HTML elements.
I want to avoid document.querySelector(...)
as the element with the data-id could be present on the page multiple times (see second list in the JS-Fiddle). Therefore, a solution within the "scope" of the Stimulus controller would be the best solution.
I am looking for a simple and elegant way (a Stimulus-way) to select a specific element from the targets elements form my Stimulus controller.
For example: I have a list of 4 links and 4 paragraphs (both inside the scope of the controller). When clicking the link, I want something to happen (e.g. adding a class) with the paragraph, which has the same data-id as the link. The paragraphs have been defined as Stimulus-Targets.
Please see this JS-Fiddle for a code-example: https://jsfiddle/nbLvafxy/
Stimulus is giving me the following accesses to the target elements:
event.currentTarget
-> gives the clicked element (the link) - where its possible to access the dataset and therefore the data-id of the clicked element (event.currentTarget.dataset.linkId
)this.elementTarget
-> returns the first element target (paragraph) - but I cannot select a specific one.this.elementTargets
-> returns an array of target-elements (paragraphs) - but also here, I cannot filter for a specific one.
For the latest one (this.elementTargets
), I would hope, there is a JS method to somehow select a specific element from this array of HTML elements.
I want to avoid document.querySelector(...)
as the element with the data-id could be present on the page multiple times (see second list in the JS-Fiddle). Therefore, a solution within the "scope" of the Stimulus controller would be the best solution.
-
I can tell you that in the few minutes of learning stimulus.js to help you out that having two controllers called "effect" and then asking just to log to the console the element corresponding to
this.linkTarget
causes the browser tab to crash. If you name them as two different data-controllers, this will stop that from happening. It seems like there is some kind of recursion at play within that library. The best solution is a pure JavaScript solution where you do in fact usequerySelector
to query the correct paragraph based on its proximity. – Marcus Parsons Commented May 18, 2022 at 2:59 -
Here's a pure JS solution in less than 10 lines to add the class
yellow
to the correct paragraph given its proximity to the clicked link: jsfiddle/8h0aLj6b. You can adapt this into your Stimulus controller I'm sure. – Marcus Parsons Commented May 18, 2022 at 3:05 -
Oh and for good measure, here's the proof of concept fiddle where the tab (almost) crashes when you try to just log the clicked element
link
element to the console because of your current naming convention: jsfiddle/skx536ap – Marcus Parsons Commented May 18, 2022 at 3:09 - As far I can see from the Stimuls Documentation, the naming convention should work like this. It is intentional to have two controllers (or more) with the same name on the same page (tinyurl./4uy4cjbm). Appreciate the pure JS solution - but the JSFiddle only shows the issue in its simplest form (adding the class is not really the problem ;-). – Michael Fehr Commented May 18, 2022 at 13:19
-
You can use
querySelector
onthis.element
(element withdata-controller=...
), so you only search inside controller you want. – janpeterka Commented May 30, 2022 at 8:30
3 Answers
Reset to default 3Here is my approach to select target by name:
// I'm trying to get "stopTarget" element
const targetName = 'stop';
console.log(this[targetName + 'Target']);
Or even easier:
console.log(this.targets.find(targetName));
I though this would work, but it doesn't
I have used multiple controllers on the same page before and it worked, but I'll agree with the one of the menter that the naming convention seems to screw things up. The targets are selected on both sides, the add class works, but it seem to reload or reconnect the page.
OH!! It's reconnecting because of the # href! Change your <a tags to button and remove the href and it works.
Sorry
The index thing would work, but elements and links are not in same order.
So, your html
<div class="flex bg-white">
<div class="half" data-controller="effect" >
<p><a href="#" data-action="effect#highlight" data-effect-target="link" data-link-id="1">Link 1</a></p>
<p><a href="#" data-action="effect#highlight" data-effect-target="link" data-link-id="2">Link 2</a></p>
<p><a href="#" data-action="effect#highlight" data-effect-target="link" data-link-id="3">Link 3</a></p>
<p><a href="#" data-action="effect#highlight" data-effect-target="link" data-link-id="4">Link 4</a></p>
<p data-effect-target="element" data-element-id="4">Element 4</p>
<p data-effect-target="element" data-element-id="3">Element 3</p>
<p data-effect-target="element" data-element-id="1">Element 1</p>
<p data-effect-target="element" data-element-id="2">Element 2</p>
</div>
<div class="half" data-controller="effect">
<p><a href="#" data-action="effect#highlight" data-effect-target="link" data-link-id="3">Link 3</a></p>
<p><a href="#" data-action="effect#highlight" data-effect-target="link" data-link-id="4">Link 4</a></p>
<p><a href="#" data-action="effect#highlight" data-effect-target="link" data-link-id="5">Link 5</a></p>
<p><a href="#" data-action="effect#highlight" data-effect-target="link" data-link-id="6">Link 6</a></p>
<p data-effect-target="element" data-element-id="4">Element 4</p>
<p data-effect-target="element" data-element-id="5">Element 5</p>
<p data-effect-target="element" data-element-id="6">Element 6</p>
<p data-effect-target="element" data-element-id="3">Element 3</p>
</div>
</div>
Stimulus controller
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["link", 'element']
connect(){
}
highlight() {
var lnk = event.target.dataset.linkId
var elems = this.elementTargets
for (var i = elems.length - 1; i >= 0; i--) {
if (elems[i].dataset.elementId == lnk){
elems[i].classList.add('text-blue-700')
console.log(elems[i])
break
}
}
}
For some reason the class added to element is in the html, but not change?(in Safari, but ok in firefox) anyway no DOM id, just targets.
The main idea is that the controller is "reusable". So, it's ok to create a number of controllers.
Instead of:
<div class="half" data-controller="effect" >
<p><a href="#" data-action="effect#highlight" data-effect-target="link" data-link-id="1">Link 1</a></p>
<p><a href="#" data-action="effect#highlight" data-effect-target="link" data-link-id="2">Link 2</a></p>
<p><a href="#" data-action="effect#highlight" data-effect-target="link" data-link-id="3">Link 3</a></p>
<p><a href="#" data-action="effect#highlight" data-effect-target="link" data-link-id="4">Link 4</a></p>
</div>
you should set
<div class="half">
<p data-controller="effect" ><a href="#" data-action="effect#highlight" data-effect-target="link" data-link-id="1">Link 1</a></p>
<p data-controller="effect" ><a href="#" data-action="effect#highlight" data-effect-target="link" data-link-id="2">Link 2</a></p>
<p data-controller="effect" ><a href="#" data-action="effect#highlight" data-effect-target="link" data-link-id="3">Link 3</a></p>
<p data-controller="effect" ><a href="#" data-action="effect#highlight" data-effect-target="link" data-link-id="4">Link 4</a></p>
</div>
Then, in controller's code:
highlight() {
var lnk = this.linkTarget;
var id = lnk.dataset.linkId;
}