Web Programming with JavaScript: From DOM to APIs

Module 3: The Person Enters Invalid Data

Course progress0%
Course content
45 min

Form Values Are Not Yet Order Data

Convert raw form values using explicit contracts

By the end of this lesson

  • Convert raw form values using explicit contracts
  • Handle empty values and cents without accidental coercion

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.

Lesson complete?

Your progress is saved on this device.