I’m a designer learning to code and I’ve built a simple form calculator for ordering cakes.
I would love it if you could show me how to make the code more idiomatic. How can it be more consistent, concise and simplified? How can I align it with best practices? What’s the right way to group and order it? In what other ways can I make it better?
It doesn’t need to support anything older than ECMAScript 6.
// Cake-ordering calculator
const calculator = document.forms[0]
const total = calculator.elements.total
const occasionOptions = {
party: 20,
birthday: 25,
anniversary: 50,
wedding: 100
}
const sizeOptions = {
six: 1,
eight: 1.5,
ten: 2,
twelve: 2.5
}
const extrasOptions = {
inscription: 10,
decoration: 25,
special: 50
}
calculator.addEventListener("click", calculateTotal)
function cake() {
const cakes = Array.from(calculator.elements["cake"]).slice(0, 3)
const raphael = calculator.elements.raphael
function isChecked(checkbox) {
return checkbox.checked
}
let count = cakes.filter(isChecked).length
if (count) {
count = count * 0.5 + 0.5
}
if (raphael.checked) {
count += 1
}
return count
}
function occasion() {
let occasionCost = 0
const occasion = calculator.elements.occasion
for (let i = 0; i < occasion.length; i++) {
if (occasion[i].checked) {
occasionCost = occasionOptions[occasion[i].id]
break
}
}
return occasionCost
}
function size() {
let sizeIndex = 1
const size = calculator.elements.size
for (let i = 0; i < size.length; i++) {
if (size[i].checked) {
sizeIndex = sizeOptions[size[i].id]
break
}
}
return sizeIndex
}
function extras() {
let extrasCost = 0
const extras = calculator.elements.extras
for (let i = 0; i < extras.length; i++) {
if (extras[i].checked) {
extrasCost = extrasCost + extrasOptions[extras[i].id]
}
}
return extrasCost
}
function calculateTotal() {
let totalCost = cake() * occasion() * size() + extras()
total.value = "$" + totalCost.toLocaleString("en")
}
// Display "extras" fieldset when "wedding" occasion is selected
const occasions = Array.from(calculator.elements.occasion)
const fieldset = Array.from(document.getElementsByTagName("fieldset"))
fieldset[3].style.display = "none"
occasions.forEach(occasion => {
occasion.addEventListener("click", () => {
if (occasion.id == "wedding") {
fieldset[3].style.setProperty("display", "inherit")
} else {
fieldset[3].style.setProperty("display", "none")
}
})
})
// Display cost after size has been selected
const sizes = calculator.elements.size
total.style.display = "none"
for (let i = 0; i < sizes.length; i++) {
sizes[i].onclick = function() {
if (this.checked) {
total.style.setProperty("display", "inherit")
} else {
total.style.setProperty("display", "none")
}
}
}
// Disable all fieldsets except the first one
const disabledFieldsets = document.querySelectorAll(
"fieldset:not(:first-of-type)"
)
for (let i = 0; i < disabledFieldsets.length; i++) {
disabledFieldsets[i].disabled = true
}
// Enable fieldsets sequentially on selection
document.querySelectorAll("fieldset").forEach(fieldset => {
fieldset.addEventListener("change", function() {
let nextFieldset = this.nextElementSibling
while (nextFieldset && !nextFieldset.disabled) {
nextFieldset = nextFieldset.nextElementSibling
}
if (nextFieldset) {
nextFieldset.disabled = false
}
})
})
// Reset form after all inputs in the first fieldset are deselected
const cakeOptions = document.querySelectorAll(
"fieldset:first-of-type input[type=checkbox]"
)
let isChecked = false
cakeOptions.forEach(function(resetWhenAllUnchecked) {
resetWhenAllUnchecked.addEventListener("click", function(e) {
if (this.checked) {
isChecked = true
} else {
if (
isChecked &&
!document.querySelectorAll(
"fieldset:first-of-type input[type=checkbox]:checked"
).length
) {
calculator.reset()
fieldset[3].style.setProperty("display", "none")
total.style.setProperty("display", "none")
}
}
})
})
<form>
<fieldset>
<legend>Select Cakes</legend>
<label><input type="checkbox" name="cake" id="leonardo">Leonardo</label>
<label><input type="checkbox" name="cake" id="donatello">Donatello</label>
<label><input type="checkbox" name="cake" id="michelangelo">Michelangelo</label>
<label><input type="checkbox" name="cake" id="raphael">Raphael</label>
<p>If you select more than one cake, the other cakes are discounted 50%.</p>
<p><small>Does not apply to Raphael.</small></p>
</fieldset>
<fieldset>
<legend>Choose Occasion</legend>
<label><input type="radio" name="occasion" id="party" required>Party</label>
<label><input type="radio" name="occasion" id="birthday">Birthday</label>
<label><input type="radio" name="occasion" id="anniversary">Anniversary</label>
<label><input type="radio" name="occasion" id="wedding">Wedding</label>
</fieldset>
<fieldset>
<legend>Choose Size</legend>
<label><input type="radio" name="size" id="six" required>6-inch</label>
<label><input type="radio" name="size" id="eight">8-inch</label>
<label><input type="radio" name="size" id="ten">10-inch</label>
<label><input type="radio" name="size" id="twelve">12-inch</label>
</fieldset>
<fieldset>
<legend>Select Extras</legend>
<label><input type="checkbox" name="extras" id="inscription">Inscription</label>
<label><input type="checkbox" name="extras" id="decoration">Decoration</label>
<label><input type="checkbox" name="extras" id="special">Special Frosting & Icing</label>
</fieldset>
<input type="text" name="total" readonly>
<input type="submit" value="Submit">
</form>