3

I am trying to create a definition list such as this one:

<dl>
   <dt>term1</dt>
   <dd>definition1</dd>
   <dt>term2</dt>
   <dd>definition2</dd>
   <dt>term3</dt>
   <dd>definition3</dd>
</dl>

by using a JSON in this form:

{
  "term1":"definition1"
  "term1":"definition2"
  "term3":"definition3"
}

. Whch is the most elegant way to do it with d3.js?

PS: I would like to use the <dl> element because it seems the most appropriate way to semantically represent a key:value pair:

Community
  • 1
  • 1
leonard vertighel
  • 1,058
  • 1
  • 18
  • 37
  • What did you end up doing with this? I just ran across this problem, and I found a solution that doesn't involve wrapping the dt/dd elements in a div (and posted [an answer](http://stackoverflow.com/a/29548501/1281433)), but I'd be interested to know whether you found another solution, too. – Joshua Taylor Apr 09 '15 at 20:42

3 Answers3

8

Solution for d3 v3.x; no longer works as of d3 4.0

I struggled with the same issue for a while. What I found was the behavior of insert() with the enter() selection does the trick. This is based on the interaction between the enter selection whose inserted or appended elements are merged into the update selection, the the way that insert works with enter selections. The relevant bits in the documentation:

selection.enter()

… The enter selection merges into the update selection when you append or insert.

selection.insert()

… For enter selections, the before selector may be omitted, in which case entering elements will be inserted immediately before the next following sibling in the update selection, if any. This allows you to insert elements into the DOM in an order consistent with bound data.

The result is that if you insert some dt elements into the enter selection, they will be merged into the update selection. Then, if you then insert some dd elements into the enter selection, they will be "inserted immediately before the next following sibling in the update selection"; that is, they'll be right after their associated dt.

var data = { a: 1, b: 2, c: 3 };
var dl = d3.select("body").append("dl").selectAll().data(Object.keys(data));
dl.enter().insert("dt").text(function(key) { return key });
dl.enter().insert("dd").text(function(key) { return data[key] });
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Community
  • 1
  • 1
Joshua Taylor
  • 84,998
  • 9
  • 154
  • 353
  • This no longer works; `selection.insert` now (since d3 4.0) defaults to null if you don't give a `before`, rather than inserting before. – Vivian Feb 24 '18 at 18:09
3

When the content is simple like you describe, the easiest way may be to just directly create paired HTML elements and bind the data to them afterward.

Also everything is much easier as a list -- D3's entries function can easily blow your object out to a list of objects:

var pairs = {
  "term1":"definition1",
  "term2":"definition2",
  "term3":"definition3"
};
var pairlist = d3.entries(pairs);  // [{"key":"term1", "value":"definition1"}, etc... ]

var dl = d3.select("dl").html( new Array(pairlist.length + 1).join("<dt/><dd/>") );

dl.selectAll("dt").data(pairlist).html(function(d) { return d.key; });
dl.selectAll("dd").data(pairlist).html(function(d) { return d.value; });

JsFiddle: http://jsfiddle.net/slushy/bbh6a4w0/

slushy
  • 3,277
  • 1
  • 18
  • 24
  • You can actually add consecutive elements using the enter selection and the insert method. I just discovered this a few days ago (and added [an answer](http://stackoverflow.com/a/29548501/1281433)). It's sort of a weird case, but I think it ends up being a little bit cleaner. – Joshua Taylor Apr 12 '15 at 01:06
  • @JoshuaTaylor hah! That's pretty hot. I'll upvote your answer. – slushy Apr 12 '15 at 16:34
  • Though this feels a bit clumsier (no `enter` or `exit`, just scrapping and rebuilding if the data changes), it works on d3 4.0, where @JoshuaTaylor 's doesn't. – Vivian Feb 24 '18 at 19:17
-1

Here is what I would do:

list = {
  "term1":"definition1",
  "term2":"definition2",
  "term3":"definition3"
}

dl = d3.select('body').append('dl')
for(var key in list) {
    dl.append('dt').text(key)
    dl.append('dd').text(list[key])
}

JsFiddle: http://jsfiddle.net/chrisJamesC/RZdQw/

Then, if you want to be more d3 specific, you can use selections (more about implementation).

By the way, using arrays might be easier than json objects to represent lists

Christopher Chiche
  • 15,075
  • 9
  • 59
  • 98
  • 2
    Thank you very much for your comment, it works very well. However, this method do not provide data binding, and then no possibility to use the `.enter()`, update, and `.exit()` methods. – leonard vertighel Jun 09 '14 at 22:03
  • 1
    @Christophe you can actually add the consecutive elements using the enter selection. E.g., you can `var x = d3.select('body').append('dl').selectAll().data(...); x.enter().insert('dt'); x.enter().insert('dd');`. I just discovered this when faced with the same problem OP faced. – Joshua Taylor Apr 12 '15 at 01:08