{# POST /admin/tables/builder — crate::pages::add_table::ui::BuilderFragment. The whole builder, which is also what `add_table.html` embeds on first load. Every control posts the entire draft back to /admin/tables/builder and swaps this markup in again, so the rules live on the server exactly as they do in the TUI client. The hidden inputs in the columns, relations and display sections are what carry the draft across requests. #} {% import "ui/alert.html" as alert %} {% import "ui/dialog.html" as dialog %} {% import "ui/toast.html" as toast %} {# A failure is a dialog, not an inline alert: the builder is taller than the viewport, so an alert at the top is off screen for anyone who has scrolled down to the column list or the Create table button. The alert stays too, as what is left on the page after the dialog is dismissed. #} {%- if let Some(message) = page.error %}{% call alert::error("Could not continue", message) %}{% endcall %}{% endif -%} {%- if let Some(message) = page.status %}{% call toast::success("Draft updated", message) %}{% endcall %}{% endif -%} {%- if let Some(message) = page.error %}{% call dialog::error("Could not continue", message) %}{% endcall %}{% endif -%}

Table details

{# Penguin UI custom radio, adapted from radio/custom-radio.html with this form's values and htmx behaviour. #}
Where should this table be available?
{% if !page.draft.global %} {% else %} {% endif %} {% if page.draft.show_profile_name_input() %} {% endif %} {% if page.draft.show_accounting_currency() %} {% else %} {% endif %} {# No hx-trigger here. A text input fires `change` on blur, so mousing into the next field would swap the whole builder out from under the click that is still in flight: the click lands on a detached node and the field only takes focus on a second click. Nothing rendered depends on the table name on its own — it filters the table out of its own link-target list, and that list is only rendered after a column-type `change`, which posts the current name along with the rest of the form. #}

Add a column

{# A compound choice is a definition row, not a column: it is named after its own type and expands into companions the server creates. There is nothing to name, so the field is replaced by what the choice will add. #} {% if !page.draft.columns.pending_is_compound() %} {% endif %} {% if page.draft.columns.show_temporal_type() %} {% endif %} {% if page.draft.columns.show_gtin_type() %} {% endif %} {% if page.draft.columns.show_decimal_arguments() %} {% endif %} {% if page.draft.columns.show_link_target() %} {% endif %} {% if page.draft.columns.show_money_options() %} {% endif %} {# Neither applies to a definition row: it leaves no column to index, and nothing to keep a quantity ledger on. #} {% if !page.draft.columns.pending_is_compound() %} {% endif %}
{% if page.draft.columns.pending_is_compound() %}

{{ page.draft.columns.pending_compound_name() }} is a definition row, not a column of its own. Adding it gives the table:

Each of these can be given a name of your own in the column list once the row is added; leave a field empty to keep the name shown here.

These names are reserved: no column of your own may use them, and the table may hold only one {{ page.draft.columns.pending_compound_name() }}. They land where this row sits in the column list, and can be moved with it.

{% endif %}

Columns {{ page.draft.columns.added.len() }}

{% if page.draft.columns.is_empty() %}

No columns yet. Describe one above and press Add column.

{% else %}

Columns are created in this order. Use the arrows to move one.

{# Every declared column, each followed by the columns the server will generate from it. `row.index` is what says which is which: a generated column has none, because it is not the user's to move, index or remove — it belongs to the definition row above it. #} {% for row in page.column_rows() %} {% endfor %}
OrderNameTypeIndexedOptions
{% if let Some(index) = row.index %} {% else %} {% endif %} {{ row.name }} {# A generated column is named by the backend, so it cannot be named in the request that creates the table — but the name is a display name, and an alias here is applied as a rename as soon as the table exists. Leaving it empty keeps the backend's name. #} {% if let Some(source) = row.alias_source %} {% endif %} {{ row.data_type }} {# A compound column expands into schema-managed companions, so there is no column of its own name to index. #} {% if row.indexable %} {% if let Some(index) = row.index %} {% endif %} {% else if row.indexed %} indexed {% else %} {% endif %} {% for tag in row.tags %}{{ tag }}{% endfor %} {% if let Some(index) = row.index %} {% endif %}
{% endif %} {# The draft itself: one set of fields per column, in order. #} {% for column in page.draft.columns.added %} {% endfor %}
{% for table in page.draft.relation_tables %} {% endfor %}

Row display columns

What identifies a row to users, in the order chosen. Pick id to clear the list.

{% for display in page.draft.row_display_columns %} {% endfor %}

Table definition preview

{% for row in page.draft.preview_rows() %} {% endfor %}
ColumnTypeOptionsSource
{{ row.mark }} {{ row.column }} {{ row.data_type }} {{ row.option }} {{ row.source }}
Cancel