I have a data structure like
[
{"name":"foo", "links":["a", "b", "c"]},
{"name":"bar", "links":["d", "e", "f"]}
]
I'd like to make a list like
<ul>
<li> foo </li>
<li> bar </li>
</ul>
which, when I click on foo, expands like
<ul>
<li> foo
<ul>
<li> a </li>
<li> b </li>
<li> c </li>
</ul>
</li>
<li> bar </li>
</ul>
I'd like to do this using d3.js. It's easy and fun to make the top list:
toplist = d3.select("body").append("ul");
toplist.selectAll("li")
.data(data)
.enter()
.append("li")
.text(function(d){return d.name;})
It's even easy to add the sublists:
items = toplist.append("ul")
.selectAll("li")
.data(function(d){return d.links})
.enter()
.append("li")
.text(function(d){return d})
I can add an on("click".. to each li in toplist, like
toplist.selectAll("li")
.data(data)
.enter()
.append("li")
.text(function(d){return d.name;})
.on("click", expand)
and move the sublist code into a function called expand. It looks like
function expand(d,i){
and then has the above items = ... code in it.
The trouble is that this then expands both foo and bar at the same time.
How do I give the foo list element a separate on click event to bar which expands only that element? Essentially, I need to pass expand the index of the data element, and have it only operate on that specific piece of the DOM.
Any help would be appreciated!
You have everything you need when expand is called. The this context is the li element that was clicked, and d is the associated data. If you only want to expand (rather than toggle between expand and collapse), then you can create the sublist by selecting this:
function expand(d) {
d3.select(this)
.on("click", null)
.append("ul")
.selectAll("li")
.data(d.links)
.enter().append("li")
.text(function(d) { return d; });
}
Note that expand also removes the click handler from the clicked item; you don't want to add the list again when clicking twice.
If you want to toggle between expand and remove, then you need to track some state: whether the submenu is open or closed. You could either do this by storing a boolean on d (d.open, say), or you could do it by querying the DOM to see if the submenu already exists: d3.select(this).select("ul").empty().
Yet another way you could do this would be to create all the submenus ahead of time, and then just toggle the visibility via the display style property. That's the simplest option if you have all of the data available; I'd probably only bother will creating the submenu dynamically if you were loading the link data asynchronously.
Live example: http://bl.ocks.org/1541816
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With