Enhance user experience with visual indicators for HTMX actions and streamline button interactions in CID group view

This commit is contained in:
Ross
2025-12-22 10:44:50 +00:00
parent d3d0e865c3
commit a02f274a4e
@@ -38,17 +38,17 @@
</div>
{% if group_type == 'cid' %}
<div class="mb-2">
<form method="post" hx-post="{% url 'generic:cid_group_generate_preview' group.pk %}" hx-target="#cid-generate-preview" hx-swap="innerHTML">
{% csrf_token %}
<label class="small text-muted">Quick generate users (one per line: name,email)</label>
<textarea name="rows" class="form-control form-control-sm mb-2" rows="5" placeholder="John Smith,j.smith@example.org\nJane Doe,jane@example.org"></textarea>
<div>
<button type="submit" class="btn btn-sm btn-outline-primary">Preview</button>
</div>
</form>
<div id="cid-generate-preview"></div>
</div>
<div class="mb-2">
<form method="post" hx-post="{% url 'generic:cid_group_generate_preview' group.pk %}" hx-target="#cid-generate-preview" hx-swap="innerHTML">
{% csrf_token %}
<label class="small text-muted">Quick generate users (one per line: name,email)</label>
<textarea name="rows" class="form-control form-control-sm mb-2" rows="5" placeholder="John Smith,j.smith@example.org\nJane Doe,jane@example.org"></textarea>
<div>
<button type="submit" class="btn btn-sm btn-outline-primary">Preview</button>
</div>
</form>
<div id="cid-generate-preview"></div>
</div>
{% endif %}
<div id="group-users-wrap">
@@ -104,15 +104,15 @@
<form class="m-0 d-inline">
<input type="hidden" name="exam_id" value="{{ exam.pk }}">
<input type="hidden" name="exam_type" value="{{ exam.app_name }}">
<button class="btn btn-sm btn-outline-success" hx-post="{% url 'generic:cid_group_add_candidates_to_exams' group.pk %}" name="action" value="add">Add</button>
<button class="btn btn-sm btn-outline-danger" hx-post="{% url 'generic:cid_group_add_candidates_to_exams' group.pk %}" name="action" value="remove">Remove</button>
<button class="btn btn-sm btn-outline-success hx-action-btn" hx-post="{% url 'generic:cid_group_add_candidates_to_exams' group.pk %}" name="action" value="add">Add</button>
<button class="btn btn-sm btn-outline-danger hx-action-btn" hx-post="{% url 'generic:cid_group_add_candidates_to_exams' group.pk %}" name="action" value="remove">Remove</button>
</form>
{% else %}
<form class="m-0 d-inline">
<input type="hidden" name="exam_id" value="{{ exam.pk }}">
<input type="hidden" name="exam_type" value="{{ exam.app_name }}">
<button class="btn btn-sm btn-outline-success" hx-post="{% url 'generic:user_group_add_candidates_to_exams' group.pk %}" name="action" value="add">Add</button>
<button class="btn btn-sm btn-outline-danger" hx-post="{% url 'generic:user_group_add_candidates_to_exams' group.pk %}" name="action" value="remove">Remove</button>
<button class="btn btn-sm btn-outline-success hx-action-btn" hx-post="{% url 'generic:user_group_add_candidates_to_exams' group.pk %}" name="action" value="add">Add</button>
<button class="btn btn-sm btn-outline-danger hx-action-btn" hx-post="{% url 'generic:user_group_add_candidates_to_exams' group.pk %}" name="action" value="remove">Remove</button>
</form>
{% endif %}
</div>
@@ -205,5 +205,47 @@
.finally(()=>{ if(btn){ const s = btn.querySelector('.spinner-border'); if(s) s.remove(); btn.disabled = false; } });
});
});
// Visual indicators for HTMX add/remove actions
document.addEventListener('DOMContentLoaded', function(){
document.body.addEventListener('htmx:beforeRequest', function(evt){
try{
const elt = evt.detail.elt;
if(!elt) return;
const btn = elt.closest && elt.closest('.hx-action-btn') ? elt.closest('.hx-action-btn') : (elt.classList && elt.classList.contains('hx-action-btn') ? elt : null);
if(!btn) return;
// stash original HTML for restore
btn.dataset._origHtml = btn.innerHTML;
btn.disabled = true;
const spinner = document.createElement('span');
spinner.className = 'spinner-border spinner-border-sm ms-2';
spinner.setAttribute('role','status'); spinner.setAttribute('aria-hidden','true');
spinner.dataset._hx_spinner = '1';
btn.appendChild(spinner);
}catch(e){ console.error(e); }
});
document.body.addEventListener('htmx:afterRequest', function(evt){
try{
const elt = evt.detail.elt;
if(!elt) return;
const btn = elt.closest && elt.closest('.hx-action-btn') ? elt.closest('.hx-action-btn') : (elt.classList && elt.classList.contains('hx-action-btn') ? elt : null);
if(!btn) return;
// remove spinner(s)
const sp = btn.querySelectorAll('span[data-_hx_spinner]');
sp.forEach(s => s.remove());
// show transient status
const status = document.createElement('span');
status.className = 'badge bg-success ms-2 hx-action-status';
status.textContent = 'Done';
btn.appendChild(status);
// restore after short delay
setTimeout(function(){
if(btn.dataset._origHtml){ btn.innerHTML = btn.dataset._origHtml; delete btn.dataset._origHtml; }
btn.disabled = false;
}, 1500);
}catch(e){ console.error(e); }
});
});
</script>
{% endblock %}