The tradeoffs of having an interactive tooltip on a (stream) table

I recently asked How would you add a conditional checkbox to a (stream) table?

The solution was to use CSS to toggle the column, but now I’m trying to do a similar task with a more complex element, it gives me a bad trade off. @garrison warned me at some point I’ll probably be pushing the boundaries of when using a stream is …convenient…, and I fear im at that point, but thought i’d gather some opinions on my solutions (see below) just in case :slight_smile:


I want to show a ‘mini-table’ when a value in my table is clicked.

Taking the above image, the ‘button’ 2 in the Dogs column was clicked, for Jeff, revealing the names of the 2 dogs Jeff owns in a ‘mini table’ to the right of the ‘2’ I clicked on.


So CSS can position the ‘mini-table’, the HTML for it is in the button of the value 2. This means I can do absolute bottom-0 left-[calc(100%+10px)] to position the mini-table

  def render(assigns) do
    ~H"""
    <.table id="pet-owner-table" rows={@streams.rows}>
      <:col :let={{_, row}} label="Name">{row.name}</:col>
      <:col :let={{_, row}} label="Dogs">
        <.tooltip_button
          row_id={row.id}
          column="dogs"
          show_tooltip?={@active_tooltip_row_id == row.id and @active_tooltip_column == "dogs"}
          tooltip_rows={@tooltip_rows}
        >
          {row.dogs}
        </.tooltip_button>
      </:col>
      ...
    </.table>
    """
  end

  ...

  def tooltip_button(assigns) do
    ~H"""
    <.button phx-click="open_tooltip" phx-value-row_id={@row_id} phx-value-column={@column}>
      {render_slot(@inner_block)}
      <div
        :if={@show_tooltip?}
        class="card bg-base-100 shadow-sm absolute bottom-0 left-[calc(100%+10px)] z-10"
      >
        <.table id="tooltip-table" rows={@tooltip_rows}>
          <:col :let={row} label="Dog Name">{row.name}</:col>
        </.table>
      </div>
    </.button>
    """
  end

As the table is using a stream, and the ‘mini-table’ is within the table, when the conditional logic @show_tooltip? changes, a render will not be triggered.

Ideally I would also would want to update the tooltip_rows aswell when the button is clicked.


Solution Trade Off
I could render all ‘mini-tables’ with a class of hidden, and use JS.toggle_class on the specific ‘mini-tooltip’ table to show/hide it Lots of HTML. I would need a ‘mini-table’ for every column and every row that has this functionality. 100 rows * 2 columns with this functionality = an additional 200 tables, with all the rows pre-filled.
Move the ‘mini-table’ to outside the stream, and use some JavaScript to calculate the position of where the ‘mini-table’ should be I don’t wanna do some nasty JS :frowning:
Move the ‘mini-table’ to outside the stream, and use the css property position-anchor position-anchor is not supported on all browsers :frowning:
Stop using a stream I’m currently making use of infinite scrolling as I have thousands of rows and I lose the ability to cleanly stream_insert and stream_delete, which I’d rather keep

So TLDR;
Solution 1 seems untenable, as I assume the sheer amount of extra elements on the DOM would quickly lead to the browser being overwhelmed. Also considering id need to pre-fill all the ‘mini-tables’ with rows too, adding additional elements.

With solution 4, keeping thousands (assuming the user has scrolled) of rows (with preloads) in the assigns doesn’t seem like a great idea either.

I guess i’m running out of excuses to not do a couple lines of JS? And when widely available swap to position-anchor

2 Likes

I mean, I think you’re still in the territory where you can paper over this and keep using streams. The functionality here is helpfully isolated to a single row (or column or whatever), meaning when you receive a click event for that row you can reload the data from the DB and render it with the mini-table. You can just re-stream that one row and it will be overwritten within the table.

Where you will run into real trouble with streams is when your UI is complex enough that the “consequences” of an interaction start to spread across multiple stream items, further compounded by state which may be “local” to the UI rather than stored in the database. Your original post, if memory serves correctly, was actually a much better example of this.

So for (a contrived) example, if you wanted to have a button which arbitrarily rendered mini-tables on every row which had a certain flag at once, you would be in trouble. Here I think you’re still okay.

1 Like

A 5th solution!

The only slight caveat is I might also want to reload the last row that was clicked on (to close the ‘mini-table’ if it was previously opened on another row), but that’s not a big deal plus I’ll already have the ID & column of the previously clicked row in the assigns.

Thank you @garrison!!!

1 Like

I think you would be fine closing the last row with JS.hide() or a CSS class. If a user opened every single row you would bloat the HTML, but that’s not likely.

1 Like