joshua-bouv

joshua-bouv

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

Take the table generated with core_components.

Say I wanted to add checkboxes to the first column of my table, like the below

However my table is using a stream for the rows, and I only want these checkboxes to show conditionally (ie when entering an ‘edit mode’)

As phx-update="stream" is set, the row will not change if I add any conditioal code inside it. For example,

<td class={[@selectable_rows? && "hidden"]}>
    <.input type="checkbox> />
</td>

When @selectable_rows? is toggled, nothing would happen (presumably because the content inside the phx-update="stream" is now essentially clientside, so LiveView won’t send any more updates)

Im not realy sure on a good way to approach my issue however

  • Is this possible to achieve without having to reset the stream?
  • Do I have to send an stream_insert to every row to trigger an update?
  • Do I have a seperate table to the left of my actual table with the checkboxes?

Would be intrested to hear some thoughts on this as none of my ideas feel very ‘good’ :frowning:

Marked As Solved

steffend

steffend

Phoenix Core Team

I just didn’t think about this. That’s a perfectly good solution, in my opinion much better than what I suggested. And you can also use toggle_class with it to toggle purely client-side!

Also Liked

garrison

garrison

Ah okay - I wasn’t going to get into this because I thought it wasn’t relevant but if you actually have that many rows you still don’t actually need to use streams.

There is no reason you can’t virtualize the table on the server instead and store the current “page” in-memory. You can load more rows on scroll up/down just like streams, but render them as LiveComponents instead so that you can perform operations on them (like the edit state change).

The weird thing is that this isn’t really more complicated than the stream approach - it’s actually much easier to reason about. Which is why I worry about streams being recommended for this in the docs.

I think the one case where streams would be a clear winner is if all of the rows somehow have to be on-screen at once. The only case where that really happens in practice is when rendering a large chart, and actually I seem to recall that being the example Chris referenced for how streams came about. I’m still not confident that is a problem LiveView really needs to solve, but that water is under the bridge.

I don’t mean to knock on the CSS trick, I actually quite like it. Solving this in CSS is great with LiveView because you get to avoid server latency.

What I was getting at is that there are variations of this problem, more complicated variations, that require actual “code” to solve. And with a streams approach solving them will get very difficult, because you don’t have the declarative rendering model to help you manage the exponentially-increasing state space of your UI.

There shouldn’t be, no. But since you’re using streams there will be no re-renders at all anyway in this case, because that’s how streams work.

steffend

steffend

Phoenix Core Team

Hey @joshua-bouv,

you’re right that when you change your selectable_rows? assign, nothing happens because on the server the stream is empty.

When you always render the column though - as you do - and you just need to toggle it from hidden to visible, you can push an event from the server. Something like this (I didn’t test the code!):

<td class={[@selectable_rows? && "hidden"]} data-toggle-row>
    <.input type="checkbox> />
</td>
def handle_event("toggle_edit_mode", _params, socket) do
  ...

  {:noreply, push_event(socket, "toggle_edit", %{"id" => "my-table"})}
end
# app.js
window.addEventListener("phx:toggle_edit", (e) => {
  document.querySelector(`${e.detail.id} [data-toggle-row]`).forEach(node => {
    if (node.classList.contains("hidden") {
      node.classList.remove("hidden");
    } else {
      node.classList.add("hidden");
    }
  })
})

Note that if you toggle the edit_mode using a button, you can also use JS.toggle_class:

<button phx-click={JS.toggle_class("hidden", to: "#my-table [data-toggle-row]") |> JS.push("toggle_edit_mode")}>Toggle edit mode</button>

If you don’t need to do anything on the server, the extra JS.push isn’t needed.

joshua-bouv

joshua-bouv

Thank you @steffend !

My brain didn’t even think about using JS for this, but it does make total sense considering the stream lives on in the client!

Building on to top of your solution, I was thinking I could potentially skip the push_event and instead attach the style to just above the stream. For example

thead.hidden-checkbox th:nth-child(1) {
    display: none;
}

tbody.hidden-checkbox td:nth-child(1) {
    display: none;
}
<tbody
  id={@id}
  phx-update={match?(%Phoenix.LiveView.LiveStream{}, @rows) && "stream"}
  class={not @selectable_rows? && "hidden-checkbox"} # this

Would you say this could also be a reasonable approach or might it be questionable behaviour?


And I will be pinching the idea of using JS.toggle_class for my edit button! Thank you!!

garrison

garrison

I think this post is a good example of why we need to be more careful about recommending streams to new users. The way they break the declarative rendering model is very unintuitive and makes it more difficult to implement basic features like this. Obviously I have already written about this on here far too extensively, but again - this is a good example.

To the OP: if your real workload is similar to the example you gave, consider whether you really need to use streams for this case. Unless you have literally thousands of rows it’s probably not necessary. If you have, say, dozens to hundreds of rows, then a LiveComponent-per-row approach will perform similarly while preserving the declarative rendering model which you were expecting.

The CSS trick is a great solution for this particular case (I have done many similar things), but it will not scale to arbitrarily complex UI - you are going to run into problems like this again, eventually, and there may not be such an easy way out.

Obviously there are myriad cases where this is a valid approach, but you have to be very careful layering on the imperative complexity here. As an app grows, you are going to have to keep stacking more and more side-effects (events) on top of each other, and then you will have to keep stacking more and more conditional cases to handle how those events interact with each other. And with this approach, now all that work is also happening in JS instead of on the server - the horror! :slight_smile:

joshua-bouv

joshua-bouv

In my case, I do, and im doing a bunch of (necessary) preloading on each row too.

I agree it’s a ‘trick’, and nobody wants a codebase based on tricks! However my actual implementation was rather undesruptive so I am not too bothered by it.

I just create an extra <:col> for my table which has my checkbox input, as you would any other column, and pass the thead and tbody hidden_checkbox class into my <.table to default the checkbox column as hidden.

As the classes aren’t actually changed in the <.table component attribute (as it’s using JS.toggle_class), there isn’t any unesccary re-rendering of that table either when I flip between edit and non-edit modes (I don’t think anyway?).

So the default core_components table is almost unchaged, other than I add an optional attrbute for the thead and thead class.

But I will be careful in the future when I face my next inevitable problem. Thank you for your advice!

Where Next?

Popular in Questions Top

Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
LegitStack
I’m hoping you guys can give me some general advice and perhaps code examples if you’re feeling up to it. I’m very interested in Elixir,...
New
quazar
How to set Jason to encode all fields in ecto schema, I don’t care about security and implementing only is taking long list of attributes...
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
chensan
I have a User schema with a :from_id field set to type :string: defmodule TweetBot.Repo.Migrations.CreateUsers do use Ecto.Migration ...
New
chewm
Hi guys, nice to meet you to the whole forum, I’m new here, I’m trying to configure visual studio code for elixir, right now the intellis...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New
lucidguppy
I have a super simple question about elixir - how would I take a file like this foo bar baz and output a new file that enumerates th...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New

Other popular topics Top

Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
quazar
How to set Jason to encode all fields in ecto schema, I don’t care about security and implementing only is taking long list of attributes...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
lk-geimfari
What is most correct way to open, read and parse JSON file with poison? For example if we have example.json file in root of some projec...
New
danschultzer
None of the current solutions worked well for me, so I went ahead and built a user management system from scratch. This project took far...
548 27727 240
New
aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
josevalim
Hi everyone, One of the features added to Elixir early on to help integration with Erlang code was the idea of overridable function defi...
New
Nvim
Elixir appears to be a superior language to Python. I don’t see any advantage of Python over Elixir. Are there any?
New

We're in Beta

About us Mission Statement