In my project, I have a widgetBar
that can contain one or more widget
s.
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 widget
s 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 widget
s 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.
What about Observable
s?
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?
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).