import either strict or allows for normalization
This commit is contained in:
34
web/templates/pages/import_export/import/fields.html
Normal file
34
web/templates/pages/import_export/import/fields.html
Normal file
@@ -0,0 +1,34 @@
|
||||
{#
|
||||
The form's fields, on their own so that "Normalize headers" can hand them
|
||||
back with the rewritten CSV in the textarea — see
|
||||
crate::pages::import_export::import::ui::ImportFields. `changed` is `None`
|
||||
when the page first renders and `Some` once a rewrite has run.
|
||||
#}
|
||||
<div id="import-fields">
|
||||
<div class="form-grid">
|
||||
<label>{{ nav.tr("import-scope") }}
|
||||
<select name="profile_name" data-profile-select required>
|
||||
<option value="">{{ nav.tr("import-choose-scope") }}</option>
|
||||
{% for profile in page.catalog.profiles %}
|
||||
<option value="{{ profile.name }}"{% if page.form.profile_name == profile.name %} selected{% endif %}>{{ profile.label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
<label>{{ nav.tr("import-target-tables") }}
|
||||
<select name="table_names" data-table-select required>
|
||||
<option value="">{{ nav.tr("import-choose-table") }}</option>
|
||||
{%- for profile in page.catalog.profiles %}{% for table in profile.tables %}
|
||||
<option value="{{ table }}" data-profile="{{ profile.name }}"{% if page.form.table_names.contains(table) %} selected{% endif %}>{{ table }}</option>
|
||||
{%- endfor %}{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
<label class="wide">{{ nav.tr("import-csv-file") }}
|
||||
<input type="file" accept=".csv,text/csv"
|
||||
onchange="this.files[0]?.text().then(value => document.getElementById('csv-data').value = value)">
|
||||
</label>
|
||||
<label class="wide">{{ nav.tr("import-csv-data") }}<textarea id="csv-data" name="csv_data" rows="14" required>{{ page.form.csv_data }}</textarea></label>
|
||||
</div>
|
||||
{%- if let Some(changed) = changed %}
|
||||
<p class="hint" role="status">{% if changed %}{{ nav.tr("import-normalized-message") }}{% else %}{{ nav.tr("import-normalized-unchanged") }}{% endif %}</p>
|
||||
{%- endif %}
|
||||
</div>
|
||||
@@ -10,34 +10,18 @@
|
||||
{% block form %}
|
||||
<form hx-post="/admin/import" hx-target="#submission-status" hx-swap="innerHTML"
|
||||
hx-disabled-elt="button[type=submit]">
|
||||
<div class="form-grid">
|
||||
<label>{{ nav.tr("import-scope") }}
|
||||
<select name="profile_name" data-profile-select required>
|
||||
<option value="">{{ nav.tr("import-choose-scope") }}</option>
|
||||
{% for profile in page.catalog.profiles %}
|
||||
<option value="{{ profile.name }}"{% if page.form.profile_name == profile.name %} selected{% endif %}>{{ profile.label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
<label>{{ nav.tr("import-target-tables") }}
|
||||
<select name="table_names" data-table-select required>
|
||||
<option value="">{{ nav.tr("import-choose-table") }}</option>
|
||||
{%- for profile in page.catalog.profiles %}{% for table in profile.tables %}
|
||||
<option value="{{ table }}" data-profile="{{ profile.name }}"{% if page.form.table_names.contains(table) %} selected{% endif %}>{{ table }}</option>
|
||||
{%- endfor %}{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
<label class="wide">{{ nav.tr("import-csv-file") }}
|
||||
<input type="file" accept=".csv,text/csv"
|
||||
onchange="this.files[0]?.text().then(value => document.getElementById('csv-data').value = value)">
|
||||
</label>
|
||||
<label class="wide">{{ nav.tr("import-csv-data") }}<textarea id="csv-data" name="csv_data" rows="14" required>{{ page.form.csv_data }}</textarea></label>
|
||||
</div>
|
||||
{% include "pages/import_export/import/fields.html" %}
|
||||
<div id="submission-status" aria-live="polite">
|
||||
{%- if let Some(message) = page.error %}{% call alert::error(nav.locale, nav.tr("import-error-title"), message) %}{% endcall %}{% endif -%}
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<a href="/">{{ nav.tr("common-cancel") }}</a>
|
||||
{# Rewrites the header rows and puts the result back in the textarea. It
|
||||
imports nothing: the file that gets imported is the one the user can see
|
||||
and has had the chance to read. #}
|
||||
<button type="button" hx-post="/admin/import/normalize" hx-include="closest form"
|
||||
hx-target="#import-fields" hx-swap="outerHTML"
|
||||
title="{{ nav.tr("import-normalize-hint") }}">{{ nav.tr("import-normalize") }}</button>
|
||||
<button type="submit">{{ nav.tr("import-import-rows") }}</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -4,30 +4,39 @@
|
||||
The options are rebuilt rather than hidden: `hidden` on an `<option>` is not
|
||||
honoured everywhere, and a hidden option is still selectable with the
|
||||
keyboard. With scripting off the dropdown lists every table it was rendered
|
||||
with, and the server still refuses a table that is not in the profile.
|
||||
with, and the server still refuses a table that is not in the scope.
|
||||
|
||||
Bound again after every HTMX swap, because "Normalize headers" replaces the
|
||||
fields — and with them both selects — with fresh nodes.
|
||||
#}
|
||||
<script>
|
||||
(function () {
|
||||
const profileSelect = document.querySelector("[data-profile-select]");
|
||||
const tableSelect = document.querySelector("[data-table-select]");
|
||||
if (!profileSelect || !tableSelect) return;
|
||||
const placeholder = tableSelect.options[0];
|
||||
const all = Array.from(tableSelect.options).slice(1);
|
||||
function sync() {
|
||||
const chosen = tableSelect.value;
|
||||
tableSelect.replaceChildren(placeholder);
|
||||
for (const option of all) {
|
||||
if (option.dataset.profile === profileSelect.value) {
|
||||
tableSelect.append(option);
|
||||
function bind() {
|
||||
const profileSelect = document.querySelector("[data-profile-select]");
|
||||
const tableSelect = document.querySelector("[data-table-select]");
|
||||
if (!profileSelect || !tableSelect || tableSelect.dataset.bound) return;
|
||||
tableSelect.dataset.bound = "true";
|
||||
const placeholder = tableSelect.options[0];
|
||||
const all = Array.from(tableSelect.options).slice(1);
|
||||
function sync() {
|
||||
const chosen = tableSelect.value;
|
||||
tableSelect.replaceChildren(placeholder);
|
||||
for (const option of all) {
|
||||
if (option.dataset.profile === profileSelect.value) {
|
||||
tableSelect.append(option);
|
||||
}
|
||||
}
|
||||
tableSelect.value = Array.from(tableSelect.options).some(
|
||||
(option) => option.value === chosen,
|
||||
)
|
||||
? chosen
|
||||
: "";
|
||||
}
|
||||
tableSelect.value = Array.from(tableSelect.options).some(
|
||||
(option) => option.value === chosen,
|
||||
)
|
||||
? chosen
|
||||
: "";
|
||||
profileSelect.addEventListener("change", sync);
|
||||
sync();
|
||||
}
|
||||
profileSelect.addEventListener("change", sync);
|
||||
sync();
|
||||
|
||||
bind();
|
||||
document.body.addEventListener("htmx:afterSwap", bind);
|
||||
})();
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user