Remember delete functionality? Me neither.
This commit is contained in:
parent
11998b6b31
commit
db287fb8ac
1 changed files with 121 additions and 54 deletions
|
|
@ -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>
|
||||
</script>
|
||||
Loading…
Add table
Add a link
Reference in a new issue