Add advanced search help panel and functionality to ExamSearchSelectMultipleWidget
This commit is contained in:
+59
-2
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user