Getting table td events to display information elsewhere

Hi there, newbie and big fan here.

I’m working creating a table that will have a sort of “lift the flap” functionality, where mousing or clicking on a td will result in more HTML-formatted detailed information getting presented at the bottom of the same Observable cell, or in another one. Here’s where I’m trying this out.

I’ve looked at a few examples, but I haven’t been able to get them to work for my case. The closest I’ve come to a solution is the “Heat Table” example here. This example relies on a somewhat elaborate table library that I can’t figure out how to translate that to my more basic table creation method.

I’ve been able to get text information to appear outside the table in the same cell, but I haven’t been able to get HTML-formatted data from an outside function to work.

Also, separately, I’m trying out tippy.js for tooltips. I have it working, but not in way that updates as cells change. I found a conversation entitled “[Tippy.js tooltip does not display on Observable [with examples]” about how to get tippy to work in an “observable way”, but I’m having problems translating that example from SVG to my particular case.

Thanks in advance for any help!

Welcome, @keviniano! I made 2 small changes to your notebook which hopefully address your issues here:

The first change is that instead of using .text(flap), I use d3.append() with a function, since the former coerces the HTML element to a string (and hence you see [object HTMLElement] or something like that) and the second properly appends the HTML element as expected. Of course I then have to clear the content of tt before each append, otherwise it just keeps accumulating more stuff, so I also added a tt.html(null).

To address the tippy issue, I added a reference to the viewof table cell in your tippy cell so that whenever you change the viewof table code, tippy is called again reactively.

2 Likes

Thanks so much @bgchen, your changes were exactly what I was needing!

The solution you did for the first issue make sense.

For the tippy issue, can you help me understand what you did there? I’ve looked around the docs and haven’t found any documenting using viewof outside of variable assignment. In all the demos I’ve looked at, values in other cells just update automagically without needing to use viewof again on the receiving side.

So, what I did doesn’t really have anything to do with “viewof” specifically. I addressed an issue which is discussed in the tutorial “Observable anti-patterns and code smells”, namely that Observable cells generally should generally work with cell references rather than by querying the DOM.

Let me try to explain what this means for the earlier version of your notebook and my changes to it.

So what was happening previously was that the unnamed cell that called tippy in the UI section had the following code:

    tippy("#t2 td", {
      content(reference) {
        const title = reference.getAttribute('title');
        reference.removeAttribute('title');
        return title;
      },
      arrow: true,
      size: 'large',
      maxWidth: 120,
      animation: 'fade',
      placement: 'right',
    })

This cell (which I’ll call the “tippy” cell) makes changes to the DOM element with id t2. That DOM element is created by your table cell (which is named “viewof table”). The fact that tippy acts on t2, which is created by viewof table means that the tippy cell should be re-run whenever viewof table changes.

However, Observable has no way of knowing that! This is because the tippy cell doesn’t contain any references to viewof table. When viewof table is re-run, the tippy cell doesn’t react, since as far as the Observable runtime is concerned, they’re independent of each other. (See the tutorial How Observable runs for more about this.)

So in my fork, I added a reference to viewof table in the tippy cell by turning it into this:

{
  viewof table; // this doesn't really do anything, 
  // but it does tell the Observable runtime that this cell depends on "viewof table"
  return tippy("#t2 td", {
      content(reference) {
        const title = reference.getAttribute('title');
        reference.removeAttribute('title');
        return title;
      },
      arrow: true,
      size: 'large',
      maxWidth: 120,
      animation: 'fade',
      placement: 'right',
    });
}

(In hindsight I could have made this a bit more compact by using a comma, but let me not get into that now.)

After this change, the Observable runtime “knows” that the tippy cell depends on viewof table and so when viewof table is re-run, the tippy cell will run as well.

One way to visualize how the dependencies changed is to use the “Notebook visualizer”.

Here’s a link to a visualization of your original version. Note that you have to click the checkbox “anonymous cells” to see the tippy cell since it is unnamed. I believe it’s cell #31, and you can see there are no arrows from viewof table to #31.

Here’s the link to a visualization of my fork. You can see that my edit creates an arrow from viewof table to #31.

Glad I could help and feel free to ask if you have any more questions!

3 Likes

Super helpful, @bgchen!

So, I’m still not getting why that line in the tippy cell needs to read as viewof table; as opposed to just table; or something like that. Is it that the value of table isn’t being immediately returned in the tippy cell? In the examples I’ve seen (like here) that reference a cell with a variable assignment prefixed with viewof, they just reference the cell name only, yet they update just fine. In what circumstances do you need to use viewof at both ends to alert the Observable runtime of the dependency?

I didn’t know about the notebook visualizer, that thing is awesome. (Also didn’t know graphviz is so easy to use in Observable, so that’s cool.)

In an Observable notebook, creating a cell named viewof X actually creates two reactive variables in the runtime, one named X which corresponds to the value of the view (typically a number or string or object that’s generated via user interaction with the view), and one named viewof X which corresponds to the view itself (the actual DOM element that’s appended to the page). (See the Introduction to Views tutorial for more detail.)

So for my fork of your notebook, if you change the reference to table instead of viewof table (try it!), the tippy cell will only re-run when the value changes. However, viewof table currently doesn’t return a value, so the tippy cell actually just waits forever without running. Even after you add a value to the view (presumably something like an object associated to the table cell that gets clicked on), I don’t think you’d actually want the tippy cell to re-run every time the value changes. tippy is just setting up the tooltips so I think you really only want the tippy cell to re-run when viewof table changes.

Hope that helps!

3 Likes

Ah, superb, I get it now. That distinction makes sense.