Refactor templates and views for improved structure and maintainability

This commit is contained in:
Ross
2025-12-08 12:31:58 +00:00
parent ad5e7aeed8
commit d7afab15e1
5 changed files with 242 additions and 83 deletions
+76 -2
View File
@@ -33,7 +33,7 @@ class UserSearchSelectMultipleWidget:
options_html = ""
selected_html = ""
for u in users:
options_html += f'<option value="{u.pk}" selected>{u.get_full_name() or u.username} - {u.email}</option>'
# determine grade_text first, then include data-user-grade attribute on option
grade_text = ""
try:
up = getattr(u, "userprofile", None)
@@ -42,6 +42,8 @@ class UserSearchSelectMultipleWidget:
grade_text = getattr(g, "name", str(g)) if g is not None else ""
except Exception:
grade_text = ""
safe_grade_attr = (grade_text.replace('"', '&quot;') if grade_text else '')
options_html += f'<option value="{u.pk}" selected data-user-grade="{safe_grade_attr}">{u.get_full_name() or u.username} - {u.email}</option>'
# Build the selected item HTML incrementally to avoid accidental
# mixing of boolean values into string concatenation (was causing
@@ -147,6 +149,16 @@ class UserSearchSelectMultipleWidget:
timeout = setTimeout(() => fetchResults(search.value), 300);
}});
// Prevent Enter from submitting the surrounding form; run the search instead
search.addEventListener('keydown', function(e) {{
if (e.key === 'Enter' || e.keyCode === 13) {{
e.preventDefault();
e.stopPropagation();
clearTimeout(timeout);
fetchResults(search.value);
}}
}});
gradeSelect && gradeSelect.addEventListener('change', function(e) {{
// re-run search with same query when grade changes
clearTimeout(timeout);
@@ -172,7 +184,16 @@ class UserSearchSelectMultipleWidget:
if (!btn) return;
const id = btn.getAttribute('data-user-id');
const text = btn.getAttribute('data-user-text') || btn.textContent.trim();
const grade = btn.getAttribute('data-user-grade') || '';
// Try button attribute, then list-item dataset, then visible badge text
let grade = btn.getAttribute('data-user-grade') || '';
if (!grade) {{
const li = btn.closest('li[data-user-id]');
if (li && li.dataset && li.dataset.userGrade) grade = li.dataset.userGrade;
else {{
const badge = btn.closest('li') ? btn.closest('li').querySelector('.badge') : null;
if (badge) grade = badge.textContent.trim();
}}
}}
addUserToField(widgetFieldName, id, text, grade);
}});
@@ -226,6 +247,49 @@ class UserSearchSelectMultipleWidget:
addUserToField(fieldName, id, text, grade);
}});
}}
// Initialize visible selected-list from any pre-existing <option selected> entries
(function initSelectedListFromOptions() {{
try {{
const sel = document.getElementById('{field_id}');
const listEl = document.getElementById('{selected_list_id}');
if (!sel || !listEl) return;
for (let i=0;i<sel.options.length;i++) {{
const opt = sel.options[i];
const id = opt.value;
if (!id) continue;
const liId = 'selected_user_' + '{self.name}' + '_' + id;
const existingLi = document.getElementById(liId);
const grade = opt.getAttribute('data-user-grade') || '';
if (existingLi) {{
// If server-rendered li exists but has no badge, add one from the option attribute
const hasBadge = existingLi.querySelector('.badge');
if (!hasBadge && grade) {{
const span = existingLi.querySelector('span');
if (span) {{
const badge = document.createElement('span');
badge.className = 'badge bg-secondary ms-2';
badge.textContent = grade;
span.appendChild(badge);
}}
}}
continue;
}}
// build li from option when not rendered server-side
const text = opt.textContent || opt.innerText || opt.text;
const li = document.createElement('li');
li.className = 'list-group-item d-flex justify-content-between align-items-center';
li.id = liId;
const span = document.createElement('span');
span.innerHTML = text + (grade ? ' <span class="badge bg-secondary ms-2">' + grade + '</span>' : '');
const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'btn btn-sm btn-outline-danger ms-2'; btn.innerText = 'Remove';
btn.addEventListener('click', function() {{ removeUserFromField('{self.name}', id); }});
li.appendChild(span); li.appendChild(btn); listEl.appendChild(li);
}}
}} catch (e) {{ /* ignore init errors */ }}
}})();
}})();
</script>
</div>
@@ -481,6 +545,16 @@ class ExamSearchSelectMultipleWidget:
timeout = setTimeout(() => fetchResults(search.value), 300);
}});
// Prevent Enter from submitting the surrounding form; run the search instead
search.addEventListener('keydown', function(e) {{
if (e.key === 'Enter' || e.keyCode === 13) {{
e.preventDefault();
e.stopPropagation();
clearTimeout(timeout);
fetchResults(search.value);
}}
}});
// Toggle help panel visibility
if (helpBtn && helpPanel) {{
helpBtn.addEventListener('click', function(e) {{