Package su.onno.ui

Class ListSpec<E>

java.lang.Object
su.onno.ui.ListSpec<E>

public final class ListSpec<E> extends Object
Builder for an entity's list/table surface, used inside EntityView.list(su.onno.ui.ListSpec<E>).

With no calls the list shows the auto-generated columns (built-in system columns + visible custom fields, in their configured order). Call columns(java.lang.String...) to take explicit control of which columns appear and in what order, or hide(java.lang.String...)/label(java.lang.String, java.lang.String) to tweak the defaults. Field names are the entity's Java field names (e.g. "displayName"); "code", "description" (catalogs) and "number", "date", "posted" (documents) address the built-in system columns.

  • Constructor Details

    • ListSpec

      public ListSpec()
  • Method Details

    • title

      public ListSpec title(String title)
    • searchable

      public ListSpec searchable(boolean searchable)
      Whether the list shows a search bar (server-side filter across text columns). Default on.
    • noSearch

      public ListSpec noSearch()
      Turn the search bar off for this list.
    • sortBy

      public ListSpec sortBy(String field)
      The column the list is sorted by initially (a field name); ascending.
    • sortBy

      public ListSpec sortBy(String field, boolean descending)
      The initial sort column + direction.
    • sortBy

      public <V> ListSpec<E> sortBy(Field<E,V> field)
      Compiler-checked initial sort field.
    • sortBy

      public <V> ListSpec<E> sortBy(Field<E,V> field, boolean descending)
      Compiler-checked initial sort field and direction.
    • columns

      public ListSpec columns(String... fields)
      Take explicit control: only these fields, in this order.
    • columns

      @SafeVarargs public final ListSpec<E> columns(Field<E,?>... fields)
      Take explicit control with compiler-checked getter references.
    • column

      public ListSpec column(String field, String label)
      Add an explicit column with a custom header label.
    • column

      public <V> ListSpec<E> column(Field<E,V> field, String label)
      Add a compiler-checked column with a custom label.
    • label

      public ListSpec label(String field, String label)
      Override a column's header label.
    • label

      public <V> ListSpec<E> label(Field<E,V> field, String label)
      Override a compiler-checked column's label.
    • hide

      public ListSpec hide(String... fields)
      Hide fields from the default column set (ignored when columns(java.lang.String...) is used).
    • hide

      @SafeVarargs public final ListSpec<E> hide(Field<E,?>... fields)
      Hide compiler-checked fields from the default set.
    • filter

      public ListSpec.FilterBuilder filter(String field)
      Declare a user-facing filter control bound to field (an entity field name, like the column/sort field names). Unlike a toolbar input — which feeds action handlers — a filter drives the list query itself: its value narrows the rows the grid shows. Returns a ListSpec.FilterBuilder; pick the control with ListSpec.FilterBuilder.options (a SELECT matched for equality), ListSpec.FilterBuilder.multiple() or ListSpec.FilterBuilder.multiOptions(java.lang.String...) (a multi-select matched as field IN (…)), ListSpec.FilterBuilder.contains()/ListSpec.FilterBuilder.startsWith() (a field-scoped typeahead for high-cardinality fields, matched case-insensitively as LIKE), or ListSpec.FilterBuilder.dateRange() (from/to pickers, a field >= from AND field <= to range).

      When several filters are declared they combine with AND: each contributes its own WHERE fragment and the row must satisfy all of them. A multiOptions filter is internally an OR/IN over its picked values, but across different filters the combination is always AND. A filter whose control is left empty (no selection, blank text) contributes no constraint, and a filter on a field the entity no longer has degrades to "no constraint" rather than failing the list.

      An @Enumeration-typed field persists as deterministic UUIDs, so the UI resolver translates a select filter's options for it: author each option as the constant name ("SHIPPED") or its @EnumLabel text, or author no options (.multiOptions()) to offer every declared value, labelled like the entity's pills.

       list.filter("season").options("2024", "2025", "2026");        // SELECT -> season = value
       list.filter("city").options("Madrid", "Paris").multiple();     // multi-select -> city IN (…)
       list.filter("doctorName").label("Doctor").contains();         // typeahead -> doctor_name ILIKE %v%
       list.filter("role").multiOptions("Хирург", "Терапевт");        // multi-select -> role IN (…)
       list.filter("checkIn").dateRange();                           // from/to pickers -> checkIn range
       // value→label split: the query matches the stored value, the dropdown shows the label
       var statuses = new LinkedHashMap<String, String>();           // ordered: dropdown follows it
       statuses.put("NEW", "Новый"); statuses.put("FILES_RECEIVED", "Файлы получены");
       list.filter("statusName").label("Статус").multiOptions(statuses);
       
    • filter

      public <V> ListSpec.FilterBuilder filter(Field<E,V> field)
      Declare a filter bound to a compiler-checked field.
    • map

      public ListSpec.MapSpec<E> map()
      Enable a map view for this list: a Table ⇄ Map toggle in the toolbar that plots the records as markers over OpenStreetMap tiles. Returns a ListSpec.MapSpec; tell it where each record's geometry comes from — a numeric latitude/longitude pair via ListSpec.MapSpec.lat(java.lang.String)/ListSpec.MapSpec.lng(java.lang.String), a GeoJSON field via ListSpec.MapSpec.geoJson(java.lang.String), or both — and optionally a ListSpec.MapSpec.label(java.lang.String) field for the marker popup and ListSpec.MapSpec.defaultView to open on the map. Field names are entity field names, like the column/sort/filter ones.

      Calling map() more than once returns the same spec (so chained calls accumulate). A map whose geo field(s) don't resolve to real columns degrades to "no map view" rather than failing the list.

       list.map().lat("latitude").lng("longitude");          // split numeric fields
       list.map().geoJson("location").defaultView();         // GeoJSON; open on the map
       
    • custom

      public ListSpec.CustomSpec custom(String type)
      Delegate the list's body to a custom renderer registered in the UI's widget registry (a consumer plugin's registerListRenderer("type", Component) via @onno/widget-sdk) — tiles, cards, a gallery, whatever the component draws. The framework keeps owning the chrome: search, declarative filters, sorting, the keyset feed, live refresh and the toolbar all still work and drive the rows the renderer receives. Returns a ListSpec.CustomSpec; optionally set the toggle ListSpec.CustomSpec.label and ListSpec.CustomSpec.defaultView to open on the custom view (a Table ⇄ custom toggle appears in the toolbar, like map()).

      Calling custom(type) again replaces the type but keeps the same spec (so chained calls accumulate, mirroring map()). A type with no registered renderer on the client degrades to the default grid rather than failing the list — same philosophy as a map() whose geo fields don't resolve.

       list.custom("bookTiles");                              // Table ⇄ custom toggle
       list.custom("bookTiles").label("Shelf").defaultView(); // open on the tiles, labelled "Shelf"
       
    • pageSize

      public ListSpec pageSize(int pageSize)
      Rows fetched per keyset window. Left unset (or <= 0) the list inherits the global default (onno.ui.list.page-size, itself 50). Clamped to the server's list ceiling.
    • cellMenu

      public ListSpec cellMenu(String field, String submenuLabel)
      Attach a declared row-action submenu to a column's cells: right-clicking the cell (e.g. a status pill) opens JUST that submenu's entries as a flat menu at the cursor — one click to the choices, instead of row-menu → submenu. The label must match a .menu("…") used by this view's ROW actions; per-row visibility/labels/colors apply exactly as in the row menu. Anywhere else on the row keeps the full context menu.
       list.cellMenu("status", "Change status");   // right-click the pill → the status choices
       
    • cellMenu

      public <V> ListSpec<E> cellMenu(Field<E,V> field, String submenuLabel)
      Attach a cell menu to a compiler-checked field.
    • cellMenus

      public Map<String,String> cellMenus()
    • groupable

      public ListSpec groupable(String... fields)
    • groupable

      @SafeVarargs public final ListSpec<E> groupable(Field<E,?>... fields)
      Declare compiler-checked grouping fields.
    • defaultGroupBy

      public ListSpec defaultGroupBy(String field)
      Open the list already grouped by field instead of flat. The field must also be declared groupable — it names one of the picker's choices, and the viewer can still switch to another grouping or back to "None". A default that isn't among the groupable columns (or doesn't resolve to a real column) is ignored with a warning rather than failing the list.
       list.groupable("status", "warehouse").defaultGroupBy("status");   // opens grouped by status
       
    • defaultGroupBy

      public <V> ListSpec<E> defaultGroupBy(Field<E,V> field)
      Open grouped by a compiler-checked field.
    • aggregate

      public ListSpec aggregate(String field, ListSpec.Agg fn)
      Declare a per-group subtotal shown on each group header (and rolled up as a grand total): an aggregate fn over a numeric field. Only meaningful alongside groupable; every group always carries its row count regardless. The subtotal is formatted with the field's own .format(...) hint, so a money column reads as money.
       list.groupable("status").aggregate("total", Agg.SUM);          // Σ total per status
       list.groupable("region").aggregate("amount", Agg.AVG, "Avg");  // labelled average per region
       
    • aggregate

      public ListSpec aggregate(String field, ListSpec.Agg fn, String label)
      As aggregate(String, Agg) with an explicit header label (else the field name).
    • aggregate

      public <N extends Number> ListSpec<E> aggregate(Field<E,N> field, ListSpec.Agg fn)
      Add an aggregate over a compiler-checked numeric field.
    • aggregate

      public <N extends Number> ListSpec<E> aggregate(Field<E,N> field, ListSpec.Agg fn, String label)
      Add a labelled aggregate over a compiler-checked numeric field.
    • rowStyle

      public ListSpec rowStyle(Function<ActionRow,ListSpec.RowStyle> style)
      Conditional row formatting: tint a row by its data. The function is evaluated per row on the server as the list feeds (same ActionRow accessor the state-aware row actions use) and returns the ListSpec.RowStyle to apply — or null for the default look. A function that throws is treated as null for that row, so one bad predicate can't break the list.
       // urgent orders read red, delivered ones green
       list.rowStyle(row -> row.bool("urgent") ? RowStyle.DANGER
               : row.enumValue("status", Status.class) == Status.DELIVERED ? RowStyle.SUCCESS
               : null);
       
    • title

      public String title()
    • include

      public List<String> include()
    • hidden

      public Set<String> hidden()
    • labels

      public Map<String,String> labels()
    • explicit

      public boolean explicit()
    • searchable

      public boolean searchable()
    • sortField

      public String sortField()
    • sortDescending

      public boolean sortDescending()
    • pageSize

      public int pageSize()
      The authored page size, or 0 to inherit the global onno.ui.list.page-size.
    • groupable

      public List<String> groupable()
      The fields offered in the group-by picker, in declaration order (empty = no grouping).
    • defaultGroupBy

      public String defaultGroupBy()
      The field the list opens grouped by, or null when it opens flat (the default).
    • aggregates

      public List<ListSpec.Aggregate> aggregates()
      The declared per-group subtotals, in declaration order.
    • rowStyleFn

      public Function<ActionRow,ListSpec.RowStyle> rowStyleFn()
      The conditional row-formatting function, or null when rowStyle(java.util.function.Function<su.onno.ui.ActionRow, su.onno.ui.ListSpec.RowStyle>) wasn't called.
    • filters

      public List<ListSpec.Filter> filters()
      The declared list filters, in declaration order.
    • mapSpec

      public ListSpec.MapSpec<E> mapSpec()
      The map view spec, or null when map() was never called (no map view).
    • customSpec

      public ListSpec.CustomSpec customSpec()
      The custom-renderer spec, or null when custom was never called (default grid only).