Open gedw99 opened 1 year ago
The concept I am working with is to use htmx to update the frontend with html or Json Data.
typically html is pushed, and api wanted to try pushing new templates at runtime
Thanks!
I'll attempt sketching a super simple kanban. Starting with the shape of the reactive object:
type Item = {
title: string;
details: string;
};
type Lane = {
title: string;
items: Item[];
};
type Project = {
title: string;
lanes: Lane[];
};
declare const sb: { data: Project };
Note, I've normalized the shape. It can be denormalized to have everything
stored on a single Item
object which would map to a single table, but that
wouldn't match the a kanban UI.
Considering that only one project would be open at a time, the main reactive
object can be the Project
. Loading an app sets up an empty project:
sb.data = {
title: '' /* placeholder title */,
lanes: []
}
A project is fetched from the server on connecting. It can be directly set onto
sb.data
.
sb.data = {
title: 'The Board',
lanes: [
{
title: 'To Do',
items: [
{ title: '...', details: '...' },
],
},
{
title: 'Done',
items: []
},
],
};
There'll be a couple of functions to update the data, a few examples:
function addItem(item: Item, laneTitle: string);
function shiftItem(itemTitle: string, toLane: string);
function deleteItem(itemTitle: string);
These function will do just one thing and that is update the reactive object, for example:
function addItem(item: Item, laneTitle: string) {
const lane = data.lanes.find(({ title }) => title === laneTitle)
lane?.items.push(item)
}
This way, they can be used for both:
For example:
addItemButton.addEventListener('click', () => {
const item = getNewItem() // Read from input elements
const laneTitle = getLaneTitle() // Read from input element/parent
// UI to reactive object
addItem(item, lane)
// UI to socket
socket.send(
JSON.stringify({
message: 'add-item',
data: {item, laneTitle}
})
)
})
socket.addEventListener('message', (event) => {
const data = JSON.parse(event.data)
// socket to reactive object (by extention UI)
if (data.message = 'add-item') {
const {item, laneTitle} = data.data
addItem(item, laneTitle)
}
})
In the above example, changes applied to the reactive object propagate to the UI. But not to the socket. This is done in the UI event listeners
A couple of complications, if required.
The reactive object can be made the center of updates such that any changes to
it are propagated to the socket too, using sb.watch
(yet to be documented).
sb.watch('lanes.0.items', () => { /* do something */})
The issue here is that since sb.watch
captures updates at the parent object
level, if any child element—irrespective of nesting—is updated, it's hard to
pinpoint the nature of the update.
For instance a shiftItem
operation consists of several atomic operations, for
example:
1. remove item from source lane O(1)
2. adjust source lane O(n_source)
3. set item in dest lane O(1)
4. adjust dest lane O(n_dest)
It'd be highly imprudent to transmit all of those changes, depending on the kanban structure in the database, it could be as simple as just updating a single cell.
And so it's better to handle UI to socket propagation in the in the UI event listener.
The example above uses optimistic updates. UI is updated (by updating the reactive object) after which the the socket message is sent.
If the update fails on the server side, then a message will be regarding this failure will be sent.
Failure can be handled in multiple ways, for instance sending the old state which can be assigned to the reactive object (which'll update the UI). Along with a toast indicating the nature of the error.
items
, lanes
, projects
to a single objectIf the data is to be denormalized to single objects to say, have a simpler table
structure, the UI side can be dependent on computed
values. This would
simplify updates.
I'm not sure how this factors in with the usage of HTMX, as per my understanding
when using HTMX, data operations such as addItem
, shiftItem
, etc would be
handled server side and the updated HTML is returned to update the UI.
W.r.t pushing templates, you can register them using sb.register
(yet to be
documented), example:
sb.register(`
<template name="blue-p">
<p style="color: blue;">
<slot />
</p>
</template>
`);
but you can't re-register them, limitation of the WebComponent spec.
@18alantom
The new docs are really good.
I am keen to try to do an example that integrates a golang backend with Strawberry.
Could you possible outline how the API should work between the 2 layers ?
I am keen to see how the state between the 2 layers are synergistic. My plan is for all mutations to go to backend , and somehow update the json on the frontend when it changes. SSE or web sockets…
I was thinking of a nice simple example like a kanban that has projects and items and tags . A tag is the kanban lane.
If you want a different example just let me know also.