<div class="wrapper">
<button
class="btn"
phx-click={JS.toggle(to: ".element-to-toggle")} # here I hope find it without specify id
>
Click me
</button>
<div class="element-to-toggle"></div>
</div>
In Phoenix.LiveView.JS how to write selector to find the sibling element (in my case the “element-to-toggle”) , without add them id ? I can’t add it id because I have lot of html wrappers structure like this in same page.
Hello and welcome!
So it’s a little bit convoluted but this is the best I could come up with:
JS commands have two scoping selectors, :inner
and :closest
. :inner
will scope to children of the current node while :closest
works like JS’s closest()
searching upward for the closest match. You can use a combination of these with a data attribute and JS.exec
:
<div
class="wrapper"
data-toggle={JS.toggle(to: {:inner, ".element-to-toggle"})
>
<button
class="btn"
phx-click={JS.exec("data-toggle", to: {:closest, ".wrapper"})}
>
Click me
</button>
<div class="element-to-toggle"></div>
</div>
Unfortunately, simply doing something like JS.toggle("some-class", to: {:closest, ".wrapper .element-to-toggle"})
doesn’t work, though this is the same deal with closest()
in JS so it stands to reason it doesn’t work in LV.
1 Like
Thank you for enthusiastic response. I will read through the documents you provided and try it ! Thank you again!
1 Like
To avoid the https://xyproblem.info/ could you please explain what is your actual goal?
Accordion like behaviour?
Something else?
Javascript is usually the last ditch effort to do something like this at the moment, when we have :has selectors and so forth.
If you show me a use case you’re trying to achieve I can have a look and most likely come up with an easier solution when javascript.
1 Like
Thank you for replying. What I want is, on the same page, there are many similar items, they may exist in the recently list
, ranking list
etc.;
Each item should has an attached review input box, and the users can click on it to make it appear/hide and then enter their review.
Therefore, I want to create a function component for these item; they should manage their own input box themselves, without the potentially conflicting (global) element IDs.
So could you not use a details element?
1 Like
Thank you, your approach is I hadn’t thought of at all ! inspired. Long live hypermedia 
There’s a catch if you want to survive a liveview update from the server, put the below code in your assets/js/app.js
to prevent liveview removing the open
attribute from your <details>
elements, this will do it for any element if you want to be specific you could say name === ‘DETAILS’ also
const liveSocket = new LiveSocket('/live', Socket, {
dom: {
onBeforeElUpdated: (fromEl: HTMLElement, toEl: HTMLElement) => {
if (fromEl.hasAttribute('open')) {
toEl.setAttribute('open', '')
}
}
}
1 Like
If the items are in the database, you can easily create unique ids for them by prefixing or suffixing them with the name of the list.
1 Like
Thank you two for the kind helps! Considering that each input box is associated with its own changeset/validate event, actually I plan to refactor them into a liveComponent; in that way, I can obtain an independent, everywhere placeable component, no more worry about the sibling element id selector issues, avoid bring in the complication of
javascript listener or such sth., maybe. I will try that approach.