// =========================
// CustomBaseValidator Class
// =========================
class CustomBaseValidator {
static allCustomValidators = {}; // All custom validators
constructor(targetErrorDisplayLocation, messageText, evaluationFunction, includeLabel = true) {
if (!targetErrorDisplayLocation)
throw new Error("targetErrorDisplayLocation is required");
// Flag for including field label in error
this.includeLabel = includeLabel;
// Resolve target element
if (targetErrorDisplayLocation instanceof HTMLElement) {
this.targetErrorDisplayEl = targetErrorDisplayLocation;
} else if (typeof targetErrorDisplayLocation === "string") {
this.targetErrorDisplayEl = document.getElementById(targetErrorDisplayLocation);
} else {
throw new Error("targetErrorDisplayLocation must be an HTMLElement or a string ID");
}
if (!this.targetErrorDisplayEl)
throw new Error("Element not found in DOM");
this.messageText = messageText || "";
// Use provided evaluation function.
if (evaluationFunction && typeof evaluationFunction === "function") {
this.evaluationFunction = evaluationFunction;
}
// Create error display element.
this.element = document.createElement("span");
this.element.id = `${this.constructor.name}_${this.targetErrorDisplayEl.id}_validator`;
this.element.style.display = "none";
this.element.errormessage = `${this.messageText}`;
this.element.evaluationfunction = this.evaluationFunction.bind(this);
// Store this instance.
CustomBaseValidator.allCustomValidators[this.element.id] = this;
// Register with the underlying system.
if (typeof Page_Validators !== "undefined") {
Page_Validators.push(this.element);
Page_Validators.sort((a, b) => {
if (a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING)
return -1;
if (a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_PRECEDING)
return 1;
return 0;
});
}
}
evaluationFunction() {
throw new Error("evaluationFunction must be overridden");
}
updateMessage(newMessage) {
if (newMessage !== undefined) this.messageText = newMessage;
const content = this.includeLabel && this.targetErrorDisplayEl.innerText
? `${this.targetErrorDisplayEl.innerText} ${this.messageText}`
: this.messageText;
this.element.errormessage = `${content}`;
}
}
// ==========================
// CustomFieldValidator Class
// ==========================
class CustomFieldValidator extends CustomBaseValidator {
static childParentMapping = {};
constructor({
controlToValidate,
messageText = "",
id = "",
evaluationFunction,
targetErrorDisplayLocation,
includeLabel = true
} = {}) {
if (!controlToValidate) throw new Error("controlToValidate is required.");
// Resolve the control element.
const control = controlToValidate instanceof HTMLElement ? controlToValidate : document.getElementById(controlToValidate);
if (!control) throw new Error("Control not found in DOM.");
const cell = control.closest('.cell');
const info = cell.querySelector('.info, .table-info');
// Get the control's label element.
const fieldLabelEL = info.querySelector('label');
if (!fieldLabelEL) throw new Error("Label for field not found.");
// Ensure consistent validation-field-label class
fieldLabelEL.classList.add('validation-field-label');
// Get the validators container from the control.
let validatorsContainer = info.querySelector('.validators');
if (!validatorsContainer) {
validatorsContainer = document.createElement("div");
validatorsContainer.className = "validators";
fieldLabelEL.insertAdjacentElement('beforebegin', validatorsContainer);
}
// Determine display target.
const displayTarget = targetErrorDisplayLocation || fieldLabelEL;
// Initialize parent with includeLabel flag.
super(displayTarget, "", evaluationFunction, includeLabel);
if (Object.keys(CustomFieldValidator.childParentMapping).includes(controlToValidate.id)) {
let parentValidation = CustomFieldValidator.childParentMapping[controlToValidate.id];
parentValidation.subValidations.push(this);
}
this.fieldLabel = fieldLabelEL;
this.validatorsContainer = validatorsContainer;
this.validatorsContainer.appendChild(this.element);
this.subValidations = [];
this.element.id = `${this.constructor.name}_${control.id}_validator`;
this.control = control;
if (messageText) this.customError = true;
this.updateMessage(messageText);
}
updateMessage(newMessage) {
if (newMessage !== undefined) this.messageText = newMessage;
const labelPart = this.includeLabel ? `${this.getControlLabelText()} `: '';
const content = `${labelPart}${this.messageText}`;
this.element.errormessage = `${content}`;
}
getChildrenValidationElements() {}
static markControlForSubValidation(childControl, parentValidation) {
CustomFieldValidator.childParentMapping[childControl.id] = parentValidation;
}
getControlLabelText() {
const fallbackLabel = {
'en': 'This',
'fr': 'Ceci'
}
if (this.control?.label) {
const firstChild = this.control.label.childNodes[0];
// Check if the first child is a text node (nodeType 3) and has a value
if (firstChild && firstChild.nodeType === 3) {
return firstChild.nodeValue.trim();
}
}
return fallbackLabel[LANG];
}
}
// ===================================
// DynamicFieldRequiredValidator Class
// ===================================
class DynamicFieldRequiredValidator extends CustomFieldValidator {
constructor({ controlToValidate, messageText = "", id = "", evaluationFunction, includeLabel = true } = {}) {
const defaultMessage =
{
'en': "is a required field",
'fr': "est obligatoire."
}
if (messageText === "") messageText = defaultMessage[LANG];
super({ controlToValidate, messageText, id, evaluationFunction });
// Optionally add a 'required' class.
if (this.control?.label != null) {
this.control.label.required = true;
}
}
evaluationFunction() {
return this.hasContent();
}
hasContent() {
if (this.control.hasOwnProperty('boolvalue')) {
return this.control.boolvalue != null;
} else if ('value' in this.control) {
return this.control.value !== "";
} else if (this.control.classList.contains('subgrid')){
return true;
} else {
throw new Error("hasContent not detected");
return false;
}
}
}
class EmailMatchValidator extends CustomFieldValidator {
constructor({ controlToValidate, controlToCompare, virtualFieldLocation, messageText, includeLabel= true }) {
if (!controlToValidate) throw new Error("controlToValidate is required.");
if ((controlToCompare && virtualFieldLocation) || (!controlToCompare && !virtualFieldLocation)) {
throw new Error("Either controlToCompare or virtualFieldLocation must be set, but not both.");
}
const control = controlToValidate instanceof HTMLElement ? controlToValidate : document.getElementById(controlToValidate);
if (!control) throw new Error("Control not found in DOM.");
controlToCompare = controlToCompare instanceof HTMLElement ? controlToCompare : document.getElementById(controlToCompare);
if (!messageText) {
const defaultMessage =
{
'en': "The email addresses entered do not match.",
'fr': "Les adresses de courrier électronique saisies ne correspondent pas."
}
messageText = defaultMessage[LANG];
}
if (controlToCompare) {
super({controlToValidate: controlToCompare, messageText: messageText, id: "verifyEmailValidator", includeLabel: includeLabel});
this.control = controlToValidate;
this.compareControl = controlToCompare;
} else {
let {input: controlToCompare, validationLocation: cc_info} = EmailMatchValidator.generateVirtualizedField(control, virtualFieldLocation);
super({ controlToValidate: controlToCompare, messageText: messageText, targetErrorDisplayLocation: cc_info, id: "verifyEmailValidator", includeLabel: includeLabel});
this.control = controlToValidate;
this.compareControl = controlToCompare;
}
}
evaluationFunction() {
return this.control.value === this.compareControl.value;
}
static generateVirtualizedField(control, virtualFieldLocation) {
let parentCell = control.closest(".cell");
let vf_isCell = virtualFieldLocation?.classList.contains('cell');
let newCell = (vf_isCell) ? virtualFieldLocation : document.createElement("td");
newCell.className = "clearfix cell text form-control-cell";
newCell.setAttribute("data-inner-field-id", "emailaddress_virtual");
newCell.setAttribute("maxchar", "100");
newCell.setAttribute("maxwords", "19");
newCell.innerHTML = `
*
Re-enter your email address.
0 / 100
`;
if (!vf_isCell) {
parentCell.insertAdjacentElement("afterend", newCell);
}
let input = newCell.querySelector("input");
initField(input)
let validationLocation = newCell.querySelector("#verify_email_label");
return {input, validationLocation};
}
}
// ===================================
// LinkedBooleanRequiredValidator Class (Parent)
// ===================================
class LinkedBooleanRequiredValidator extends DynamicFieldRequiredValidator {
static isLinkedParent = true;
constructor({ controlToValidate, linkedBooleanControl, validCondition = true, messageText = "", requiredOnlyIfParentRequired = true,} = {}) {
super({ controlToValidate, messageText });
this.bool_control = linkedBooleanControl;
this.valid_condition = validCondition;
this.requiredOnlyIfParentRequired = requiredOnlyIfParentRequired;
CustomFieldValidator.markControlForSubValidation(controlToValidate, this);
// Attach listener to linkedBooleanControl
this.bool_control.addEventListener('change', () => {
this.syncRequiredLabel();
});
// Initial sync
this.syncRequiredLabel();
}
evaluationFunction() {
const boolRequired = this.bool_control.info?.classList?.contains("required");
const boolNull = this.bool_control.boolvalue === null;
const parentControlValid = (this.bool_control.boolvalue == this.valid_condition);
if (boolNull || !parentControlValid) {
this.subValidations.forEach((val) => { val.element.enabled = false });
} else {
this.subValidations.forEach((val) => { val.element.enabled = true });
}
if (boolNull || !boolRequired || !parentControlValid) return true;
return this.hasContent();
}
syncRequiredLabel() {
const label = this.control?.label;
if (!label) return;
const parentBoolRequired = this.bool_control.label.required;
const boolNull = this.bool_control.boolvalue === null;
const parentControlValid = (this.bool_control.boolvalue == this.valid_condition);
const shouldBeRequired = (!this.requiredOnlyIfParentRequired || parentBoolRequired) && parentControlValid && !boolNull;
label.required = shouldBeRequired;
}
}
// ===================================
// LinkedSelectRequiredValidator Class (Parent)
// ===================================
class LinkedSelectRequiredValidator extends DynamicFieldRequiredValidator {
static isLinkedParent = true;
constructor({
controlToValidate,
linkedSelectControl,
validValues = [], // array of values that trigger requirement
messageText = "",
requiredOnlyIfParentRequired = true,
invertLogic = false
} = {}) {
super({ controlToValidate, messageText });
this.select_control = linkedSelectControl;
this.valid_values = validValues;
this.requiredOnlyIfParentRequired = requiredOnlyIfParentRequired;
this.invertLogic = invertLogic;
CustomFieldValidator.markControlForSubValidation(controlToValidate, this);
// Attach listener to linkedSelectControl
this.select_control.addEventListener("change", () => {
this.syncRequiredLabel();
});
// Initial sync
this.syncRequiredLabel();
}
evaluationFunction() {
const selectRequired = this.select_control.info?.classList?.contains("required");
const selectValue = this.select_control.value;
let parentControlValid = (this.invertLogic) ? !this.valid_values.includes(selectValue) : this.valid_values.includes(selectValue);
if (!parentControlValid) {
this.subValidations.forEach((val) => {
val.element.enabled = false;
});
} else {
this.subValidations.forEach((val) => {
val.element.enabled = true;
});
}
if (!selectRequired || !parentControlValid) return true;
return this.hasContent();
}
syncRequiredLabel() {
const label = this.control?.label;
if (!label) return;
const parentSelectRequired = this.select_control.label.required;
const selectValue = this.select_control.value;
let parentControlValid = (this.invertLogic) ? !this.valid_values.includes(selectValue) : this.valid_values.includes(selectValue);
const shouldBeRequired =
(!this.requiredOnlyIfParentRequired || parentSelectRequired) &&
parentControlValid;
label.required = shouldBeRequired;
}
}
// ===================================
// AtLeastOneValidator Class (Child)
// ===================================
class AtLeastOneValidator extends CustomFieldValidator {
constructor({
controlToValidate,
controlToCompare,
messageText = ""
} = {}) {
super({
controlToValidate,
messageText,
includeLabel: false
});
this.controlToCompare = controlToCompare instanceof HTMLElement
? controlToCompare
: document.getElementById(controlToCompare);
if (!this.controlToCompare) {
// Throw the error *here* if the control is missing
throw new Error(`Control to compare not found in DOM for ID: ${controlToCompare}`);
}
if (messageText === "") {
// const label1 = this.getControlLabelText(this.control.id);
// const label2 = this.getControlLabelText(controlToValidate);
const label1 = this.getControlLabelText(this.controlToCompare.id);
const label2 = this.getControlLabelText(this.control.id);
const enMsg = `You must provide a value for either ${label1} or ${label2}.`;
const frMsg = `Vous devez fournir une valeur soit pour ${label1} ou ${label2}.`;
messageText = LANG === 'fr' ? frMsg : enMsg;
this.updateMessage (messageText);
}
this.element.evaluationfunction = this.evaluationFunction.bind(this);
}
// Override the base class's evaluation function
evaluationFunction() {
// Get values of Field 1 and Field 2, trimming whitespace
const value1 = this.controlToCompare.value.trim();
const value2 = this.control.value.trim();
// It fails if BOTH are empty.
return value1 !== "" || value2 !== "";
}
}
// =========================
// SubgridValidator Class (Child)
// =========================
class SubgridValidator extends CustomFieldValidator {
constructor({
subgridElement,
rowEvaluationFunction,
minValidRows = 1,
maxValidRows = Infinity,
aggregator,
messageText = "",
markSectionInvalid = true,
useMissingList = false,
rowNameSelector = '[data-attribute^="gac_name"]'
} = {}) {
if (!subgridElement) throw new Error("subgridElement is required.");
const subgridEl = subgridElement instanceof HTMLElement ? subgridElement : document.getElementById(subgridElement);
if (!subgridEl) throw new Error("Subgrid element not found in DOM.");
// Generate markup.
const { infoEl, labelEl, validatorsContainer } = SubgridValidator.generateMissingMarkup(subgridEl);
// Default aggregator: count valid rows.
if (typeof aggregator !== "function") {
aggregator = (results, min, max) => {
const count = results.filter(Boolean).length;
return count >= min && count <= max;
};
}
// Default row evaluation.
if (typeof rowEvaluationFunction !== "function") {
rowEvaluationFunction = row => true;
}
super({
controlToValidate: subgridElement,
messageText: messageText
});
validatorsContainer.appendChild(this.element);
this.element.id = `SubgridValidator_${subgridEl.id}_validator`;
this.isFirstRun = true; // Instance-level flag for each subgrid
this.rowEvaluationFunction = rowEvaluationFunction;
this.minValidRows = minValidRows;
this.maxValidRows = maxValidRows;
this.aggregator = aggregator;
this.useMissingList = useMissingList;
this.rowNameSelector = rowNameSelector;
this.missingItems = [];
if (minValidRows > 0) {
infoEl.classList.add("required");
}
this.element.evaluationfunction = this.evaluationFunction.bind(this);
this.markSectionInvalid = markSectionInvalid;
this.element.evaluationfunction = this.evaluationFunction.bind(this);
// Setup the subgrid loading handler (runs every refresh of a subgrid);
$(subgridEl.firstChild).on("loaded", ()=>{ SubgridValidator.subgridReloadHandler(this);});
}
static async subgridReloadHandler(validatorInstance) {
// Check if it's the first run (don't excute on page load, only after a change is made in the subgrid)
if (validatorInstance.isFirstRun) {
validatorInstance.isFirstRun = false; // Set the flag to false after the first run
return; // Skip the validation for the first run
}
if (validatorInstance.markSectionInvalid && FormSteps.thisStepValid) {
if (!validatorInstance.element.evaluationfunction()) {
FormSteps.markStepIncompleteAsync();
}
}
}
static generateMissingMarkup(subgridEl) {
const CELL = subgridEl.closest(".subgrid-cell");
let infoEl = CELL.querySelector(".info, .table-info");
if (!infoEl) {
infoEl = document.createElement("h3");
infoEl.className = "info form-subgrid-heading";
CELL.prepend(infoEl);
}
let labelEl = infoEl.querySelector("label");
if (!labelEl) {
labelEl = document.createElement("label");
labelEl.className = "field-label";
labelEl.style.display = "block";
if (CELL.id) {
labelEl.setAttribute("for", CELL.id);
labelEl.id = `${CELL.id}_label`;
}
labelEl.innerText = CELL.getAttribute("data-label") || "Subgrid";
infoEl.appendChild(labelEl);
}
let validatorsContainer = infoEl.querySelector(".validators");
if (!validatorsContainer) {
validatorsContainer = document.createElement("div");
validatorsContainer.className = "validators";
infoEl.appendChild(validatorsContainer);
}
return { infoEl, labelEl, validatorsContainer };
}
evaluationFunction() {
const rows = Array.from(
this.control.querySelectorAll("table tbody > tr[data-entity]")
);
this.missingItems = [];
const results = rows.map((row) => {
const isValidRow = this.rowEvaluationFunction(row);
row.dataset.valid = isValidRow;
if (!isValidRow && this.useMissingList) {
const nameCell = row.querySelector(this.rowNameSelector);
if (nameCell) {
const nameText = nameCell.getAttribute("data-value") || nameCell.innerText.trim();
this.missingItems.push(nameText);
}
}
return isValidRow;
});
const validCount = results.filter(Boolean).length;
let failCondition = "";
if (this.minValidRows > 0 && rows.length === 0) {
failCondition = "noRows";
} else if (this.minValidRows == 'all' && validCount != rows.length) {
failCondition = "min"; //Note requires a rowEvaluationFunction
} else if (validCount < this.minValidRows) {
failCondition = "min";
} else if (validCount > this.maxValidRows) {
failCondition = "max";
}
if (!this.customError) {
const errorMessage = this.generateMessage(failCondition, validCount, rows.length);
this.updateMessage(errorMessage);
}
return failCondition === "";
}
generateMessage(failCondition, validCount, totalRows) {
if (this.useMissingList && this.missingItems.length > 0) {
const header = LANG === 'fr'
? "Les documents suivants sont manquants et doivent être téléchargés dans votre demande :"
: "The following documents are missing and need to be uploaded to your application:";
const listItems = this.missingItems.map(item => `
${item}
`).join('');
return `
${header}
${listItems}
`;
}
let message = "";
if (LANG === 'fr') {
switch (failCondition) {
case "noRows":
message = `Au moins ${this.minValidRows} enregistrement(s) requis, mais aucun trouvé.`;
break;
case "min":
message = `${validCount} enregistrement(s) valides trouvés; le minimum requis est ${this.minValidRows}.`;
break;
case "max":
message = `Trop d'enregistrements valides: ${validCount} trouvés; le maximum autorisé est ${this.maxValidRows}.`;
break;
}
} else {
switch (failCondition) {
case "noRows":
message = `At least ${this.minValidRows} record(s) required, but none were found.`;
break;
case "min":
message = `${validCount} valid record(s) found; minimum required is ${this.minValidRows}.`;
break;
case "max":
message = `Too many valid records: ${validCount} found; maximum allowed is ${this.maxValidRows}.`;
break;
}
}
return message;
}
getValidRows() {
const rows = Array.from(
this.subgridEl.querySelectorAll("table tbody > tr[data-entity]")
);
return rows.filter(this.rowEvaluationFunction);
}
getInvalidRows() {
const rows = Array.from(
this.subgridEl.querySelectorAll("table tbody > tr[data-entity]")
);
return rows.filter(row => !this.rowEvaluationFunction(row));
}
}
// ===================================
// PostalZipValidator Class
// ===================================
class PostalZipValidator extends CustomFieldValidator {
constructor({ controlToValidate, countryControl, id = "" } = {}) {
const defaultMessage =
{
'en': "Please enter a valid Postal or ZIP code format.",
'fr': "Veuillez saisir un code postal ou ZIP valide."
}
super({ controlToValidate, messageText: defaultMessage[LANG], id, includeLabel: false });
this.countryControl = countryControl;
// Optionally add a 'required' class.
this.control.label.required = true;
}
evaluationFunction() {
function validateUSZip(zip) {
const zipRegex = /^\d{5}(-\d{4})?$/;
return zipRegex.test(zip);
}
function validateCanadianPostal(postal) {
const postalRegex = /^[A-Za-z]\d[A-Za-z][ ]?\d[A-Za-z]\d$/;
return postalRegex.test(postal);
}
if (this.countryControl.value == 'd800ecae-fd66-ef11-a670-000d3af45d64'){
return validateCanadianPostal(this.control.value);
}
if (this.countryControl.value == 'c200ecae-fd66-ef11-a670-000d3af45d64'){
return validateUSZip(this.control.value);
}
return true;//All other countries
}
}
class DateAfterTodayValidator extends CustomFieldValidator {
constructor({ controlToValidate, messageText = "" } = {}) {
super({
controlToValidate,
messageText: messageText || (
LANG === "fr"
? "ne peut pas être antérieure à aujourd’hui."
: "cannot be before today."
)
});
// wire up our check
this.element.evaluationfunction = this.evaluationFunction.bind(this);
}
evaluationFunction() {
const raw = this.control.value;
if (!raw) return true;
const picked = new Date(raw);
const today = new Date(new Date().toDateString());
return !isNaN(picked) && picked >= today;
}
}
class DateBeforeEqualTodayValidator extends CustomFieldValidator {
constructor({ controlToValidate, messageText = "" } = {}) {
super({
controlToValidate,
messageText: messageText || (
LANG === "fr"
? "ne peut pas être postérieure à aujourd’hui."
: "cannot be after today."
)
});
// wire up our check
this.element.evaluationfunction = this.evaluationFunction.bind(this);
}
evaluationFunction() {
const raw = this.control.value;
if (!raw) return true;
const picked = new Date(raw);
const today = new Date(new Date().toDateString());
return !isNaN(picked) && picked <= today;
}
}
class DateRangeFieldValidator extends CustomFieldValidator {
constructor({
startDateControl,
endDateControl,
maxRange = Infinity, // Maximum duration after start
minRange = new Duration({ days: 0 }), // Minimum duration after start
minStart = null, // Earliest allowed start date (String of ISO date, or Date object or "today")
maxEnd = null, // Latest allowed end date (String of ISO date, or Date object)
maxFiscalYears = Infinity, // Max number of fiscal years the range can span
messageText = "",
id = "",
lang = LANG,
targetErrorDisplayLocation = null
} = {}) {
const displayTarget = targetErrorDisplayLocation || endDateControl;
super({
controlToValidate: endDateControl,
messageText: "",
id,
evaluationFunction: undefined,
targetErrorDisplayLocation: displayTarget
});
this.startDateControl = startDateControl;
this.endDateControl = endDateControl;
this.maxRange = maxRange;
this.minRange = minRange;
this.minStartRaw = minStart; // store raw input (e.g. "today")
this.maxEndRaw = maxEnd; // store raw input in case we want same treatment
this.minStartIsToday = (minStart === "today"); // keep this flag for messaging
this.maxFiscalYears = maxFiscalYears;
this.lang = lang;
this.element.isvalid = true;
this.updateMessage(this.generateMessage());
}
normalizeDateInput(input) {
if (input === "today") {
return new Date(new Date().toDateString()); // today at midnight
}
if (input instanceof Date) return input;
if (typeof input === "string" || typeof input === "number") return new Date(input);
return null;
}
generateMessage(failCondition) {
const locale = this.lang === "fr" ? "fr-CA" : "en-CA";
const shortLang = this.lang || "en";
const options = { year: "numeric", month: "short", day: "numeric" };
const messages = {
recordCount: {
en: {
noRows: min => `At least ${min} record(s) required, but none were found.`,
min: (valid, min) => `${valid} valid record(s) found; minimum required is ${min}.`,
max: (valid, max) => `Too many valid records: ${valid} found; maximum allowed is ${max}.`,
},
fr: {
noRows: min => `Au moins ${min} enregistrement(s) requis, mais aucun trouvé.`,
min: (valid, min) => `${valid} enregistrement(s) valides trouvés; le minimum requis est ${min}.`,
max: (valid, max) => `Trop d'enregistrements valides: ${valid} trouvés; le maximum autorisé est ${max}.`,
},
},
dateRange: {
en: {
endDate: "must be after the start date.",
minRange: min => `Minimum range is ${min}.`,
maxRange: max => `Maximum range is ${max === Infinity ? "∞" : max}.`,
bothRange: (min, max) => `Date must be between ${min} and ${max}.`,
minStartToday: "Start date cannot be before today.",
minStart: date => `Start date cannot be before ${date}.`,
maxEnd: date => `End date cannot be after ${date}.`,
fiscal: (max, html) => ` spans more than ${max} fiscal years: ${html}`,
},
fr: {
endDate: "doit être postérieure à la date de début.",
minRange: min => `La plage minimale est de ${min}.`,
maxRange: max => `La plage maximale est de ${max === Infinity ? "∞" : max}.`,
bothRange: (min, max) => `La date doit être comprise entre ${min} et ${max}.`,
minStartToday: "La date de début ne peut pas être antérieure à aujourd’hui.",
minStart: date => `La date de début ne peut pas être antérieure au ${date}.`,
maxEnd: date => `La date de fin ne peut pas être postérieure au ${date}.`,
fiscal: (max, html) => ` s'étend sur plus de ${max} années fiscales : ${html}`,
},
},
};
const t = {
count: messages.recordCount[shortLang],
date: messages.dateRange[shortLang]
};
switch (failCondition) {
case "noRows":
return t.count.noRows(this.minValidRows);
case "min":
return t.count.min(this.validCount, this.minValidRows);
case "max":
return t.count.max(this.validCount, this.maxValidRows);
case "endDate":
return t.date.endDate;
case "minRange":
return t.date.minRange(this.minRange.toString(locale));
case "maxRange":
return t.date.maxRange(this.maxRange.toString(locale));
case "bothRange":
return t.date.bothRange(this.minRange.toString(locale), this.maxRange.toString(locale));
case "minStartToday":
return t.date.minStartToday;
case "minStart":
return t.date.minStart(this.normalizeDateInput(this.minStartRaw).toLocaleDateString(locale));
case "maxEnd":
return t.date.maxEnd(this.maxEnd.toLocaleDateString(locale));
case "fiscal": {
const start = new Date(this.startDateControl.value);
const end = new Date(this.endDateControl.value);
const fiscalYearFor = d => d.getMonth() < 3 ? d.getFullYear() - 1 : d.getFullYear();
const startFY = fiscalYearFor(start);
const endFY = fiscalYearFor(end);
const fiscalYearsSpanned = [];
for (let year = startFY; year <= endFY; year++) {
const from = new Date(year, 3, 1);
const to = new Date(year + 1, 2, 31);
const label = `${from.toLocaleDateString(locale, options)} – ${to.toLocaleDateString(locale, options)}`;
fiscalYearsSpanned.push(`