Long time no post, but I ran into a seemingly simple UI pattern that I wasn’t sure how to implement using Ember and wanted to get some second opinions before moving forward.
We have a fairly standard list view; a parent component that renders a bunch of list items. Each list item can toggle a “detail view” that shows or hides more information. To make our users happy, the parent component will have the ability to toggle all list items at once.
How should I set this up? Is there a good pattern within the Ember community to do this?
My first instinct is to keep track of the open/closed states within some kind of array in the parent component, but I think I would prefer if the list items themselves were responsible for tracking their own state.
In Ember we prefer DDAU but this is one of those cases where a parent needs to send a signal to the children.
One pattern we sometimes use in Discourse is an event emit/subscribe. Ember comes with a mixin to handle this. What I would suggest is creating a service that exposes an instance of an object with that mixin. You could call it appEvents for example.
We’ve done something similar to what @eviltrout proposed, but we’ve ended up using something a bit different. We keep all the state in the parent component and don’t use a service.
That’s a good instinct, and if the requirements grow any more complex than just “open all” / “close all”, it’s probably the way to go. It doesn’t need to be awkward:
export default class extends Component {
@tracked
openedItems = new Set();
@action
openAll() {
let openedItems = new Set();
for (let item of this.items) {
openedItems.add(item);
}
this.openedItems = openedItems;
}
@action
closeAll() {
this.openedItems = new Set();
}
@action
openOne(item) {
let openedItems = { this };
openedItems.add(item);
this.openedItems = openedItems;
}
@action
closeOne(item) {
let openedItems = { this };
openedItems.delete(item);
this.openedItems = openedItems;
}
}
I wrote mine before I saw @ScottAmesMessinger’s reply. His is basically the same pattern, and his is more useful as an example if you’re not using tracked properties yet.
I use the exact same pattern that @ScottAmesMessinger proposes but I think you need to be aware of two things.
First is that you need to have an unique way to identify each list item, as an id or something. If you don’t have an id and you are iterating the list with the {{#each list as |item index|}} helper you could use the index param as identifier, but if you change the order of the list while you have expanded items then that identifier is useless. If you can’t identify each list item the @eviltrout approach is better because each element is aware of its own status and will respond to the hide-all or show-all event accordingly. The same with @ef4 code because you store the full item.
Second is a question. What about the performance? I don’t know much about it and I haven’t done any test but I suppose that working with an array of ids is more performant than doing it with a Set as @ef4 suggests, isn’t it? I suppose that is less memory consuming and faster to work with an array of strings or integers than a Set of objects, but as I say I’m not an expert and is only an idea that came to mind when I read the answer.
In any case, I’m glad to read questions and answers like these becase all of them are super useful and I have learned something new.
I don’t think the choice of data structure here is likely to have a measurable impact until you have tens of thousands of items. And maybe not even then. Native types like Array and Set are quite fast and don’t interact at all with the slower / more complex parts of the browser API surface, like the DOM.