7
\$\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

The 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 the instance
  • Names changed to be descriptive
  • Replaced error handler logic when start/stop are clicked twice
  • Time tracker logic changed. The count is calculated using Date.now() rather than counting setInterval iterations
  • Script tag position changed to head using defer
  • The 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\$

1 Answer 1

1
\$\begingroup\$

This is overall very good-looking; I would be willing to support/maintain this.

I could only find one thing, which is your use of semicolons: sometimes they are missing, and sometimes they are extraneous. You can use https://jshint.com/ to check for this.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.