What’s the canonical way of implementing an editable tabular data / grid / table in Phoenix LiveView?
Think of a spreadsheet but only with a minimal feature set (inline editing, showing select inputs, errors, validations, suggestions, etc.).
I don’t really need multi-collaboration in real time for now.
I have to deal with anywhere between 300 - 600 rows of 30 - 50 columns and probably more in the future.
I have tried the following approaches:
1) Streams + HTML table + form per row
HTML does not allow to have a form per row so it does not work. If I’m using streams, I need to have a form per row so on input change, the Live View gets all the data for the given row, not just for the cell being currently edited.
2) Streams + HTML table + form per table using form attribute on form input
Could not make the change tracking work. The form just sends empty params.
3) Streams + HTML table + form per cell
- the form will only send one field at a time (except for the field where I explicit add
phx-value_some_field
but I’d need to repeat that over each input for all of the fields of the record I want to update…) - and then in the
handle_event("validate")
, I would have to do the following:
def handle_event("validate", params, socket) do
changesets = socket.assigns.record_changesets
changeset = Enum.find(changesets, &(get_field(&1, :id) == params["id"])
changeset = Record.changeset(changeset.data, params)
socket = stream_insert(:changesets, changeset)
{:noreply, socket}
end
Meaning holding the collection in both the assigns and in the stream, which seems to defeat the purpose of streams in the first place.
4) Streams + indivual form input without any forms
So I have:
<tr>
<td><.input type="text" value={get_field(changeset, :some_field)} phx-change="validate" phx-value_id={get_field(changeset, :id)} .../></td>
<td><.input type="select" options={@options} value={get_field(changesetm :some_other_field)} phx-change="validate" phx-value_id={get_field(changesetm :id))} /></td>
</tr>
The problem with stream_insert
is that I lose the focus on the cell that was currently being edited. All of the examples that I could find about streams seems to point out that when you want to edit a streamed item, you open a modal, go to another route, etc … I have not seen many use-case for inline editing, which makes me wonder if I am supposed to use streams for that at all.
5) CSS grid
CSS grid does not seem fit for purpose for tabular data. You can’t wrap your headers / subheaders or rows into a parent element and apply classes to the children so it becomes hard to have stuff like highlight the entire row on cell hover. I’m concerned about using CSS grid especially when requirements will grow in complexity in the future.
6) LiveView + JS library (AG-Grid, Handsontable, etc.) via Phoenix hook
Those are either expensive, or I need to duplicate some of the business logic over to JavaScript, which I’d like to avoid as it seems hard to maintain over time.