Skip to main content

KanbanBoard

KanbanBoard is a workflow/status board that renders cards in columns and lets the user drag cards between columns. A drop moves the card optimistically and then asks the consumer to persist the change — a rejected persist reverts the card to its source column automatically.

Package: io-oidis-usercontrols (Bootstrap generation)

import { KanbanBoard } from "@io-oidis-usercontrols/Io/Oidis/UserControls/Bootstrap/UserControls/KanbanBoard.js";

Basic Usage​

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

// Panel
export class ProjectsPanel extends BasePanel {

public readonly board : KanbanBoard;

constructor() {
super();
this.board = new KanbanBoard();
}

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

$instance.board.setColumns([
{ key: "NEW", title: "New", badgeClass: "bg-secondary" },
{ key: "IN_PROGRESS", title: "In progress", badgeClass: "bg-primary" },
{ key: "DONE", title: "Done", badgeClass: "bg-success" },
{ key: "CONVERTED", title: "Converted", badgeClass: "bg-success", dropDisabled: true }
]);

$instance.board.OnCardMove(async ($cardId : string, $fromColumn : string, $toColumn : string) : Promise<boolean> => {
// the card is already shown in $toColumn; return false (or throw) to revert it back to $fromColumn
return await this.client.UpdateStatus($cardId, $toColumn);
});

$instance.board.OnCardClick(($cardId : string) : void => {
this.openDetail($cardId);
});

const records : IRecord[] = await this.client.ListRecords();
$instance.board.ShowData(records.map(($record : IRecord) : IKanbanCard => {
return {
id: $record.Id,
column: $record.Status,
contentHtml: `<div class="fw-bold">${this.escapeHtml($record.Name)}</div>`,
draggable: $record.Status !== "CONVERTED"
};
}));
}
Escape your card content

contentHtml is injected into the card body as-is. The consumer is responsible for escaping its own data before building the HTML string.

Interfaces​

IKanbanColumn​

PropertyTypePurpose
keystringUnique column identifier, referenced by IKanbanCard.column
titlestringColumn header text
badgeClass?stringBootstrap badge classes for the header counter, e.g. "bg-success"
dropDisabled?booleanCards cannot be dropped into this column
count?numberOverrides the displayed counter (e.g. server-side total); local card count otherwise

IKanbanCard​

PropertyTypePurpose
idstringUnique card identifier, passed to the move and click callbacks
columnstringKey of the column the card sits in
contentHtmlstringPre-rendered card body HTML — the consumer escapes its own data
draggable?booleanDefault true; false means the card cannot be dragged (e.g. terminal state)

IKanbanBoardLocalization​

PropertyTypeDefaultPurpose
emptyColumnstring"No items"Placeholder shown in a column without cards

Optimistic Move and Revert​

Dropping a card into another column updates the board state and the DOM immediately, then awaits the OnCardMove() callback:

  • callback resolves true — the move is confirmed, the card stays in the target column,
  • callback resolves false or throws — the move is reverted: the card returns to its source column and the board re-renders.

Dropping a card back into its source column is a no-op — OnCardMove() is not called.

$instance.board.OnCardMove(async ($cardId : string, $fromColumn : string, $toColumn : string) : Promise<boolean> => {
const result : IUpdateResult = await this.client.UpdateStatus($cardId, $toColumn);
return result.success;
});

getCards() returns the current card state including optimistic moves, so it always reflects what the user sees.

Terminal States​

Two flags cover states that must not participate in dragging — for example a derived CONVERTED stage that is computed on the server and cannot be entered or left manually:

  • IKanbanColumn.dropDisabled — the column never accepts drops and never highlights as a drop target,
  • IKanbanCard.draggable: false — the card cannot be picked up (it remains clickable).

Readonly(true) disables all dragging on the whole board while keeping cards clickable.

Responsive Layout​

  • Desktop (lg and up): columns sit side by side in one horizontal flex row that scrolls inside the board — the page itself never scrolls horizontally.
  • Below lg: columns stack vertically full-width and drag is inactive; cards remain clickable, so OnCardClick() stays the navigation path.

Drag affordances: the dragged card gets an is-dragging class and a hovered valid drop column gets is-drop-target; dropDisabled columns never highlight.

HTML5 drag-and-drop

The board uses native HTML5 DnD, which touch devices do not support. On touch (and below the lg breakpoint in general) the board falls back to click-through — moves are done from the card detail instead.

setColumnFooter() renders a muted footer under a column, e.g. a "+12 more" hint when the column shows only a subset of a larger server-side total (combine with IKanbanColumn.count):

$instance.board.setColumnFooter("NEW", "+12 more");
$instance.board.setColumnFooter("NEW", ""); // clears the footer

Localization​

$instance.board.Localization({
emptyColumn: "Žádné položky"
});

API Reference​

MethodSignaturePurpose
setColumns($columns : IKanbanColumn[]) : voidDefine/replace columns, re-renders
ShowData($cards : IKanbanCard[]) : voidReplace all cards, groups by column, re-renders
getCards() : IKanbanCard[]Current card state incl. optimistic moves
Readonly($value? : boolean) : booleanGet/set read-only state; true disables all dragging
OnCardMove($callback : ($cardId, $fromColumn, $toColumn) => Promise<boolean>) : voidPersist callback; false/throw reverts the move
OnCardClick($callback : ($cardId : string) => void) : voidCard click handler
Localization($value? : IKanbanBoardLocalization) : IKanbanBoardLocalizationGet/set UI labels
setColumnFooter($columnKey : string, $html : string) : voidMuted footer under a column; "" clears

Consumers​

Reference integrations live in io-oidis-labs: the Projects board (project workflow states) and Lead Radar (lead pipeline with a derived, drop-disabled CONVERTED stage).


See also: ResponsiveTable | User Controls