Skip to content

Add server data mode#15

Merged
Krever merged 4 commits intomainfrom
server-mode
Jan 21, 2026
Merged

Add server data mode#15
Krever merged 4 commits intomainfrom
server-mode

Conversation

@Krever
Copy link
Collaborator

@Krever Krever commented Jan 20, 2026

Summary by CodeRabbit

  • New Features

    • Datatables gain server-side mode for filtering, sorting, and pagination; loading/error states and APIs let UI reflect server activity and ingest server-reported totals.
    • Updates now indicate when a server fetch is required to keep remote data in sync.
  • UI

    • Loading overlays/spinners, disabled export during loading, and dismissible error alerts.
  • Examples & Tests

    • Playground/examples add client/server toggle, simulated server fetches, and comprehensive server-mode tests.
  • Documentation

    • Added Server Mode docs and Tyrian integration examples.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 20, 2026

📝 Walkthrough

Walkthrough

Adds server-mode datatable support: a new LoadingState enum; TableState extended with serverMode, totalOverride, and loading plus helpers; UI renderers show loading overlays/errors and disable Export; examples and tests add simulated server fetch flows and Tyrian async wiring; docs updated with Server Mode guidance.

Changes

Cohort / File(s) Summary
Core State Management
forms4s-core/src/main/scala/forms4s/datatable/LoadingState.scala, forms4s-core/src/main/scala/forms4s/datatable/TableState.scala
Add LoadingState enum; extend TableState[T] with serverMode: Boolean, totalOverride: Option[Int], loading: LoadingState; add setLoading, setError, setServerData, and serverMode factory; adjust display/total behavior for server mode.
Update Logic
forms4s-core/src/main/scala/forms4s/datatable/TableUpdate.scala
Add needsServerFetch: Boolean to determine which updates require server fetches (filters, sorts, paging, etc.).
Tests
forms4s-core/src/test/scala/forms4s/datatable/TableStateSpec.scala
Add server-mode tests: defaults, loading transitions, error handling, server-data ingestion, display/index behavior, pagination and query param expectations.
Examples / Playground
forms4s-examples/src/main/scala/forms4s/example/components/DatatablePlayground.scala, forms4s-examples/src/main/scala/forms4s/example/docs/DatatableExample.scala, forms4s-examples/src/main/scala/forms4s/example/docs/DatatableTyrianExample.scala
Add DataMode enum and DatatableMsg cases for mode selection, server data and errors; simulate server fetch (simulateServerFetch/fetchFromServer); wire mode switching and server responses into playground and Tyrian example.
Tyrian Renderers
forms4s-tyrian/src/main/scala/forms4s/tyrian/datatable/...
BootstrapTableRenderer.scala, BulmaTableRenderer.scala, RawTableRenderer.scala
Make renderers loading-aware: derive isLoading from state.loading; render spinner overlays and dismissible error alerts/notifications; disable/replace Export CSV UI while loading.
Documentation
website/docs/datatables/index.mdx
Add Server Mode section and Tyrian integration notes showing server-side filtering/sorting/pagination, loading/error handling, and example references.

Sequence Diagrams

sequenceDiagram
    actor User
    participant UI as UI Component
    participant State as TableState
    participant Server as Server
    participant Renderer as Table Renderer

    User->>UI: change filter/sort/page or toggle Server Mode
    UI->>State: apply TableUpdate(msg)
    State->>State: check needsServerFetch(msg)
    alt fetch required
        State->>State: setLoading()
        State-->>UI: state (loading=Loading)
        UI->>Renderer: render(state)
        Renderer-->>User: show spinner overlay
        UI->>Server: fetchFromServer(query params)
        Server-->>UI: respond(data, totalCount) or error
        alt success
            UI->>State: setServerData(data, totalCount)
            State-->>UI: state (data, totalOverride, loading=Idle)
            UI->>Renderer: render(state)
            Renderer-->>User: display table
        else error
            UI->>State: setError(message)
            State-->>UI: state (loading=Failed(message))
            UI->>Renderer: render(state)
            Renderer-->>User: show error alert
        end
    else no fetch
        State-->>UI: updated client-side state
        UI->>Renderer: render(state)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • Datatables #11 — Modifies the same datatable core types and rendering logic; appears directly related to adding server-mode and loading state handling.

Poem

🐰 I hopped from client piles to server's gate,
Spun a tiny spinner, patient as slate,
If errors nibble, I tuck them away,
Pages fetch fresh, then tables display,
— your cheerful rabbit 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add server data mode' directly and clearly summarizes the main change: introducing server-side data handling capabilities to the datatable system. It accurately reflects the primary objective across all modified files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In
`@forms4s-examples/src/main/scala/forms4s/example/components/DatatablePlayground.scala`:
- Around line 75-112: Guard against stale server responses by ignoring
ServerDataReceived and ServerError when the current model's dataMode !=
DataMode.Server and by adding a request token to detect out-of-order replies:
add a requestId field to the model (e.g., in the same case class that holds
tableState and dataMode), have fetchFromServer attach the current requestId to
the outgoing request and include it in the
DatatableMsg.ServerDataReceived/ServerError messages, then in the update
handlers for DatatableMsg.ServerDataReceived and DatatableMsg.ServerError check
that model.dataMode == DataMode.Server and that the response requestId matches
model.requestId before mutating tableState (otherwise ignore the message); also
increment/replace requestId whenever you call fetchFromServer (e.g., in
DataMode.Server switch and when needsServerFetch(msg) triggers).

In
`@forms4s-tyrian/src/main/scala/forms4s/tyrian/datatable/BootstrapTableRenderer.scala`:
- Around line 14-21: The dismiss button in BootstrapTableRenderer's errorAlert
uses data-bs-dismiss which requires Bootstrap JS; instead wire an onClick that
dispatches the existing TableUpdate action to clear the error (handle the
LoadingState.Failed case by replacing the button(...) with one that calls the
view's event dispatcher or message emitter to send the TableUpdate/ClearError
message), ensuring the handler updates state.loading to no-error so the alert is
removed; reference the errorAlert val, LoadingState.Failed, and the
TableUpdate/clear-error message used elsewhere in the renderer to keep behavior
consistent with other interactions.

In
`@forms4s-tyrian/src/main/scala/forms4s/tyrian/datatable/BulmaTableRenderer.scala`:
- Around line 14-21: The delete button in BulmaTableRenderer's errorNotification
(when state.loading is LoadingState.Failed) has no onClick handler, so it never
clears the error; add an onClick that dispatches a TableUpdate to clear the
loading/error state (e.g., introduce a new TableUpdate.ClearError variant and
emit it from the button's onClick), and update the table update/handler logic to
handle TableUpdate.ClearError by setting state.loading to the neutral/success
state (or None) so the notification is removed; alternatively remove the delete
button if you prefer not to support dismissal.
🧹 Nitpick comments (2)
website/docs/datatables/index.mdx (1)

81-125: Tighten the server-mode snippet to avoid an unused variable.
queryParams is declared but not used; consider removing it or using it in the request.

♻️ Suggested edit
 def fetchFromServer(state: TableState[Employee]): Cmd[IO, Msg] = {
-  val queryParams = state.toQueryParams
-  // Send queryParams to your API and return DataLoaded or DataFailed
+  // Send query params to your API and return DataLoaded or DataFailed
   Cmd.Run(
     httpClient.get(s"/api/employees?${state.toQueryString}")
       .map(response => Msg.DataLoaded(response.data, response.totalCount))
       .handleError(e => Msg.DataFailed(e.getMessage))
   )
 }
forms4s-tyrian/src/main/scala/forms4s/tyrian/datatable/RawTableRenderer.scala (1)

155-167: Inconsistent start calculation when no data is present.

Unlike BulmaTableRenderer and BootstrapTableRenderer (which both use if (state.totalFilteredItems == 0) 0 else state.page.offset + 1), this implementation will display "Showing 1 to 0 of 0 entries" when there's no data.

Suggested fix for consistency
 override def renderInfo[T](state: TableState[T]): Html[TableUpdate] = {
-    val start    = state.page.offset + 1
+    val start    = if (state.totalFilteredItems == 0) 0 else state.page.offset + 1
     val end      = math.min(state.page.offset + state.page.pageSize, state.totalFilteredItems)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In
`@forms4s-examples/src/main/scala/forms4s/example/docs/DatatableTyrianExample.scala`:
- Around line 43-59: Define the missing Model type and needsServerFetch helper:
add a case class Model(tableState: TableState[Employee]) (or appropriate alias)
so update can reference model.tableState, and implement needsServerFetch(msg:
TableUpdate | whatever payload type Msg.TableMsg carries): Boolean that returns
true for actions that require server fetch (e.g., filter changes, sort changes,
page changes). Ensure the signature of needsServerFetch matches how it’s called
in update (takes the same table message type used by tableState.update), and
keep update logic unchanged so setLoading, fetchFromServer, setServerData, and
setError on TableState continue to compile.
- Around line 61-68: The fetchFromServer function references an undefined
httpClient and the file lacks proper structural closure; fix by either (a)
adding a simple placeholder HTTP client or import used by fetchFromServer (e.g.,
ensure an httpClient with a get method returning an effect compatible with
Cmd.Run is in scope) and keeping the function inside a containing object/trait
so the file compiles, or (b) if this is illustrative, move fetchFromServer (and
related types TableState[Employee] and Msg) into documentation (e.g., a .md) or
mark the file to be excluded from compilation; specifically update the code
around fetchFromServer, TableState, Employee and Msg to live inside a top-level
object (or provide the missing httpClient instance) and close the file
structure.
- Around line 38-41: The enum declaration for Msg is using invalid indentation
syntax causing "indented definitions expected" — update the enum Msg definition
to valid Scala syntax by either using brace-based form or correct indent-based
block, e.g., place the cases TableMsg, DataLoaded, and DataFailed inside a
proper enum body; ensure the case with payloads is declared as "case
TableMsg(msg: TableUpdate)" (and DataLoaded(data: Vector[Employee], totalCount:
Int), DataFailed(error: String)) so references like Msg.TableMsg resolve and the
compiler no longer errors on the enum block.

@Krever Krever merged commit bc1a8d3 into main Jan 21, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant