{# Grid component (GridComponent in components.py, request half in grid.py; docs in manual/grid.org). One rendering of the table: without JS it works through links and row forms, and construct-grid.min.js hydrates the same markup rather than rebuilding it. No-JS controls carry ``js-hide``. Forms live *outside* the table (a
cannot be a child of ) and controls join them via ``form="…"``: one row form for edit or add, one delete form whose buttons each submit their own row id. #} {% macro cell_input(field, name, value, form_id, choices, error, input_types) %} {#- The no-JS twin of buildEditor() in frontend/src/grid/cells.js: same widget per field type, so the row form and the in-cell editor never disagree about what a value looks like while it is being typed. -#} {%- if field.field_type == 'bool' -%} {%- elif choices is not none -%} {%- elif field.field_type == 'text' -%} {%- else -%} {%- endif -%} {%- if error %}{{ error }}{% endif -%} {% endmacro %} {% if writable and form_action %} {# The row form: update when a row is open for editing, create otherwise. Only one of the two rows is ever rendered, so one form serves both. #} {{ csrf_field }} {% if edit_row %}{% endif %} {% if spec.can_delete %}
{{ csrf_field }}
{% endif %} {% endif %} {% if spec.filterable %}
{% for key, value in carry_args %} {% endfor %}
{% endif %} {% if config %} {# Read once at hydration. A parse failure leaves the table exactly as the server rendered it, which is a working read-only grid. ``tojson`` rather than plain interpolation: a " in a cell value able to close the element. #} {{ csrf_field }} {% endif %}
{% if spec.caption %}{% endif %} {% for name, field in spec.columns %} {# aria-sort carries the CURRENT direction. The link's title says what clicking will do next, which is the opposite thing. #} {% endfor %} {% if writable %}{% endif %} {% if spec.filterable %} {% for name, field in spec.columns %} {% endfor %} {% if writable %} {% endif %} {% endif %} {% for row in rows %} {% if edit_row and row.id|string == edit_row.id|string %} {% for cell in row.cells %} {% endfor %} {% else %} {% for cell in row.cells %} {% endfor %} {% if writable %} {% endif %} {% endif %} {% else %} {% if not spec.columns %} {% endif %} {% endfor %} {% if new_row %} {# The blank add row. Hidden once scripting is present: the client maintains its own, which inserts one cell at a time. #} {% for name, field in spec.columns %} {% endfor %} {% endif %}
{{ spec.caption }}
{{ sort_links[name].label }}{% if sort_links[name].dir == 'asc' %} {% elif sort_links[name].dir == 'desc' %} {% endif %}
{{ cell_input(cell.field, cell.name, edit_row.prefill.get(cell.name), gid ~ '-rowform', choices.get(cell.name), edit_row.errors.get(cell.name), input_types) }} {{ t.cancel }}
{{ cell.text }} ✎ {% if spec.can_delete %} {% endif %} {# Where the client puts its own delete button. It needs a slot of its own because the cell is not empty: the no-JS controls above are still here, merely hidden. #}
{{ t['no-columns'] }}
{{ cell_input(field, name, new_row.prefill.get(name), gid ~ '-rowform', choices.get(name), new_row.errors.get(name), input_types) }}