Untitled

 avatar
user_5659935
javascript
9 months ago
14 kB
12
Indexable
// Constants
const CONFIG = {
    MAX_DIGITS: 16,
    LOCALE: 'de-DE',
    MAX_DECIMALS: 10,
    DECIMAL_SEPARATOR: ',',
    THOUSANDS_SEPARATOR: '.'
};

const OPERATORS = {
    ADD: '+',
    SUBTRACT: '−',
    MULTIPLY: '×',
    DIVIDE: '÷'
};

// Display Manager Class
class DisplayManager {
    constructor(displayElement, equationElement) {
        this.display = displayElement;
        this.equation = equationElement;
    }

    updateResult(value) {
        this.display.textContent = this.format(value);
    }

    updateEquation(text) {
        this.equation.textContent = text;
    }

    clearEquation() {
        this.equation.textContent = '';
    }

    format(value) {
        if (value === 'Error' || !isFinite(value)) {
            return 'Error';
        }

        return value.toLocaleString(CONFIG.LOCALE, {
            maximumFractionDigits: CONFIG.MAX_DECIMALS
        });
    }

    getCurrentValue() {
        const displayText = this.display.textContent;
        if (displayText === 'Error') return 0;

        // Remove thousands separators and replace decimal separator
        let cleanString = displayText.replace(new RegExp('\\' + CONFIG.THOUSANDS_SEPARATOR, 'g'), '');
        cleanString = cleanString.replace(CONFIG.DECIMAL_SEPARATOR, '.');
        
        return parseFloat(cleanString) || 0;
    }
}

// History Manager Class
class HistoryManager {
    constructor(listElement, noHistoryElement) {
        this.list = listElement;
        this.noHistoryMessage = noHistoryElement;
    }

    addEntry(equation, result) {
        if (this.noHistoryMessage) {
            this.noHistoryMessage.style.display = 'none';
        }

        const item = document.createElement('li');
        
        const equationSpan = document.createElement('span');
        equationSpan.className = 'history-equation';
        equationSpan.textContent = equation;

        const resultSpan = document.createElement('span');
        resultSpan.className = 'history-result';
        resultSpan.textContent = result;

        item.appendChild(equationSpan);
        item.appendChild(resultSpan);
        this.list.appendChild(item);
    }

    clear() {
        this.list.innerHTML = '';
        if (this.noHistoryMessage) {
            this.noHistoryMessage.style.display = 'block';
        }
    }
}

// Input Handler Class
class InputHandler {
    constructor(calculator) {
        this.calc = calculator;
        this.currentInput = '0';
    }

    handleDigit(digit) {
        if (this.calc.waitingForNewInput) {
            this.currentInput = digit;
            this.calc.waitingForNewInput = false;
        } else {
            const cleanInput = this.currentInput.replace(new RegExp('\\' + CONFIG.THOUSANDS_SEPARATOR, 'g'), '');
            const digitCount = cleanInput.replace(CONFIG.DECIMAL_SEPARATOR, '').length;

            if (digitCount >= CONFIG.MAX_DIGITS) {
                return this.currentInput;
            }

            if (this.currentInput === '0') {
                this.currentInput = digit;
            } else {
                this.currentInput += digit;
            }
        }

        return this.formatInput(this.currentInput);
    }

    handleDecimal() {
        if (this.calc.waitingForNewInput) {
            this.currentInput = '0' + CONFIG.DECIMAL_SEPARATOR;
            this.calc.waitingForNewInput = false;
        } else if (!this.currentInput.includes(CONFIG.DECIMAL_SEPARATOR)) {
            this.currentInput += CONFIG.DECIMAL_SEPARATOR;
        }

        return this.formatInput(this.currentInput);
    }

    formatInput(value) {
        const parts = value.split(CONFIG.DECIMAL_SEPARATOR);
        const integerPart = parts[0] || '0';
        const decimalPart = parts[1];

        let formatted = parseFloat(integerPart).toLocaleString(CONFIG.LOCALE, {
            maximumFractionDigits: 0
        });

        if (decimalPart !== undefined) {
            formatted += CONFIG.DECIMAL_SEPARATOR + decimalPart;
        }

        this.currentInput = value;
        return formatted;
    }

    reset() {
        this.currentInput = '0';
    }

    backspace(currentDisplay) {
        let current = currentDisplay.replace(new RegExp('\\' + CONFIG.THOUSANDS_SEPARATOR, 'g'), '');
        
        if (current.length <= 1 || current === '0') {
            this.currentInput = '0';
            return '0';
        }

        current = current.slice(0, -1);
        this.currentInput = current;
        return this.formatInput(current);
    }
}

// Calculator Engine Class
class CalculatorEngine {
    constructor(displayManager, historyManager) {
        this.display = displayManager;
        this.history = historyManager;
        this.inputHandler = new InputHandler(this);
        
        this.firstValue = null;
        this.operator = null;
        this.waitingForNewInput = false;
    }

    handleNumber(value) {
        let result;
        if (value === '.') {
            result = this.inputHandler.handleDecimal();
        } else {
            result = this.inputHandler.handleDigit(value);
        }
        this.display.updateResult(result);
    }

    handleOperator(op) {
        const currentValue = this.display.getCurrentValue();

        // Chain calculations: if we have a pending operation, calculate it first
        if (this.firstValue !== null && this.operator !== null && !this.waitingForNewInput) {
            const result = this.calculate(this.firstValue, this.operator, currentValue);
            
            const equation = `${this.display.format(this.firstValue)} ${this.operator} ${this.display.format(currentValue)} =`;
            const formattedResult = this.display.format(result);
            
            this.history.addEntry(equation, formattedResult);
            this.display.updateResult(formattedResult);
            
            this.firstValue = result;
        } else {
            // Only set firstValue if we're starting fresh or changing operators
            this.firstValue = currentValue;
        }

        this.operator = op;
        this.waitingForNewInput = true;
        this.display.updateEquation(`${this.display.format(this.firstValue)} ${op}`);
    }

    handleEquals() {
        // Don't do anything if there's no complete operation
        // Must have: firstValue, operator, and NOT waiting for input
        if (this.firstValue === null || this.operator === null || this.waitingForNewInput) {
            return;
        }

        const secondValue = this.display.getCurrentValue();
        const result = this.calculate(this.firstValue, this.operator, secondValue);

        const equation = `${this.display.format(this.firstValue)} ${this.operator} ${this.display.format(secondValue)} =`;
        const formattedResult = this.display.format(result);

        this.history.addEntry(equation, formattedResult);
        this.display.updateResult(formattedResult);
        this.display.clearEquation();

        this.firstValue = result;
        this.operator = null;
        this.waitingForNewInput = true;
    }

    calculate(first, op, second) {
        try {
            let result;
            
            switch(op) {
                case OPERATORS.ADD:
                    result = first + second;
                    break;
                case OPERATORS.SUBTRACT:
                    result = first - second;
                    break;
                case OPERATORS.MULTIPLY:
                    result = first * second;
                    break;
                case OPERATORS.DIVIDE:
                    if (second === 0) {
                        throw new Error('Cannot divide by zero');
                    }
                    result = first / second;
                    break;
                default:
                    result = second;
            }

            if (!isFinite(result)) {
                throw new Error('Result is not finite');
            }

            return result;
        } catch (error) {
            console.error('Calculation error:', error);
            return 'Error';
        }
    }

    handlePercent() {
        const currentValue = this.display.getCurrentValue();
        const result = currentValue / 100;
        this.display.updateResult(result);
        this.waitingForNewInput = true;
    }

    handleSquareRoot() {
        const currentValue = this.display.getCurrentValue();
        
        if (currentValue < 0) {
            this.display.updateResult('Error');
        } else {
            const result = Math.sqrt(currentValue);
            this.display.updateResult(result);
        }
        
        this.waitingForNewInput = true;
    }

    handleNegate() {
        const currentValue = this.display.getCurrentValue();
        this.display.updateResult(-currentValue);
    }

    handleClear() {
        this.firstValue = null;
        this.operator = null;
        this.waitingForNewInput = false;
        this.inputHandler.reset();
        this.display.updateResult('0');
        this.display.clearEquation();
    }

    handleClearEntry() {
        this.inputHandler.reset();
        this.display.updateResult('0');
    }

    handleBackspace() {
        const currentDisplay = this.display.display.textContent;
        const result = this.inputHandler.backspace(currentDisplay);
        this.display.updateResult(result);
    }
}

// UI Controller Class
class UIController {
    constructor() {
        this.initializeElements();
        this.initializeManagers();
        this.bindEvents();
    }

    initializeElements() {
        this.menuBtn = document.querySelector('.menu-btn');
        this.appContainer = document.querySelector('.calculator-app');
        this.keypad = document.querySelector('.keypad');
    }

    initializeManagers() {
        const displayElement = document.querySelector('.result-display');
        const equationElement = document.querySelector('.equation-display');
        const historyList = document.querySelector('.history-list');
        const noHistoryMsg = document.querySelector('.no-history-message');

        this.displayManager = new DisplayManager(displayElement, equationElement);
        this.historyManager = new HistoryManager(historyList, noHistoryMsg);
        this.calculator = new CalculatorEngine(this.displayManager, this.historyManager);
    }

    bindEvents() {
        // Sidebar toggle
        this.menuBtn.addEventListener('click', () => {
            this.appContainer.classList.toggle('sidebar-open');
        });

        // Single event listener for all calculator buttons
        this.keypad.addEventListener('click', (e) => {
            const btn = e.target.closest('button');
            if (!btn || !btn.dataset.action) return;

            this.handleButtonClick(btn);
        });

        // Keyboard support
        document.addEventListener('keydown', (e) => this.handleKeyboard(e));
    }

    handleButtonClick(btn) {
        const action = btn.dataset.action;

        switch(action) {
            case 'number':
                this.calculator.handleNumber(btn.dataset.value);
                break;
            case 'operator':
                this.calculator.handleOperator(btn.dataset.operator);
                break;
            case 'equals':
                this.calculator.handleEquals();
                break;
            case 'clear':
                this.calculator.handleClear();
                break;
            case 'clear-entry':
                this.calculator.handleClearEntry();
                break;
            case 'backspace':
                this.calculator.handleBackspace();
                break;
            case 'percent':
                this.calculator.handlePercent();
                break;
            case 'sqrt':
                this.calculator.handleSquareRoot();
                break;
            case 'negate':
                this.calculator.handleNegate();
                break;
        }
    }

    handleKeyboard(e) {
        const keyMap = {
            '0': () => this.calculator.handleNumber('0'),
            '1': () => this.calculator.handleNumber('1'),
            '2': () => this.calculator.handleNumber('2'),
            '3': () => this.calculator.handleNumber('3'),
            '4': () => this.calculator.handleNumber('4'),
            '5': () => this.calculator.handleNumber('5'),
            '6': () => this.calculator.handleNumber('6'),
            '7': () => this.calculator.handleNumber('7'),
            '8': () => this.calculator.handleNumber('8'),
            '9': () => this.calculator.handleNumber('9'),
            '.': () => this.calculator.handleNumber('.'),
            ',': () => this.calculator.handleNumber('.'),
            '+': () => this.calculator.handleOperator(OPERATORS.ADD),
            '-': () => this.calculator.handleOperator(OPERATORS.SUBTRACT),
            '*': () => this.calculator.handleOperator(OPERATORS.MULTIPLY),
            '/': () => { e.preventDefault(); this.calculator.handleOperator(OPERATORS.DIVIDE); },
            'Enter': () => { e.preventDefault(); this.calculator.handleEquals(); },
            '=': () => this.calculator.handleEquals(),
            'Escape': () => this.calculator.handleClear(),
            'c': () => this.calculator.handleClear(),
            'C': () => this.calculator.handleClear(),
            'Backspace': () => { e.preventDefault(); this.calculator.handleBackspace(); },
            '%': () => this.calculator.handlePercent()
        };

        const handler = keyMap[e.key];
        if (handler) {
            handler();
        }
    }
}

// Initialize the calculator
const calculator = new UIController();
Editor is loading...
Leave a Comment