Modules 1 through 17 covered the pieces one at a time. This is the first Practice Project: putting variables, forms, conditionals, switch statements, and output encoding together into one small, complete application. A calculator is deliberately simple, so the focus stays on the structure around the arithmetic.
Learning Objectives
After completing this project, you'll be able to:
- Build a form that submits two numbers and an operator back to the same page.
- Set safe defaults for form fields with cfparam.
- Validate that inputs are actually numbers before doing any arithmetic.
- Choose an operation with cfswitch and handle an unknown operator.
- Prevent division by zero before it happens, not after.
- Echo values back into the form and results into the page safely with output encoding.
How the Calculator Works
Form Is Submitted
two numbers and an operator
Validate the Inputs
both must be numeric
Choose and Run the Operation
cfswitch on the operator
Render the Result
encoded before output
Step 1: Set Defaults and Build the Form
cfparam makes sure each form field exists before the page reads it. It won't overwrite a value that's already there, so on a fresh page load the defaults apply, and on a submitted form the user's own values are kept.
<cfparam name="form.num1" default=""> <cfparam name="form.num2" default=""> <cfparam name="form.operator" default="+"> <cfset result = ""> <cfset errorMsg = "">
<form method="post" action="calculator.cfm">
<input type="text" name="num1" value="#encodeForHTMLAttribute(form.num1)#">
<select name="operator">
<option value="+" <cfif form.operator EQ "+">selected</cfif>>+</option>
<option value="-" <cfif form.operator EQ "-">selected</cfif>>-</option>
<option value="*" <cfif form.operator EQ "*">selected</cfif>>*</option>
<option value="/" <cfif form.operator EQ "/">selected</cfif>>/</option>
</select>
<input type="text" name="num2" value="#encodeForHTMLAttribute(form.num2)#">
<button type="submit" name="calculate" value="1">Calculate</button>
</form>Setting value="#encodeForHTMLAttribute(form.num1)#" is the attribute-context encoding from the XSS Prevention lesson. Echoing unencoded user input back into an input's value attribute is exactly the kind of mistake that lesson covered.
Step 2: Validate Before You Calculate
isNumeric() confirms a value can be read as a number before any arithmetic runs. Validating first means a typo like "twenty" produces a clear message instead of a ColdFusion error page.
| Input | isNumeric() | Result |
|---|---|---|
| 23 | true | Accepted |
| 5e2 (scientific notation) | true | Accepted, equals 500 |
| twenty | false | Rejected with a message |
| (empty) | false | Rejected with a message |
isNumeric() accepts scientific notation, so "5e2" is a valid input for this calculator. If that's not what you want, add an extra check, a number-only rule is a product decision, not something isNumeric() enforces on its own.
Step 3: Calculate with cfswitch
cfswitch picks the operation based on the operator the user chose. A cfdefaultcase catches anything unexpected, so an unknown operator can't silently produce an empty result.
<cfif structKeyExists(form, "calculate")>
<cfif NOT isNumeric(form.num1) OR NOT isNumeric(form.num2)>
<cfset errorMsg = "Both values must be numbers.">
<cfelseif form.operator EQ "/" AND form.num2 EQ 0>
<cfset errorMsg = "You can't divide by zero.">
<cfelse>
<cfswitch expression="#form.operator#">
<cfcase value="+"><cfset result = form.num1 + form.num2></cfcase>
<cfcase value="-"><cfset result = form.num1 - form.num2></cfcase>
<cfcase value="*"><cfset result = form.num1 * form.num2></cfcase>
<cfcase value="/"><cfset result = form.num1 / form.num2></cfcase>
<cfdefaultcase><cfset errorMsg = "Unknown operator."></cfdefaultcase>
</cfswitch>
</cfif>
</cfif>A Real Detail: Check for Zero Before You Divide
The division case checks form.num2 EQ 0 in the branch before the cfswitch runs, not inside the division itself. By the time the code reaches the cfswitch, both inputs have already passed isNumeric(), so the comparison is safe. Putting the zero check first means the error message is clear and the division never runs with a zero divisor, rather than trying the division and handling a failure afterward.
Step 4: Render the Result Safely
<cfif errorMsg NEQ "">
<p class="error">#encodeForHTML(errorMsg)#</p>
</cfif>
<cfif result NEQ "">
<p class="result">Result: #encodeForHTML(result)#</p>
</cfif>Even though result and errorMsg are generated by this page, not typed by the user, they still get encoded. Output encoding is a habit applied everywhere, not a judgment made per value.
Common Beginner Mistakes
Skipping validation and letting ColdFusion throw an error on bad input
A value like "twenty" used in arithmetic produces a ColdFusion error page. Check isNumeric() first and show a clear message instead.
Dividing before checking for zero
Check form.num2 EQ 0 in the branch before the division runs, not after something has already failed.
Echoing user input into the form without encoding it
Use encodeForHTMLAttribute() inside the value attribute, and encodeForHTML() for any output in the page body.
Forgetting cfdefaultcase in a cfswitch
An unrecognized operator falls through with no output at all, which looks like a bug to the user. A default case makes the unexpected input visible.
Best Practices
- Validate every input before using it in an operation, not inside the operation.
- Set cfparam defaults at the top of the page so every variable is always defined.
- Encode every value that reaches the page, including ones your own code generated.
- Keep the calculation logic in one place so the structure is easy to read and extend.
Interview Questions
Why validate inputs before the arithmetic instead of catching errors afterward?
Validating first gives a clear, user-facing message and avoids running an operation on values that can't be meaningfully used. Catching errors afterward makes the failure harder to explain and can leave partial state behind.
Why does cfparam not overwrite an existing form value with its default?
cfparam only assigns the default when the variable doesn't already exist. On a submitted form the field exists, so the user's value is kept and the default is ignored.
What does cfdefaultcase protect against in a cfswitch?
An operator value that matches none of the cfcase branches. Without a default case, the switch silently does nothing, with one, the unexpected value can be reported.
Summary
In this project, you built a two-number calculator: set defaults with cfparam, validated both inputs with isNumeric(), chose the operation with cfswitch, guarded division by zero before dividing, and encoded every value that reached the page.
What's Next?
The next project is a Contact Form, which adds the next layer: validating a larger set of fields and sending the submission somewhere useful.