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’ 
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.
2 Likes
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!!
1 Like
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!
2 Likes
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! 
1 Like
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!
1 Like
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.
3 Likes
Streams will let you update every item (probably row in your case) individually (which is good for avoiding huge diffing & needless updates), while avoiding the need to store the state on the BE (which is a pro for a large collection, but also a lot of coding overhead).
Regardless of streams, this problem sounds like something you’d either way want a fine-grained control over UI and updates so IMO it should probably be a JS hook for the entire table, with 2-way event dance between the hook and the live view on the server. It intuitively feels like you may never be able to bend a simple generic DOM patcher such as morphdom to do justice to the needs and UX of such complex element. As extra bonus, you get to use any tabular data library, but even if done from scratch in pure JS it’d be easier than fighting LV all the time. Even with all the recent goodness of client-side JS calls (JS.add_class
etc).
Then, as a reverse exercise, if I’d try to look for any serious reason to go with pure LV, it’d only be that non-LV locks you out from using LV components for table items. This may indeed be a con if you’d have complex stuff there that you’d like to render consistently with the rest of the app. In such case however you could consider using a web component with LV yielding cells into a slot in it. Such component may still be wrapped into JS hook for all the interaction with the server.
But if it’s a simple quick POC for small data and you can hack your way with LV, then sure, it’s fine if it’s fine. 
1 Like