Enhance focus handling in modals to improve user experience by scrolling to and focusing on relevant fields
This commit is contained in:
@@ -29,6 +29,31 @@ function _close_shift_modal_targets(){
|
|||||||
</div>
|
</div>
|
||||||
</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>
|
<script>
|
||||||
(function(){
|
(function(){
|
||||||
// Constraint type schemas: name -> fields metadata
|
// Constraint type schemas: name -> fields metadata
|
||||||
|
|||||||
@@ -9,8 +9,8 @@
|
|||||||
<div style="margin-top:0.25rem;">
|
<div style="margin-top:0.25rem;">
|
||||||
<span class="tags">
|
<span class="tags">
|
||||||
{% for c in s.constraints %}
|
{% for c in s.constraints %}
|
||||||
{# Build a simple tooltip showing key: value pairs for the constraint on hover #}
|
{# Build a simple tooltip showing key: value pairs for the constraint on hover; make clickable to open constraint editor #}
|
||||||
<span class="tag is-light" title="{% for k,v in c.items %}{{ k }}: {{ v }}{% if not forloop.last %}; {% endif %}{% endfor %}">{{ c.name }}</span>
|
<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 %}
|
{% endfor %}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -30,6 +30,37 @@
|
|||||||
</div>
|
</div>
|
||||||
</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>
|
<script>
|
||||||
(function(){
|
(function(){
|
||||||
const textarea = document.getElementById('id_nwds');
|
const textarea = document.getElementById('id_nwds');
|
||||||
|
|||||||
@@ -20,19 +20,19 @@
|
|||||||
<div style="margin-top:0.25rem;">
|
<div style="margin-top:0.25rem;">
|
||||||
<span class="tags">
|
<span class="tags">
|
||||||
{% for it in w.options.hard_day_exclusions %}
|
{% 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 %}
|
{% endfor %}
|
||||||
{% for it in w.options.hard_day_dependencies %}
|
{% 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 %}
|
{% endfor %}
|
||||||
{% for it in w.options.forced_assignments %}
|
{% 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 %}
|
{% endfor %}
|
||||||
{% for it in w.options.forced_assignments_by_date %}
|
{% 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 %}
|
{% endfor %}
|
||||||
{% for it in w.options.allowed_multi_shift_sets %}
|
{% 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 %}
|
{% endfor %}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -364,7 +364,7 @@ def worker_edit(request, rota_id, worker_id):
|
|||||||
# invalid: return form modal with errors
|
# invalid: return form modal with errors
|
||||||
modal_html = render_to_string(
|
modal_html = render_to_string(
|
||||||
"rota/partials/worker_form_modal.html",
|
"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,
|
request=request,
|
||||||
)
|
)
|
||||||
return HttpResponse(modal_html)
|
return HttpResponse(modal_html)
|
||||||
@@ -373,7 +373,7 @@ def worker_edit(request, rota_id, worker_id):
|
|||||||
form = WorkerForm(instance=worker)
|
form = WorkerForm(instance=worker)
|
||||||
modal_html = render_to_string(
|
modal_html = render_to_string(
|
||||||
"rota/partials/worker_form_modal.html",
|
"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,
|
request=request,
|
||||||
)
|
)
|
||||||
return HttpResponse(modal_html)
|
return HttpResponse(modal_html)
|
||||||
@@ -1339,7 +1339,7 @@ def shift_add(request, rota_id):
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
# invalid: return modal with form and errors
|
# 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)
|
return HttpResponse(modal_html)
|
||||||
|
|
||||||
else:
|
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 "",
|
"constraints": json.dumps(shift.get("constraints", [])) if shift.get("constraints") is not None else "",
|
||||||
}
|
}
|
||||||
form = ShiftForm(initial=initial)
|
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)
|
return HttpResponse(modal_html)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user