Refactor templates and views for improved structure and maintainability
This commit is contained in:
+76
-2
@@ -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('"', '"') 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) {{
|
||||
|
||||
Reference in New Issue
Block a user