Add advanced search help panel and functionality to ExamSearchSelectMultipleWidget

This commit is contained in:
Ross
2025-12-08 11:15:03 +00:00
parent 6fafccc907
commit a4f4ba464a
+59 -2
View File
@@ -347,8 +347,28 @@ class ExamSearchSelectMultipleWidget:
<input type="search" id="{search_id}" class="form-control form-control-sm" placeholder="Search exams by name or code" />
<!-- optional type indicator -->
{f'<div class="badge bg-secondary small">{self.exam_model._meta.verbose_name.title()}</div>' if self.exam_model is not None else ''}
<button type="button" id="exam_search_help_btn_{self.name}" class="btn btn-sm btn-outline-secondary" aria-expanded="false" aria-controls="exam_search_help_panel_{self.name}" title="Advanced search help">?</button>
</div>
<div id="{results_id}" class="mt-2"></div>
<div id="exam_search_help_panel_{self.name}" class="card card-body mt-2 d-none" style="font-size:.9rem;">
<strong>Advanced search tips</strong>
<ul class="mb-0 mt-1">
<li>Basic: type any part of an exam's name, code or identifier (case-insensitive substring match).</li>
<li>Field-specific searches: prefix with <code>field:term</code> to restrict the search. Supported fields:
<ul class="mb-0 mt-1">
<li><code>name:</code> — matches the exam name.</li>
<li><code>code:</code> — matches an exam code or short identifier.</li>
<li><code>id:</code> or <code>pk:</code> — match by numeric id.</li>
<li><code>date:</code> — match by date (YYYY-MM-DD) where available.</li>
<li><code>type:</code> — narrow to an exam type (e.g. <code>type:anatomy</code>).</li>
</ul>
</li>
<li>Type filter: use the dropdown to restrict results to a specific exam type in addition to your query.</li>
<li>To add exams, click the <em>Add</em> button beside a result. Use <em>Add all results</em> to import visible matches.</li>
<li>Wildcard: use <code>*</code> to broaden results; wildcards may return more results and are rate-limited.</li>
</ul>
</div>
</div>
<div>
@@ -364,6 +384,11 @@ class ExamSearchSelectMultipleWidget:
const results = document.getElementById('{results_id}');
const select = document.getElementById('{field_id}');
const selectedList = document.getElementById('{selected_list_id}');
const helpBtn = document.getElementById('exam_search_help_btn_{self.name}');
const helpPanel = document.getElementById('exam_search_help_panel_{self.name}');
const defaultModel = '{self.exam_model._meta.label if self.exam_model is not None else ''}';
const widgetFieldName = '{self.name}';
let timeout = null;
function fetchResults(q) {{
@@ -371,7 +396,8 @@ class ExamSearchSelectMultipleWidget:
results.innerHTML = '';
return;
}}
const url = '{url}?field=' + encodeURIComponent('{self.name}') + '&q=' + encodeURIComponent(q) + ("&model=" + encodeURIComponent('{self.exam_model._meta.label}') || '');
const modelParam = defaultModel ? '&model=' + encodeURIComponent(defaultModel) : '';
const url = '{url}?field=' + encodeURIComponent(widgetFieldName) + '&q=' + encodeURIComponent(q) + modelParam;
fetch(url)
.then(r => r.text())
.then(html => {{ results.innerHTML = html; }});
@@ -382,14 +408,34 @@ class ExamSearchSelectMultipleWidget:
timeout = setTimeout(() => fetchResults(search.value), 300);
}});
// Toggle help panel visibility
if (helpBtn && helpPanel) {{
helpBtn.addEventListener('click', function(e) {{
e.preventDefault();
helpPanel.classList.toggle('d-none');
const expanded = !helpPanel.classList.contains('d-none');
helpBtn.setAttribute('aria-expanded', expanded ? 'true' : 'false');
}});
}}
// Delegate add clicks from results
results.addEventListener('click', function(e) {{
const btn = e.target.closest('.exam-add-btn');
if (!btn) return;
const id = btn.getAttribute('data-exam-id');
const text = btn.getAttribute('data-exam-text') || btn.textContent.trim();
addExamToField('{self.name}', id, text);
addExamToField(widgetFieldName, id, text);
}});
// Wire up "Add all results" button if present
const addAllBtn = results.parentElement.querySelector('.exam-add-all-btn');
if (addAllBtn) {{
addAllBtn.addEventListener('click', function(e) {{
e.preventDefault();
addAllExamsFromResults(widgetFieldName);
}});
}}
window.addExamToField = function(fieldName, id, text) {{
const sel = document.getElementById('id_' + fieldName);
if (!sel) return;
@@ -414,6 +460,17 @@ class ExamSearchSelectMultipleWidget:
const li = document.getElementById('selected_exam_' + fieldName + '_' + id);
if (li && li.parentNode) li.parentNode.removeChild(li);
}}
window.addAllExamsFromResults = function(fieldName) {{
const list = document.getElementById('exam_search_results_list_' + fieldName);
if (!list) return;
const items = list.querySelectorAll('li[data-exam-id]');
items.forEach(function(it) {{
const id = it.getAttribute('data-exam-id');
const text = it.getAttribute('data-exam-text') || it.textContent.trim();
addExamToField(fieldName, id, text);
}});
}}
}})();
</script>
</div>