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; | |
| } | |
| } |