4
\$\begingroup\$

I'm learning oop and also trying to learn and implement some design patterns. This question is a follow up of Stopwatch interface OOP (Vanilla JS)

The app is available in https://nacho-p5.github.io/

Here's the code:

Repo: https://github.com/nacho-p5/nacho-p5.github.io

Community gave me good feedback and I made the following changes to my first codebase:

  • Changed syntax to ES6
  • Methods added to prototype rather than instance
  • Vars, attrs and methods names changed to be descriptive
  • Replaced error handler logic when start/stop are clicked twice
  • Time tracker logic changed. Count is calculated using Date.now() rather than counting setInterval iterations
  • Script tag position changed to head using defer
  • Stopwatch class should not know about the DOM -> Observer pattern implemented

As I wrote in the last point, I refactored the code using the observer pattern. I had two goals doing that:

  1. Implement the observer pattern
  2. One class - One thing: separation of concerns in stopwatch, ui and controller class

I know that trying to implement design patterns in a simple problem like this seems like overwork. This is purely for learning purposes.

What do you think about the code? Any feedback will be appreciated :)

stopwatch.js

class Stopwatch {
    _timer = 0;
    isRunning = false;
    startTime = 0;
    elapsedTime = 0
    observers = []

    get timer() {
        return this._timer
    }
    
    set timer(val) {
        this._timer = val
        this.notifyController(val)
    }

    registerObserver(observer) {
        this.observers.push(observer);
    };

    notifyController(val) {
        this.observers.forEach(observer => {observer.update(val)})
    }

    updateTime() {
        const newTime = Date.now() - this.startTime + this.elapsedTime;
        this.timer = newTime;
    };

    start() {
        if (!this.isRunning) {
            this.isRunning = true;
            this.startTime = Date.now();
            this.setIntervalID = setInterval(this.updateTime.bind(this), 100);
        };
    };

    stop() {
        if (this.isRunning) {
            clearInterval(this.setIntervalID);
            this.isRunning = false;
            this.elapsedTime = this._timer;
        };
    };

    reset() {
        clearInterval(this.setIntervalID);
        this.isRunning = false
        this.elapsedTime = 0;
        this.startTime = 0;
        this.timer = 0;
    };
};

class UI {
    constructor(displayID, btnStartID, btnStopID, btnResetID) {
        // HTML Components
        this.buttons = {
            start: document.getElementById(btnStartID),
            stop: document.getElementById(btnStopID),
            reset: document.getElementById(btnResetID)
        },
        this.display = document.getElementById(displayID)
    };

    resetAllButtonsStyle() {
        Object.values(this.buttons).forEach(e => e.classList.remove('activeBtn'))
    };

    showButtonAsActive(btn) {
        this.resetAllButtonsStyle();
        btn.classList.add('activeBtn')
    };

    updateDisplay(value) {
        this.display.innerText = value;
    };
}

class Controller {
    constructor(sw, ui) {
        this.sw = sw;
        this.ui = ui;

        // Add event listeners
        this.ui.buttons.start.addEventListener('click', function() {
            sw.start();
            ui.showButtonAsActive(this);
        });
        this.ui.buttons.stop.addEventListener('click', function() {
            if (sw.isRunning) {
                sw.stop();
                ui.showButtonAsActive(this);
            };
        });
        this.ui.buttons.reset.addEventListener('click', function() {
            sw.reset();
            ui.resetAllButtonsStyle();
        });
    }

    update(val) {
        ui.updateDisplay((val/1000).toFixed(3))
    }
}


// Initialize classes

const ui = new UI('sw-display', 'btnStart', 'btnStop', 'btnReset');
const sw = new Stopwatch();
const controller = new Controller(sw, ui);

// Register controller in sw 
sw.registerObserver(controller);

////////////////////////

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.