Skip to main content

ResponsiveTable

ResponsiveTable<T extends ResponsiveTableItem> is a responsive data table with built-in column filtering, pagination, select-all, and row state management. It is an alternative to Table<T> designed for layouts that need runtime column toggling and responsive row rendering.

Package: io-oidis-usercontrols

Basic Usage​

Declare a ResponsiveTable in the panel, bind it in innerHtml(), and configure it in the controller:

// Panel
export class ProductPanel extends BasePanel {

public readonly table : ResponsiveTable<ProductTableItem>;

constructor() {
super();
this.table = new ResponsiveTable<ProductTableItem>();
}

public innerHtml() : string {
return `
<div data-oidis-bind="${this.table}"></div>
`;
}
}
// Controller — bind in beforeLoad()
public async beforeLoad($instance : ProductPanel) : Promise<void> {

$instance.table.BindData(
async ($offset : number, $limit : number) => {
return await this.client.ListProducts({ offset: $offset, limit: $limit });
},
async ($data : IProduct, $item : ProductTableItem) => {
$item.Data($data);
$item.itemId.Text($data.Id);
$item.text.Text($data.Name);
},
async ($item : ProductTableItem) => {
EventsBinding.SingleClick($item, async () : Promise<void> => {
this.currentRecord = $item.Data();
this.switchToEditMode($instance);
});
}
);

await $instance.table.ShowData();
}

The 3-Callback BindData Pattern​

ResponsiveTable uses the same 3-callback pattern as Table<T>, with slight signature differences:

$instance.table.BindData(
// 1. Fetch — retrieve paginated data
async ($offset : number, $limit : number) : Promise<IModelListResult> => {
return await this.client.List({ offset: $offset, limit: $limit });
},

// 2. Row mapping — populate each item with data
async ($data : IMyData, $item : MyTableItem, $index : number) : Promise<void> => {
$item.Data($data);
$item.itemId.Text($data.Id);
$item.text.Text($data.Name);
},

// 3. Row create (optional) — setup row-level interactions
async ($item : MyTableItem, $index : number) : Promise<void> => {
EventsBinding.SingleClick($item, async () : Promise<void> => {
// Handle row click
});
}
);

// Trigger initial data load
await $instance.table.ShowData();

Callback Details​

Callback 1: Fetch ($offset : number, $limit : number) => IModelListResult

  • Called every time ShowData() is invoked or the user navigates to a new page
  • Receives pagination offset and limit from the table
  • Must return { data: any[], size: number, offset: number, limit: number }
  • The size count drives pagination controls

Callback 2: Row Mapping ($data : any, $item : T, $index : number) => void

  • Called once per record in the result set
  • $data is the raw record from the fetch result
  • $item is the typed ResponsiveTableItem row
  • Store data on the row with $item.Data($data) for later retrieval

Callback 3: Row Create ($item : T, $index : number) => void (optional)

  • Called once when a row is first created (not on re-bind)
  • Bind click handlers and other one-time event setup here
  • Row instances are reused across page changes

Column Configuration​

Column visibility is configured through the IResponsiveTableColumn interface:

PropertyTypePurpose
idstringUnique column identifier
textstringDisplay text for column header
iconstringSVG icon HTML for the filter chip
controlsGuiCommons[]References to cell elements in each row
enabledbooleanWhether column is currently visible
hiddenbooleanWhether column is excluded from filter UI
modifiers?string[]Optional CSS modifier classes
width?stringOptional column width
sortAscendingByDefault?booleanDirection a fresh sort by this column starts with (true reads best for text, false for numbers)

The default filter configuration provides two columns: ID ("id") and Content ("text"). Override getFilterConfig() to define custom columns:

protected getFilterConfig() : IResponsiveTableColumn[] {
return [
{ id: "id", text: "ID", icon: "...", controls: [], enabled: true, hidden: false },
{ id: "name", text: "Name", icon: "...", controls: [], enabled: true, hidden: false },
{ id: "status", text: "Status", icon: "...", controls: [], enabled: false, hidden: false }
];
}

Column Filter Management​

The column filter allows users to toggle column visibility at runtime. Filter state is automatically persisted to localStorage.

MethodSignaturePurpose
FilterSelectorVisible($value? : boolean) : booleanShow/hide the column filter dropdown
Filter() : voidApply current filter configuration to all rows
Init($filter? : string[]) : voidInitialize or restore filter state from localStorage
// Initialize with specific columns enabled
$instance.table.Init(["id", "name", "status"]);

// Hide the filter selector (columns still respect persisted state)
$instance.table.FilterSelectorVisible(false);

// Manually re-apply the current filter
$instance.table.Filter();
Filter persistence

Init() reads persisted filter state from localStorage using the component class name as key. If no persisted state exists, it falls back to the getFilterConfig() defaults. Pass an explicit $filter array to override persisted state.

Header Sorting​

The table tracks a sort key and direction and exposes them to subclasses. Header cells are plain markup (not child components), so a single click listener on the table resolves clicks through the data-sort attribute emitted by getSortableHeaderLabel():

// Subclass: render sortable header labels inside the header markup
headerCells += this.getSortableHeaderLabel($column.label, $column.key);

// Consumer: seed defaults and react to clicks
table.SortKey("score");
table.SortAscending(false);
table.setOnSortChange(($key : string, $ascending : boolean) : void => {
// re-fetch or re-sort, then table.ShowData()
});

Clicking the active column flips the direction; clicking a new column starts at that column's sortAscendingByDefault (defaults to ascending). Setting SortKey()/SortAscending() repaints the ↑/↓ indicators without firing the handler.

Resizable Columns​

ColumnsResizable(true) lets the user drag a column's width from its header. It works like sorting: the subclass places a handle into each header cell with getResizeHandle($key) (a data-resize span on the cell's right edge — the header cell must be position: relative), and one listener on the table follows the drag. A double click on the handle restores the default width.

The table does not size cells itself. It keeps the widths per table class in its persistence (they survive a reload) and publishes them as CSS custom properties on the table element (the column id is converted to kebab-case, e.g. lastChecked → last-checked):

PropertyValue
--column-width-<id><width>px
--column-flex-<id>0 0 <width>px

Column cells opt in from the stylesheet, falling back to their normal size:

// Subclass constructor + header markup
this.ColumnsResizable(true);
headerCell += this.getResizeHandle($column.id);
@media (min-width: 992px) {
.my-table-col--name {
flex: var(--column-flex-name, 2 2 0);
}
}

Because only the rules that read the properties change, a narrow card layout that stacks the cells (and does not use them) is unaffected by the stored widths.

Pagination​

ResponsiveTable includes a built-in Pagination component. Pagination is managed automatically when using BindData() and ShowData().

MethodSignaturePurpose
ItemsPerPage($value? : number) : numberGet/set items per page (default 25)
PagesLimit($value? : number) : numberGet/set max page buttons
SelectPage($index : number, $from? : number) : voidNavigate to a page offset
setOnPageSelect($handler : ($index : number) => Promise<void>) : voidSet page change callback
getIndex() : numberGet current pagination offset
// Configure items per page
$instance.table.ItemsPerPage(50);

// Force refresh and reset to first page
await $instance.table.ShowData(true);
Automatic page handling

When using BindData(), page change callbacks are wired automatically — ShowData() is called on every page navigation. You only need setOnPageSelect() if you require additional logic beyond the default data fetch.

Selection​

ResponsiveTable provides built-in select-all and per-row selection:

MethodSignaturePurpose
setOnAllSelect($handler : ($pageIndex : number, $selected : boolean) => Promise<void>) : voidSelect-all checkbox handler
setOnItemSelect($handler : ($item : T) => Promise<void>) : voidIndividual item select handler
// Handle select-all
$instance.table.setOnAllSelect(async ($pageIndex : number, $selected : boolean) : Promise<void> => {
// $selected is the new checkbox state
// $pageIndex is the current pagination offset
});

// Handle individual selection
$instance.table.setOnItemSelect(async ($item : ProductTableItem) : Promise<void> => {
const record : IProduct = $item.Data();
// Handle selection change
});

The select-all checkbox automatically reflects the aggregate state: checked when all visible items are selected, unchecked when none are selected, and indeterminate when some are selected.

ResponsiveTableItem​

ResponsiveTableItem is the base row class. Each row contains built-in selection, ID, and text controls:

PropertyTypePurpose
selectCheckBoxRow selection checkbox
itemIdLabelID column label
textLabelText column label
idContentGuiCommonsID cell container (used for filter mapping)
textContentGuiCommonsText cell container (used for filter mapping)

Key Methods​

MethodSignaturePurpose
setState($value : ResponsiveTableItemType) : voidApply a visual state class
Restore() : voidReset item for reuse (unchecks select, clears labels)

The default innerHtml() renders each row as a responsive Bootstrap grid with bordered cells containing icons and labels.

Row States​

The ResponsiveTableItemType enum provides visual state classes applied to rows:

StateCSS ClassVisual Effect
DELETEDDeletedStrikethrough + muted text
ARCHIVEDArchivedMuted/grayed appearance
IN_PROGRESSInProgressIn-progress indicator
COMPLETEDCompletedCompleted indicator
// In row mapping callback
async ($data : IProduct, $item : ProductTableItem) : Promise<void> => {
$item.Data($data);
$item.itemId.Text($data.Id);
$item.text.Text($data.Name);

if ($data.IsDeleted) {
$item.setState(ResponsiveTableItemType.DELETED);
} else if ($data.IsArchived) {
$item.setState(ResponsiveTableItemType.ARCHIVED);
}
};

Subclassing​

Custom Table Item​

Extend ResponsiveTableItem to add project-specific columns:

export class ProductTableItem extends ResponsiveTableItem {

public readonly price : Label;
public readonly category : Label;
public readonly priceContent : GuiCommons;
public readonly categoryContent : GuiCommons;

constructor() {
super();
this.price = new Label();
this.category = new Label();
this.priceContent = new GuiCommons();
this.categoryContent = new GuiCommons();
}

protected innerHtml() : string {
return `
<div class="row g-0 ResponsiveTableItem" style="border-radius: 6px;border-width: 1px;border-style: solid;margin-bottom: 3px;" data-oidis-bind="${this}">
<div class="col-auto text-start align-self-center" style="width: 25px;">
<input class="form-check-input" type="checkbox" data-oidis-bind="${this.select}" style="margin-left: 4px;" />
</div>
<div class="col">
<div class="row g-0">
<div class="col-12 col-md-6 col-lg-auto" data-oidis-bind="${this.idContent}">
<div style="border-radius: 4px;border: 1px solid rgb(115,115,115);margin: 2px;min-width: 106px;font-weight: bold;">
<span data-oidis-bind="${this.itemId}"></span>
</div>
</div>
<div class="col" data-oidis-bind="${this.textContent}">
<div style="border-radius: 4px;border: 1px solid rgb(115,115,115);margin: 2px;">
<span data-oidis-bind="${this.text}"></span>
</div>
</div>
<div class="col" data-oidis-bind="${this.priceContent}">
<div style="border-radius: 4px;border: 1px solid rgb(115,115,115);margin: 2px;">
<span data-oidis-bind="${this.price}"></span>
</div>
</div>
<div class="col" data-oidis-bind="${this.categoryContent}">
<div style="border-radius: 4px;border: 1px solid rgb(115,115,115);margin: 2px;">
<span data-oidis-bind="${this.category}"></span>
</div>
</div>
</div>
</div>
</div>`;
}
}
Create every added member in the constructor

Nothing in the framework instantiates members for you. A member declared as = undefined stays undefined — the template renders the text undefined and GuiCommons.Register() logs <Class>.<member> was declared but instance is missing. Did you miss it in constructor?.

Custom Table​

Extend ResponsiveTable to provide custom filter columns, item instances, localization, and filter mapping:

export class ProductTable extends ResponsiveTable<ProductTableItem> {

constructor() {
super();
this.localization = {
selectAll: "Select all",
show : "Show",
showMore : "More columns",
noData : "No products found"
};
}

protected getNewItemInstance() : ProductTableItem {
return new ProductTableItem();
}

protected getFilterConfig() : IResponsiveTableColumn[] {
return [
{ id: "id", text: "ID", icon: "...", controls: [], enabled: true, hidden: false },
{ id: "text", text: "Name", icon: "...", controls: [], enabled: true, hidden: false },
{ id: "price", text: "Price", icon: "...", controls: [], enabled: true, hidden: false },
{ id: "category", text: "Category", icon: "...", controls: [], enabled: false, hidden: false }
];
}

protected setItemToFilterMapping($item : ProductTableItem) : void {
this.assignFilterTo("id", $item.idContent);
this.assignFilterTo("text", $item.textContent);
this.assignFilterTo("price", $item.priceContent);
this.assignFilterTo("category", $item.categoryContent);
}
}
Override all three methods together

When subclassing ResponsiveTable, always override getNewItemInstance(), getFilterConfig(), and setItemToFilterMapping() together. The filter mapping must match the column IDs defined in the filter config, and the item instance must have the corresponding content containers.

Localization​

The IResponsiveTableLocalization interface controls UI labels:

PropertyTypeDefaultPurpose
selectAllstring"select all"Select-all checkbox label
showstring"Show"Column filter prefix label
noDatastring"No data found"Empty state alert message
showMorestring"Show more"Hidden columns dropdown button text

Set localization in the constructor before rendering:

constructor() {
super();
this.localization = {
selectAll: "Vybrat vše",
show : "Zobrazit",
showMore : "Další sloupce",
noData : "Žádná data"
};
}

API Reference​

ResponsiveTable<T>​

MethodSignaturePurpose
Enabled($value? : boolean) : booleanGet/set enabled state (cascades to table and pagination)
FilterSelectorVisible($value? : boolean) : booleanGet/set column filter dropdown visibility
ItemsPerPage($value? : number) : numberGet/set items per page
PagesLimit($value? : number) : numberGet/set max pagination buttons
getIndex() : numberGet current pagination offset
SelectPage($index : number, $from? : number) : voidNavigate to page; $from controls pagination visibility
setOnPageSelect($handler) : voidSet page change handler
setOnAllSelect($handler) : voidSet select-all handler
setOnItemSelect($handler) : voidSet per-item select handler
Filter() : voidApply current column filter to all rows
Init($filter? : string[]) : voidInitialize/restore filter from localStorage
Clear() : voidRemove all items, show no-data alert
Restore() : voidHide and reset all items for reuse
AddItem() : Promise<T>Create and add a new row item
getItem($index : number) : TGet item by index
IsEmpty() : booleanWhether the table has no items
BindData($onFetch, $onRowMapping, $onRowCreate?) : voidBind the 3-callback data pattern
SortKey($value? : string) : stringGet/set the active sort column key (setter repaints indicators)
SortAscending($value? : boolean) : booleanGet/set the sort direction (setter repaints indicators)
setOnSortChange($handler) : voidEnable header-click sorting and set the change handler
ColumnsResizable($value? : boolean) : booleanGet/set drag-to-resize of columns (widths persisted, exposed as CSS custom properties)
ApplyColumnWidths() : voidRe-publish the stored widths after the table element was re-rendered
ShowData($resetOffset? : boolean) : Promise<void>Fetch and render data; pass true to reset to first page

ResponsiveTableItem​

Method / PropertyTypePurpose
selectCheckBoxRow selection checkbox
itemIdLabelID column label
textLabelText column label
idContentGuiCommonsID cell container
textContentGuiCommonsText cell container
setState($value : ResponsiveTableItemType) : voidApply visual state class
Restore() : voidReset for reuse

Protected Methods (for subclassing)​

MethodSignaturePurpose
getNewItemInstance() : TFactory method for row items
getFilterConfig() : IResponsiveTableColumn[]Define available columns
setItemToFilterMapping($item : T) : voidMap item containers to column IDs
assignFilterTo($id : string, $control : GuiCommons) : voidRegister a control under a column ID
filterRow($item : T) : voidCustom per-row filter logic (override)
getFilterSelectorInstance() : DropDownListFactory for the "show more" dropdown
getHeaderItem($column : IResponsiveTableColumn) : InlineItemFactory for filter chip items
getItems() : T[]Access the internal items array
getFilterById($id : string) : IResponsiveTableColumnLookup column config by ID
getSortableHeaderLabel($label : string, $key : string) : stringBuild a clickable header label span with a sort indicator
renderSortIndicators() : voidRepaint the ↑/↓ indicators from the current sort state
getResizeHandle($key : string) : stringBuild the column-resize drag handle for a header cell (empty while resizing is off)

See also: Tables | User Controls | Pagination