Skip to main content

Campaign Ordering/Editing Posting Requirements Changes

We have received feedback from ATSs regarding currency input validations in posting requirements form for contracts.


For some posting requirements, HAPI Backend has float rule and HAPI Backend Job Post Implementation Guide suggests this type of validation for such fields should be: float - numbers with optional comma (,) as a thousand separator and optional period (.) as a decimal point however HAPI Elements was preventing the comma from being entered. Some examples for this case are Minimum salary (not displayed on ad) and Maximum salary (not displayed on ad) fields for Seek Australia.


With changes introduced in version 3.1, user can now enter commas. Because there are numerous ways a user can enter commas, depending on their locale, HAPI Elements converts the output to what HAPI Backend expects. We do this conversion via an overlay 1.5 second(s) after user stopped typing via a timeout.


The overlay looks like: Campaign Form Posting Requirements Input Auto Correct Overlay


We also fix mistakes that a user can make. List of valid inputs:

  • 1.00
  • 10.50
  • 100.00
  • 1,000.00
  • 10,000.25
  • 100,000.00
  • 1,000,000.00
  • 10,000,000.00
  • 100,000,000.00
  • 1,000,000,000.00
  • 5,000,000,000.50

List of invalid inputs that are corrected:

  • 1,00 comma replaced with dot
  • 1,000,00 last comma replaced with dot
  • 100,000,000,000,00 last comma replaced with dot
  • 10,000.000 last zero removed for 2 fraction digits
  • 1.000.000 first dot replaced with comma
  • 1,000,000.000 last zero removed for 2 fraction digits

Here is how the function works:

export const getOccurrenceOf = (str: string | undefined, char: string) => {
if (!str) {
return 0
}
const regex = new RegExp(`\\${char}`, "g") // Escape special characters
return (str.match(regex) || []).length
}

export const replaceFirstOccurrence = (
str: string | undefined,
targetChar: string,
replacementChar: string,
) => {
if (!str) {
return ""
}
const escapedChar = targetChar.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&") // Escape special regex chars
return str.replace(new RegExp(escapedChar), replacementChar)
}

export const replaceLastOccurrence = (
str: string | undefined,
targetChar: string,
replacementChar: string,
) => {
if (!str) {
return ""
}
const escapedChar = targetChar.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&") // Escape special regex chars
const lastIndex = str.lastIndexOf(escapedChar)
if (lastIndex === -1) return str // If char not found, return original string
return str.slice(0, lastIndex) + replacementChar + str.slice(lastIndex + 1)
}

export const getStringifiedNumberToFloatProperties = (
num: string | undefined,
) => {
const dotCount = getOccurrenceOf(num, ".")
const commaCount = getOccurrenceOf(num, ",")
const containsDots = dotCount > 0
const containsCommas = commaCount > 0

return { dotCount, commaCount, containsDots, containsCommas }
}

export const convertStringifiedNumberToFloat = (
num: string | undefined,
isRequired: boolean,
) => {
const defaultFloat = isRequired ? "0.00" : ""
if (!num || num.trim() === "") {
num = defaultFloat
}
const {
commaCount: commaCountForFixingWronglyFormatted,
dotCount: dotCountForFixingWronglyFormatted,
containsDots: containsDotsForFixingWronglyFormatted,
containsCommas: containsCommasForFixingWronglyFormatted,
} = getStringifiedNumberToFloatProperties(num)

// first fix wrongly formatted num
if (
commaCountForFixingWronglyFormatted > 1 &&
!containsDotsForFixingWronglyFormatted
) {
num = replaceLastOccurrence(num, ",", ".")
} else if (
dotCountForFixingWronglyFormatted > 1 &&
!containsCommasForFixingWronglyFormatted
) {
num = replaceFirstOccurrence(num, ".", ",")
}

const {
containsDots: containsDotsForIntlFix,
containsCommas: containsCommasForIntlFix,
} = getStringifiedNumberToFloatProperties(num)

if (containsCommasForIntlFix && containsDotsForIntlFix) {
num = num.replace(/,/g, "")
} else if (!containsDotsForIntlFix && containsCommasForIntlFix) {
num = num.replace(/,/g, ".")
}

const numberCastedString = Number(num)

if (num.trim() === "" || isNaN(numberCastedString)) {
return defaultFloat
}

const intlFormatted = new Intl.NumberFormat("en-US", {
style: "decimal",
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}).format(numberCastedString)

return intlFormatted
}

As part of this change, the following translations were added:

Key"en" value"de" value"nl" value"fr" value
common.field-should-be-floatThis field can only contain numbers, dots and commas.Dieses Feld darf nur Zahlen, Punkte und Kommas enthalten.Dit veld kan alleen cijfers, punten en komma's bevatten.Ce champ ne peut contenir que des chiffres, des points et des virgules.
common.autocorrection-must-be-appliedFollowing auto-correction must be applied, click to applyFolgende Autokorrektur muss angewendet werden, klicken Sie zum AnwendenDe volgende automatische correctie moet worden toegepast, klik om toe te passenLa correction automatique suivante doit être appliquée, cliquez pour appliquer
common.diff-added-removedRed indicates removed values, green indicates added valuesRot zeigt entfernte Werte, Grün zeigt hinzugefügte WerteRood geeft verwijderde waarden aan, groen geeft toegevoegde waarden aanLe rouge indique les valeurs supprimées, le vert indique les valeurs ajoutées