The input is text
A price field contains an empty string. Calling Number on it yields zero. Would that correctly represent a free item? No: the user has not supplied a price.
Create labeled inputs named customerName, quantity and unitPrice, a select named customerType and a checkbox named active. Use a form with id quote-form. Name attributes are the keys collected by FormData; id attributes associate labels and allow selection. An unchecked checkbox is absent from FormData, and disabled controls are also excluded.
New notation: recognize the accepted text
The parser below uses a regular expression, a pattern for text. In this pattern, ^ and $ anchor the whole value; \d+ requires one or more digits; \. means a literal decimal point; {1,2} allows one or two following digits; and ? makes the decimal part optional. Parentheses capture the whole and fractional portions. exec returns those captures or null when the text does not match.
After a match, match[1] is the whole-unit text and match[2] is the optional fraction. ?? supplies an empty string only when that fraction is null or undefined. padEnd(2, '0') turns a fraction such as 5 into 50 cents. Trace these steps for 12.5 before running the function; do not treat the pattern as an unexplained formula.
export function parsePriceCents(raw) {
const match = /^(\d+)(?:\.(\d{1,2}))?$/.exec(String(raw).trim());
if (!match) throw new Error('Use a nonnegative price with up to two decimal places');
const cents = Number(match[1]) * 100 + Number((match[2] ?? '').padEnd(2, '0'));
if (!Number.isSafeInteger(cents)) throw new Error('Price is too large');
return cents;
}
The expression accepts whole units or one/two decimal places using a dot. It does not accept commas, currency symbols or scientific notation. That is this exercise's explicit format, not a claim about all local conventions. Inputmode decimal is a keyboard hint; it does not validate or normalize the value.
Connect collection to conversion
Inside a submit handler, construct new FormData(form). Read the quantity text, reject a blank value, then require digits and a safe integer. Read active with data.has('active'). Pass the converted record to the existing domain validation. Client-side conversion and domain policy are separate boundaries.
FormData can contain File values for file inputs. Our named fields are text controls, but a reusable parser should verify the values it expects. Do not convert every unknown value to text and assume that makes it acceptable.
Build it yourself
Write readOrderForm(form) returning either an order or field-specific issues. Parse first, validate the domain second, calculate third. Keep the raw input visible after rejection so the person can correct it.
Verification table
| Price text | Expected |
|---|---|
| 150 | 15000 cents |
| 0.10 | 10 cents |
| 20.5 | 2050 cents |
| empty | Reject |
| 1.234 | Reject |
| -1 | Reject |
| 1,50 | Reject with format explanation |
Counterexample and correction
Multiplying Number(raw) by 100 before checking the format loses the distinction between blank input and zero and can expose floating-point rounding surprises. Parse the accepted decimal structure, then build the integer amount. The domain must still reject an unsupported quantity or inactive customer.
Change request
If comma decimals become required, update the input contract and examples before the parser. Decide whether a comma means a decimal separator or thousands grouping; silently guessing can change the amount by orders of magnitude.