Add visual indicators for required fields and ARIA attributes in shift forms
This commit is contained in:
@@ -8,3 +8,34 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Mark required fields with a visual asterisk and set ARIA attributes
|
||||||
|
(function(){
|
||||||
|
try{
|
||||||
|
const form = document.currentScript.previousElementSibling;
|
||||||
|
if(!form) return;
|
||||||
|
const requiredEls = form.querySelectorAll('input[required], select[required], textarea[required]');
|
||||||
|
requiredEls.forEach(el=>{
|
||||||
|
el.setAttribute('aria-required','true');
|
||||||
|
// try to find a label: either <label for="id_x"> or a sibling .label
|
||||||
|
const id = el.id;
|
||||||
|
let lbl = id ? form.querySelector('label[for="'+id+'"]') : null;
|
||||||
|
if(!lbl){
|
||||||
|
// look for nearest label element in the same .field
|
||||||
|
const field = el.closest('.field');
|
||||||
|
if(field) lbl = field.querySelector('label');
|
||||||
|
}
|
||||||
|
if(lbl){
|
||||||
|
const already = lbl.querySelector('.required-indicator') || lbl.querySelector('.has-text-danger') || /\*\s*$/.test(lbl.textContent);
|
||||||
|
if(!already){
|
||||||
|
const span = document.createElement('span');
|
||||||
|
span.className = 'required-indicator has-text-danger';
|
||||||
|
span.textContent = ' *';
|
||||||
|
lbl.appendChild(span);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}catch(e){ /* ignore */ }
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|||||||
@@ -60,12 +60,12 @@ function _close_shift_modal_targets(){
|
|||||||
{name: "exclude_days", label: "Exclude days (comma-separated)", type: "list"},
|
{name: "exclude_days", label: "Exclude days (comma-separated)", type: "list"},
|
||||||
{name: "allow_self", label: "Allow self", type: "bool"}
|
{name: "allow_self", label: "Allow self", type: "bool"}
|
||||||
]},
|
]},
|
||||||
"min_summed_grade_by_shifts_per_day": {label: "Min summed grade by shifts per day", fields: [ {name: "shifts", label: "Shifts (comma-separated)", type: "list"}, {name: "min_grade_sum", label: "Minimum grade sum", type: "int"}, {name: "days", label: "Days (comma-separated)", type: "list"} ]},
|
"min_summed_grade_by_shifts_per_day": {label: "Min summed grade by shifts per day", fields: [ {name: "shifts", label: "Shifts (comma-separated)", type: "list", required: true}, {name: "min_grade_sum", label: "Minimum grade sum", type: "int", required: true}, {name: "days", label: "Days (comma-separated)", type: "list", required: true} ]},
|
||||||
"limit_grade_number": {label: "Limit grade number", fields: [ {name: "grade", label: "Grade", type: "int"}, {name: "max_number", label: "Max number", type: "int"} ]},
|
"limit_grade_number": {label: "Limit grade number", fields: [ {name: "grade", label: "Grade", type: "int", required: true}, {name: "max_number", label: "Max number", type: "int", required: true} ]},
|
||||||
"minimum_grade_number": {label: "Minimum grade number", fields: [ {name: "grade", label: "Grade", type: "int"}, {name: "min_number", label: "Min number", type: "int"} ]},
|
"minimum_grade_number": {label: "Minimum grade number", fields: [ {name: "grade", label: "Grade", type: "int", required: true}, {name: "min_number", label: "Min number", type: "int", required: true} ]},
|
||||||
"require_remote_site_presence": {label: "Require remote site presence", fields: [ {name: "site", label: "Site", type: "text"}, {name: "required_number", label: "Required number", type: "int"} ]},
|
"require_remote_site_presence": {label: "Require remote site presence", fields: [ {name: "site", label: "Site", type: "text", required: true}, {name: "required_number", label: "Required number", type: "int", required: true} ]},
|
||||||
"max_shifts_per_week": {label: "Max shifts per week", fields: [ {name: "max_shifts", label: "Max shifts", type: "int"} ]},
|
"max_shifts_per_week": {label: "Max shifts per week", fields: [ {name: "max_shifts", label: "Max shifts", type: "int", required: true} ]},
|
||||||
"max_shifts_per_week_block": {label: "Max shifts per week block", fields: [ {name: "week_block", label: "Week block", type: "int"}, {name: "max_shifts", label: "Max shifts", type: "int"} ]}
|
"max_shifts_per_week_block": {label: "Max shifts per week block", fields: [ {name: "week_block", label: "Week block", type: "int", required: true}, {name: "max_shifts", label: "Max shifts", type: "int", required: true} ]}
|
||||||
};
|
};
|
||||||
|
|
||||||
const textarea = document.getElementById('id_constraints');
|
const textarea = document.getElementById('id_constraints');
|
||||||
@@ -148,6 +148,13 @@ function _close_shift_modal_targets(){
|
|||||||
SCHEMAS[t].fields.forEach(f=>{
|
SCHEMAS[t].fields.forEach(f=>{
|
||||||
const fieldWrap = document.createElement('div'); fieldWrap.className='field';
|
const fieldWrap = document.createElement('div'); fieldWrap.className='field';
|
||||||
const label = document.createElement('label'); label.className='label'; label.textContent = f.label;
|
const label = document.createElement('label'); label.className='label'; label.textContent = f.label;
|
||||||
|
// append required indicator if schema marks this field required
|
||||||
|
if(f.required){
|
||||||
|
if(!label.querySelector('.required-indicator')){
|
||||||
|
const span = document.createElement('span'); span.className='required-indicator has-text-danger'; span.textContent=' *';
|
||||||
|
label.appendChild(span);
|
||||||
|
}
|
||||||
|
}
|
||||||
fieldWrap.appendChild(label);
|
fieldWrap.appendChild(label);
|
||||||
const control = document.createElement('div'); control.className='control';
|
const control = document.createElement('div'); control.className='control';
|
||||||
let input;
|
let input;
|
||||||
@@ -168,6 +175,7 @@ function _close_shift_modal_targets(){
|
|||||||
cb.checked = !!curb;
|
cb.checked = !!curb;
|
||||||
cb.setAttribute('data-field-name', f.name);
|
cb.setAttribute('data-field-name', f.name);
|
||||||
cb.onchange = syncToData;
|
cb.onchange = syncToData;
|
||||||
|
if(f.required){ cb.required = true; cb.setAttribute('aria-required','true'); }
|
||||||
control.appendChild(cb);
|
control.appendChild(cb);
|
||||||
fieldWrap.appendChild(control);
|
fieldWrap.appendChild(control);
|
||||||
fieldsContainer.appendChild(fieldWrap);
|
fieldsContainer.appendChild(fieldWrap);
|
||||||
@@ -181,6 +189,7 @@ function _close_shift_modal_targets(){
|
|||||||
input = document.createElement('input'); input.className='input'; input.type='text'; input.oninput = syncToData;
|
input = document.createElement('input'); input.className='input'; input.type='text'; input.oninput = syncToData;
|
||||||
}
|
}
|
||||||
input.setAttribute('data-field-name', f.name);
|
input.setAttribute('data-field-name', f.name);
|
||||||
|
if(f.required){ input.required = true; input.setAttribute('aria-required','true'); }
|
||||||
control.appendChild(input);
|
control.appendChild(input);
|
||||||
fieldWrap.appendChild(control);
|
fieldWrap.appendChild(control);
|
||||||
fieldsContainer.appendChild(fieldWrap);
|
fieldsContainer.appendChild(fieldWrap);
|
||||||
@@ -247,6 +256,34 @@ function _close_shift_modal_targets(){
|
|||||||
const form = textarea.closest('form'); if(form){ form.addEventListener('submit', ()=>{ /* already synced via inputs */ }); }
|
const form = textarea.closest('form'); if(form){ form.addEventListener('submit', ()=>{ /* already synced via inputs */ }); }
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
// Mark required fields in the modal shift form with a visible asterisk and add ARIA attributes
|
||||||
|
(function(){
|
||||||
|
try{
|
||||||
|
const modalForm = document.querySelector('#shift-modal form');
|
||||||
|
if(!modalForm) return;
|
||||||
|
const requiredEls = modalForm.querySelectorAll('input[required], select[required], textarea[required]');
|
||||||
|
requiredEls.forEach(el=>{
|
||||||
|
el.setAttribute('aria-required','true');
|
||||||
|
const id = el.id;
|
||||||
|
let lbl = id ? modalForm.querySelector('label[for="'+id+'"]') : null;
|
||||||
|
if(!lbl){
|
||||||
|
const field = el.closest('.field');
|
||||||
|
if(field) lbl = field.querySelector('label');
|
||||||
|
}
|
||||||
|
if(lbl){
|
||||||
|
const already = lbl.querySelector('.required-indicator') || lbl.querySelector('.has-text-danger') || /\*\s*$/.test(lbl.textContent);
|
||||||
|
if(!already){
|
||||||
|
const span = document.createElement('span');
|
||||||
|
span.className = 'required-indicator has-text-danger';
|
||||||
|
span.textContent = ' *';
|
||||||
|
lbl.appendChild(span);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}catch(e){ /* ignore */ }
|
||||||
|
})();
|
||||||
</script>
|
</script>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="control">
|
<div class="control">
|
||||||
|
|||||||
Reference in New Issue
Block a user