Skip to content

Add DynamicDataTable, ProgressBar, CollapsiblePanel, icons and color themes to wicket-extensions - #1637

Draft
reiern70 wants to merge 10 commits into
masterfrom
dynamic-data-table
Draft

reiern70 wants to merge 10 commits into
masterfrom
dynamic-data-table

Conversation

@reiern70

@reiern70 reiern70 commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Work in progress. This pull request is still a draft and many more changes will follow; feedback on the direction is very welcome. The branch is rewritten from time to time, so that every commit stays a self-contained change.

Adds a DynamicDataTable and a set of supporting components to wicket-extensions, each usable on its own:

Component Package Issue
DynamicDataTable org.apache.wicket.extensions.ajax.markup.html.repeater.data.table.dynamic
ResizableColumnsBehavior org.apache.wicket.extensions.markup.html.repeater.data.table
ProgressBar org.apache.wicket.extensions.markup.html.progress #1635
Themes org.apache.wicket.extensions.theme #1638
CollapsiblePanel org.apache.wicket.extensions.markup.html.collapsible #1636
FloatingPanel org.apache.wicket.extensions.markup.html.floating
ClipboardCopyBehavior org.apache.wicket.extensions.markup.html.clipboard
Icons org.apache.wicket.extensions.markup.html.icon
TabsStyleBehavior (see Themes) org.apache.wicket.extensions.markup.html.tabs #1645

Issues:

Everything is new API for 11.0.0 and works under a strict Content Security Policy: no inline script or style. The only visible change for existing applications is the UploadProgressBar, which now renders the ProgressBar's bar (see Themes). There are WicketTester tests, QUnit tests and examples; mvn clean verify -Pjs-test is green.

Commits, in order, on top of master ee62ec8c01 (up to date with master as of 2026-10-05):

  1. Keep the veil script compilable by a second build (Add Ajax veil behaviors that block the page, or a component, while a request runs #1631)
  2. Set up the web socket connection without jQuery (The web socket setup script fails on a page without jQuery #1640)
  3. CollapsiblePanel (Add a collapsible panel component to wicket-extensions #1636)
  4. FloatingPanel
  5. ClipboardCopyBehavior
  6. IIcon, SvgIcon and FontAwesomeIcon
  7. DynamicDataTable, with the ProgressBar and ResizableColumnsBehavior (Add a generic ProgressBar component to wicket-extensions #1635)
  8. Color themes (Add color themes shared by the components of wicket-extensions #1638), applied to the components before it
  9. ModalDialog, auto-complete, tabs and the upload progress bar follow the themes (Make ModalDialog follow the shared themes of wicket-extensions #1645, part of Make the other components of wicket-extensions support the shared themes #1639), with the themed ajax examples and a theme editor
  10. An Extensions index in the examples

Themes

Colors defined once, as CSS custom properties (--wicket-theme-*), and read by the style sheets of the components below.

  • Theme ships default, blue, light blue, red, orange, grey, green and dark; Theme.CSS is the style sheet defining them.
  • The veil of wicket-extensions (Add Ajax veil behaviors that block the page, or a component, while a request runs #1631) follows the theme too: its dimming (--wicket-theme-veil) and spinner.
  • A theme also sets the color scheme of the form controls inside themed components, so text fields and check boxes match the theme even on a page shown in dark mode.
  • ThemeBehavior puts a theme's CSS class on a page or on a single component and contributes the style sheet. The theme comes from a model, so re-rendering switches it.
  • Themes are switched by class only. A component outside a theme keeps its default look.
  • An application defines a theme of its own as a CSS class setting the same properties.
  • An element in a theme also carries the marker class wicket-theme; a custom theme puts it next to its own class.
  • ModalDialog (Make ModalDialog follow the shared themes of wicket-extensions #1645), AutoCompleteTextField, TabbedPanel (with TabsStyleBehavior: tabs, pills or underline) and UploadProgressBar (now looking like the ProgressBar) follow the themes too; outside a theme they keep their look, except the UploadProgressBar, whose bar is now the ProgressBar's.
  • Supporting the themes in the remaining components (DataTable, Palette, trees, ...) is Make the other components of wicket-extensions support the shared themes #1639.

CollapsiblePanel

A title and a body the user can expand and collapse, rendered as native <details> / <summary>.

  • Works without JavaScript and is keyboard accessible.
  • Collapsed by default; setExpanded(true) renders it expanded.
  • setRememberExpanded(true) reports every toggle via Ajax, so the panel keeps the user's choice when it or its page is rendered again.
  • The title is a model, rendered escaped; the body is any component returned by newBody(String).
  • The body text color is configurable through the CSS custom property --wicket-collapsible-text.
  • Inside a theme it takes its colors from it: the title in the theme's primary colors, like a table header.

FloatingPanel

A window with a title bar and a body, for content floating on top of a page or a component.

  • The title bar shows the title, escaped, and a close button calling onClose(target) (hidden when isClosable() is false).
  • The body is any component returned by newBody(id).
  • The close button, an SVG cross centered in the title bar, follows the body in the markup, so a focus trap starts in the body.
  • Inside a theme the title bar takes the colors of the table headings.

ClipboardCopyBehavior

Copies a text to the clipboard and shows a check mark where the copy was made.

  • Wicket.Clipboard.copy(text, iconTarget) copies from any script, shows the check mark over the end of iconTarget and announces the copy to screen readers.
  • Any element with data-wicket-copy copies its value on a click, or a double click with data-wicket-copy-on="dblclick", also in markup rendered in the browser.
  • ClipboardCopyBehavior writes these attributes (escaped) for a component and renders the script.

Icons

IIcon is an icon that renders its own markup, so components can take any icon.

  • FontAwesomeIcon: the 2001 solid icons of Font Awesome Free, as <i class="fas fa-name">; the page loads the Font Awesome style sheet.
  • SvgIcon: the same icons as inline SVG, one em high, in the current text color, needing no style sheet or font.
  • Wicket ships no Font Awesome content. SvgIcon reads the icons from the Font Awesome web jar the application puts on its class path, keeping the attribution comment of each file. The icons are CC BY 4.0, Category B under the ASF third-party policy, which may not be part of a source release; wicket-extensions uses the web jar for its tests only.

ProgressBar

A generic progress bar, for the progress of any work, not only uploads.

  • Renders a native <progress> element for a percentage clamped to 0..100.
  • A null value renders an indeterminate bar, for work whose end is unknown.
  • The filled part is striped and animated, respecting prefers-reduced-motion; a finished bar is solid.
  • The percentage is bold, on a chip of the track's color, so it stays readable over the filled part and on dark pages; the middle of its digits is placed on the middle of the bar in every browser with the CSS cap unit, checked by a QUnit test.
  • Its markup is also available as a string, from ProgressBar.markup(value) and ProgressBar.indeterminateMarkup(), for bars rendered in the browser.
  • Inside a theme it takes its colors from it.

ResizableColumnsBehavior

Lets the user resize the columns of a table by dragging the edge of a header, or with the arrow keys on it.

  • Two modes: within the table's width (a column takes width from its neighbour), or stretching the table. The DynamicDataTable uses the stretching one, or none.
  • While dragging, a line with arrowheads shows where the edge will land, a shaded band the new extent of the column and a label its width; the width is applied on release.
  • The guide is removed whatever ends the drag: release, cancel, Escape, the window losing focus, an error or a new drag.
  • Columns can opt out. The widths are reported to the server, so they survive re-rendering.
  • Initial widths can be given as proportions, and the table can take the full width of its parent, following the window until the user resizes a column.

DynamicDataTable

A table that renders only the <table> and its header on the server. The rows come as JSON from the table's own endpoint, and each cell is a column template evaluated in the browser (built-in {{path}} / {{{path}}} engine, or Handlebars).

DataTable renders every cell as a component, so each refresh re-renders the whole body on the server, and a click on a row that has changed or gone since it was rendered targets a component that no longer exists. DynamicDataTable looks the clicked row up by its key through IDynamicDataProvider instead, and calls onNotFoundAction() if the row is gone.

  • Columns: templates from code or from .html resources resolved like markup (style, variation, locale); headers as any component; row numbers; a progress bar column.
  • Actions: plain markup carrying data-dt-action, dispatched by the table. IconBasedRowAction shows an action as an icon button with a tooltip, any IIcon (built-in edit, delete, download, add, or SvgIcon / FontAwesomeIcon), optionally asking for a confirmation first. IconBasedToolbarAction is an icon button in the navigation. AjaxDownloadActionColumnContributor downloads a row, as JSON by default or in any format (getContent, getContentType), as a link or an icon.
  • Overlay: showOverlay(component, target) shows any component, typically a FloatingPanel with an edit form or a confirmation, in a window over a veil that blocks only the table; focus stays in the window, and Escape closes it unless something in it was changed.
  • Veil: a LocalVeilBehavior blocks the table while one of its Ajax requests runs (newVeilBehavior()).
  • Keys: typed row keys (IIdentifiable<K>, formatKey / parseKey).
  • Paging and sorting: navigation on top, at the bottom or both; rows-per-page choice; sorting through SortableDynamicColumn and ISortableDynamicDataProvider.
  • Selection: kept on the server by key (SelectionColumn, ISelection), including "select all rows of the provider".
  • Toolbars: like DataTable's (addTopToolbar / addBottomToolbar); the navigation and the selection bar are toolbars, with a slot before the navigator (for example a filter) and pluggable toolbar actions after it.
  • CSV export: CsvExportToolbarAction exports the selected rows, or all rows if none is selected; columns take part through getExportValue. java.time values are written in ISO-8601.
  • Hiding columns: columns marked IHideableColumn can be hidden by the user with a ColumnChooserToolbarAction; the application hides any column with setColumnVisible.
  • Moving columns: columns marked IMovableColumn are dragged by a grip in front of their title (pointer events, so mouse, pen and touch), a marker with arrowheads showing where they will land, or moved with the arrow keys; other columns keep their places.
  • Remembering the layout: order, visibility and widths are kept as a ColumnState in an IColumnStateStore, loaded when the table is created and saved after every change by the user, once a store is set: SessionColumnStateStore keeps them for the session, an application's own store can keep them in its database, by user, so they survive restarts. Columns are referred to by their unique getId().
  • Layout: setColumnProportions(double...) and setAdjustToParentWidth(boolean), with any resizing mode. A table rendered again via Ajax gets its rows with the response, so its body never shows empty.
  • Resizing: through ResizableColumnsBehavior's script. While the columns can be resized, every row ends with an empty cell leaving room after the last column; getCellCount() gives the colspan of a whole row.
  • Theming: header, rows, toolbars and their form controls (filter field, rows-per-page choice, check boxes) take the theme's colors.
  • Web socket push: refresh, replace the rows, or repaint a single row, also from a background thread (send(Application, sessionId, IKey)).

Templates are trusted markup written into the page as is. The Javadoc of IDynamicColumn says they have to be authored by the developer.

Examples

  • The DynamicDataTable example runs over a thousand contacts, with a filter, CSV export, live progress bars, width and proportion options, a theme drop-down, an icon set drop-down, a column chooser, movable columns, a column state kept in the session or in a file (with a reset), adding a contact, an address column, a validated edit form in a floating panel in the table's overlay, delete with a confirmation, a jCard (RFC 7095) download of a contact, phone numbers copied to the clipboard on a double click, and its settings as drop-downs.
  • The ajax examples of the themed components show their explanation in a collapsible panel and offer a theme drop-down; a theme editor example edits a theme's colors with a live preview and gives the CSS class.
  • An Icons page shows the built-in icons, SvgIcon and FontAwesomeIcon, filtered by name; a click copies an icon's Java constant.
  • The ajax examples get a progress bar page and a collapsible panel page.
  • The home page gets an "Extensions" index of the examples built on wicket-extensions; repeaters (with the DynamicDataTable), tree, wizard, breadcrumb, captcha and dates move there from the home page, and the veil example and the Icons page are listed there.

@reiern70 reiern70 self-assigned this Oct 2, 2026
@reiern70

reiern70 commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor Author

@papegaaij this branch is in WIP... and I still need to do several iterations. But anyways I would love your feedback. On ideas about what such components should support.

@reiern70
reiern70 force-pushed the dynamic-data-table branch from 5b3a2f4 to adade30 Compare October 2, 2026 17:15
@reiern70 reiern70 changed the title Add DynamicDataTable, a table whose rows are rendered in the browser from JSON Add DynamicDataTable, ProgressBar and CollapsiblePanel to wicket-extensions Oct 2, 2026
@codecov-commenter

codecov-commenter commented Oct 2, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.06412% with 163 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.90%. Comparing base (ee62ec8) to head (1398213).

Additional details and impacted files
@@             Coverage Diff              @@
##             master    #1637      +/-   ##
============================================
+ Coverage     61.95%   65.90%   +3.95%     
- Complexity    11280    11877     +597     
============================================
  Files          1250     1301      +51     
  Lines         48429    54030    +5601     
  Branches       6792     6960     +168     
============================================
+ Hits          30002    35609    +5607     
+ Misses        15729    15673      -56     
- Partials       2698     2748      +50     
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@reiern70
reiern70 force-pushed the dynamic-data-table branch from adade30 to 4a8db01 Compare October 2, 2026 17:31
@reiern70 reiern70 changed the title Add DynamicDataTable, ProgressBar and CollapsiblePanel to wicket-extensions Add DynamicDataTable, ProgressBar, CollapsiblePanel and color themes to wicket-extensions Oct 2, 2026
@reiern70
reiern70 force-pushed the dynamic-data-table branch 3 times, most recently from deccd56 to 9f02bcb Compare October 2, 2026 20:22
A build of wicket-extensions without clean failed in the resources optimizer:
the Map and for...of loops in wicket-veil.js made the Closure compiler, which
transpiles to ES5, add its polyfill prelude to wicket-veil.min.js, and the
next build, which compiles that file again, rejected the prelude's @define
statements (JSC_NON_CONST_DEFINE). Only a clean build passed.

The registry of local veils is now a plain object without prototype, read with
Object.keys, which compiles without polyfills, so the minified script compiles
again and repeated builds pass. The behavior of the veil is unchanged.

GitHub issue #1631
@reiern70
reiern70 force-pushed the dynamic-data-table branch 2 times, most recently from e4e1395 to 6f2fdea Compare October 3, 2026 18:10
The script that sets up a page's web socket connection,
wicket-websocket-setup.js.tmpl, stored the page id, the context, the connection
token and the other settings with jQuery.extend(Wicket.WebSocket, ...).
Wicket's JavaScript no longer needs jQuery, so on a page that does not load it
a WebSocketBehavior failed with "ReferenceError: jQuery is not defined": the
connection was never opened and no push message reached the page.

The settings are now copied with Object.assign, which does the same for this
flat object. Pages that load jQuery see no difference; pages without it get a
working web socket connection. WebSocketTesterBehaviorTest checks that the
rendered setup script does not refer to jQuery.

GitHub issue #1640
…apse

Pages often carry content that only some users need all the time: long
explanations, help texts, advanced filter forms. Applications keep building
their own show/hide toggles for this, usually with inline script or inline
style, both of which a strict Content Security Policy blocks.

CollapsiblePanel in wicket-extensions renders the native <details> and
<summary> elements: it works without JavaScript, is keyboard accessible and
needs no inline script or style. It is collapsed by default and can be
rendered expanded with setExpanded(true). With setRememberExpanded(true) every
toggle is reported via Ajax, so the panel keeps the user's choice when it or
its page is rendered again. The title is a model, rendered escaped; the body
is any component returned by newBody(String). The color of the body's text is
the CSS custom property --wicket-collapsible-text if it is set, else the color
of the panel's parent.

The ajax examples get a CollapsiblePanel page showing a collapsed, an expanded
and a remembering panel, the last with a form as its body, with some space
between the panels and the body text in normal weight. WicketExamplePage gets
newExplanation(String, IModel), so an example can show its explanation in a
collapsed panel. The OSGi export list of wicket-extensions gains the package.

GitHub issue #1636
Content shown on top of a page or a component, such as a form editing a row of
a table, keeps needing the same frame: a title, a way to close it and a body.
Applications build it each time, often with inline styles a strict Content
Security Policy blocks.

FloatingPanel, in the new package
org.apache.wicket.extensions.markup.html.floating, renders a title bar with
the title, escaped, and a close button calling onClose(target), hidden when
isClosable() is false, above the body returned by newBody(id). It needs no
inline script or style. The panel is a CSS grid: the close button, a cross
drawn as SVG so it is centered whatever the font, comes after the body in the
markup and is placed in the title bar by the grid, centered vertically, so a
focus trap starts in the body; it is named for screen readers from
FloatingPanel.close.

The OSGi export list and module-info gain the package.

New API only; nothing existing changes for applications.
Applications showing values users copy, such as phone numbers or identifiers,
write their own clipboard code, and rarely confirm that the copy happened.

The new package org.apache.wicket.extensions.markup.html.clipboard brings
wicket-clipboard.js: Wicket.Clipboard.copy(text, iconTarget) copies the text,
then shows a check mark over the end of iconTarget, inside its box so a cell
clipping its content does not hide it, and announces the copy to screen
readers. Every element with a data-wicket-copy attribute copies its value on a
click, or on a double click with data-wicket-copy-on="dblclick", which also
works for markup rendered in the browser. ClipboardCopyBehavior writes these
attributes, escaped, for a component and renders the script, its style sheet
and the localized message (ClipboardCopyBehavior.copied); renderHeadItems()
renders them for markup rendered elsewhere. Browsers allow writing to the
clipboard only in a secure context and in response to a user action.

The OSGi export list and module-info gain the package; the script and its
tests run with the other JavaScript tests.

New API only; nothing existing changes for applications.
Components showing an icon, such as the action buttons of a table, either
hard-code their own few SVG paths or ask the application for markup, with no
common type to pass an icon around.

The new package org.apache.wicket.extensions.markup.html.icon has:
- IIcon, an icon that renders its own markup (getMarkup()), written into the
  page as is; serializable and usable as a lambda.
- FontAwesomeIcon, the 2001 solid icons of Font Awesome Free as an enum, whose
  markup is <i class="fas fa-name" aria-hidden="true"></i>; the page has to
  load the Font Awesome style sheet. getName() gives the Font Awesome name,
  such as pen-to-square; the digits are DIGIT_0 to DIGIT_9.
- SvgIcon, the same icons as inline SVG, one em high, as wide as their view
  box, filled with the current color and hidden from screen readers, so they
  need no style sheet or font.

Wicket ships no Font Awesome content: SvgIcon reads each icon, the first time
it is rendered, from the Font Awesome Free web jar
(org.webjars.npm:fortawesome__fontawesome-free) the application puts on its
class path, and keeps the attribution comment of the file. Without the web jar
it throws a WicketRuntimeException naming the dependency. The icons are
licensed under CC BY 4.0, a Category B license under the ASF third-party
policy, which may not be included in source releases; this keeps it out of
Wicket's artifacts altogether. wicket-extensions uses the web jar for its tests
only; its version is managed in the root pom (fontawesome-free.version).

The OSGi export list and module-info gain the package.

New API only; nothing existing changes for applications.
@reiern70
reiern70 force-pushed the dynamic-data-table branch from 6f2fdea to aad2ddb Compare October 4, 2026 01:45
@reiern70 reiern70 changed the title Add DynamicDataTable, ProgressBar, CollapsiblePanel and color themes to wicket-extensions Add DynamicDataTable, ProgressBar, CollapsiblePanel, icons and color themes to wicket-extensions Oct 4, 2026
…from JSON

DataTable renders every cell as a Wicket component, so each refresh re-renders
the whole body on the server and a click on a row that has changed or gone
since it was rendered targets a component that no longer exists.

DynamicDataTable, in wicket-extensions, renders only the <table> and its
header. The rows come as JSON from the table's own endpoint, on load, on
refresh(target) and optionally on a poll interval, and each cell is a column
template evaluated in the browser by a template engine: a built-in one with
{{path}} (escaped) and {{{path}}} (raw), or Handlebars. Links in the cells are
plain markup carrying data-dt-action; a click goes to the table, which looks
the row up by its key through IDynamicDataProvider and hands it to the
IAjaxActionColumnContributor registered for the action, or to
onNotFoundAction() if the row is gone. Rendered rows carry an id,
<tableId>-row-<key>, next to data-key, so keys have to be unique and stable.
The action prefix _wicket is reserved for the actions the table handles
itself; registering a contributor for such an action throws an
IllegalArgumentException.

Templates are trusted markup written into the page as is; the Javadoc of
IDynamicColumn says they have to be authored by the developer.

Columns can read their templates from .html resources the way a component
reads its markup: ResourceDynamicColumn and ResourceDynamicColumnContributor
look the file up through the application's IResourceStreamLocator for the
table's style, variation and locale, take the content of its <wicket:panel>
(so a license header outside it is left out), honour stripComments, and are
cached per table outside development mode. Columns can render their header as
any component through IDynamicColumn#newHeader(String), a label of getHeader()
by default.

Row keys are typed: DynamicDataTable, IDynamicDataProvider,
ISortableDynamicDataProvider and SortableDynamicDataProvider take a type
parameter K. keyOf(T) defaults to IIdentifiable<K>;
SortableDynamicDataProvider takes the key type and an optional key function
such as Contact::getId. Keys become strings only at the browser boundary,
through the provider's formatKey(K) and parseKey(String), the latter
converting with the application's converter for getKeyType() by default.

The table
- is paged like DataTable, its navigation on top, at the bottom or both, the
  navigator, its label and an optional rows-per-page choice on one line
- has toolbars like DataTable: rows of the table above or below the body,
  added with addTopToolbar/addBottomToolbar (AbstractDynamicToolbar). The
  navigation (DynamicNavigationToolbar) and the selection bar
  (DynamicSelectionToolbar) are toolbars themselves, created by
  newNavigationToolbar/newSelectionToolbar and placed by a ToolbarPosition.
  The navigation has a slot in front of the navigator (newPrefix), for example
  for a filter, and one after it for table-wide actions, added with
  addToolbarAction(IDynamicToolbarAction). While a prefix is shown the
  navigation stays visible and only its paging part is repainted, so a filter
  field keeps its focus.
- exports to CSV with CsvExportToolbarAction, an icon button in the
  navigation: the selected rows if there are any, all rows of the provider in
  the current order otherwise. A column takes part through
  IDynamicColumn#isExportable and getExportValue(row), or
  AbstractDynamicColumn#setExportValue; columns without a value are left out.
  Dates and times of java.time are written in ISO-8601, so other tools read
  them unambiguously; other values go through the table's converters. The file
  name, delimiter and download location are configurable.
- sorts by the columns wrapped in a SortableDynamicColumn, an
  ISortableDynamicDataProvider receiving the sort state; sortable headers show
  an up and a down arrow, the current one highlighted
- numbers its rows across pages with a RowNumberColumn and marks them even and
  odd
- lets the user resize its columns through the new ResizableColumnsBehavior
  script, stretching the table (ColumnResizing.STRETCH, the default) or not
  at all (NONE); a column can opt out, and the widths are reported to the
  server and rendered again from there. While dragging, a line with arrowheads
  shows where the edge will land, a shaded band the new extent of the column
  and a label its width in pixels. They are removed whatever ends the drag: a
  release, a cancel, Escape, the window losing focus, an error, or a new drag;
  the click a browser fires when the drag ends reaches no button under the
  pointer. On hover the handle shows a line in its middle. While the columns
  can be resized, every row ends with an empty cell, 3em wide, leaving room
  after the last column; header cells marked data-resizable-skip are left out
  of the columns but keep their width, also while the table is hidden when it
  is attached. getCellCount() gives the colspan of a whole row.
- lets the user hide columns marked IHideableColumn with a
  ColumnChooserToolbarAction, a button in the navigation opening a list of
  those columns with a tick on the shown ones and an Apply button; the
  application hides any column with setColumnVisible(column, visible).
  getColumns() returns every column, getVisibleColumns() the shown ones, and
  CSV export writes the shown exportable columns in display order.
- lets the user move columns marked IMovableColumn: a grip in front of the
  title drags the column with the mouse, a pen or a finger (pointer events,
  not HTML5 drag and drop), a ghost of the header following the pointer and a
  marker with arrowheads showing where it will land; dropping on the left or
  right half of a column puts it before or after. The arrow keys on the grip
  move it one place. Columns without the mark keep their places: no marker is
  shown where they would move, and the server rejects such a move.
  moveColumn(column, index) moves a column for the application, its width and
  proportion along.
- remembers the order, visibility and widths of its columns as a ColumnState
  in an IColumnStateStore, loaded when the table is initialized and saved
  after every move, show, hide or resize by the user, once the application
  sets a store with setColumnStateStore: SessionColumnStateStore keeps them
  in the session, so reloading the page keeps the layout, and an application
  keeps them elsewhere, for example by user in its database, with a store of
  its own. A table has no store by default. getColumnState() and
  setColumnState(state) read and apply a state. Columns are referred to by
  IDynamicColumn#getId() (AbstractDynamicColumn#setId; a SortableDynamicColumn
  has the id of the column it wraps), or by their index in the constructor's
  list for columns without one; ids have to be unique, which the table checks
  (IllegalArgumentException). The key comes from getColumnStateKey(), by
  default the page class and the table's path.
- sends the rows of its page along when it is rendered again in an Ajax
  request, for example after a move, instead of letting the browser fetch
  them, so its body never shows empty; the JSON is escaped so it cannot end
  the script.
- sizes its columns in proportion to each other with
  setColumnProportions(double...), and takes the full width of its parent with
  setAdjustToParentWidth(true), following the window's size until the user
  resizes a column; both work with every ColumnResizing
- selects rows with a SelectionColumn: a checkbox per row and one in the
  header for the rows of the page, the selection kept on the server by key so
  it survives paging, sorting, refreshing and pushes. Once the whole page is
  selected, a bar offers to select every row of the provider. Sorting clears
  the selection. getSelection() returns an ISelection, a list of keys or all
  rows, whose fetch() reads the rows from the provider lazily;
  onSelectionChanged is called after every change. Every row is sent with its
  state, available to templates as {{@selected}}.
- shows any component in an overlay, a window over a veil covering only the
  table: showOverlay(component, target), closeOverlay(target),
  isOverlayShown() and onOverlayClosed(target). The focus is trapped in the
  window while it is shown, and the window is placed within the part of the
  table the user sees. Escape closes it unless something in it was changed: a
  form field differing from its initial value, or an element marked
  data-dt-changed, for example a form rendered again with errors.
- is blocked while one of its Ajax requests runs, by a LocalVeilBehavior from
  newVeilBehavior(), which shows a spinner when the request takes a while;
  returning null leaves the table usable
- centers its cells vertically, shows the text of its navigation in bold, and
  fills action buttons with the accent color on hover

IconBasedRowAction is a row action shown as an icon button, its tooltip as the
button's title and aria-label. The icon is an IIcon: one of the built-in
IconBasedRowAction.Icon (edit, delete, download, add), an SvgIcon, a
FontAwesomeIcon or the application's own; getIcon() is asked on every render.
IconBasedToolbarAction is the same for the navigation, an icon button calling
onClick(table, target). With a
confirmation, setConfirmation(model) or getConfirmation(row), a click first
asks the question with Cancel and Confirm, in a FloatingPanel titled with the
tooltip, in the table's overlay, and onRowAction(row, target) runs only once
the user confirms and the row still exists.

AjaxDownloadActionColumnContributor is an action whose link downloads the
clicked row as a JSON file, produced by the table's serializer from the row
looked up again by its key when the file is requested (404 if it is gone). It
exposes the AjaxDownloadBehavior settings: the location, the SameSite
attribute of the completion cookie, the file name, and success and failure
callbacks. setIcon(IIcon) shows it as an icon button, and getContent(row) and
getContentType() replace the JSON with another format.
IAjaxActionColumnContributor gets a default bind(DynamicDataTable), called
once when the contributor is registered, for such behaviors.

Pushing over web sockets: an IWebSocketLightWeightMessage carries tableId,
typeId and its own data, and is sent with send(IWebSocketConnection),
send(Component), which finds the page's connection, or send(Application,
sessionId, IKey), which needs no request and can be called from a background
thread with what WebSocketBehavior#onConnect provides. Each table registers
the browser-side handlers of its IWebSocketMessageTypes, called as
function(message). Registered by default are UpdateRowsMessageType (repaints
the rows from the message), RefreshMessageType (fetches the rows again, see
refreshViaWebSockets()) and UpdateRowMessageType, which repaints one row,
found by its id and ignored if it is not shown; build it with
DynamicDataTable#newUpdateRowMessage(row) or, without the table, with
UpdateRowMessageType.newMessage(tableId, key, data). Client-side instances are
kept in Wicket.DynamicDataTable.instances and destroyed when the table is
removed in an Ajax or web socket request, or when Wicket removes or replaces
its element or one of its ancestors in the page. refresh(target) and sorting
send the rows with the Ajax response instead of letting the browser fetch them
again. onRowsSent(keys) tells the application which rows the browser shows, so
it can push updates for those only. wicket-extensions gains an optional
dependency on wicket-native-websocket-core for this, like the one on Jackson.

ProgressBar, in the new package
org.apache.wicket.extensions.markup.html.progress, renders a native <progress>
element for a percentage clamped to 0..100, with no inline style, so it works
under a strict content security policy. A null value renders an indeterminate
bar, for work whose end is unknown (also available as
ProgressBar.indeterminateMarkup()). The bar is striped and animated, unless
the user prefers reduced motion; a finished bar, at 100, is solid. Its label
is bold, on a chip of the track's color, so it stays readable over the filled
part and on dark pages; the middle of its digits is placed on the middle of
the bar (an empty inline-block as high as the label, middle-aligned and
raised by 1cap - 1ex), in every browser supporting the cap unit. Its markup is
also available from
ProgressBar.markup(value), which ProgressBarColumn uses as the template of a
DynamicDataTable column; the table renders the header items of columns
implementing IHeaderContributor, so the column brings the bar's style sheet
along.

The texts of the table are in the default bundle and, at the request of the
author, in German in Initializer_de; other translations are left to native
speakers.

The OSGi export list of wicket-extensions gains the new package and the
dynamic table package.

The repeater examples get a DynamicDataTablePage over a thousand contacts,
showing an ID column, an address column and an Actions column whose buttons
edit, delete and download a contact: edit opens, in a FloatingPanel in the
table's overlay, a validated form for the name and the address (country, city
and street, all required), delete asks for a confirmation and download gives
a jCard (RFC 7095) of the contact. The page shows paging with 25, 50, 100 or
500 rows, sorting, resizing, full width and column proportions,
selection, CSV export, a filter by name in front of the navigation, push
refresh and push of rows, and a Progress column whose values a timer advances,
pushing only the rows the browser shows, while the page's web socket
connection is open; every column reads a localized template file and the long
explanation sits in a CollapsiblePanel. The /repeater application now runs on
JavaxWebSocketFilter. The ajax examples get a stand-alone progress bar page
with determinate and indeterminate bars. A WicketTester test covers the
page's actions and its edit form. A Selenium test of the push refresh
runs only with -Pselenium, which needs Chrome; the Jetty the example tests
start now registers Wicket's web socket endpoint for it. The phone numbers are
labelled and aligned, and copy to the clipboard on a double click; the
settings of the example are drop-downs in a grid. The example adds contacts
with an IconBasedToolbarAction, hides Home phone, Cell phone, City and Country
at first and lets the columns after ID be hidden and moved; the selection, #,
Actions and ID columns cannot be resized. An "Icons" option switches the
actions between the built-in icons, SvgIcon and FontAwesomeIcon, and a
"Column state" option between the session and a FileColumnStateStore keeping
the state in a file of the temporary directory, with "Reset the columns". The
examples depend on the Font Awesome web jar, serve its style sheet and fonts
through FontAwesomeResourceReference and name it in their NOTICE and, appended
by the release build, in their LICENSE, with the licenses of its icons (CC BY
4.0), fonts (OFL 1.1) and code (MIT). The example's columns have ids and keep
their layout in the session. A new Icons
page shows the three sets of icons, filtered by name, copying an icon's Java
constant when clicked. A QUnit page checks that the progress bar's label is
centered, at sizes from 12 to 64 pixels.

New API only; nothing existing changes for applications.

GitHub issue #1635
Every component of wicket-extensions brings its own style sheet with its own
hard-coded colors, so an application wanting its tables, progress bars and
panels to match its look overrides each style sheet separately.

The new package org.apache.wicket.extensions.theme defines the colors once, as
CSS custom properties named --wicket-theme-* (primary, on-primary,
primary-dark, accent, surface, surface-alt, border, text, toolbar, hover,
highlight, selected, progress, progress-dark, track, color-scheme). A theme is
a CSS class setting them; Theme ships DEFAULT, BLUE, RED, GREY, GREEN, ORANGE,
LIGHT_BLUE and DARK, and Theme.CSS is the style sheet defining them.
--wicket-theme-color-scheme gives the color scheme of the form controls inside
a themed component, so a text field or check box matches the theme even on a
page that declares color-scheme: light dark and is shown in dark mode.
ThemeBehavior puts a theme's class on a component, next to the marker class
wicket-theme (Theme.MARKER_CSS_CLASS), and contributes the style sheet; the
theme comes from a model, so re-rendering the component switches
it. A theme on the page applies to every themable component inside it, a theme
on one component only to that one. Themes are switched by class only and need
no inline style, so they work under a strict Content Security Policy. An
application defines a theme of its own as a CSS class setting the same
properties, and puts it on an element together with wicket-theme, which
components use for the parts they style only inside a theme.

The DynamicDataTable (header, rows, toolbars and their form controls), the
resize handles, guide, band and label of ResizableColumnsBehavior, the
column move ghost and drop marker, the ProgressBar and the
CollapsiblePanel (title in the theme's primary colors, like a table header,
with a border and rounded corners, and the body text in the theme's text color
unless --wicket-collapsible-text is set) read their colors from the theme, and
so do the table's overlay window, its buttons and the icon actions, the
FloatingPanel (title bar in the colors of the table headings), the check mark
of the clipboard copy, and the veil of wicket-extensions: --wicket-theme-veil
dims what a veil or an overlay blocks, and the spinner takes the accent and
track colors. The DARK theme sets dark surfaces and the dark color scheme for
form controls. The OSGi export list gains the package. They read the
properties only inside a theme's class, so a component without a theme looks
as it did before; the progress bar and the resize guide keep their current
colors as fallbacks.

The examples get ThemeChoice, a drop-down switching the theme via Ajax, on the
DynamicDataTable, progress bar and collapsible panel pages.

New API only; nothing existing changes for applications.

GitHub issue #1638
…hemes

The DynamicDataTable, the ProgressBar, the CollapsiblePanel and the
FloatingPanel take their colors from the shared themes of wicket-extensions,
but the other components on the same page keep their own hard-coded look:
a white modal dialog with no header on a dark theme, yellow auto-complete
suggestions, tabs without a style and an upload progress bar unlike the
ProgressBar.

- ModalDialog: its DefaultTheme reads the theme. The overlay dims with
  --wicket-theme-veil and the dialog has --wicket-theme-surface, falling back
  to the previous rgba(0, 0, 0, 0.2) and white. Inside a theme the dialog
  also takes the text and border colors, the color scheme and the accent for
  links, and content using the classes modal-dialog-header, modal-dialog-body
  and modal-dialog-footer gets a header in the primary colors and a footer in
  the toolbar color. Outside a theme a dialog looks as before.
- AutoCompleteTextField: the suggestions are appended to the body, outside the
  theme of the field, so the script copies the theme properties of the field
  onto them, through the CSS object model, and marks them wicket-aa-themed.
  A new style sheet, AbstractAutoCompleteBehavior.THEME_CSS, styles marked
  suggestions only: surface, text and border colors, the hover color under
  the pointer and the primary colors for the selected one. Fields outside a
  theme are not marked and keep their look; DefaultCssAutoCompleteTextField's
  style sheet is unchanged.
- TabbedPanel: TabsStyleBehavior gives a tabbed panel one of the looks of
  TabsStyle (TABS, PILLS, UNDERLINE) by a CSS class, with colors from the
  theme and fallbacks outside one. Tabbed panels without the behavior are
  untouched.
- UploadProgressBar renders the markup of the ProgressBar, a native
  <progress> with the percentage as label, and its style sheet, so both bars
  look the same and follow the theme; a test keeps the markup in line with
  ProgressBar.markup(). The script sets the value and the label, ignores a
  status without a number instead of failing and stopping the polling, and
  still updates the old markup of an application that replaced it.
  UploadProgressBar.css only places the bar and the status; getCss()
  returning null now leaves out the ProgressBar's style sheet too.

The ajax examples of these components get the layout of the DynamicDataTable
example: the explanation in a CollapsiblePanel and a theme drop-down, in the
page's theme. The tabbed panel example also chooses the look of the tabs. A
new theme editor example edits the colors of a theme with a picker per
property, shows themed components following every change and gives the CSS
class defining the theme.

What applications see: an UploadProgressBar looks like a ProgressBar, so CSS
written for its old .wupb-border, .wupb-background and .wupb-foreground
elements no longer applies; such an application keeps its own look by
returning its style sheet from getCss() and replacing the markup with a
variation. Every page with an AutoCompleteTextField loads one small extra
style sheet, which styles nothing outside a theme. The rest is new API or
applies only inside a theme.

GitHub issue #1645
Part of GitHub issue #1639
The examples index lists the components of wicket-extensions under the
technique they use (repeaters, Ajax, ...), so someone looking for what
wicket-extensions offers has to know where each one lives.

The home page gets an "Extensions" entry leading to ExtensionsIndex, a
bundle-driven index like the other ones, linking the repeaters (among them the
DynamicDataTable), trees, modal dialog, progress bar, collapsible panel,
auto-complete, tabbed panel, wizard, breadcrumb, captcha, dates and the other
examples built on wicket-extensions. The repeaters, tree, wizard, breadcrumb,
captcha and dates entries move from the home page into this index, and it
lists the veil example of the ajax examples, the theme editor and the Icons
page of the repeater examples.
@reiern70
reiern70 force-pushed the dynamic-data-table branch from aad2ddb to 1398213 Compare October 5, 2026 11:23
@reiern70

reiern70 commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author
dynamic-data-table-demo.mp4

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.

2 participants