kanban — u_pbt_kanban #
← Component reference · Guide contents
kanban is a board of cards sorted into columns — the stages of a piece of work: to do, in progress, in review, done. The user drags a card from one column to another, or up and down in its own;
ue_card_movedtells you which one, from where, to where and at which place. Each card shows a title, coloured tags, its assignee, a due date that turns red when late and a badge; each column has its work-in-progress limit and folds. The board is filled by hand or from a DataStore, which receives the moves.
▶ See it live — Demo application, Kanban board tile: the preview, the code behind it and this page, side by side.
At a glance #
| Userobject | u_pbt_kanban |
| Item classes | n_pbt_kanban_column (of_column(key)), n_pbt_kanban_card (of_card(key)) |
| Used for | Following a flow of work: tickets, tasks, orders, applications |
| Principle | A card has a key unique on the whole board; its column is a state that changes at every move |
| Opt-in options | ib_veto_moves, ib_write_back, ib_reorderable, ib_toolbar, ib_add_button, ib_quick_add |
Quick start #
// The columns, in the order of the work
uo_board.of_add_column(/*key*/ "todo", /*title*/ "To do")
uo_board.of_add_column(/*key*/ "doing", /*title*/ "In progress")
uo_board.of_add_column(/*key*/ "done", /*title*/ "Done")
// The cards : a key unique on the board, a column, a title
uo_board.of_add_card(/*key*/ "t1", /*column*/ "todo", /*title*/ "Login page")
uo_board.of_add_card(/*key*/ "t2", /*column*/ "doing", /*title*/ "Crash on save")
uo_board.of_card(/*key*/ "t2").is_tags = "Bug"
uo_board.of_card(/*key*/ "t2").is_assignee = "Alan Turing"
// event ue_card_moved of uo_board : (string as_key, string as_from_column,
// string as_to_column, long al_position, long al_from_position)
// Save the new state of the task
UPDATE tasks SET status = :as_to_column WHERE id = :as_key;
Columns, cards and places #
A column is named by its key, a card too — and the key of a card is unique on the whole board, not only in its column: a card keeps its key when it changes column, its handles stay valid and every event names it alone. The column of a card is read by of_card(key).of_column(), its place by of_position(); a place counts from 1, hidden cards included.
A column has a work-in-progress limit (il_limit): its header counts 3/5, and it turns red past it. The limit is soft — the card lands anyway; for a hard limit, refuse the move in ue_card_moving by comparing il_limit with of_visible_count() of the column, what the header counts (a hidden card does not count). ib_done marks the column of finished work: its cards are never late. A column folds into a narrow strip (its chevron, ib_collapsed); a card still drops on it, at the end.
A move of the user (a drag, Ctrl + arrows, or the card lifted with Space and dropped) and a move of your code (of_move_card) raise the same ue_card_moved, as SelectItem raises the event of the click. With ib_veto_moves, ue_card_moving is asked first, in both cases: returning FALSE leaves the card where it is, and of_move_card then returns -4.
With ib_reorderable = true, the user reorders the columns too: he drags the header of a column (a vertical accent bar shows where it will land, Escape cancels) or presses Ctrl + Shift + Left / Right. ue_column_moved gives the column and its two places, counted from 1 among all the columns; of_move_column does the same from your code and raises the same event, ue_column_moving being asked first with ib_veto_moves. Off by default: the columns are the process of your application. What the user arranged — the order of the columns and their fold — is read by of_get_layout, given back by of_set_layout, and every change raises ue_layout_changed with the whole layout: store it, give it back at the next start.
What a card shows #
A card shows its title (is_title), its text (is_text, cut after four lines), its tags (is_tags, separated by commas, coloured by is_tag_tones: Bug=danger|UI=info), its assignee (is_assignee, a round avatar with the initials), its due date (is_due, yyyy-mm-dd: red once past, orange on the day, compared with is_today or the date of the workstation) and a badge (is_badge). Title, text and badge accept the rich text markup.
The body of the cards can be written by a template, is_card_template: rich text where {customer} is a field of the card — one of yours (of_set_field), a column of the DataStore it came from, or a built-in field (title, text, tags, assignee, initials, badge, due, column, column_title, key). {due:dddd d mmmm} writes a date as a DataWindow display format does (the tokens d dd ddd dddd m mm mmm mmmm yy yyyy, a text between double quotes — outside quotes, a letter d, m or y is a token —, [shortdate] and [longdate] in the language of the display), {?amount}…{/amount} keeps a piece when the field says something, {!amount}…{/amount} when it says nothing, {x:raw} keeps a value as markup, {{ }} are braces. A value is always shown as it is, never read as markup.
// A field of our own, then the body of every card
uo_board.of_set_field(/*key*/ "t1", /*field*/ "customer", /*value*/ "Acme")
uo_board.is_card_template = "{?customer}For [b]{customer}[/b]{/customer}{?due} - due {due:d mmm}{/due}"
From a DataStore #
of_from_datastore makes one card of each row of a DataStore, a DataWindow or a DataWindowChild — the same call, and a DataWindow is written back itself. Its key is the RowID of the row — of_card(key).of_row() gives the number of its row as long as the DataStore is neither reset nor retrieved again — or the value of the ROLE_KEY column. A row whose key is empty, not valid, already met or held by a card added by hand is not loaded: the call returns 0 and of_get_last_error names it by its number — give your hand cards keys that are not numbers. A column plays a role when it is named like it (title, text, tags, assignee, due, badge, color, order, column, key) or when of_map says so. The ROLE_COLUMN column says in which column of the board the card stands; a value no column holds gets a column of its own, at the end — a value holding / or | takes a key where they become - (R&D-QA) and keeps its value, the one a move writes back; an empty value is not loaded (of_get_last_error names it), a CHAR value padded with blanks finds its column. A column born of a value goes once empty, unless your code set a property or a colour on it. ROLE_ORDER sorts the cards of a column: the rank is the place of a card among the cards of the DataStore of its column, from 1 (a card added by hand takes no rank). Keyed by ROLE_KEY, a second load, of_map and of_reload_row update the cards in place: a card that comes back keeps its flags, its fields and its handle. Keyed by the RowID, a second of_from_datastore is a new load: PowerBuilder gives the same RowIDs again after a Reset or a Retrieve, to other records — every card of the old rows goes (its handle is freed) and the cards are made anew; of_map and of_reload_row stay in place. A card your code removed does not come back with of_map: its row stays in the DataStore, the next of_from_datastore reads it again. Dates and decimals are read in the regional settings of the workstation (a decimal field reads 1234.5). Every column is a field of the template.
With ib_write_back = true, a card of a row writes its new column into the row (and, with ROLE_ORDER, the ranks of both columns) before it moves — the row is found by its RowID and by nothing else: ue_card_moved reads the DataStore up to date, the Update stays yours. The column receives the value the target column stands for (R&D/QA, never its key R&D-QA). All or nothing: a value the DataStore refuses — the column or a rank — puts back what was already written, leaves the card where it was (it never leaves to come back) and raises ue_write_back_failed; of_move_card then returns -4. One move of the user is written at a time: a gesture made before the previous one is written is ignored, and said. After changing a row yourself, of_reload_row sends it to the board again.
// The columns of the board : the values of the status column
uo_board.of_add_column(/*key*/ "open", /*title*/ "Open")
uo_board.of_add_column(/*key*/ "active", /*title*/ "Active")
uo_board.of_add_column(/*key*/ "closed", /*title*/ "Closed")
// Which column of the DataStore plays which role
uo_board.of_map(/*role*/ u_pbt_kanban.ROLE_COLUMN, /*column*/ "status")
uo_board.of_map(/*role*/ u_pbt_kanban.ROLE_ASSIGNEE, /*column*/ "owner")
uo_board.of_map(/*role*/ u_pbt_kanban.ROLE_ORDER, /*column*/ "rank")
// A move goes into the DataStore, then the rows become cards
uo_board.ib_write_back = true
uo_board.of_from_datastore(/*ads*/ ids_tasks)
Properties #
| Property | Type | Default | Purpose | ||
|---|---|---|---|---|---|
is_card_template | string | "" | The body of every card, written by a template (see above). Empty: the text of the card (is_text). The search reads what the template writes | ||
is_count_template | string | "" | The count in the header of every column, written by a template: {count} the visible cards, {limit} the limit (empty without one), {shown} the cards the search keeps, {sum:points} the sum of a field over the visible cards (one of yours or a column of the DataStore), {title} and {key} of the column; {?limit}…{/limit} keeps a piece when there is a limit. Empty: 3, 3/5, 1 . 3/5 during a search. Past its limit the column turns red whatever the template says, and its header says it to a screen reader. {sum:} reads a number written with a dot or in the language of the display (1,250 in English); a text counts for nothing. Without a key, the count and the sum are those of the cards the demo draws | ||
is_tag_tones | string | "" | The tone of each tag, tag=tone joined by ` | : Bug=danger | UI=info. Tones: TONE_SUCCESS, TONE_WARN, TONE_DANGER, TONE_INFO (the accent), TONE_NEUTRAL`. A tag finds its tone whatever its case; a tag not listed, or an unknown tone, is neutral |
is_today | string | "" | The day the due dates are compared with, yyyy-mm-dd; empty: the date of the workstation. A date that is not one is refused: the old one stays, of_get_last_error says why | ||
is_filter | string | "" | The search: only the cards whose title, body, tags, assignee, badge, key, a field of yours or a column of the DataStore contain this text stay, whatever the case and the accents. The count of a column then says the cards found first (1 . 3). The search box of the toolbar writes it too: read back live | ||
ib_toolbar | boolean | false | A toolbar above the board, with a search box that writes is_filter (Escape empties it) | ||
ib_add_button | boolean | false | An + Add a card button at the foot of every open column: it raises ue_new_requested. A double click on the empty part of a column raises it too, button or not | ||
ib_quick_add | boolean | false | Quick add: the + Add a card button and the Insert key open a title field at the foot of the column; Enter raises ue_new_requested with the title typed (an empty title asks nothing), the field stays open for the next card, Escape closes it. The board still adds nothing: your code adds the card | ||
ib_read_only | boolean | false | The user moves nothing: no drag, no Ctrl + arrows, no Delete, no add button. He still selects and opens the cards; your code still moves them | ||
ib_reorderable | boolean | false | The user reorders the columns: he drags their header or presses Ctrl + Shift + Left / Right; ue_column_moved then ue_layout_changed follow. ib_read_only stops it too; your code still moves them (of_move_column). Read back live | ||
ib_veto_moves | boolean | false | Opt-in: ask ue_card_moving before every move, of the user and of of_move_card alike. Off by default, like every cancelable event of the library | ||
ib_write_back | boolean | false | A board fed by of_from_datastore: a card of a row writes its new column (and the ranks, with ROLE_ORDER) into the DataStore before it moves; a value refused leaves it in place (ue_write_back_failed, of_move_card returns -4). A card added by hand is never written | ||
ii_column_width | integer | 272 | The width of an open column, in pixels, from 180 to 600 (a value outside is brought back inside); 0: the open columns share the width of the board, 180 pixels at least each. The board scrolls sideways when the columns do not fit | ||
is_theme_style | string | "" | Visual style of the component (THEME_STYLE_* constants); empty = the application's, followed at every change | ||
is_theme_mode | string | "" | Light or dark variant (THEME_MODE_* constants); empty = the application's, followed at every change | ||
il_theme_accent | long | -1 | Accent colour of this component (-1 = the application accent, or the theme's) | ||
is_tooltip | string | "" | Simple tooltip shown when hovering the component | ||
is_super_tooltip_title | string | "" | Title of the rich tooltip (takes precedence over is_tooltip) | ||
is_super_tooltip_text | string | "" | Text of the rich tooltip (rich markup accepted) | ||
is_super_tooltip_image | string | "" | Image of the rich tooltip |
Properties of a column — n_pbt_kanban_column #
Its handle, through of_column(key). Its of_count() counts the cards it holds and its of_keys_at(n) returns the key of the n-th, in their order; of_child_keys returns them all in one read.
| Property | Type | Default | Purpose |
|---|---|---|---|
is_title | string | "" | Title of the column. Accepts rich text markup. Empty, the column shows its key |
il_limit | long | 0 | Work-in-progress limit: 0, none. With a limit the header says 3/5, and the column turns red once it holds more; while a card is dragged over a full column, it shows it. Soft limit: refuse in ue_card_moving for a hard one |
ib_collapsed | boolean | false | true folds the column into a narrow strip: its title written downwards and its count stay, its cards hide, a card still drops on it (at the end). ue_column_toggled follows, as for a click on its chevron. Read back live |
ib_visible | boolean | true | false takes the column and its cards out of the board; they keep their place and their order |
ib_done | boolean | false | The column of finished work: its cards are never shown late nor due today |
of_keys_at (long al_index) → string | — | — | The key of the card at rank al_index (from 1) in the column, in their order; empty past either end. A card being named by its key alone, this is what of_card takes |
of_child_keys (ref string as_keys[]) → long | — | — | Fills as_keys[] with the keys of every card of the column, in their order — what of_keys_at(1..n) gives — and returns their count, in one read: walking 2,000 cards with of_keys_at reads the list 2,000 times |
of_position ( ) → long | — | — | Returns the place of the column on the board now, counted from 1 among all the columns (hidden ones included) — the user may have moved it; 0 when the board does not hold it |
of_visible_count ( ) → long | — | — | Returns how many cards of the column are visible, what its header counts against il_limit (2/3): a card hidden by ib_visible does not count, a card out of the search does; of_count() counts them all. The number to compare with il_limit for a hard limit; 0 when the board does not hold the column |
is_tooltip | string | "" | Simple tooltip shown when hovering the item |
is_super_tooltip_title | string | "" | Title of the item rich tooltip (takes precedence over is_tooltip) |
is_super_tooltip_text | string | "" | Text of the item rich tooltip (rich markup accepted) |
is_super_tooltip_image | string | "" | Image of the item rich tooltip |
il_accent | long | -1 | Accent of the column: the bar on top of it and its count take it, never its cards (-1 = the one of the component) |
il_back_color | long | -1 | Background of the column, as RGB(r,g,b) (-1 = what the theme gives) |
il_text_color | long | -1 | Text colour of the column (-1 = what the theme gives) |
il_back_color_hover | long | -1 | Background of the header of the column under the pointer (-1 = what the theme gives). The colours of a column stop at its cards |
il_text_color_hover | long | -1 | Text colour of the header of the column under the pointer (-1 = what the theme gives) |
Properties of a card — n_pbt_kanban_card #
| Property | Type | Default | Purpose |
|---|---|---|---|
is_title | string | "" | Title of the card, in bold. Accepts rich text markup. Empty, the card shows its key |
is_text | string | "" | Text under the title, cut after four lines (markup accepted). Shown while is_card_template is empty |
is_tags | string | "" | Tags, separated by commas: Bug, UI. Each one is a chip coloured by is_tag_tones |
is_assignee | string | "" | The person the card is given to: a round avatar with the initials, of the same colour at every load, the name as tooltip |
is_due | string | "" | Due date, yyyy-mm-dd ("": none): red once past, orange on the day, never in an ib_done column. A date that is not one is refused: the old one stays, of_get_last_error of the board says why |
is_badge | string | "" | Small badge at the top right (markup accepted): an estimate, a number, in the accent |
ib_visible | boolean | true | false takes the card out of the board; it keeps its key, its column and its place, and no longer counts in its column |
ib_read_only | boolean | false | The user can neither drag this card, nor move it with the keyboard, nor ask to delete it; your code still moves it |
of_column ( ) → string | — | — | The key of the column the card stands in now — the user may have moved it. Read only: a card moves through of_move_card |
of_position ( ) → long | — | — | Returns the place of the card in its column now, counted from 1 (hidden cards included), 0 when the board does not hold it |
of_row ( ) → long | — | — | Returns the row number today, in the rows given to of_from_datastore, of the row the card comes from — after a sort, a filter or the deletion of other rows; 0 for a card added by hand or whose row is gone |
is_tooltip | string | "" | Simple tooltip shown when hovering the item |
is_super_tooltip_title | string | "" | Title of the item rich tooltip (takes precedence over is_tooltip) |
is_super_tooltip_text | string | "" | Text of the item rich tooltip (rich markup accepted) |
is_super_tooltip_image | string | "" | Image of the item rich tooltip |
il_accent | long | -1 | Accent of the card: a stripe on its leading edge, its selection ring and its badge take it; it wins over the colour of a ROLE_COLOR column. -1 = the one of the component, no stripe |
il_back_color | long | -1 | Background of the card, as RGB(r,g,b) (-1 = what the theme gives) |
il_text_color | long | -1 | Text colour of the card (-1 = what the theme gives) |
il_back_color_hover | long | -1 | Background of the card under the pointer (-1 = what the theme gives) |
il_text_color_hover | long | -1 | Text colour of the card under the pointer (-1 = what the theme gives) |
Three reads of the handle, read only: of_column() returns the key of the column the card stands in now, of_position() its place in that column counted from 1 (0 when the board does not hold it), of_row() the number of its row in the DataStore (0 for a card added by hand). A card moves through of_move_card of the board.
Methods #
| Method | Purpose | |
|---|---|---|
of_add_card (string as_key, string as_column, string as_title) | Adds a card at the bottom of a column. Its key is unique on the whole board. Returns 0 once added, -5 for an empty key, one holding / or ` | , one already taken (a card of a DataStore included), or an unknown column, -2` when the component is not created |
of_add_column (string as_key, string as_title) | Adds a column at the right of the board. Returns 0 once added, -5 for an empty key, one holding / or ` | , or one already taken, -2` when the component is not created |
of_card (string as_key) | Returns the handle of a card (created on first access, freed when the card is removed) | |
of_clear ( ) | Empties the board: columns, cards (those of a DataStore included) and their handles; the options stay. Returns 0 once applied, -2 when the component is not created | |
of_clear_cards ( ) | Removes every card and its handle, keeps the columns: to load the cards again. Returns 0 once applied, -2 when the component is not created | |
of_clear_selection ( ) | Leaves no card selected: ue_selection_changed follows with an empty key; nothing selected, nothing is raised. Returns 0 once applied, -2 when the component is not created | |
of_column (string as_key) | Returns the handle of a column (created on first access, freed when the column is removed) | |
of_from_datastore (datastore ads) | Makes a card of each row of the DataStore (see above). A second load updates the cards in place with a ROLE_KEY key, makes them anew with the RowID key; the cards of the rows gone leave, those added by hand stay; a row not loaded is named by of_get_last_error. Returns 0 once loaded, -5 when the DataStore is not valid or has no column, -2 when the component is not created | |
of_from_datastore (datawindow adw) | The same from the rows of a DataWindow — the one the user sees beside it: ib_write_back writes into this DataWindow. Returns 0 once loaded, -5 when the DataWindow is not valid, has no column or cannot share its rows (ShareData), -2 when the component is not created | |
of_from_datastore (datawindowchild adwc) | The same from the rows of a DataWindowChild (a drop-down DataWindow, a nested report), written back by ib_write_back. Returns 0 once loaded, -5 when it is not valid, has no column or cannot share its rows (ShareData), -2 when the component is not created | |
of_get_field (string as_key, string as_field) | Reads a field of a card, live: one of yours, a column of its DataStore or a built-in field (column_title, initials…). An empty string when it has none | |
of_get_last_error ( ) | Why the last call went wrong: a method that returned -5 or -4, a date refused (is_due, is_today), the rows a load left out, by their number. Emptied by every call; otherwise the last error of the library | |
of_get_layout ( ) | What the user arranged, as JSON: {"columns":[{"key":"todo","collapsed":false},...]} — every column in its order (hidden ones included), with its fold. To store, then give back to of_set_layout. Titles, limits and cards are yours: not in it. Read live | |
of_map (string as_role, string as_column) | Gives a column of the DataStore a role (ROLE_KEY, ROLE_COLUMN, ROLE_TITLE, ROLE_TEXT, ROLE_TAGS, ROLE_ASSIGNEE, ROLE_DUE, ROLE_BADGE, ROLE_COLOR, ROLE_ORDER). After of_from_datastore, the rows are read again in place: a moved card stays where it was dropped and keeps its flags and its handle. An empty column gives the role back to the column named like it. A card your code removed does not come back. Returns 0 once set, -5 for an unknown role or a column the DataStore does not have, -2 when the component is not created | |
of_move_card (string as_key, string as_column, long al_position) | Moves a card to a column, at a place counted from 1 (past the last one: at the end), like a drag: the question with ib_veto_moves, then ue_card_moved, and, with ib_write_back, the DataStore written before the move. Returns 0 once moved (or already there: nothing is raised), -4 when ue_card_moving refused, the DataStore refused the new column or a rank (the card stays, ue_write_back_failed, of_get_last_error says why) or the board is waiting for the page (a call from a timer during a read: neither the question nor the write can be made), -5 for an unknown card or column, -2 when the component is not created | |
of_move_column (string as_key, long al_position) | Moves a column to a place counted from 1 among all the columns, hidden ones included (past the last one: the last), like a drag of its header: the question with ib_veto_moves, then ue_column_moved and ue_layout_changed. Neither ib_reorderable nor ib_read_only stop your code. Returns 0 once moved (or already there: nothing is raised), -4 when ue_column_moving refused, -5 for an unknown column, -2 when the component is not created | |
of_reload_row (long al_row) | Sends a row of the DataStore of of_from_datastore to the board again, read now, after your code changed it; its card is updated in place (flags, fields and handle stay) and follows its column and its rank. Returns 0 once sent, -5 when nothing is bound, the row does not exist, its key is held by a card added by hand, its column value is empty or its ROLE_KEY value is no longer the key of its card — a key is an identity: after an Update that gives the keys, load again with of_from_datastore (of_get_last_error says it, nothing changed), -2 when the component is not created | |
of_remove_card (string as_key) | Removes a card and frees its handle; removing it while selected clears the selection. Returns 0 once removed, -5 for an unknown card, -2 when the component is not created | |
of_remove_column (string as_key) | Removes a column and all its cards, with their handles. Returns 0 once removed, -5 for an unknown column, -2 when the component is not created | |
of_reset ( ) | Empties the board, forgets the DataStore and puts every property back to its default. Returns 0 once applied, -2 when the component is not created | |
of_select_card (string as_key) | Selects a card like a click, without moving the view (like SelectRow of a DataWindow), and ue_selection_changed follows. Returns 0 once selected (or already selected: nothing is raised), -5 for an unknown card or one not drawn (hidden, in a folded or hidden column, out of the search, beyond the demo cap), -2 when the component is not created | |
of_selected_key ( ) | The key of the selected card, whoever selected it; empty if none. Read live | |
of_set_field (string as_key, string as_field, string as_value) | Gives a card a field of your own, for is_card_template; it wins over a DataStore column of the same name. A field name: letters, digits and _, no leading digit. Returns 0 once set, -5 for an unknown card or a name that is not one, -2 when the component is not created | |
of_set_layout (string as_layout_json) | Gives back a layout read by of_get_layout or received by ue_layout_changed: the columns it names in its order, then the others in theirs, each with its fold; a column the board does not hold is ignored. ue_layout_changed is raised once when something changed (neither ue_column_moved nor ue_column_toggled: it puts a state back). Returns 0 once applied, -5 when the text is empty or is not a JSON object, -2 when the component is not created |
Events #
| Event | Raised when |
|---|---|
ue_card_moved (string as_key, string as_from_column, string as_to_column, long al_position, long al_from_position) | A card moved: dragged, moved with the keyboard, or by your code (of_move_card). The two column keys (the same one for a move inside a column), al_position its place in the target column and al_from_position the place it left, counted from 1. The card is already in place; with ib_write_back, the DataStore is already written |
ue_card_moving (string as_key, string as_from_column, string as_to_column, long al_position) → boolean | Cancelable, asked before a move, only when ib_veto_moves = true: drag, keyboard and of_move_card. Returning FALSE leaves the card where it is. The place for a hard limit: of_column(as_to_column).of_visible_count() >= il_limit |
ue_card_clicked (string as_key) | A card was clicked (and selected). Not at the end of a drag |
ue_card_opened (string as_key) | A double click on a card, or Enter: open your editor |
ue_card_rclicked (string as_key, long al_x, long al_y) | A right click on a card (selected first). al_x, al_y are screen pixels; for a PowerBuilder menu, PopMenu(PointerX(), PointerY()) of your window |
ue_column_moved (string as_key, long al_from, long al_to) | A column moved (ib_reorderable): its header dragged, Ctrl + Shift + Left / Right, or your code (of_move_column). al_from and al_to are its places among all the columns, hidden ones included, from 1. The column is already in place; ue_layout_changed follows |
ue_column_moving (string as_key, long al_from, long al_to) → boolean | Cancelable, asked before a column moves, only when ib_veto_moves = true (the same opt-in as the cards): drag of the header, keyboard and of_move_column. Returning FALSE leaves the column where it is, and of_move_column returns -4 |
ue_column_rclicked (string as_key, long al_x, long al_y) | A right click on the header of a column. Screen pixels, like ue_card_rclicked |
ue_column_toggled (string as_key, boolean ab_collapsed) | A column folded (ab_collapsed = TRUE) or opened: its chevron, a double click on its strip, the keyboard on its header (Enter or Space fold or open it, + opens it, - folds it), or its ib_collapsed set by your code |
ue_delete_requested (string as_key) | Delete was pressed on the selected card. The board removes nothing: of_remove_card once your application agrees. Never on a read-only card or board |
ue_layout_changed (string as_layout_json) | The layout changed: a column moved or folded, by the user or by your code, or of_set_layout. as_layout_json is the whole layout, as of_get_layout gives it: store it |
ue_new_requested (string as_column, string as_title) | The user asks for a new card in a column: its + Add a card button, the Insert key or a double click on its empty part; as_title is the title typed in the quick-add field (ib_quick_add), empty otherwise. The board adds nothing: of_add_card, after your own dialog if you like |
ue_selection_changed (string as_from_key, string as_key) | The selected card changed: a click, the keyboard, your code (of_select_card, of_clear_selection), or the selected card left the drawing (removed, hidden, folded, out of the search: as_key is then empty) |
ue_write_back_failed (string as_key, string as_col, string as_value, string as_reason) | ib_write_back: the DataStore refused the new column of a moved card (a value its column cannot hold, a row deleted since the load). A rank refused too (as_reason then starts with « the rank of row »). Nothing stays written: what was is put back. The card did not move (the DataStore is written before the move), ue_card_moved is not raised and of_move_card returns -4 |
ue_ready ( ) | The component has finished loading; everything sent beforehand has been replayed |
ue_runtime_missing ( ) | The WebView2 runtime is missing: the component stays empty |
ue_bg_color (long al_color) | The component has computed its theme background color; the userobject has already adopted it (backcolor) |
Keyboard and mouse #
- Drag a card: past 5 pixels, a ghost follows the pointer, the target column lights up and an accent bar shows where the card will land; the board and the column scroll near their edges, and go on while the pointer stays there. Escape cancels.
- Arrows: the card above, below, or at the same rank in the next column; a folded column, or one that draws no card, is reached by its header. Home / End: the first, the last of the column.
- Ctrl + Up / Down moves the card up or down in its column; Ctrl + Left / Right passes it to the next open column, at the same rank (a folded column is jumped over; Space then the arrows stop on it). Read right to left, Left and Right are inverted.
- Space lifts the selected card: the arrows carry it (a bar shows where it will land), Space or Enter drops it — the same question and the same write as a drag —, Escape puts it back. A screen reader hears each step.
- The headers are always reachable: Up on the first card, Left / Right from one header to the next; on a header, Enter or Space folds or opens the column, + opens it, - folds it, Down goes back to the first card.
- Enter or a double click opens the card, Delete asks to delete it, Insert asks for a new card in the column (on a card or a header), a right click on a card or a header: its event.
- The board is one stop of the Tab key: the selected card takes it, else the first card, else the first header.
- With
ib_reorderable: dragging the header of a column moves it (a vertical bar shows where it will land, Escape cancels); Ctrl + Shift + Left / Right, on a card or on a header, moves its column, inverted right to left.
Examples #
A hard limit and a rule of passage #
// Once, after the board is built : ask before each move
uo_board.ib_veto_moves = true
// event ue_card_moving of uo_board : a card reaches Done only from Review
if as_to_column = "done" and as_from_column <> "review" then return false
// and a column past its limit takes no more card (what its header counts :
// the visible cards)
if as_to_column <> as_from_column then
if this.of_column(/*key*/ as_to_column).il_limit > 0 and this.of_column(/*key*/ as_to_column).of_visible_count() >= this.of_column(/*key*/ as_to_column).il_limit then return false
end if
return true
Finding the columns again at the next start #
// Once, after the board is built : the user may move the columns
uo_board.ib_reorderable = true
// The layout stored at the last start, if any
uo_board.of_set_layout(/*layout_json*/ ProfileString(gs_ini, "board", "layout", ""))
// event ue_layout_changed of uo_board : store every change
SetProfileString(gs_ini, "board", "layout", as_layout_json)
Opening a card in your window #
// event ue_card_opened of uo_board : the row of the card, edited in your window
OpenWithParm(w_task_edit, uo_board.of_card(/*key*/ as_key).of_row())
// The row may have changed : the card follows it
uo_board.of_reload_row(/*row*/ uo_board.of_card(/*key*/ as_key).of_row())
A team board: points, shared width, quick add #
// The estimate of a card, a field of our own
uo_board.of_set_field(/*key*/ "t1", /*field*/ "points", /*value*/ "5")
// Every header : its cards, its limit, the sum of the points of its cards
uo_board.is_count_template = "{count}{?limit}/{limit}{/limit} - {sum:points} pts"
// The open columns share the width of the board
uo_board.ii_column_width = 0
// + Add a card opens a title field at the foot of the column
uo_board.ib_add_button = true
uo_board.ib_quick_add = true
// event ue_new_requested of uo_board : the card typed, with a key of ours
uo_board.of_add_card(/*key*/ "new" + String(Cpu()), /*column*/ as_column, /*title*/ as_title)
Good practice #
- Few columns, named by the stage and not by the person: five or six read at a glance.
- A work-in-progress limit on the active columns: it is what turns a board into a steering tool.
- Save in
ue_card_moved(or letib_write_backwrite the DataStore): the board shows the new state before the database knows it. - A short template: two lines of body are enough, the detail goes into your edit window.
- Tags with a constant tone:
Bugalways red, from one window to another. - Keys that are not numbers for your hand cards, on a board fed by a DataStore: a card keyed
12keeps the row of RowID 12 from being loaded (of_get_last_errorsays it). - A key of the data (
ROLE_KEY) when the rows are read again: with the RowID key, a newof_from_datastoremakes the cards anew.
Inherited from the common base #
These members exist on every visual component — they are not specific to this one. They are detailed once, in the transverse chapters; this table only says where to read them.
| Members | Role | Detailed in |
|---|---|---|
of_count · of_keys_at · of_has | Walk what the component holds | 3.2 Items |
of_reset | Put the component back to zero | 3.6 Resetting a component: of_reset() |
of_register_shortcut · of_clear_shortcuts | The component's keyboard chords | 3.5 Keyboard shortcuts |
of_is_created · of_is_ready · of_get_last_error | Whether it was born, whether it is ready, what failed | 3.7 Diagnostics |
of_save_as_png · of_save_as_jpg | Export the rendering as an image | 3.8 Exporting the rendering as an image |
of_set_redraw | Group changes into a single repaint | 3.10 Best practices |
of_preload_icons | Icons shown with no delay | Instant display: of_icon |
of_set_translation | Translate one of the component's labels | 5.2 Adapting a label: of_set_translation |
of_focus_webview | Give the component the focus | 6.4 Keyboard and focus |
of_print · of_print_to_pdf | Print, or write a PDF | 6.9 Printing |
of_set_property · of_get_property · of_component_name | Driving a property by its name | 3.1 The property engine |
is_accessible_name · is_accessible_description | The name and description a screen reader says | 3.4 Optional properties and events |
Two helpers are not inherited: of_icon and of_escape_markup live on n_pbt_utils. Declare one — n_pbt_utils lnv_utils, nothing to create — and call them on it.