Question: How to update table data?
Is it possible to update table data at runtime? I want to add certain interactive features (just like visidata does), for example: change the sort order, remove columns, add dynamic columns (counter or other calculations).
I already have code with which I can highlight the "active column" using the tab key. To implement this I already have to re-assign all cells with new content because it doesn't seem possible to change the style of a cell (well, maybe it is possible, but only if all cells are of type StyledCell). However, so I know I could just change every cell arbitrarily, maybe I'd even find a way to add or remove columns...
But the method I came up with so far doesn't look very effective to me. Maybe there's an API method to do such things?
If not, please consider this as a feature request 😁
Somewhat related: https://github.com/Evertras/bubble-table/issues/6
There's not a 'nice' way to do what you're doing that I can think of, but this should be solvable by adding a styling func to StyledCell rather than a static style. This would allow you to put a func in that checks for your active column index and applies a highlight or not. Does that seem reasonable?
Basically similar to how we do row-specific styling as a func, but for cells.
Added a cell style func in the PR linked above. Even if this doesn't fix your issue, this will be a helpful thing to have generally, so no work wasted.
The cell style func is given the column as part of its input. This should allow you to compare the column key with your tracked column selection key and highlight/ignore as you wish.
Released with v0.18.0
I got a little fixated on the styling problem, but for updating table data at runtime:
https://github.com/Evertras/bubble-table/tree/main/examples/updates
The short version is that you can't edit underlying cell data, you must supply all the data at once if you change anything. This keeps Bubble Tea's immutable data flow intact. This is quicker than it sounds because we'd be doing a full rerender anyway.
Yes, the cell style func helps indeed, but I'm still not yet there.
My problem is, that my model has a member variable holding the current selected column. Pressing the TAB key modifies this member variable. However, the cell func cannot access the model. So I added a wrapper to include the model:
controllerWrapper := func(input table.StyledCellFuncInput) lipgloss.Style {
return CellController(input, filtertbl)
}
// setup table data
for idx, entry := range data.entries {
rowdata := make(table.RowData, len(entry))
for i, cell := range entry {
rowdata[strings.ToLower(data.headers[i])] =
table.NewStyledCellWithStyleFunc(cell+" ", controllerWrapper)
}
rows[idx] = table.NewRow(rowdata)
}
The cell func itself then just returns the appropriate style:
func CellController(input table.StyledCellFuncInput, m FilterTable) lipgloss.Style {
if m.headerIdx[input.Column.Key()] == m.selectedColumn {
return StyleSelected
}
return NoStyle
}
Now, this works very good for the initial selected first column. However, when I press TAB, the Update() function is being called, which replaces the model instance with a copy, because Update() doesn't return a pointer, but a value. This is the relevant code in my Update() func, copied verbatim from an example (but all examples use this method):
func (m FilterTable) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var (
cmd tea.Cmd
cmds []tea.Cmd
)
m.Table, cmd = m.Table.Update(msg) // copy by value
cmds = append(cmds, cmd)
[..]
So, I may press TAB as often as I want, the current selected cell in the model visible by the cell style func remains 0.
Any idea how to solve this?
Okay. Now I'm seeing the problem. What you're starting to do here is breaking Bubble Tea's idea of one way data flow, because now we're trying to check a 'parent' link pointer rather than use pure functions from top to bottom. This is why specifically everything is copied by value and pointers are never returned from table operations, and only used internally for optimization purposes.
(edit: see next comment for the hopefully simpler solution, but leaving this here for posterity)
~~NOTE: for the below, I'd need to send in the Row for the styledcell input as well. Which is probably something we want to add anyway, and would be a quick addition.~~ I've added Row as an input for StyleFuncInput and published as v0.19.1, so the below should now work as the quick fix.
The immediate fix I would try is adding a 'hidden' field of metadata like this: https://github.com/Evertras/bubble-table#a-note-on-metadata and in the example: https://github.com/Evertras/bubble-table/tree/main/examples/metadata
You could add a 'selectedColumn' field to each row entry. This would require you to update every row with the selected column entry and apply WithRows() with the new data each time, but this should actually be very quick unless you have a LOT of rows and need performance. Then in the style func, you can check the selectedColumn field of the row that's been given and apply or not apply style as you wish.
As a longer term fix, it may be interesting to add some general table metadata that can be set which would regenerate the table; this would then be passed to things such as the style func as input, the filter, etc., which might unlock some additional interesting functionality without breaking Bubble Tea's flow because the metadata application is still keeping everything pure. This shouldn't be too difficult to add as a feature to give you a cleaner way to do this. I can give you a quick mockup of what the code might look like in use as well.
All right, I may have channeled some caffeine and just done this: #210
Specifically, take a look at this: https://github.com/Evertras/bubble-table/pull/210/files#diff-9436cd23ad7124d93a374133eb636290312a248e0e74649bd641ecaff6e2432f
Basically the pokemon example now tracks a favorite element which the user can rotate through with a keypress, and the style dynamically updates without modifying any of the underlying data or the style func; only the metadata is updated, which then propagates throughout. Essentially this would be your active column being set as metadata, and then you should be able to make style choices from there.
This way Bubble Tea is happy because data is still flowing downhill the entire time, it's a lot quicker because you're not updating a bunch of row data, and it's still very clean because you're just updating a single value and applying WithGlobalMetadata() to update the active column styling.
Does this seem reasonable?
Sounds good, I'll try it. But this will take a little time, because of a case of illness in my family.
Sorry to hear that! I'm going to merge this for now as I think it's useful regardless, obviously take your time and hope everything works out.
Ok, I have found a way to do it, I am doing it like the developer of gh-dash: I am using a Context to store stuff which needs to be persistent across table copies. That way it is very easy to modify table content, because the master copy of the data is a member of that context. The context itself is stored as a pointer in the model, so there's just one copy of the context all the time. Might not meet your idea of data flow, but provides the most flexibility to me (and my futue self).
However, many many thanks for your patience and engagement, I appreciate it very much 👍
If it works for you in your setup, that's what's important! Glad you found something that fits, and likewise thanks for the engagement; even if you don't use the things I added, they're still useful to someone I think so nothing wasted. :D