Skip to content

Commit f9e23ba

Browse files
Attach listeners to data-modal when they become visible (#206)
1 parent cb7e40d commit f9e23ba

2 files changed

Lines changed: 53 additions & 2 deletions

File tree

src/components/modal.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ let $openModal: Modal|undefined = undefined;
1515
let lastFocusElement: HTMLElement|undefined = undefined;
1616

1717
const TITLE_ID = 'boost-modal-title';
18+
const elementsWithModalListeners = new WeakSet();
1819

1920
function tryClose() {
2021
if ($openModal && $openModal.canClose) $openModal.close();
@@ -51,12 +52,12 @@ export class Modal extends CustomElementView {
5152
this.$video = this.$('video') as MediaView|undefined;
5253

5354
const $buttons = $$(`[data-modal=${this.id}]`);
54-
for (const $b of $buttons) $b.on('click', () => this.open());
55+
for (const $b of $buttons) this.attachListener($b);
5556

5657
// Look for new modals to open, after browser navigation.
5758
Router.on('afterChange', ({$viewport}) => {
5859
const $buttons = $viewport.$$(`[data-modal=${this.id}]`);
59-
for (const $b of $buttons) $b.on('click', () => this.open());
60+
for (const $b of $buttons) this.attachListener($b);
6061
});
6162

6263
// Open modals that are shown on pageload
@@ -90,6 +91,26 @@ export class Modal extends CustomElementView {
9091
});
9192
}
9293

94+
/**
95+
* Attaches a click listener to an element with data-modal attribute, if there isn't one already.
96+
*/
97+
attachListener($button: ElementView) {
98+
if (elementsWithModalListeners.has($button._el)) return;
99+
100+
$button.on('click', () => this.open());
101+
elementsWithModalListeners.add($button._el);
102+
}
103+
104+
/**
105+
* Removes the click listener from an element
106+
*/
107+
removeListener($button: ElementView) {
108+
if (!elementsWithModalListeners.has($button._el)) return;
109+
110+
$button.off('click');
111+
elementsWithModalListeners.delete($button._el);
112+
}
113+
93114
open(noAnimation = false) {
94115
if (this.isOpen) return;
95116

src/elements.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,11 +197,41 @@ export abstract class BaseView<T extends HTMLElement|SVGElement> {
197197

198198
if (show) {
199199
this.$placeholder.insertBefore(this);
200+
this.reattachModalListenersInSubtree();
200201
} else {
202+
this.removeModalListenersInSubtree();
201203
this.detach();
202204
}
203205
}
204206

207+
private reattachModalListenersInSubtree() {
208+
this.updateModalListenersInSubtree((modal, el) => modal.attachListener?.(el));
209+
}
210+
211+
private removeModalListenersInSubtree() {
212+
this.updateModalListenersInSubtree((modal, el) => modal.removeListener?.(el));
213+
}
214+
215+
/**
216+
* Updates modal listeners for `this` element and its descendants with data-modal attributes.
217+
* Used when elements are shown/hidden via :if to attach/remove listeners
218+
*/
219+
private updateModalListenersInSubtree(
220+
operation: (modal: Modal, element: ElementView) => void
221+
) {
222+
const modalElements: ElementView[] = this.hasAttr('data-modal') ? [this] : [];
223+
const childElementsWithModalAttrs = this.$$('[data-modal]');
224+
modalElements.push(...childElementsWithModalAttrs);
225+
226+
for (const $el of modalElements) {
227+
const modalId = $el.attr('data-modal');
228+
if (!modalId) continue;
229+
230+
const $modal = $(`x-modal#${modalId}`) as Modal;
231+
if ($modal) operation($modal, $el);
232+
}
233+
}
234+
205235
private makeDynamicAttribute(name: string, value: string, model: Observable) {
206236
if (name.startsWith('@')) {
207237
const event = name.slice(1);

0 commit comments

Comments
 (0)