Reputation: 1457
Is it possible (and is it appropriate) to use nested/child targets of parent target?
For example i have “N+“ menu items, each item (wrapper) contain link and list.
I could add data-main-menu-target="menuItem"
to each of parent items and then iterate over them in controller loop using this.menuItemTargets.forEach(...)
But what's the best practice to find menu-item-link and menu-item-list for each menu-item target on each loop iteration?
In general i could add targets also for those elements, e.g. menuItemLink
& menuItemList
, but how then i could select them from parent menuItem
target, Is it possible to do something like menuItemTarget.find(this.this.menuListTarget)
?
To visualise the structure is the following:
data-controller="main-menu"
data-main-menu-target="menuItem"
data-main-menu-target="menuLink"
data-main-menu-target="menuList"
....
data-main-menu-target="menuItem"
data-main-menu-target="menuLink"
data-main-menu-target="menuList"
....
How then select "menuLink
" for certain "menuItem
" target on each loop?
Upvotes: 3
Views: 6199
Reputation: 1457
Answer from another conversation,
A) For menuLink and menuList, you handle it by yourself: use CSS classes, and then use normal selectors. So, once you've used the menuItem target to find the menuItem you want, you would then do menuItem.querySelector('.menu-link'). Not a Stimulus solution, but it's pretty simple and it's nice to be able to "back out" and do things manually if you need to.
B) I'm not sure what your overall Stimulus controller is meant to do, but it's possible that there should be a menu-item controller that lives on the menuItem target. Depending on what you're trying to accomplish, that could replace the main-menu controller or, more likely (because I'm assuming you are doing some "work" on the top level main-menu where you want to be aware of all of the "items"), in addition to the main-menu controller. With this setup, your main-menu controller could loop over the menuItem targets and, in each one, directly use its underlying controller instance - even calling methods on it. This is not something I showed on the tutorial, but it's not an uncommon pattern: you would expose the "controller instance" of the "menu-item" controller on its element - e.g. https://www.betterstimulus.... (the big difference in that example is that both of the controllers are on the same element - so adjust accordingly).
(c) weaverryan
Upvotes: 0
Reputation: 5186
You could structure your controller so that you have one menu
controller that gets used on both the root menu and also the sub-menus within them. This could be recursively accessed from whatever is deemed to to be the root.
ul
for a menu, each child should be an item
target.item
we may have a link
target OR another sub-menu which itself is another menu
controller and the pattern continues.<nav>
<ul class="menu-list" data-controller="menu" data-menu-target="root">
<li data-menu-target="item">
<a data-menu-target="link">Team Settings</a>
</li>
<li data-menu-target="item">
<ul data-controller="menu">
<li data-menu-target="item">
<a data-menu-target="link">Members</a>
</li>
<li data-menu-target="item">
<a data-menu-target="link">Plugins</a>
</li>
<li data-menu-target="item">
<a data-menu-target="link">Add a member</a>
</li>
</ul>
</li>
<li data-menu-target="item">
<a data-menu-target="link">Invitations</a>
</li>
<li data-menu-target="item">
<a data-menu-target="link">Cloud Storage Environment Settings</a>
</li>
</ul>
</nav>
this.hasRootTarget
.data-controller='menu'
.setTimeout
to wait for any sub-controllers to connect, there may be a nicer event propagation way to do this.getControllerForElementAndIdentifier
method.link
target OR a nested array which itself will contain the sub link
targets.item
and see what links are 'contained' within it.class MenuController extends Controller {
static targets = ['item', 'link', 'root'];
connect() {
if (this.hasRootTarget) {
setTimeout(() => {
// must use setTimeout to ensure any sub-menus are connected
// alternative approach would be to fire a 'ready' like event on submenus
console.log('main menu', this.getMenuStructure());
});
}
}
getMenuStructure() {
const links = this.linkTargets;
return this.itemTargets.map((item) => {
const child = item.firstElementChild;
const subMenu = this.application.getControllerForElementAndIdentifier(
child,
this.identifier
);
const menuLinks = links.filter((link) => item.contains(link));
return subMenu ? subMenu.getMenuStructure() : menuLinks;
});
}
}
firstElementChild
and this may not be the way we want to do things in Stimulus, but you could simply add another target type of 'sub-menu' to be more explicit and follow the pattern of finding the 'link' within each item this way.data-controller="menu"
on a data-menu-target="item"
as this will remove the item
from the parent scope. As per the docs on scopes.that element and all of its children make up the controller’s scope.
Upvotes: 2