Replacing a side menu improvised out of buttons with structured, themed, collapsible navigation
Opt-in options
ib_auto_width, ib_reorderable
This is the only component in the library that publishes ib_auto_width: its natural width actually means something, since the collapsed rail is far narrower than the expanded bar. The common case is in fact already covered without enabling anything — ib_collapsed = true shrinks the bar down to the rail, and gives its width back when it expands.
An entry identifier is only unique within its section, so there is no shortcut straight to an entry. Every access goes through the section, which makes the code unambiguous — see Hierarchies.
true collapses the bar into an icon rail: the labels disappear, the icons stay clickable
ib_auto_width
boolean
false
Opt-in: same for the width, including when expanded (the bar sizes itself to the longest label). Collapsing into the rail already shrinks by itself; ue_auto_width follows in both cases
ib_reorderable
boolean
false
Opt-in: the user can drag an entry to another position. The move stays inside its section — an entry id is only unique there, so crossing would risk two identical keys (raises ue_item_reordered)
ib_veto_selection
boolean
true
Ask before the selection moves (raises ue_selection_changing, which can refuse). On by default: scripting nothing always lets the move happen. Set it to false to drop the round trip to PowerBuilder (~35 ms) where it would show — keyboard navigation, a selection moved in a loop
is_theme_style
string
fluent
Visual style of the component (THEME_STYLE_* constants)
is_theme_mode
string
light
Light or dark variant (THEME_MODE_* constants)
il_theme_accent
long
-1
Accent color of this component (-1 = the theme accent)
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)
Moves an existing entry within its section, preserving its state
of_remove_item (string as_section, string as_id)
Removes one entry, designated by its section / identifier pair
of_clear ( )
Empties the bar: every section and every entry
of_select_item (string as_section, string as_id)
Selects an entry — strictly equivalent to the user clicking it: ue_selection_changing is asked first, then ue_selection_changed announces the move
of_get_layout ( )
Reads the current arrangement back as JSON: the sections in order, each with its entries in order and whether it is folded. Store it (file, database, registry) and hand it back with of_set_layout at the next start. The same pair carries the same names on every component that can be rearranged
of_set_layout (string as_layout_json)
Restores an arrangement read with of_get_layout or received with ue_layout_changed. What the layout does not name keeps its place at the end: a layout saved yesterday must not make what was added since disappear. Applying it raises no event — you supplied it
of_clear_selection ( )
Leaves no entry selected. Announced like any other move
of_selected_key ( )
Identifier of the selected entry, "" if there is none. Always the current one: a click and of_select_item both come back through ue_selection_changed
of_selected_section ( )
Section holding the selected entry — an entry id is only unique inside its section, so the pair identifies the selection. "" if there is none
of_reset ( )
Empties the bar, then brings the component back to its brand-new state
The selection has moved — by a click or through of_select_item. Same arguments as ue_selection_changing: the question and its outcome read the same way, and the as_from_* pair designates the entry left (empty if there is none)
The user finished dragging an entry. ai_index is its new rank inside its section, starting from 1. Keep that order to give the user their bar back as they left it
ue_layout_changed (string as_layout_json)
The arrangement changed — the user rearranged something, or your own code did. Carries the whole layout, not just what moved: persisting it is one assignment
// Collapse into an icon rail : the bar shrinks by itself (and gets its width
// back when you expand it).
uo_nav.ib_collapsed = true
// ue_auto_width event of uo_nav : (long al_width)
// The bar has just adopted its ideal width : realign whatever sits to its right.
uo_contenu.x = uo_nav.x + al_width
uo_contenu.width = parent.width - uo_contenu.x
// ue_selection_changed event of uo_nav : (string as_from_section, string as_from_id,
// string as_section, string as_id)
// The section is part of the key : two sections may both have a "liste" entry.
choose case as_section
case "dossiers" ; of_ouvrir_dossier(as_id)
case "outils" ; of_lancer_outil(as_id)
end choose
// The question is asked by DEFAULT: nothing to enable. This line does the
// opposite, dropping it where arbitration is useless and the cost would show.
uo_nav.ib_veto_selection = false
// ue_selection_changing event of uo_nav :
// (string as_from_section, string as_from_id, string as_section, string as_id)
// Returning FALSE keeps the user on the entry being left.
if of_saisie_en_cours(as_from_section, as_from_id) then
MessageBox("Entry", "Finish the current record before navigating away.")
return false
end if
return true
n_pbt_listbar_item lnv_entree
// The full path is mandatory : component -> section -> entry
lnv_entree = uo_nav.of_section("dossiers").of_item("recents")
lnv_entree.is_text = "Recent (12)"
lnv_entree.is_image = "mono:img\clock_full.svg"
// Gray out or hide according to permissions, without rebuilding the bar
uo_nav.of_section("outils").of_item("import").ib_enabled = of_a_le_droit("import")
uo_nav.of_section("outils").of_item("export").ib_visible = gb_mode_expert
// Move the selection elsewhere : the accent band follows
uo_nav.of_select_item("dossiers", "clients")
// Or clear it completely
uo_nav.of_clear_selection()
Keep the section handle returned by of_add_header in a local variable: it reads better than calling of_section(...) again for every entry.
Use mono: for icons: they recolor with the theme, light and dark alike, and stay legible once the bar is collapsed into a rail.
An empty section title works as a discreet grouping: the entries are spaced apart without any header row appearing.
ib_collapsed shrinks the userobject down to the rail, but it does not move your other controls: handle ue_auto_width to take up the freed space, otherwise it just stays empty.
In rail mode only the icons remain: set a tooltip on every entry so the labels stay available.
The question is asked by default: a ue_selection_changing left empty always allows the move, you have nothing to do. Switch it off with ib_veto_selection = false where the click is repeated — keyboard navigation, a selection driven in a loop — since every question costs a round trip to PowerBuilder.
Prefer ib_enabled = false over ib_visible = false when the entry will become available again: the menu does not change shape under the user's eyes.