Enhance user experience with visual indicators for HTMX actions and streamline button interactions in CID group view
This commit is contained in:
@@ -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 %}
|
||||
Reference in New Issue
Block a user