Search code examples
javascripttypescriptdesign-patternses6-proxy

Registering property changes of an object from another object


In my project, I have a widgetBar that can contain one or more widgets.

When clicking on the widget icon in the widgetBar, a panel is opened to show the conent of the pressed widget (kind of a navigation bar with all dropdown menus).

Each widget is a class that has a togglePanel() method that is bind to an onlcick event on the widget's icon and openes/closes the panel. This method also sets the widget's isActive property to either true (when panel opens), or false (when panel closes).

class Widget {
    isActive: boolean = false;
    element: HTMLElement;
    id: string;

    constructor(id: string) {
        this.id = id;
        this.element = document.crateElement('DIV');
        // ... this.element and other things represent the widget DOM object
        this.element.onclick = this.togglePanel.bind(this);
    }

    togglePanel() {
        if (this.panel.classList.contains('active')) {
            this.openPanel();
            this.isActive = true;
        } else {
            this.closePanel();
            this.isActive = false;
        }
    }
}

The WidgetBar constructor takes a list of widgets as argument that becomes its this.widgets property. It also has a getWidgets() methos that gives access to this property.

class WidgetBar {
    private widgets: Widget[];

    constructor(widgets: Widget[]) {
        this.widgets = widgets;
    }

    getWidgets() {
        return this.widgets;
    }
}

The (my project's) rule is that whenever a widget opens its panel, all the other panels should be closed.

How may I automatically check from the WidgetBar if one of its widgets has changed its isActive property so that I can make sure no more than one panel at a time is opened?

I read about Proxy, but I am not sure this is what could help me solving this problem.

EDIT

What about Observables?

Would it be a good idea to make isActive property of the Widget class an Observable, and subscribe from WidgetBar to listen for any update, like explained in this answer?


Solution

  • It's been a bit tricky for me, but finally I used rxjs Subjects to solve my issue.

    What I did was to create a new property this.widgetActivated$ = new Subject(); which I populate calling .next() with an object like {id: currentWidget.id, isActive: currentWidget.isActive} whenever a widget panel is opened. Then I subscribe to it and call closePanel() on the other widgets which are active and have a different id.

    This is the updated code:

    interface widgetStatus {
        id: string;
        isActive: boolean;
    }
    
    class WidgetBar {
        private widgets: Widget[];
        private widgetActivated$: Subject<widgetStatus>; // IMPORTANT CHANGE
    
        constructor(widgets: Widget[]) {
            this.widgetActivated$ = new Subject(); // IMPORTANT CHANGE
            this.widgets = widgets;
        }
    
        getWidgets() {
            return this.widgets;
        }
    
        private widgetHandler(widget: Widget) {
            fromEvent(widget.widgetBox, 'click').subscribe(
                () => this.toggleWidgetPanel(widget)
            );
            this.widgetActivated$.subscribe(status => {
                const widgetsToClose = this.getWidgets().filter(widget => 
                    widget.isActive === true && widget.id !== status.id);
                if (widgetsToClose) {
                    widgetsToClose.forEach(widgetToClose => this.closePanel(widgetToClose));
                }
            })
        }
    
        private toggleWidgetPanel(widget: Widget) {
            if (!widget.getPanel().classList.contains("active")) {
                this.openPanel(widget);
            } else {
                this.closePanel(widget);
            }
        }
    
        private openPanel(widget: Widget) {
            widget.getPanel().classList.add("active");
            widget.isActive = true;
            this.widgetActivated$.next(this.getWidgetStatus(widget)); // IMPORTANT CHANGE
        }
    
        private closePanel(widget: Widget) {
            widget.getPanel().classList.remove("active");
        }
    
        private getWidgetStatus(widget: Widget): widgetStatus {
            return {id: widget.id, isActive: widget.isActive};
        }
    
    }
    

    I also converted this.element.onclick = this.togglePanel.bind(this); to be fromEvent(widget.widgetBox, 'click').subscribe(() => this.toggleWidgetPanel(widget)); as I felt it is more readable and it didn't bring lot of issues about thisArg context (see this other question of mine).