Remember delete functionality? Me neither.

This commit is contained in:
Yaro Kasear 2025-10-23 15:46:52 -05:00
parent 11998b6b31
commit db287fb8ac

View file

@ -1,11 +1,101 @@
<div class="btn-group">
<button type="submit" class="btn btn-primary" id="submit">Save</button>
<button type="button" class="btn btn-outline-primary" onclick="location.href='{{ url_for("entry.entry_new", model=(field['attrs']['data-model'])) }}'">New</button>
<button type="submit" class="btn btn-outline-primary" id="submit">Save</button>
<button type="button" class="btn btn-outline-success"
onclick="location.assign({{ url_for('entry.entry_new', model=field['attrs']['data-model']) | tojson }})"
id="new">New</button>
<button type="button" class="btn btn-outline-danger" id="delete" onclick="deleteEntry()">Delete</button>
</div>
<script>
window.newDrafts = window.newDrafts || [];
window.deletedIds = window.deletedIds || [];
const LIST_URL = {{ url_for('listing.show_list', model = field['attrs']['data-model']) | tojson }};
// Build delete URL only if we have an id, or leave it empty string
{% set model = field['attrs']['data-model'] %}
{% set obj_id = field['template_ctx']['values'].get('id') %}
{% set delete_url = obj_id and url_for('crudkit.' ~model ~ '.rest_delete', obj_id = obj_id) %}
const DELETE_URL = {{ (delete_url or '') | tojson }};
// Form metadata
const formEl = document.getElementById({{ (field['attrs']['data-model'] ~ '_form') | tojson }});
const model = {{ field['attrs']['data-model'] | tojson }};
const idVal = {{ field['template_ctx']['values'].get('id') | tojson }};
const hasId = idVal !== null && idVal !== undefined;
if (!hasId) {
const delBtn = document.getElementById('delete');
delBtn.disabled = true;
delBtn.classList.add('disabled');
}
async function deleteEntry() {
const delBtn = document.getElementById('delete');
if (!DELETE_URL) return;
if (!window.confirm('Delete this entry?')) return;
delBtn.disabled = true;
try {
const res = await fetch(DELETE_URL, {
method: 'DELETE',
credentials: 'same-origin',
headers: { 'Accept': 'application/json' },
});
let data = null;
if (res.status !== 204) {
const text = await res.text();
if (text) {
const ct = res.headers.get('content-type') || '';
data = ct.includes('application/json') ? JSON.parse(text) : { message: text };
}
}
if (!res.ok) {
const msg = (data && (data.detail || data.error || data.message)) ||
`Request failed with ${res.status} ${res.statusText}`;
const err = new Error(msg);
err.status = res.status;
throw err;
}
queueToast((data && (data.detail || data.message)) || 'Item deleted.', 'success');
location.assign(LIST_URL);
} catch (err) {
if (err?.name === 'AbortError') {
toastMessage('Network timeout while deleting item.', 'danger');
} else if (err?.status === 409) {
toastMessage(`Delete blocked: ${err.message}`, 'warning');
} else {
toastMessage(`Network error: ${String(err?.message || err)}`, 'danger');
}
} finally {
delBtn.disabled = false;
}
}
{% if field['attrs']['data-model'] == 'worklog' %}
function collectExistingUpdateIds() {
return Array.from(document.querySelectorAll('script[type="application/json"][id^="md-"]'))
.map(el => Number(el.id.slice(3)))
.filter(Number.isFinite);
}
function collectDeletedIds() { return (window.deletedIds || []).filter(Number.isFinite); }
function collectEditedUpdates() {
const updates = [];
const deleted = new Set(collectDeletedIds());
for (const id of collectExistingUpdateIds()) {
if (deleted.has(id)) continue;
updates.push({ id, content: getMarkdown(id) });
}
for (const md of (window.newDrafts || [])) if ((md ?? '').trim()) updates.push({ content: md });
return updates;
}
{% endif %}
function formToJson(form) {
const fd = new FormData(form);
@ -29,52 +119,28 @@
if (el.type === 'checkbox') {
const group = form.querySelectorAll(`input[type="checkbox"][name="${CSS.escape(el.name)}"]`);
out[el.name] = group.length > 1
? Array.from(group).filter(i => i.checked).map(i => i.value ?? true)
: el.checked;
? Array.from(group).filter(i => i.checked).map(i => i.value ?? true)
: el.checked;
}
});
return out;
}
function collectExistingUpdateIds() {
return Array.from(document.querySelectorAll('script[type="application/json"][id^="md-"]'))
.map(el => Number(el.id.slice(3)))
.filter(Number.isFinite);
}
function collectEditedUpdates() {
const updates = [];
const deleted = new Set(collectDeletedIds());
for (const id of collectExistingUpdateIds()) {
if(deleted.has(id)) continue; // skip ones marked for deletion
updates.push({ id, content: getMarkdown(id) });
}
for (const md of (window.newDrafts || [])) if ((md ?? '').trim()) updates.push({ content: md });
return updates;
}
function collectDeletedIds() { return (window.deletedIds || []).filter(Number.isFinite); }
// much simpler, and correct
const formEl = document.getElementById({{ (field['attrs']['data-model'] ~ '_form') | tojson }});
const model = {{ field['attrs']['data-model'] | tojson }};
const idVal = {{ field['template_ctx']['values'].get('id') | tojson }};
const hasId = idVal !== null && idVal !== undefined;
// Never call url_for for update on the "new" page.
// Create URL is fine to build server-side:
const createUrl = {{ url_for('entry.create_entry', model=field['attrs']['data-model']) | tojson }};
// Update URL is assembled on the client to avoid BuildError on "new":
// URLs for create/update
const createUrl = {{ url_for('entry.create_entry', model = field['attrs']['data-model']) | tojson }};
const updateUrl = hasId ? `/entry/${model}/${idVal}` : null;
formEl.addEventListener("submit", async e => {
formEl.addEventListener('submit', async e => {
e.preventDefault();
const submitBtn = document.getElementById('submit');
submitBtn.disabled = true;
const json = formToJson(formEl);
if (model === 'inventory' && typeof getMarkdown === 'function') {
const md = getMarkdown();
json.notes = (typeof md === 'string') ? getMarkdown().trim() : '';
json.notes = (typeof md === 'string') ? md.trim() : '';
} else if (model === 'worklog') {
json.updates = collectEditedUpdates();
json.delete_update_ids = collectDeletedIds();
@ -89,33 +155,34 @@
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(json),
credentials: 'same-origin'
});
const reply = await res.json();
if (reply.status === 'success') {
window.newDrafts = [];
window.deletedIds = [];
if (!hasId && reply.id) {
window.queueToast('Created successfully.', 'success');
window.newDrafts = [];
window.deletedIds = [];
window.location.assign(`/entry/${model}/${reply.id}`);
return;
} else {
window.queueToast('Updated successfully.', 'success');
if (model === 'worklog') {
for (const id of collectDeletedIds()) {
const li = document.getElementById(`note-${id}`);
if (li) li.remove();
}
}
window.newDrafts = [];
window.deletedIds = [];
window.location.replace(window.location.href);
queueToast('Created successfully.', 'success');
location.assign(`/entry/${model}/${reply.id}`);
return;
}
queueToast('Updated successfully.', 'success');
if (model === 'worklog') {
for (const id of collectDeletedIds()) {
document.getElementById(`note-${id}`)?.remove();
}
}
location.replace(location.href);
} else {
toastMessage(reply.message || 'Server reported failure.', 'danger');
}
} catch (err) {
toastMessage(`Network error: ${String(err)}`, 'danger');
} finally {
submitBtn.disabled = false;
}
});
</script>