Skip to content

robots4life/svelte-calculator

Repository files navigation

Svelte Calculator

The aim is to have a simple basic calculator working, while, and for brevity, rules that can break the evaluation of the calculation, are not included.

1. Create markup and styles

<main>
	<div class="calculator">
		<div class="display">123</div>
		<div class="digits">
			<button class="clear span-3">C</button>
			<button class="operator divide">/</button>
			<button class="number">1</button>
			<button class="number">2</button>
			<button class="number">3</button>
			<button class="operator multiply">*</button>
			<button class="number">4</button>
			<button class="number">5</button>
			<button class="number">6</button>
			<button class="operator subtract">-</button>
			<button class="number">7</button>
			<button class="number">8</button>
			<button class="number">9</button>
			<button class="operator add">+</button>
			<button class="number span-2">0</button>
			<button class="dot">.</button>
			<button class="operator equal">=</button>
		</div>
	</div>
</main>
.calculator {
	width: 100%;
}
.display {
	display: flex;
	align-items: center;
	justify-content: end;
	padding-right: 0.4rem;
	width: 100%;
	height: 4rem;
	font-size: 2rem;
	font-weight: bold;
	letter-spacing: 0.2rem;
	background-color: lightslategrey;
	color: white;
	border-radius: 12px;
	margin-bottom: 1rem;
}
.digits {
	display: grid;
	grid-template-columns: repeat(4, 1fr);
	gap: 1rem;
}
.span-2 {
	grid-column: span 2;
}
.span-3 {
	grid-column: span 3;
}
button {
	display: flex;
	justify-content: center;
	align-items: center;
	height: 4rem;
	font-size: 2rem;
	border: 2px solid black;
	border-radius: 12px;
}
.operator {
	font-size: 3rem;
	font-weight: bold;
	background-color: orange;
}

2. Iterate over numbers with each

const numbers_1_3 = ['1', '2', '3'];
const numbers_4_6 = ['4', '5', '6'];
const numbers_7_9 = ['7', '8', '9'];
{#each numbers_1_3 as number}
	<button class="number" id="{number}">{number}</button>
{/each}

3. Handle calculator state and input

This handles correct evaluation of the calculation with operator precedence.

By binding buttonDivide and buttonMultiply to a variable of type HTMLButtonElement one could enforce rules that i.e. do not allow having a calculation that starts with ** or //.

const numbers_1_3 = ['1', '2', '3'];
const numbers_4_6 = ['4', '5', '6'];
const numbers_7_9 = ['7', '8', '9'];

let selectedOperator = '';
let display: string = '';
let displayArray: string[] = [];

let buttonDivide: HTMLButtonElement;
let buttonMultiply: HTMLButtonElement;

const handleClear = () => {
	console.log('clear');
	displayArray = [];
	display = '';
	selectedOperator = '';
};

const handleNumber = (number: string) => {
	console.log('number : ', number);

	displayArray.push(number);
	display = displayArray.join('');
};

const handleOperator = (operator: string) => {
	console.log('operator : ', operator);
	selectedOperator = operator;

	displayArray.push(operator);
	console.log(displayArray);

	display = displayArray.join('');
	console.log(display);

	if (operator === '=') {
		// remove "=" from array
		displayArray.pop();
		console.log('displayArray : ', displayArray);

		let numbers: number[] = [];

		interface Calc {
			numbers: number[];
			operators: string[];
			math: (number | string)[];
		}

		let calc: Calc = { numbers: [], operators: [], math: [] };
		let numbersIndex: number = 0;
		let operatorsIndex: number = 0;

		displayArray.forEach((element, index, array) => {
			// if string element is a number
			if (!isNaN(parseInt(element))) {
				numbers.push(parseInt(element));
				calc.numbers[numbersIndex] = +numbers.join('');

				// if the last element in the displayArray is iterated
				if (index === array.length - 1) {
					calc.math.push(calc.numbers[calc.numbers.length - 1]);
				}
			}
			// if string element is an operator
			if (isNaN(parseInt(element))) {
				calc.operators[operatorsIndex] = element;

				calc.math.push(calc.numbers[calc.numbers.length - 1]);
				calc.math.push(calc.operators[calc.operators.length - 1]);

				operatorsIndex++;
				numbersIndex++;
				numbers = [];
			}
		});
		console.log('numbers : ', numbers);
		console.log('calc : ', calc);

		let calculationString = Object.values(calc.math).join('');
		console.log('calculationString : ', calculationString);

		let result = eval(calculationString);
		console.log('result : ', result);

		display = result;
	}
};

const handleDot = (dot: string) => {
	console.log('dot : ', dot);
	displayArray.push(dot);
};
<div class="calculator">
	<div class="display">{display}</div>
	<div class="digits">
		<button class="clear span-3" on:click="{()" ="">handleClear()}>C</button>
		<button
			bind:this="{buttonDivide}"
			class="operator divide {selectedOperator === '/' ? 'op-active' : ''}"
			on:click="{()"
			=""
		>
			handleOperator('/')}>/
		</button>
		{#each numbers_1_3 as number}
		<button class="number" on:click="{()" ="">handleNumber(number)}>{number}</button>
		{/each}
		<button
			bind:this="{buttonMultiply}"
			class="operator multiply {selectedOperator === '*' ? 'op-active' : ''}"
			on:click="{()"
			=""
		>
			handleOperator('*')}>*
		</button>
		{#each numbers_4_6 as number}
		<button class="number" on:click="{()" ="">handleNumber(number)}>{number}</button>
		{/each}
		<button
			class="operator subtract {selectedOperator === '-' ? 'op-active' : ''}"
			on:click="{()"
			=""
		>
			handleOperator('-')}>-
		</button>
		{#each numbers_7_9 as number}
		<button class="number" on:click="{()" ="">handleNumber(number)}>{number}</button>
		{/each}
		<button class="operator add {selectedOperator === '+' ? 'op-active' : ''}" on:click="{()" ="">
			handleOperator('+')}>+
		</button>
		<button class="number span-2" on:click="{()" ="">handleNumber('0')}>0</button>
		<button class="dot" on:click="{()" ="">handleDot('.')}>.</button>
		<button class="operator equal {selectedOperator === '=' ? 'op-active' : ''}" on:click="{()" ="">
			handleOperator('=')}>=
		</button>
	</div>
</div>

Reference

https://stackoverflow.com/questions/175739/how-can-i-check-if-a-string-is-a-valid-number

https://stackoverflow.com/questions/2276021/evaluating-a-string-as-a-mathematical-expression-in-javascript

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_precedence