Enhance focus handling in modals to improve user experience by scrolling to and focusing on relevant fields

This commit is contained in:
Ross
2025-12-17 16:11:37 +00:00
parent c59a03fa31
commit 86201ec1d7
5 changed files with 67 additions and 11 deletions
@@ -29,6 +29,31 @@ function _close_shift_modal_targets(){
</div>
</div>
{% if focus %}
<script>
(function(){
try{
const focusName = '{{ focus }}';
const listEl = document.getElementById('constraint-list');
const addBtn = document.getElementById('add-constraint');
if(listEl){
// Ensure at least one row exists
if(listEl.children.length === 0){ if(addBtn) addBtn.click(); }
setTimeout(()=>{
// Try to find a select matching the constraint name
const selects = listEl.querySelectorAll('select');
let matched = null;
selects.forEach(s=>{ if(s.value === focusName) matched = s; });
if(matched){ try{ matched.scrollIntoView({behavior:'smooth', block:'center'}); matched.focus(); matched.dispatchEvent(new Event('change',{bubbles:true})); return; }catch(e){} }
// otherwise, focus first row and set its type to the requested focus
const firstSel = selects[0];
if(firstSel){ try{ firstSel.value = focusName; firstSel.dispatchEvent(new Event('change',{bubbles:true})); firstSel.focus(); firstSel.scrollIntoView({behavior:'smooth', block:'center'}); }catch(e){} }
}, 120);
}
}catch(e){}
})();
</script>
{% endif %}
<script>
(function(){
// Constraint type schemas: name -> fields metadata
@@ -9,8 +9,8 @@
<div style="margin-top:0.25rem;">
<span class="tags">
{% for c in s.constraints %}
{# Build a simple tooltip showing key: value pairs for the constraint on hover #}
<span class="tag is-light" title="{% for k,v in c.items %}{{ k }}: {{ v }}{% if not forloop.last %}; {% endif %}{% endfor %}">{{ c.name }}</span>
{# Build a simple tooltip showing key: value pairs for the constraint on hover; make clickable to open constraint editor #}
<a href="javascript:void(0)" class="tag is-light" title="{% for k,v in c.items %}{{ k }}: {{ v }}{% if not forloop.last %}; {% endif %}{% endfor %}" hx-get="{% url 'rota:shift_edit' rota.id forloop.parentloop.counter0 %}?focus={{ c.name }}" hx-target="#modal" hx-swap="innerHTML">{{ c.name }}</a>
{% endfor %}
</span>
</div>
@@ -30,6 +30,37 @@
</div>
</div>
{% if focus %}
<script>
(function(){
try{
const f = '{{ focus }}';
const listId = f + '-list';
const addBtnId = 'add-' + f;
const listEl = document.getElementById(listId);
if(listEl){
// if no rows exist, trigger the Add button to create one
if(listEl.children.length === 0){
const btn = document.getElementById(addBtnId);
if(btn) btn.click();
}
// after render, scroll into view and focus the first input
setTimeout(()=>{
const el = document.getElementById(listId);
if(el){
try{ el.scrollIntoView({behavior:'smooth', block:'center'}); }catch(e){}
const firstRow = el.querySelector('.box') || el.firstElementChild;
if(firstRow){
const input = firstRow.querySelector('input, select, textarea, [data-field]');
if(input){ try{ input.focus(); }catch(e){} }
}
}
}, 120);
}
}catch(e){}
})();
</script>
{% endif %}
<script>
(function(){
const textarea = document.getElementById('id_nwds');
@@ -20,19 +20,19 @@
<div style="margin-top:0.25rem;">
<span class="tags">
{% for it in w.options.hard_day_exclusions %}
<span class="tag is-light" title="{{ it|escape }}">HDE</span>
<a href="javascript:void(0)" class="tag is-light" title="{{ it|escape }}" hx-get="{% url 'rota:worker_edit' rota.id w.id %}?focus=hde" hx-target="#modal" hx-swap="innerHTML">HDE</a>
{% endfor %}
{% for it in w.options.hard_day_dependencies %}
<span class="tag is-light" title="{{ it|escape }}">HDD</span>
<a href="javascript:void(0)" class="tag is-light" title="{{ it|escape }}" hx-get="{% url 'rota:worker_edit' rota.id w.id %}?focus=hdd" hx-target="#modal" hx-swap="innerHTML">HDD</a>
{% endfor %}
{% for it in w.options.forced_assignments %}
<span class="tag is-light" title="{{ it|escape }}">FA</span>
<a href="javascript:void(0)" class="tag is-light" title="{{ it|escape }}" hx-get="{% url 'rota:worker_edit' rota.id w.id %}?focus=fa" hx-target="#modal" hx-swap="innerHTML">FA</a>
{% endfor %}
{% for it in w.options.forced_assignments_by_date %}
<span class="tag is-light" title="{{ it|escape }}">FAD</span>
<a href="javascript:void(0)" class="tag is-light" title="{{ it|escape }}" hx-get="{% url 'rota:worker_edit' rota.id w.id %}?focus=fad" hx-target="#modal" hx-swap="innerHTML">FAD</a>
{% endfor %}
{% for it in w.options.allowed_multi_shift_sets %}
<span class="tag is-light" title="{{ it|escape }}">AMS</span>
<a href="javascript:void(0)" class="tag is-light" title="{{ it|escape }}" hx-get="{% url 'rota:worker_edit' rota.id w.id %}?focus=ams" hx-target="#modal" hx-swap="innerHTML">AMS</a>
{% endfor %}
</span>
</div>
+4 -4
View File
@@ -364,7 +364,7 @@ def worker_edit(request, rota_id, worker_id):
# invalid: return form modal with errors
modal_html = render_to_string(
"rota/partials/worker_form_modal.html",
{"form": form, "form_action": request.get_full_path(), "rota": rota},
{"form": form, "form_action": request.get_full_path(), "rota": rota, "focus": request.GET.get('focus') or request.POST.get('focus')},
request=request,
)
return HttpResponse(modal_html)
@@ -373,7 +373,7 @@ def worker_edit(request, rota_id, worker_id):
form = WorkerForm(instance=worker)
modal_html = render_to_string(
"rota/partials/worker_form_modal.html",
{"form": form, "form_action": request.path, "rota": rota, "worker": worker, "token_link": request.build_absolute_uri(reverse('rota:request_leave_token', args=[worker.request_token])) if worker and getattr(worker, 'request_token', None) else ''},
{"form": form, "form_action": request.path, "rota": rota, "worker": worker, "token_link": request.build_absolute_uri(reverse('rota:request_leave_token', args=[worker.request_token])) if worker and getattr(worker, 'request_token', None) else '', "focus": request.GET.get('focus')},
request=request,
)
return HttpResponse(modal_html)
@@ -1339,7 +1339,7 @@ def shift_add(request, rota_id):
return response
# invalid: return modal with form and errors
modal_html = render_to_string("rota/partials/shift_form_modal.html", {"form": form, "rota": rota, "form_action": request.path}, request=request)
modal_html = render_to_string("rota/partials/shift_form_modal.html", {"form": form, "rota": rota, "form_action": request.path, "focus": request.GET.get('focus') or request.POST.get('focus')}, request=request)
return HttpResponse(modal_html)
else:
@@ -1398,7 +1398,7 @@ def shift_edit(request, rota_id, idx):
"constraints": json.dumps(shift.get("constraints", [])) if shift.get("constraints") is not None else "",
}
form = ShiftForm(initial=initial)
modal_html = render_to_string("rota/partials/shift_form_modal.html", {"form": form, "rota": rota, "form_action": request.path}, request=request)
modal_html = render_to_string("rota/partials/shift_form_modal.html", {"form": form, "rota": rota, "form_action": request.path, "focus": request.GET.get('focus')}, request=request)
return HttpResponse(modal_html)