YOUSEFUL / DELPHI / COMPONENTS
TAtlasKanbanModel
Non-visual Kanban data model: ordered columns with optional work-in-progress limits, cards with stable ids, vetoable moves and a validated JSON format.
Description
The model holds the board; TAtlasKanban only draws it. Put the model on a form or data module from the Atlas UI palette page, or create it in code. It needs no window, so it also works in console programs, services and tests.
Columns have a unique id, a title and a limit (0 means no limit). They are added with AddColumn and read by position with ColumnCount, ColumnID, ColumnTitle and ColumnLimit. There is no method to remove, rename or reorder a column, and no method to change a card in place; build a new board document and pass it to LoadJSON for those.
Cards have an id that is unique across the whole board, a title and an optional detail text. They are added to the end of a column with AddCard, moved with MoveCard, removed with RemoveCard and read with CardCount and Card.
Validation. Every invalid call raises EAtlasKanban and leaves the board exactly as it was: a blank or duplicate id, a blank title, a negative limit, an unknown card or column, a destination index out of range, or a card that would exceed a column's limit. A move vetoed by BeforeMove is not an error: MoveCard returns False.
Notifications. Every change (a column or card added, a card removed or moved, a board loaded) repaints every board that shows the model and then fires OnChange, once per change. Several boards may show the same model. While BeforeMove runs, the model refuses all changes with EAtlasKanban (Cannot mutate the board during a move callback); reading it is allowed.
Lifetime. Freeing the model detaches every board that shows it; each board then shows Assign a Kanban model to begin.
The model is not thread-safe. Use it from one thread, normally the main thread when a board shows it.
Members
| Member | Kind | What it does |
|---|---|---|
Create | constructor | Creates an empty model with no columns. |
Destroy | destructor | Detaches every TAtlasKanban that shows the model (their Model becomes nil), then frees the columns and cards. |
AddColumn | method | Appends a column. |
AddCard | method | Appends a card to the end of column ColumnID. |
RemoveCard | method | Removes the card with id ID from whichever column holds it. |
MoveCard | method | Moves card ID to position Index of column ColumnID. |
ColumnCount | method | The number of columns. |
ColumnID | method | The id of the column at position Index. |
ColumnTitle | method | The title of the column at position Index. |
ColumnLimit | method | The work-in-progress limit of the column at position Index; 0 means no limit. |
CardCount | method | The number of cards in the column at position Column (a position, not an id). |
Card | method | A copy of card Index in the column at position Column, as a TAtlasKanbanCard record. |
ToJSON | method | The whole board as compact JSON, on one line: {"schema":1,"columns":[{"id":...,"title":...,"limit":...,"cards":[{"id":...,"title":...,"detail":...}]}]}. |
LoadJSON | method | Replaces the whole board with the one in Text. |
BeforeMove | event | Called by MoveCard, and so by every mouse and keyboard move on a board, after the move has passed its checks and before the card moves. |
OnChange | event | Fires after every change to the board, once per AddColumn, AddCard, RemoveCard, successful MoveCard and LoadJSON, after the boards that show the model have been told to repaint. |
Constructors
Create constructor
constructor Create(AOwner: TComponent); override;Creates an empty model with no columns. Pass nil and free the model yourself, or pass a form or data module as owner.
Model := TAtlasKanbanModel.Create(Self);Destructors
Destroy destructor
destructor Destroy; override;Detaches every TAtlasKanban that shows the model (their Model becomes nil), then frees the columns and cards. Call Free, not Destroy.
Events
BeforeMove event
property BeforeMove: TAtlasBeforeCardMove read FBeforeMove write FBeforeMove;Called by MoveCard, and so by every mouse and keyboard move on a board, after the move has passed its checks and before the card moves. Allow arrives as True; set it to False to decline. The handler receives the card id and the ids of the source and target columns (the same id for a reorder inside a column), but not the target position. Inside the handler the model may be read but not changed: AddColumn, AddCard, RemoveCard, MoveCard and LoadJSON raise EAtlasKanban. An exception raised by the handler propagates out of MoveCard with the board unchanged. See TAtlasBeforeCardMove.
Model.BeforeMove := ReviewRule.BeforeMove;OnChange event
property OnChange: TNotifyEvent read FOnChange write FOnChange;Fires after every change to the board, once per AddColumn, AddCard, RemoveCard, successful MoveCard and LoadJSON, after the boards that show the model have been told to repaint. It does not fire for a declined or refused move, or for a move to where the card already is. Sender is the model.
Model.OnChange := BoardChanged;Methods
AddColumn method
procedure AddColumn(const ID, Title: string; Limit: Integer = 0);Appends a column. ID identifies the column in AddCard, MoveCard, BeforeMove and the JSON; Title is the header text. Limit is the work-in-progress limit, the most cards the column may hold; 0, the default, means no limit. Raises EAtlasKanban (Invalid or duplicate column) when ID or Title is empty or only spaces, Limit is negative, or a column with that id exists. Fires OnChange.
Model.AddColumn('active', 'In progress', 3);AddCard method
procedure AddCard(const ColumnID, ID, Title: string; const Detail: string = '');Appends a card to the end of column ColumnID. The board shows Title in bold and Detail below it. Raises EAtlasKanban (Invalid or duplicate card) when the column does not exist, ID or Title is empty or only spaces, or any column already has a card with that id; raises EAtlasKanban (Column work-in-progress limit reached) when the column is full. Fires OnChange.
Model.AddCard('todo', 'order-42', 'Prepare customer order', 'Pick, pack and label');RemoveCard method
procedure RemoveCard(const ID: string);Removes the card with id ID from whichever column holds it. Raises EAtlasKanban (Unknown card) when there is no such card. A board that had the card selected clears its selection. Fires OnChange.
Model.RemoveCard('order-42');MoveCard method
function MoveCard(const ID, ColumnID: string; Index: Integer = -1): Boolean;Moves card ID to position Index of column ColumnID. -1, the default, appends it to the end. For a move inside the card's own column the positions are counted without the card itself.
The checks come first, and each raises EAtlasKanban without changing anything: an unknown card or column (Unknown card or column), an index outside 0..count (Invalid destination index), or a move into another column that is already at its limit (Column work-in-progress limit reached). A move inside one column never counts against the limit. When the card would end up where it already is, MoveCard returns True at once, without calling BeforeMove or firing OnChange.
Otherwise BeforeMove is called; if it clears Allow, the result is False and nothing changes. If not, the card moves, OnChange fires and the result is True.
if not Model.MoveCard('order-42', 'active', 0) then
ShowMessage('The move was declined');ColumnCount method
function ColumnCount: Integer;The number of columns. Columns are numbered from 0 in the order they were added.
ColumnID method
function ColumnID(Index: Integer): string;The id of the column at position Index. An index outside 0..ColumnCount - 1 raises the RTL's EArgumentOutOfRangeException.
ColumnTitle method
function ColumnTitle(Index: Integer): string;The title of the column at position Index. An index out of range raises EArgumentOutOfRangeException.
ColumnLimit method
function ColumnLimit(Index: Integer): Integer;The work-in-progress limit of the column at position Index; 0 means no limit. An index out of range raises EArgumentOutOfRangeException.
CardCount method
function CardCount(Column: Integer): Integer;The number of cards in the column at position Column (a position, not an id). An index out of range raises EArgumentOutOfRangeException.
Card method
function Card(Column, Index: Integer): TAtlasKanbanCard;A copy of card Index in the column at position Column, as a TAtlasKanbanCard record. Changing the copy does not change the board. Indexes out of range raise EArgumentOutOfRangeException.
Title := Model.Card(0, 0).Title;ToJSON method
function ToJSON: string;The whole board as compact JSON, on one line: {"schema":1,"columns":[{"id":...,"title":...,"limit":...,"cards":[{"id":...,"title":...,"detail":...}]}]}. Columns and cards keep their order. The result can be passed to LoadJSON of any model.
LoadJSON method
procedure LoadJSON(const Text: string);Replaces the whole board with the one in Text. The document is first built in a separate model with AddColumn and AddCard, so it gets the same validation (unique ids, non-blank titles, limits); only when all of it is valid does it replace the current board, and then OnChange fires once. On any failure the current board is unchanged.
Every field is required: schema (must be 1), columns, and in each column id, title, limit and cards, and in each card id, title and detail (which may be empty). Text that is not a JSON object, or has another schema, raises EAtlasKanban (Unsupported board schema); text longer than 4 MiB (4,194,304 characters) raises EAtlasKanban (Board JSON exceeds 4 MiB); a missing field raises the RTL's EJSONException (for example Value 'detail' not found). {"schema":1,"columns":[]} empties the board. Boards that showed a card that no longer exists clear their selection.
Model.LoadJSON(TFile.ReadAllText(FileName, TEncoding.UTF8));Examples
Build a board, move a card and print it as JSON
Creates a model in code with an unlimited and a limited column, adds two cards and moves one. Prints {"schema":1,"columns":[{"id":"todo","title":"To do","limit":0,"cards":[{"id":"invoice-7","title":"Send invoice 7","detail":""}]},{"id":"active","title":"Active","limit":3,"cards":[{"id":"order-42","title":"Prepare customer order","detail":"Pick, pack and label"}]}]}.
uses System.SysUtils, Atlas.Kanban;
procedure PrintBoardJson;
var
Model: TAtlasKanbanModel;
begin
Model := TAtlasKanbanModel.Create(nil);
try
Model.AddColumn('todo', 'To do');
Model.AddColumn('active', 'Active', 3);
Model.AddCard('todo', 'order-42', 'Prepare customer order', 'Pick, pack and label');
Model.AddCard('todo', 'invoice-7', 'Send invoice 7');
if Model.MoveCard('order-42', 'active') then
Writeln(Model.ToJSON);
finally
Model.Free;
end;
end;
Save a board to a file and load it back
Writes the board as UTF-8 JSON and reads it back. LoadBoardFile catches both exceptions that a damaged or hand-edited file can cause; in either case the model keeps the board it had. A missing file still raises the RTL's EFOpenError.
uses System.SysUtils, System.IOUtils, System.JSON, Atlas.Kanban;
procedure SaveBoardFile(Model: TAtlasKanbanModel; const FileName: string);
begin
TFile.WriteAllText(FileName, Model.ToJSON, TEncoding.UTF8);
end;
// Returns '' on success, or the reason the file was refused. On failure the
// model keeps the board it had.
function LoadBoardFile(Model: TAtlasKanbanModel; const FileName: string): string;
begin
Result := '';
try
Model.LoadJSON(TFile.ReadAllText(FileName, TEncoding.UTF8));
except
on E: EAtlasKanban do
Result := E.Message; // wrong schema, blank or duplicate id, full column, over 4 MiB
on E: EJSONException do
Result := E.Message; // a required field is missing
end;
end;
Related
TAtlasKanban · TAtlasKanbanCard · TAtlasBeforeCardMove · EAtlasKanban