I'll update this later
A Pen by Ted Pritchard on CodePen.
I'll update this later
A Pen by Ted Pritchard on CodePen.
<div id="container"> | |
<!-- This element's contents will be replaced with your component. --> | |
</div> |
const KeyType = Object.freeze({ | |
numeric: Symbol("numeric"), | |
operator: Symbol("operator"), | |
clear: Symbol("clear"), | |
dot: Symbol("dot"), | |
result: Symbol("result"), | |
}); | |
class Button extends React.Component { | |
handleClick = () => { | |
this.props.onClick(this.props.value); | |
} | |
render() { | |
return ( | |
<div onClick={this.handleClick}> | |
{this.props.value} | |
</div> | |
); | |
} | |
} | |
class CalculatorInput extends React.Component { | |
createButtons(buttons) { | |
return buttons.map(button => | |
<Button | |
value={button.keyValue} | |
onClick={() => this.props.onInput(button)} | |
/> | |
); | |
} | |
render() { | |
const topPanelButtons = [ | |
{ keyValue: '+', keyType: KeyType.operator }, | |
{ keyValue: '-', keyType: KeyType.operator }, | |
{ keyValue: '\u00d7', keyType: KeyType.operator }, | |
{ keyValue: '\u00f7', keyType: KeyType.operator } | |
]; | |
const centrePanelButtons = [ | |
{ keyValue: '9', keyType: KeyType.numeric }, | |
{ keyValue: '8', keyType: KeyType.numeric }, | |
{ keyValue: '7', keyType: KeyType.numeric }, | |
{ keyValue: '6', keyType: KeyType.numeric }, | |
{ keyValue: '5', keyType: KeyType.numeric }, | |
{ keyValue: '4', keyType: KeyType.numeric }, | |
{ keyValue: '3', keyType: KeyType.numeric }, | |
{ keyValue: '2', keyType: KeyType.numeric }, | |
{ keyValue: '1', keyType: KeyType.numeric }, | |
{ keyValue: '0', keyType: KeyType.numeric }, | |
{ keyValue: '.', keyType: KeyType.dot }, | |
{ keyValue: 'C', keyType: KeyType.clear } | |
]; | |
const topPanelButtonDivs = this.createButtons(topPanelButtons); | |
const centrePanelButtonDivs = this.createButtons(centrePanelButtons); | |
return ( | |
<> | |
<div class="top-panel"> | |
{topPanelButtonDivs} | |
</div> | |
<div class="centre-panel"> | |
{centrePanelButtonDivs} | |
</div> | |
<div class="bottom-panel" id="result"> | |
<Button | |
value="=" | |
onClick={() => this.props.onInput({ keyValue: '=', keyType: KeyType.result })} | |
/> | |
</div> | |
</> | |
); | |
} | |
} | |
class ResultDisplay extends React.Component { | |
render() { | |
return ( | |
<div class="input">{this.props.displayValue}</div> | |
); | |
} | |
} | |
const CalcState = Object.freeze({ | |
resulted: Symbol("resulted"), | |
cleared: Symbol("cleared"), | |
input: Symbol("input") | |
}); | |
const InputState = Object.freeze({ | |
acceptInput: Symbol("acceptInput"), | |
acceptNumeric: Symbol("acceptNumeric"), | |
acceptNumericOrOperator: Symbol("acceptNumericOrOperator") | |
}); | |
function nextInputState(currentInputState, keyType) { | |
if (currentInputState === InputState.acceptNumeric) { | |
return InputState.acceptNumericOrOperator; | |
} | |
if (currentInputState === InputState.acceptNumericOrOperator) { | |
if (keyType === KeyType.operator) { | |
return InputState.acceptInput; | |
} | |
if (keyType === KeyType.numeric) { | |
return InputState.acceptNumericOrOperator; | |
} | |
} | |
if (keyType === KeyType.dot) { | |
return InputState.acceptNumeric; | |
} | |
if (keyType === KeyType.operator) { | |
return InputState.acceptInput; | |
} | |
if (keyType === KeyType.dot) { | |
return InputState.acceptNumericOrOperator; | |
} | |
return InputState.acceptInput; | |
} | |
function initial(state, keyValue, keyType) { | |
return { | |
...state, | |
result: {display: keyValue, keyTypes: [KeyType.numeric], inputStates: [InputState.acceptInput]}, | |
lastKeyType: keyType, | |
calcState: CalcState.input, | |
inputState: InputState.acceptInput | |
} | |
} | |
function concat(state, keyValue, keyType) { | |
const display = state.result.display += keyValue; | |
const keyTypes = [...state.result.keyTypes, keyType]; | |
const inputState = nextInputState(state.inputState, keyType) | |
const inputStates = [...state.result.inputStates, inputState] | |
return { | |
...state, | |
inputState, | |
result: {display, keyTypes, inputStates}, | |
lastKeyType: keyType, | |
calcState: CalcState.input | |
} | |
} | |
function clearLast(state) { | |
let display; | |
let keyTypes; | |
let inputStates; | |
let lastKeyType; | |
let calcState; | |
let inputState; | |
if (state.result.display.length > 1) { | |
display = state.result.display.slice(0, -1); | |
keyTypes = state.result.keyTypes.slice(0, -1); | |
lastKeyType = keyTypes[keyTypes.length -1]; | |
calcState = state.calcState; | |
inputStates = state.result.inputStates.slice(0, -1);; | |
inputState = inputStates[inputStates.length -1] | |
} else { | |
display = '0'; | |
keyTypes = [KeyType.numeric]; | |
lastKeyType: KeyType.numeric; | |
calcState = CalcState.cleared; | |
inputState = InputState.acceptInput; | |
inputStates = [inputState]; | |
}; | |
return { | |
...state, | |
result: {display, keyTypes, inputStates }, | |
lastKeyType, | |
calcState, | |
inputState | |
} | |
} | |
function calulateResultState(state) { | |
const display = calculateResult(state.result.display); | |
const keyTypes = [KeyType.numeric]; | |
const inputStates = [InputState.acceptInput]; | |
return { | |
...state, | |
result: {display, keyTypes, inputStates}, | |
calcState: CalcState.resulted, | |
inputState: InputState.acceptInput, | |
lastKeyType: KeyType.numeric | |
}; | |
function calculateResult(inputString) { | |
// forming an array of numbers. eg for above string it will be: numbers = ["10", "26", "33", "56", "34", "23"] | |
var numbers = inputString.split(/\+|\-|\×|\÷/g); | |
// forming an array of operators. for above string it will be: operators = ["+", "+", "-", "*", "/"] | |
// first we replace all the numbers and dot with empty string and then split | |
var operators = inputString.replace(/[0-9]|\./g, "").split(""); | |
console.log(inputString); | |
console.log(operators); | |
console.log(numbers); | |
console.log("----------------------------"); | |
// now we are looping through the array and doing one operation at a time. | |
// first divide, then multiply, then subtraction and then addition | |
// as we move we are alterning the original numbers and operators array | |
// the final element remaining in the array will be the output | |
var divide = operators.indexOf("÷"); | |
while (divide != -1) { | |
numbers.splice(divide, 2, numbers[divide] / numbers[divide + 1]); | |
operators.splice(divide, 1); | |
divide = operators.indexOf("÷"); | |
} | |
var multiply = operators.indexOf("×"); | |
while (multiply != -1) { | |
numbers.splice(multiply, 2, numbers[multiply] * numbers[multiply + 1]); | |
operators.splice(multiply, 1); | |
multiply = operators.indexOf("×"); | |
} | |
var subtract = operators.indexOf("-"); | |
while (subtract != -1) { | |
numbers.splice(subtract, 2, numbers[subtract] - numbers[subtract + 1]); | |
operators.splice(subtract, 1); | |
subtract = operators.indexOf("-"); | |
} | |
var add = operators.indexOf("+"); | |
while (add != -1) { | |
// using parseFloat is necessary, otherwise it will result in string concatenation :) | |
numbers.splice(add, 2, parseFloat(numbers[add]) + parseFloat(numbers[add + 1])); | |
operators.splice(add, 1); | |
add = operators.indexOf("+"); | |
} | |
return numbers[0]; | |
} | |
} | |
function calculateState(state, keyValue, keyType) { | |
if (keyType === KeyType.result) { | |
return calulateResultState(state) | |
} | |
if (keyType === KeyType.clear) { | |
return clearLast(state); | |
} | |
if (state.calcState === CalcState.resulted && keyType === KeyType.operator) { | |
return concat(state, keyValue, keyType); | |
} | |
if ((state.calcState === CalcState.cleared || state.calcState === CalcState.resulted) && keyType === KeyType.numeric ) { | |
return initial(state, keyValue, keyType); | |
} | |
if (state.calcState === CalcState.input) { | |
if (keyType === KeyType.clear) { | |
return clearLast(state); | |
} | |
if (state.inputState === InputState.acceptInput) { | |
return concat(state, keyValue, keyType); | |
} | |
if (state.inputState === InputState.acceptNumeric && keyType === KeyType.numeric) { | |
return concat(state, keyValue, keyType); | |
} | |
if (state.inputState === InputState.acceptNumericOrOperator && (keyType === KeyType.numeric || keyType === KeyType.operator)) { | |
return concat(state, keyValue, keyType); | |
} | |
} | |
} | |
function add(num1, num2) { | |
return num1 + num2; | |
} | |
function subtract(num1, num2) { | |
return num1 - num2; | |
} | |
function multiply(num1, num2) { | |
return num1 * num2; | |
} | |
function divide(num1, num2) { | |
return num1 / num2; | |
} | |
function clear() { | |
return 0; | |
} | |
class Calculator extends React.Component { | |
constructor(props) { | |
super(props); | |
this.handleInput = this.handleInput.bind(this); | |
this.state = { | |
result: { display: '0', keyTypes: [KeyType.numeric], inputStates: [InputState.acceptInput] }, | |
calcState: CalcState.cleared, | |
inputState: InputState.acceptInput, | |
lastKeyType: KeyType.numeric | |
}; | |
console.log(this.state); | |
} | |
handleInput(button) { | |
this.setState(calculateState(this.state, button.keyValue, button.keyType)); | |
} | |
render() { | |
return ( | |
<div class="calculator"> | |
<ResultDisplay displayValue={this.state.result.display}/> | |
<CalculatorInput onInput={this.handleInput} /> | |
</div> | |
); | |
} | |
} | |
ReactDOM.render( | |
<Calculator />, | |
document.getElementById('container') | |
); |
<script src="https://unpkg.com/react/umd/react.development.js"></script> | |
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script> |
$padding: 10px; | |
$width: 500px; | |
$dark-grey: #ddd; | |
$mid-grey: #bbb; | |
$light-grey: #aaa; | |
$light-blue: #1857bb; | |
$dark-blue: #4d90fe; | |
$button-width: 17%; | |
@mixin box-shadow($top: 0px, $left: 1px, $blur: 4px, $spread: 0px, $color: rgba(0, 0, 0, 0.2)) { | |
box-shadow: $top $left $blur $spread $color; | |
} | |
body { | |
width: $width; | |
margin: 4% auto; | |
font-family: "Source Sans Pro", sans-serif; | |
letter-spacing: 5px; | |
font-size: 1.8rem; | |
-moz-user-select: none; | |
-webkit-user-select: none; | |
-ms-user-select: none; | |
} | |
.calculator { | |
padding: $padding * 2; | |
@include box-shadow(); | |
border-radius: 1px; | |
} | |
.input { | |
border: 1px solid $dark-grey; | |
border-radius: 5px; | |
height: 60px; | |
padding-right: 15px; | |
padding-top: $padding; | |
text-align: right; | |
margin: 0 6px 5px 5px; | |
font-size: 2.5rem; | |
overflow-x: auto; | |
} | |
.centre-panel { | |
display: flex; | |
flex-wrap: wrap; | |
justify-content: space-around; | |
div { | |
display: inline-block; | |
border: 1px solid $dark-grey; | |
border-radius: 5px; | |
flex-basis: $button-width; | |
text-align: center; | |
padding: $padding; | |
margin: 10px 4px 10px 0; | |
cursor: pointer; | |
background-color: #f9f9f9; | |
&:hover { | |
background-color: #f1f1f1; | |
@include box-shadow(); | |
border-color: $mid-grey; | |
} | |
} | |
} | |
.top-panel { | |
display: flex; | |
flex-wrap: wrap; | |
justify-content: space-around; | |
div { | |
display: inline-block; | |
border: 1px solid $mid-grey; | |
border-radius: 5px; | |
width: $button-width; | |
text-align: center; | |
padding: $padding; | |
margin: 20px 4px 10px 0; | |
cursor: pointer; | |
background-color: $dark-grey; | |
&:hover { | |
background-color: $dark-grey; | |
border-color: $light-grey; | |
@include box-shadow(); | |
} | |
&:active { | |
font-weight: bold; | |
} | |
} | |
} | |
div.bottom-panel { | |
display: flex; | |
justify-content: space-around; | |
border: 1px solid #3079ed; | |
border-radius: 5px; | |
flex-basis: 100%; | |
text-align: center; | |
padding: $padding; | |
margin: 10px 6px 10px 5px; | |
vertical-align: top; | |
cursor: pointer; | |
color: #fff; | |
background-color: #4d90fe; | |
&:hover { | |
background-color: #307cf9; | |
@include box-shadow(); | |
border-color: $light-blue; | |
} | |
&:active { | |
font-weight: bold; | |
} | |
} |