#12 - form live preview

This commit was merged in pull request #14.
This commit is contained in:
Martin Slachta
2026-06-14 14:13:37 +02:00
parent 0fc0addf47
commit 2890a9b993
8 changed files with 375 additions and 32 deletions
+78 -20
View File
@@ -126,20 +126,28 @@ class RsvFormsPage extends RsvAdminPage {
echo RsvFormBuilder::create('edit_form_definition', get_rest_url(null, 'reservations/v1/form-definition/' . $id), 'PUT', 'Form definition updated.')
->text('name', 'Name', '', true, $form_def['name'])
->select('definition.email_key', 'Email Key', $email_key_options, "Form field that holds the submitter's email address.", true, $definition['email_key'] ?? '')
->textarea('definition.success_message', 'Success message', 'Shown to the visitor after a successful submission. HTML is allowed. Use <reservation-summary></reservation-summary> to display the selected reservations. Leave blank for the default message.', false, $definition['success_message'] ?? '')
->code('definition.success_message', 'Success message', 'Shown to the visitor after a successful submission. HTML is allowed. Use <reservation-summary></reservation-summary> to display the selected reservations. Leave blank for the default message.', $definition['success_message'] ?? '')
->render();
?>
<hr>
<h2>Form Elements</h2>
<p>Define the fields that will appear in this form.</p>
<div id="form_elements_table"></div>
<p>
<button type="button" class="button" id="rsv_add_element_btn">+ Add Element</button>
</p>
<p class="submit">
<button type="submit" form="edit_form_definition" class="button button-primary">Update Form Definition</button>
</p>
<?php
RsvColumnLayout::split('3:2')
->column(function (): void { ?>
<h2>Form Elements</h2>
<p>Define the fields that will appear in this form.</p>
<div id="form_elements_table"></div>
<p>
<button type="button" class="button" id="rsv_add_element_btn">+ Add Element</button>
<button type="submit" form="edit_form_definition" class="button button-primary">Update Form Definition</button>
</p>
<?php })
->column(function (): void { ?>
<h3>Live preview</h3>
<div id="rsv_form_preview" class="rsv-form-preview" style="position: sticky; top: 40px;"></div>
<?php })
->output();
?>
<?php $this->elements_table_script($elements_with_ids, $next_id, 'edit_form_definition', $element_types, $timetables); ?>
<?php
@@ -235,6 +243,50 @@ class RsvFormsPage extends RsvAdminPage {
};
})(<?= $elements_json ?>, <?= $next_id ?>);
function rsv_collect_definition() {
const form = document.getElementById('<?= $form_id ?>');
const get = (n) => form?.querySelector(`[name="${n}"]`)?.value ?? '';
return {
name: get('name'),
definition: {
email_key: get('definition.email_key'),
success_message: get('definition.success_message'),
elements: rsv_elements_source.get_all(),
},
};
}
const rsv_preview_el = document.getElementById('rsv_form_preview');
let rsv_preview_timer = null;
function rsv_schedule_preview() {
if (!rsv_preview_el) return;
clearTimeout(rsv_preview_timer);
rsv_preview_timer = setTimeout(rsv_render_preview, 300);
}
function rsv_render_preview() {
if (!rsv_preview_el) return;
fetch('<?= get_rest_url(null, 'reservations/v1/form-definition/preview') ?>', {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-WP-Nonce': ReservairServiceAPI.nonce,
},
body: JSON.stringify(rsv_collect_definition()),
})
.then(r => r.ok ? r.json() : r.json().then(e => { throw new Error(e.error || 'Preview failed'); }))
.then(data => {
rsv_preview_el.innerHTML = data.html || '<p class="rsv-preview-empty">No fields to preview yet.</p>';
})
.catch(() => { rsv_preview_el.innerHTML = '<p class="rsv-preview-empty">Preview unavailable.</p>'; });
}
// The preview form is inert: block submission (capture so it works after re-render).
rsv_preview_el?.addEventListener('submit', (e) => e.preventDefault(), true);
function rsv_render_element_inline_form(dt, row, data) {
const builder = RsvInlineFormBuilder.create(rsv_elements_source)
.fieldset('Element', '50%')
@@ -297,8 +349,8 @@ class RsvFormsPage extends RsvAdminPage {
id: data?.id,
colspan: 6,
save_label: 'Save',
on_success: () => elements_dt.refresh(),
on_cancel: () => elements_dt.refresh(),
on_success: () => { elements_dt.refresh(); rsv_schedule_preview(); },
on_cancel: () => { elements_dt.refresh(); rsv_schedule_preview(); },
});
// Type swaps whole fieldsets, so re-render the inline form on change.
@@ -342,14 +394,17 @@ class RsvFormsPage extends RsvAdminPage {
'Move Up': RsvDataGrid.func_action(function(dt, row, data) {
rsv_elements_source.move_up(data.id);
dt.refresh();
rsv_schedule_preview();
}),
'Move Down': RsvDataGrid.func_action(function(dt, row, data) {
rsv_elements_source.move_down(data.id);
dt.refresh();
rsv_schedule_preview();
}),
'Remove': RsvDataGrid.func_action(function(dt, row, data) {
rsv_elements_source.remove(data.id);
dt.refresh();
rsv_schedule_preview();
}),
}),
'label': RsvDataGrid.column('Label', false),
@@ -382,6 +437,7 @@ class RsvFormsPage extends RsvAdminPage {
document.getElementById('rsv_add_element_btn').onclick = function() {
rsv_elements_source.add();
elements_dt.refresh();
rsv_schedule_preview();
};
}
@@ -402,17 +458,19 @@ class RsvFormsPage extends RsvAdminPage {
}
rsv_email_key_select?.addEventListener('focus', rsv_sync_email_key_options);
const rsv_meta_form = document.getElementById('<?= $form_id ?>');
['name', 'definition.email_key', 'definition.success_message'].forEach((n) => {
const el = rsv_meta_form?.querySelector(`[name="${n}"]`);
el?.addEventListener('input', rsv_schedule_preview);
el?.addEventListener('change', rsv_schedule_preview);
});
RsvAdminForm.bind(document.getElementById('<?= $form_id ?>'), {
transform: (body) => ({
name: body.name,
definition: {
email_key: body.definition?.email_key ?? '',
success_message: body.definition?.success_message ?? '',
elements: rsv_elements_source.get_all(),
},
}),
transform: () => rsv_collect_definition(),
refresh: () => { if (typeof forms_dt !== 'undefined') forms_dt.refresh(); },
});
rsv_render_preview();
</script>
<?php
}