Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

gobj-ui: Component gclasses

Component gclasses

Each component is a gclass, and each one needs its registration before an application creates an instance of it. Register them next to the gclasses of @yuneta/gobj-js, at start up.

import { register_c_yui_shell, register_c_yui_nav } from "@yuneta/gobj-ui";

register_c_yui_shell();
register_c_yui_nav();

The shell and the navigation

register_c_yui_shell()

C_YUI_SHELL draws the frame of the application from one JSON file: the toolbar, the menus and the zones. Its API is in The shell.

register_c_yui_nav()

C_YUI_NAV draws the menu of the shell. Each zone takes its own layout, and the layout changes with the width of the screen.

register_c_yui_pager()

C_YUI_PAGER holds a stack of pages inside one zone, for a movement that goes in and returns.

register_c_yui_wizard()

C_YUI_WIZARD drives a sequence of steps with a way forward and a way back.

register_c_yui_service_view()

C_YUI_SERVICE_VIEW puts a view of a service in a zone of the shell.

yui_mount_service_view(host, spec)

Puts a view of a service in a host, from a description of it.

expose_view_container(host, view)

Gives the container of a view to the host that holds it.


Windows

register_c_yui_window()

C_YUI_WINDOW draws a window that floats above the application.

A window is a dialog (since gobj-ui 7.26.43):

const win = gobj_create_service("keys", "C_YUI_WINDOW", {
    title_prefix: topic,            // data, not translated
    title:        "keys",           // an i18n key
    body:         $box,
    focus:        ".KEYS_FILTER",   // this box gets the focus
    modal:        true,             // the reader answers it first
}, gobj);
gobj_start(win);                    // opened by the reader: the focus goes in

register_c_yui_window_manager()

C_YUI_WINDOW_MANAGER holds the windows, and it draws the dock of them.

A floating dock keeps a band at the top or at the bottom of the viewport. It writes the height of the band in two CSS variables on the root element: --yui-dock-inset-top and --yui-dock-inset-bottom (since gobj-ui 7.26.43). A window that fills the screen (the sheet of a phone, a maximized window) does not go into the band. An inline dock and an empty dock keep no band. An element of the application that fills the screen can use the same variables:

.MY_FULL_PANEL {
    position: fixed;
    top:    var(--yui-dock-inset-top, 0px);
    bottom: var(--yui-dock-inset-bottom, 0px);
    left: 0;
    right: 0;
}

Data

register_c_yui_form()

C_YUI_FORM is the one engine of forms of the library. The attribute render_mode chooses between the form that runs a command and the form that edits a record.

Two attributes tell what the form holds. changes is true when the form is not what is stored: the reader changed it, or the record has no value for a field that has a default, and the form shows the default. The save button lights when changes is true. edited is true only when the reader changed the form (typed in it, cleared it, or pasted into it) after the record was loaded, saved or undone (since gobj-ui 7.26.39). A window or a sheet asks “all changes will be lost” before it closes only when edited is true: the form answers EV_WINDOW_TO_CLOSE with abort_close and warning.

What the reader does to a TABLE of the form is edited like anything typed: a cell edited, a row added or deleted, in a nested table too, whatever the record the form opened with (since gobj-ui 7.26.41: in 7.26.39 and 7.26.40 a form that opened changed did not hear its tables, and closing it lost a table edit without a question). The rows the form loads itself are not. The titles of the tables’ columns change language with the shell’s EV_LANGUAGE_CHANGED, like the rest of the form.

Undo shows the rows that the form loaded (since gobj-ui 7.26.43). Tabulator keeps the objects that it gets, and a cell edit writes into them, so each table gets its own copy of the rows of the record. Before 7.26.43 the table got the objects of the record: an edit wrote into the record, Undo loaded the edited record again and cleared edited, and the close did not ask. What a save publishes is a copy too.

The nested tables of a row live as long as the row. A reload, Undo, Clear, a row that the reader deletes and the destroy of the form destroy them. A row that Tabulator draws again keeps its nested tables and their rows. A table loads all its rows in one call and draws them one time: 300 rows load in 0.14 s (20 s before 7.26.43). A nested table is built with its rows (since gobj-ui 7.26.45): 300 rows with a nested table each load in about 2.4 s (3.2 s in 7.26.43 and 7.26.44, 40 s before 7.26.43), in Chromium with Tabulator 6.6.0.

A reload, Undo, Clear or the close of the form with a cell editor open closes the editor first, and drops what is typed in it (since gobj-ui 7.26.45). Tabulator 6.6.0 throws when it wipes its rows with an editor open in any row but the first. What the reader types in a cell editor sets edited at once, before the editor commits (since gobj-ui 7.26.46), as a field of the form does. Thus a host that reads edited before it reloads or closes the form can ask first.

if(gobj_read_bool_attr(form, "edited")) {
    // the reader typed, maybe in a cell not committed yet: ask before the reload
}

An edit that the reader cancels (Escape, or a box left as it was) gives back the edited and changes that the form had when that editor opened (since gobj-ui 7.26.47). Before, the typed text still counted, and the close asked about a change that was gone. If something else changed the form while the editor was open, the cancel gives nothing back.

Enter in a box of the form does not submit the form (since gobj-ui 7.26.47). Before, the “add” button of a table was the first submit button of the form: Enter in any field added an empty row, and in a form with one text box Enter reloaded the page.

A time or now column of a table is edited in a datetime-local box, to the second, with the same conversion as the time field of the form: an epoch in seconds, or ISO text for a string column (since gobj-ui 7.26.46). Before, the editor of Tabulator needed luxon, which gobj-ui does not supply: it opened empty and failed when the reader confirmed the value.

The cell shows the time as text in the language of the application, with a clock of 24 hours (since gobj-ui 7.26.52). It is the same text that the cells of C_YUI_TREEDB_TOPIC_WITH_FORM show, and it comes from fmt_date_time():

es    14 mar 2026, 09:26:53
en    Mar 14, 2026, 09:26:53

When the language changes, the form writes the time cells of each table again, cell by cell. It does not draw the rows again, because a row that is drawn again builds its nested tables again, empty. A cell with an open editor is not changed; when the editor closes, the cell shows the new language. From gobj-ui 7.26.47 to 7.26.51 the cell showed 2026-03-14 09:26:53; before, the raw epoch.

A date with one part cleared is not a value (since gobj-ui 7.26.47). Enter keeps the editor open, with a red outline and aria-invalid, until the date is complete or the reader presses Escape. When the reader leaves the cell, the edit is cancelled and the cell keeps its time. Before, one Backspace and Enter erased the stored time. To clear a time, clear all the parts of the box: an empty box writes null. When the last part is clear, the box is valid again and aria-invalid goes (since gobj-ui 7.26.49). In the box, the Left and Right arrows move between the parts. Tab belongs to the table: it leaves the cell and opens the editor of the next cell.

Enter on a time that the reader did not change cancels the edit, also on the hour (since gobj-ui 7.26.49). The browser writes 2026-10-25T02:30:00 back as 2026-10-25T02:30. Before, the text did not match, and in the hour that occurs two times in autumn the form wrote the earlier hour: -3600 s.

The time field of the form reads and writes a column as the cell editor does (since gobj-ui 7.26.47). A column with no time shows an empty box; before, the field showed January 1970 and a save wrote 0. A string column is read and written as ISO text; before, the field read it as a number and wrote an epoch. A template field with no real type is an epoch (since gobj-ui 7.26.49). In 7.26.47 and 7.26.48 the field showed no time and a save wrote null:

const template = {
    appointment: "time.writable",           // an epoch in seconds
    since:       "string.time.writable",    // an ISO text
};

A time field that the reader did not change is saved with the value of the record (since gobj-ui 7.26.51), as the cell is. The form keeps the value that filled the box and the text that the box gives back. When the box still shows that text, the save uses that value. Before, the save converted the box again. In the hour that occurs two times in autumn, a time of the second pass went back -3600 s, also when the reader changed only another field:

// Europe/Madrid: 01:30Z on 2026-10-25 is 02:30 of the second pass
record:          {when: 1792891800}
box:             2026-10-25T02:30
save, no change: {when: 1792891800}      // 7.26.50: 1792888200

A wall time that the reader types in that hour is the first pass. That limit stays: the box holds no offset.

A field that Save refuses (an empty required box, a date with a part cleared) has a red outline and aria-invalid="true", and the line under the box is its aria-describedby (since gobj-ui 7.26.49). Before, only the outline showed the error, and a screen reader does not see an outline.

const kw = {abort_close: false};
gobj_send_event(form, "EV_WINDOW_TO_CLOSE", kw, gobj);
if(kw.abort_close) {
    // the reader changed the form: ask kw.warning ("all changes will be lost")
}

The tables of a form select no rows. To select rows with a click, set selectableRows in tabulator_settings; the attribute selectable (true by default) then says whether a click selects. Before gobj-ui 7.26.45 a click selected nothing with selectableRows: true, and selected a row with selectable: false.

gobj_create("form", "C_YUI_FORM", {
    template:           template,
    tabulator_settings: {selectableRows: 1},    // a click selects one row
}, parent);

The checkbox column (with_checkbox) also obeys selectable. When the form is not selectable, the column is hidden (gobj-ui 7.26.46): the row checkbox of Tabulator shows a tick even when the selection is refused, and the header checkbox selects all rows without a check. The column follows each write of selectable, in every table (since gobj-ui 7.26.47; in 7.26.46 the value at the build of the table stayed). When selectable becomes false, the selected rows are deselected (since gobj-ui 7.26.49). Before, they stayed selected, and the reader had no checkbox and no click to remove the selection. The header checkbox is named “select all rows” and a row checkbox “select row”; both change language with the form. A click in a nested table selects only in that table.

gobj_create("form", "C_YUI_FORM", {
    template:      template,
    with_checkbox: true,       // a checkbox column ...
    selectable:    false,      // ... is hidden: nothing can be selected
}, parent);

gobj_write_bool_attr(form, "selectable", true);   // the column is shown

register_c_yui_json()

C_YUI_JSON draws a JSON value, and it draws a big one without the cost of the whole tree. It draws it three ways, and each one answers a different question:

The view_mode attribute selects the first view. The switch of the toolbar changes it.

Next to a number under a time key (t, tm, __t__, from_t, fr_t, and the other keys of TIME_FIELDS), the tree writes the time in the language of the application, with a clock of 24 hours. The number can be in seconds or in milliseconds:

"t": 1785312000      29 jul 2026, 10:00:00        (es, Europe/Madrid)
"t": 1785312000      Jul 29, 2026, 10:00:00       (en)

The viewer subscribes to EV_LANGUAGE_CHANGED of the shell, and when the language changes it draws the tree again (since gobj-ui 7.26.52). The text view and the graph are not drawn again. Before, the time was the text of toLocaleString(), in the language of the browser, and it did not change with the language of the application.

register_c_yui_json_pad()

C_YUI_JSON_PAD is a pad for JSON from outside the application. Paste the JSON, and a C_YUI_JSON child shows it. When the text is not JSON, the last document stays on the pad and a line under the text gives the reason.

The pad has two panes. The second pane opens with the second json button. The compare button shows the differences of the two documents in place of the two viewers: one row for each id of the flat form (json2flat), with the kind (added, removed or changed) and the two values. The pad keeps the two texts and its layout in localStorage, under the key in its storage_key attribute. An empty storage_key keeps nothing.

register_c_yui_json_graph()

C_YUI_JSON_GRAPH draws a JSON value as a graph.

register_c_yui_period()

C_YUI_PERIOD chooses a range of time. Its calendar takes the language of the application. The algebra behind it is in Time and periods.


TreeDB

register_c_yui_treedb_topics()

C_YUI_TREEDB_TOPICS draws the topics of a treedb as cards, with a panel of information.

When the connection drops (gobj-ui 7.25.13, 7.25.14). The view answers each form write that is in flight as refused. The form stays open on the typed values. While the session is down, the view does not get the node events of other writers. Thus, when the session is up again, the view reads each open table one time. An “up” event that comes before the transport of the view is in session does not read. For example:

// Session up, the tables `users` and `roles` are open.
// The session drops         -> the writes in flight are answered as refused
// EV_TRANSPORT_STATE {connected: true}, and the transport is in session
//                           -> one `nodes` read of `users` and one of `roles`

register_c_yui_treedb_graph()

C_YUI_TREEDB_GRAPH draws a treedb as the graph that it is: the topics are the nodes, and the links between hook and foreign key are the edges.

register_c_yui_treedb_schema()

C_YUI_TREEDB_SCHEMA draws the schema of a treedb. It draws it as the schema literal in C draws it: one card for each topic, and the fields of the topic in the sequence of the schema. The marks on a field are the marks of the literal ({}, [], (↖), *, #), plus (2) on a secondary key (pkey2s) and (t) on the time key (tkey). For example, the topic binaries of the agent declares 'pkey2s': 'version', so its card draws the row version (2), in bold like the pkey.

An edge is a hook. It goes between the field that declares the hook and the foreign-key field of the child that the hook names. The arrowhead is on the hook, because the reference belongs to the child and points to its parent. This is the direction of the ↖ in a foreign-key mark.

The diagram keeps the scale that it is drawn at. It does not zoom to the container when it appears.

register_c_yui_schema_editor()

C_YUI_SCHEMA_EDITOR edits the schemas that a yuno keeps in its treedb_system_schema. The store keeps a schema in three flat topics: treedbs, topics and cols. This gclass shows them as one schema: a treedb, its topics, and the columns of a topic in their sequence.

An edit is a draft. A write of this gclass changes no version, and the treedb does not use the change yet. The host makes the change public in two steps:

  1. It saves the draft with the command save-schema of C_TREEDB. This command increments the topic_version of each topic that changed, and the schema_version of the treedb, one time.

  2. It puts the saved schema in use with apply-schema, and restarts the yuno that owns the treedb.

The gclass writes topic_version only when the operator types it in the form of a topic. The topic list and the column screen mark the topics that hold a draft. The host tells the gclass which topics hold a draft that is not saved, with EV_DRAFTS. Each EV_DRAFTS replaces the previous one.

The gclass also does these operations:

This example mounts the editor and gives it the events that it needs from the host:

// A named SERVICE: it is the `src` of the commands that it sends to the
// backend. Its parent gets EV_POSITION_CHANGED, EV_RECORD_WRITTEN and
// EV_SCHEMA_CHECKED.
let editor = gobj_create_service("schemas", "C_YUI_SCHEMA_EDITOR", {
    gobj_remote_yuno: transport,            // the treedb service, or an adapter
    treedb_name:      "treedb_system_schema",
    base_route:       "/schemas"
}, gobj);
gobj_start(editor);

// the session of the transport goes up or down
gobj_send_event(editor, "EV_TRANSPORT_STATE", {connected: true}, gobj);
// the url changes under the route of the view
gobj_send_event(editor, "EV_SHOW", {subpath: "treedb_authzs/users"}, gobj);
// the answer of saved-schema says which topics hold a draft
gobj_send_event(editor, "EV_DRAFTS", {drafts: {treedb_authzs: ["users"]}}, gobj);

When the connection drops. A drop stops the load or the write that is in flight. The gclass reads the schemas again when the session is up again. A write that the drop stopped shows the connection dropped during the write.

When the schemas are read again. While the schemas load, the gclass shows a loading screen. A dialog that is open stays, but its Save shows the schemas are loading: wait for them. When the load ends, the gclass closes each dialog that shows the old schemas, and shows the schemas were read again: open the dialog again. A load that fails keeps the schemas on the screen. The next action of the operator then reads them again.

The Save of a form while a reload is owed (gobj-ui 7.25.16). After a load that failed, the Save of an open form reads the schemas again first, and shows the schemas shown may be out of date: they are read again, and the form opens again on them with your changes. When the load ends, the gclass closes the form and opens it again on the new schemas. It puts back only the fields that the operator changed. Thus a field that the operator did not change shows the value that the store has now, and the next Save does not write an old value. If the column or the topic of the form is not in the new schemas, the form does not open again, and the gclass shows the schemas were read again and what the form was editing is not there any more. For example:

// a load failed: a reload is owed. The column form of db.users.id is open,
// and the operator typed "Identifier" in its header.
form.querySelector(".SCHEMA_COL_FORM_SAVE").click();   // the schemas load again
// ... the load ends: the form opens again, header "Identifier",
//     the other fields as the store has them now

EV_REFRESH while a write is in flight (gobj-ui 7.25.16). The gclass does the refresh when the writes end. Before, the refresh started at once, and the writes that were still in the queue were not sent:

gobj_send_event(editor, "EV_CONFIRMED", {what: "topic", topic: "users",
    model_gen: n}, editor);                             // 2 deletes in the queue
gobj_send_event(editor, "EV_REFRESH", {}, gobj);        // it waits
// ... the 2 deletes are done: the schemas load again

When the host moves the view (gobj-ui 7.25.15). The shell keeps a dialog open when only the subpath of the url changes. If EV_SHOW moves the view to a different position, the gclass closes the form, the import or the orphans, and shows the view moved: open the dialog again. The export and the check stay open. A confirmation that the operator answers on a different position does nothing, and shows the same text. For example:

gobj_send_event(editor, "EV_EDIT_COLUMN", {col: "id"}, gobj);    // on db/users
gobj_send_event(editor, "EV_SHOW", {subpath: "db"}, gobj);       // the form closes

The texts in code above are i18n keys. The locales of the application must have them.

register_c_yui_treedb_topic_with_form()

C_YUI_TREEDB_TOPIC_WITH_FORM draws one topic with the form of its records.

A cell of a time or now column shows the time in the language of the application, with a clock of 24 hours (since gobj-ui 7.26.52), as the time cells of C_YUI_FORM. When the language changes, the table rebuilds its columns and the cells show the new language:

{id: "last_seen", header: "Last seen", type: "integer",
 flag: ["persistent", "writable", "time"]}
// 1791983109 in Europe/Madrid:  es "14 oct 2026, 15:05:09"  en "Oct 14, 2026, 15:05:09"

Before, the cell used the language of the browser and a clock of 12 hours (14 oct 2026, 03:05:09 p. m.), and the language button of the application did not change it.

register_c_g6_nodes_tree()

C_G6_NODES_TREE draws a tree of nodes with the library G6.

A Save that the backend does not accept (gobj-ui 7.25.15). The Save of the graph writes one record in __graphs__ for each topic whose arrangement changed. Nothing answers such a write when it is done. Thus the host must tell the graph when the write is not done: the backend refuses it, the transport refuses it, or there is no session. The host sends EV_GRAPHS_WRITE_REFUSED with the topic. The graph then writes that topic again at the next Save. C_YUI_TREEDB_GRAPH does this. A different host must do it too:

// the answer to the update-node of __graphs__ is an error
gobj_send_event(engine, "EV_GRAPHS_WRITE_REFUSED", {topic: record.topic}, gobj);

From gobj-ui 7.25.16 the Save stays lit until a Save writes the topic. Before, the next change of the history (or of the mode, or of the theme) turned the Save off again, and a refusal in the mode reading did not show.

The canvas of a graph is a control. It is in the Tab order (gobj-ui 7.26.28), so it has a name and it shows a focus ring (gobj-ui 7.26.42). The name says which graph it is, and it is a key of the app: json graph, gobj tree graph, treedb graph, fsm graph and schema graph. An app that mounts one of these graphs needs its key in each locale, or its prebuild fails.

// src/locales/en.js
"json graph":      "JSON graph",
"gobj tree graph": "Gobj tree graph",
"treedb graph":    "Treedb graph",
"fsm graph":       "State machine graph",

register_c_yui_gobj_tree_js()

C_YUI_GOBJ_TREE_JS draws the tree of the gobjs of the application, for a development panel.

register_c_yui_gclass()

C_YUI_GCLASS shows what a gclass IS: its attributes, its commands, its events and its states, read from the descriptor the runtime holds. It draws the descriptor and not a document written beside it, so what it shows cannot go stale.

register_c_yui_fsm_graph()

C_YUI_FSM_GRAPH draws the state machine of a gclass as a graph: one node per state and one edge per event that moves between two of them. It is what C_YUI_GCLASS opens for the states of a gclass.


Charts and maps

register_c_yui_uplot()

C_YUI_UPLOT draws a chart of a series of time with the library uPlot.

The axes and the grid take the ink of the theme, light or dark, and the chart draws them again when the theme changes. uPlot always draws an X axis and a Y axis; the chart inks both, also when the axes attribute gives only one (its default [{}]). Before gobj-ui 7.26.40 the Y axis kept the black of uPlot, and its values could not be read on the dark scheme. An application that makes a uPlot of its own gets the same axes from yui_uplot_axes() (src/yui_uplot_ink.js), and redraws the chart on a change of the theme (yui_watch_theme() sends EV_THEME):

import {yui_uplot_axes} from "@yuneta/gobj-ui/src/yui_uplot_ink.js";

const chart = new uPlot({
    width: 600,
    height: 300,
    series: [{}, {label: "temperature"}],
    axes: yui_uplot_axes([{}]),       // the X and the Y, with the ink of the theme
}, data, $el);

The texts of a time axis and of the legend are in the language of the application, with a clock of 24 hours (since gobj-ui 7.26.52). uPlot writes them from its own templates, in English and with 12 hours: 3pm and 10/14 on the axis, 2026-10-14 3:05pm in the legend. The chart gives uPlot the option fmtDate from yui_uplot_fmt_date() (src/yui_uplot_time.js): the axis shows 15:00, and 14/10 in Spanish; the legend shows the text of fmt_date_time(). When the language changes, the chart draws the axes and the legend again. A uPlot of the application takes the same option:

import {yui_uplot_fmt_date} from "@yuneta/gobj-ui/src/yui_uplot_time.js";

const chart = new uPlot({
    width: 600,
    height: 300,
    series: [{}, {label: "temperature"}],
    fmtDate: yui_uplot_fmt_date(() => i18next.language),
}, data, $el);
// on EV_LANGUAGE_CHANGED:
chart.redraw(true, true);
chart.setLegend(null, false);

register_c_yui_map()

C_YUI_MAP draws a map with the library maplibre. The controls are in Map controls.