Calculator
A four-function calculator built from designed components: one click handler for
every button, the key's value read off the button itself, and the display updated
through the text capability.
You'll use: events,
author data, and the
text capability.
Set it up in Flex
The display — one text element with the locator calc-display.
The buttons — any clickable element (a rectangle or a group works well). Give every button:
- the same locator,
calc-key, so one line of code covers all of them, and - an SDK Data entry named
keyholding that button's value:
| Buttons | key value |
|---|---|
| Digits | 0 … 9 |
| Operators | +, -, *, / |
| Others | ., =, C (clear) |
Putting the value in SDK Data rather than reading the visible label keeps the
script working when the design changes — restyle the buttons, translate them, swap
C for an icon, and the logic is untouched.
The script
Paste into the experience's custom body HTML.
<script type="module">
import { connect } from '@ceros/flex-experience-sdk'
const experience = await connect()
const display = experience.findByLocator('calc-display')
// Small, interdependent, synchronous state — a plain object is the right tool.
const state = { current: '0', previous: null, op: null, justEvaluated: false }
function render() {
display.text.setText(state.current)
}
function inputDigit(d) {
if (state.current === '0' || state.justEvaluated) {
state.current = d
state.justEvaluated = false
} else {
state.current += d
}
}
function inputDot() {
if (state.justEvaluated) {
state.current = '0'
state.justEvaluated = false
}
if (!state.current.includes('.')) state.current += '.'
}
function compute(a, b, op) {
switch (op) {
case '+':
return a + b
case '-':
return a - b
case '*':
return a * b
case '/':
return b === 0 ? NaN : a / b
default:
return b
}
}
function chooseOp(op) {
if (state.op && state.previous !== null && !state.justEvaluated) evaluate()
state.previous = parseFloat(state.current)
state.op = op
state.justEvaluated = true // the next digit starts a fresh operand
}
function evaluate() {
if (state.op === null || state.previous === null) return
const result = compute(state.previous, parseFloat(state.current), state.op)
state.current = Number.isFinite(result) ? String(result) : 'Error'
state.previous = null
state.op = null
state.justEvaluated = true
}
function clear() {
state.current = '0'
state.previous = null
state.op = null
state.justEvaluated = false
}
function handleKey(key) {
if (/^[0-9]$/.test(key)) inputDigit(key)
else if (key === '.') inputDot()
else if (key === 'C') clear()
else if (key === '=') evaluate()
else chooseOp(key) // + - * /
render()
}
// One handler for every button: which key fired comes off the button itself.
experience.findByLocator('calc-key').on('component.click', (event) => {
const key = event.target.getData('key')
if (key) handleKey(key)
})
render() // show the initial 0
</script>
Or have Flex AI build it
A calculator is a lot of buttons to place by hand. Paste the prompt below into Flex AI in the Flex editor and it builds the layout, names every button, and writes the script — giving you a working experience to try the SDK against in one step.
In this experience, build a four-function calculator:
- A text element for the display, with the SDK locator calc-display.
- A grid of clickable buttons for 0-9, +, -, *, /, ., =, and C.
- Give every button the same SDK locator, calc-key, plus an SDK Data
entry named key holding that button's value ("7", "+", "C", and so on).
- Add SDK code with a single click handler on calc-key that reads
getData('key'), keeps the calculator state in plain JavaScript, and
renders to calc-display with text.setText.
Handle chained operations, a leading zero, one decimal point per
operand, divide-by-zero, and C to clear.
Flex AI writes the script into the experience's custom body HTML, which is the same place you'd paste it yourself. Two things to expect:
- You still have to publish (or open the standalone preview) to see it run. Custom body HTML doesn't execute in the editor's Preview tab.
- Read the code before you ship it. It's generated, so treat it as a first draft — the sections below explain what a good version looks like.
Why it's written this way
- One subscription, not one per button. Every button shares the
calc-keylocator, so a singleon('component.click', …)covers all of them. Add a button in Flex, give it a locator and akey, and it works with no code change. event.targetis the button that fired, as a full component — sogetData('key')reads that button's own value. See what a handler receives.- The state lives in plain JavaScript. It's small, synchronous, and tightly interdependent, which is exactly where a plain object beats a store. Reach for the store when you want to subscribe to a value or keep it across reloads.
render()is the only thing that touches the page. Every other function just changes state. That separation is what keeps the logic easy to follow.
Variations
Highlight the active operator. Give the operator buttons a "selected" state in
Flex and drive it from chooseOp:
const keys = experience.findByLocator('calc-key')
function highlight(op) {
keys.forEach((key) => {
const isActive = key.getData('key') === op
if (isActive) key.states.activate('selected')
else key.states.deactivate('selected')
})
}
Add keyboard support. The buttons already dispatch through handleKey, so the
keyboard is a few lines:
window.addEventListener('keydown', (event) => {
const key =
event.key === 'Enter' ? '=' : event.key === 'Escape' ? 'C' : event.key
if (/^[0-9+\-*/.=C]$/.test(key)) handleKey(key)
})
Remember the last result. Persist it with the store:
import { createStore, localStorageAdapter } from '@ceros/flex-experience-sdk'
const store = createStore(
{ lastResult: null },
{ persist: localStorageAdapter('calc.state') },
)
// in evaluate(), after computing:
store.set('lastResult', state.current)