Calculator Project Feedback

Hey campers,

I just completed my first independent project after finishing the HTML, CSS, and JS sections of the curriculum. It’s a simple calculator app that supports memory, deletion, clear, decimals, and sign changes. How did I do?

I’d love tips on how to refactor buildExpression() to be more DRY. It’s super repetitive, what with all the leftSide and rightSide checks! But, I’m stumped as to a better approach. I’m sure it’s super obvious.

Here’s a link to my calculator

Here’s the complete source code (HTML, CSS, JS) on gitHub

const numberParent = document.querySelector(".numbers");
const numbers = numberParent.children;
const operatorParent = document.querySelector(".operators");
const operators = operatorParent.children;
const display = document.getElementById("display");
const equalsButton = document.querySelector(".equals");
const calculator = document.querySelector(".calculator-container");
const body = document.querySelector("body")

let leftSide = '';
let rightSide = '';
let operation = '';
let isOperated = false;
let solution = '';

function handleFloat(string){
    if (string.includes(".")){
        return string;
    } else {
        return string+=".";
    }
    
}

function changeSign(string){
    if (!string) {
        return '';
    }
    return (parseFloat(string) * -1).toString()
}

function deleteCharacter(string) {
    let arr = string.split("")
    arr.pop()
    return arr.join("");
}

function setMemory(){
    let value = parseFloat(display.innerText)
    if (typeof value === "number" && !isNaN(value)){
        sessionStorage.setItem("memory", value);
        }
}

function getMemory(){
    if (!isNaN(sessionStorage.getItem("memory"))){
        return sessionStorage.getItem("memory");
    } 
}

function handleInput(e) {
    let input = ''
    if (e.type === "keydown") {
        let inputRegex = /=|\d|\*|\+|\-|\/|\./gi
        if (e.key === "Backspace" || e.key === "Enter") {
            input = e.key;
        }
        if (inputRegex.test(e.key)) {
            input = e.key
        }
    } else if (e.type === "click") {
        input = e.target.value;
        
    }
   
    return input; 
}

function handleClear(){  
    leftSide = '';
    rightSide = '';
    solution = '';
    operation = '';
    isOperated = false;
}

function updateDisplay() {
    display.innerText = leftSide+operation+rightSide;
    if (solution || parseInt(solution) === 0) {
        display.innerText = solution;
    }
}

function buildExpression(input) {
    if(display.innerText === "DIVIDE BY ZERO ERROR"){
        display.innerText = "";
    }
    if(/\d/ig.test(input)){
        if (isOperated === false) {
            leftSide += input;
        } else {
            rightSide += input;      
        }
    } else if (/\*|\+|\-(?![a-z])|\//.test(input)) {
       
        if (typeof parseFloat(display.innerText) === "number" & !isOperated) {
            leftSide = display.innerText;
        }
         if (!leftSide || rightSide) {
            return;
        } 
        isOperated = true;
        operation = input;
             
    } else if (input === ".") {
        if (isOperated) {
            rightSide = handleFloat(rightSide)
        } else {
            leftSide = handleFloat(leftSide)
        }
        
    } else if (input === "sign") {
        if (isOperated) {
            rightSide = changeSign(rightSide)
        } else if (leftSide === ""){
            leftSide = changeSign(parseFloat(display.innerText))
        } else {
            leftSide = changeSign(leftSide)
        }
    } else if (input === "Backspace") {
         if (isOperated) {
            if (!rightSide){
                operation = '';
                isOperated = false;
            }
            rightSide = deleteCharacter(rightSide);
        } else {
            leftSide = deleteCharacter(leftSide)
        }
    } else if (input === "clear") {
        handleClear();
        display.innerText = '';
        localStorage.clear;
    } else if (input === "memory-recall"){
        if (isOperated) {
            rightSide = getMemory() ? getMemory() : rightSide;
        } else {
            leftSide = getMemory() ? getMemory() : leftSide;
        }
    }
    updateDisplay()
}


function evaluateExpression(){
    if (leftSide && !rightSide) {
        return leftSide;
    } else {
        let parsedLeft = parseFloat(leftSide)
        let parsedRight = parseFloat(rightSide);
        let result;
        switch (operation) {
            case '+':
                result = parsedLeft + parsedRight;
                break;
            case '-':
                result = parsedLeft - parsedRight;
                break;
            case '*':
                result = parsedLeft * parsedRight;
                break;
            case '/':
                if (parsedRight === 0) {
                    result = "DIVIDE BY ZERO ERROR"
                    break;
                }
                result = parsedLeft / parsedRight;
                break;
        }
        isSolved = true;
        solution = result;
        updateDisplay();
    };
    handleClear();
}

calculator.addEventListener("click", (e)=> {
    if (handleInput(e)==="memory-set"){
        setMemory()}
    buildExpression(handleInput(e))
    if (handleInput(e)==="=") {
        evaluateExpression()
    }
})
body.addEventListener("keydown", (e)=> {
    buildExpression(handleInput(e))
    if (handleInput(e)==="="||handleInput(e)==="Enter") {
        evaluateExpression()
    }
})



1 Like

Having working code is a great starting point for improvements.

You are right buildExpression is looking a bit complicated, without a clear way to make it simpler. Perhaps instead of multiple times checking isOperated, checking it earlier would decide whether to handle left or right side.

Remember also to try to split changes into smaller steps. This helps with keeping things working during the modifications.

1 Like

I like the cactus theme :cactus:

I was pleasantly surprised to find the numpad working for input. However when I pressed “enter” it clears when I was expecting to see the result. I have to press the “=” key on the keyboard which is a bit awkward when doing calculations using the numpad.

Oops, enter appears to work now.

Or is it? If I press 55+5 with the mouse and hit enter the screen says 5.

If I keep hitting enter it keeps putting 5s into the screen.

I would expect the del key to clear the screen. Backspace works well.

If I click AC then on numpad “55+5 enter” it clears the screen.

So there’s a bit of UX hiccups here to look at.

If I refresh the page then on numpad “55+5 enter” it shows 60 :white_check_mark: But if I use the mouse then go back to keypad it no longer works correctly.

1 Like

Some feedback:

  1. Instead of manually checking *|+|-|/ with regex, keep a Set.
    const validOps = new Set([‘+’, ‘-’, ‘*’, ‘/’]);

  2. updateDisplay() currently mixes raw expression and solution logic. A cleaner split is:.
    function expressionString() {
    return sides.left + operation + sides.right;
    }

    function updateDisplay() {
    display.innerText = solution !== ‘’ ? solution : expressionString();
    }

  3. localStorage.clear; → doesn’t actually clear memory. You probably meant localStorage.clear().?