Skip to main content

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 key holding that button's value:
Buttonskey value
Digits0 … 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-key locator, so a single on('component.click', …) covers all of them. Add a button in Flex, give it a locator and a key, and it works with no code change.
  • event.target is the button that fired, as a full component — so getData('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)