Skip to content

Commit b7bdce4

Browse files
committed
Blazor event registry + Updated API
1 parent 1af8316 commit b7bdce4

File tree

8 files changed

+107
-56
lines changed

8 files changed

+107
-56
lines changed

src/Components/Web.JS/dist/Release/blazor.server.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Components/Web.JS/dist/Release/blazor.web.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Components/Web.JS/dist/Release/blazor.webview.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Components/Web.JS/src/Boot.Web.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import { NavigationEnhancementCallbacks, attachProgressivelyEnhancedNavigationLi
1919
import { WebRootComponentManager } from './Services/WebRootComponentManager';
2020
import { hasProgrammaticEnhancedNavigationHandler, performProgrammaticEnhancedNavigation } from './Services/NavigationUtils';
2121
import { attachComponentDescriptorHandler, registerAllComponentDescriptors } from './Rendering/DomMerging/DomSync';
22-
import { CallbackCollection } from './Services/CallbackCollection';
2322

2423
let started = false;
2524

@@ -44,14 +43,12 @@ function boot(options?: Partial<WebStartOptions>) : Promise<void> {
4443
setWebAssemblyOptions(options?.webAssembly);
4544

4645
const rootComponentManager = new WebRootComponentManager(options?.ssr?.circuitInactivityTimeoutMs ?? 2000);
47-
const enhancedPageUpdateCallbacks = new CallbackCollection();
48-
49-
Blazor.registerEnhancedPageUpdateCallback = (callback) => enhancedPageUpdateCallbacks.registerCallback(callback);
46+
const enqueueDispatchEnhancedLoad = createEnhancedLoadDispatcher();
5047

5148
const navigationEnhancementCallbacks: NavigationEnhancementCallbacks = {
5249
documentUpdated: () => {
5350
rootComponentManager.onDocumentUpdated();
54-
enhancedPageUpdateCallbacks.enqueueCallbackInvocation();
51+
enqueueDispatchEnhancedLoad();
5552
},
5653
};
5754

@@ -68,6 +65,24 @@ function boot(options?: Partial<WebStartOptions>) : Promise<void> {
6865
return Promise.resolve();
6966
}
7067

68+
// This function ensures that 'enhancedload' only gets invoked once
69+
// for any synchronous sequence of document updates via SSR.
70+
function createEnhancedLoadDispatcher() {
71+
let isDispatchPending = false;
72+
73+
return function() {
74+
if (isDispatchPending) {
75+
return;
76+
}
77+
78+
isDispatchPending = true;
79+
setTimeout(() => {
80+
isDispatchPending = false;
81+
Blazor._internal.dispatchEvent('enhancedload', {});
82+
}, 0);
83+
};
84+
}
85+
7186
Blazor.start = boot;
7287
window['DotNet'] = DotNet;
7388

src/Components/Web.JS/src/GlobalExports.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { RootComponentsFunctions } from './Rendering/JSRootComponents';
1818
import { attachWebRendererInterop } from './Rendering/WebRendererInteropMethods';
1919
import { WebStartOptions } from './Platform/WebStartOptions';
2020
import { RuntimeAPI } from 'dotnet';
21+
import { EventRegistry } from './Services/EventRegistry';
2122

2223
// TODO: It's kind of hard to tell which .NET platform(s) some of these APIs are relevant to.
2324
// It's important to know this information when dealing with the possibility of mulitple .NET platforms being available.
@@ -29,11 +30,12 @@ import { RuntimeAPI } from 'dotnet';
2930
// * Blazor._internal.{foo}: internal, platform-agnostic Blazor APIs
3031
// * Blazor.platform.{somePlatformName}.{foo}: public, platform-specific Blazor APIs (would be empty at first, so no initial breaking changes)
3132
// * Blazor.platform.{somePlatformName}.{_internal}.{foo}: internal, platform-specific Blazor APIs
32-
interface IBlazor {
33+
export interface IBlazor {
3334
navigateTo: (uri: string, options: NavigationOptions) => void;
3435
registerCustomEventType: (eventName: string, options: EventTypeOptions) => void;
3536

36-
registerEnhancedPageUpdateCallback?: (callback: () => void) => { dispose(): void };
37+
addEventListener: typeof EventRegistry.prototype.addEventListener;
38+
removeEventListener: typeof EventRegistry.prototype.removeEventListener;
3739
disconnect?: () => void;
3840
reconnect?: (existingConnection?: HubConnection) => Promise<boolean>;
3941
defaultReconnectionHandler?: DefaultReconnectionHandler;
@@ -47,6 +49,7 @@ interface IBlazor {
4749
domWrapper: typeof domFunctions;
4850
Virtualize: typeof Virtualize;
4951
PageTitle: typeof PageTitle;
52+
dispatchEvent: typeof EventRegistry.prototype.dispatchEvent;
5053
forceCloseConnection?: () => Promise<void>;
5154
InputFile?: typeof InputFile;
5255
NavigationLock: typeof NavigationLock;
@@ -91,9 +94,13 @@ interface IBlazor {
9194
}
9295
}
9396

97+
const eventRegistry = new EventRegistry();
98+
9499
export const Blazor: IBlazor = {
95100
navigateTo,
96101
registerCustomEventType,
102+
addEventListener: eventRegistry.addEventListener.bind(eventRegistry),
103+
removeEventListener: eventRegistry.removeEventListener.bind(eventRegistry),
97104
rootComponents: RootComponentsFunctions,
98105
runtime: {} as RuntimeAPI,
99106

@@ -106,8 +113,11 @@ export const Blazor: IBlazor = {
106113
NavigationLock,
107114
getJSDataStreamChunk: getNextChunk,
108115
attachWebRendererInterop,
116+
dispatchEvent: eventRegistry.dispatchEvent.bind(eventRegistry),
109117
},
110118
};
111119

120+
eventRegistry.attachBlazorInstance(Blazor);
121+
112122
// Make the following APIs available in global scope for invocation from JS
113123
window['Blazor'] = Blazor;

src/Components/Web.JS/src/Services/CallbackCollection.ts

Lines changed: 0 additions & 35 deletions
This file was deleted.
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
import { IBlazor } from '../GlobalExports';
5+
6+
// The base Blazor event type.
7+
// Properties listed here get assigned by the event registry in 'dispatchEvent'.
8+
interface BlazorEvent {
9+
blazor: IBlazor;
10+
type: keyof BlazorEventMap;
11+
}
12+
13+
// Maps Blazor event names to the argument type passed to registered listeners.
14+
export interface BlazorEventMap {
15+
'enhancedload': BlazorEvent;
16+
}
17+
18+
export class EventRegistry {
19+
private readonly _eventListeners = new Map<string, Set<(ev: any) => void>>();
20+
21+
private _blazor: IBlazor | null = null;
22+
23+
public attachBlazorInstance(blazor: IBlazor) {
24+
this._blazor = blazor;
25+
}
26+
27+
public addEventListener<K extends keyof BlazorEventMap>(type: K, listener: (ev: BlazorEventMap[K]) => void): void {
28+
let listenersForEventType = this._eventListeners.get(type);
29+
if (!listenersForEventType) {
30+
listenersForEventType = new Set();
31+
this._eventListeners.set(type, listenersForEventType);
32+
}
33+
34+
listenersForEventType.add(listener);
35+
}
36+
37+
public removeEventListener<K extends keyof BlazorEventMap>(type: K, listener: (ev: BlazorEventMap[K]) => void): void {
38+
const listenersForEventType = this._eventListeners.get(type);
39+
if (!listenersForEventType) {
40+
return;
41+
}
42+
43+
listenersForEventType.delete(listener);
44+
}
45+
46+
public dispatchEvent<K extends keyof BlazorEventMap>(type: K, ev: Omit<BlazorEventMap[K], keyof BlazorEvent>): void {
47+
if (this._blazor === null) {
48+
throw new Error('Blazor events cannot be dispatched until a Blazor instance gets attached');
49+
}
50+
51+
const listenersForEventType = this._eventListeners.get(type);
52+
if (!listenersForEventType) {
53+
return;
54+
}
55+
56+
const event: BlazorEventMap[K] = {
57+
...ev,
58+
blazor: this._blazor,
59+
type,
60+
};
61+
62+
for (const listener of listenersForEventType) {
63+
listener(event);
64+
}
65+
}
66+
}

src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/EnhancedNav/PageThatPreservesContent.razor

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,17 @@
2222
preservedContent.textContent = 'Preserved content';
2323
nonPreservedContent.textContent = 'Non preserved content';
2424
25-
let registration = null;
25+
const onEnhancedLoad = (ev) => {
26+
window.enhancedPageUpdateCount++;
27+
}
2628
2729
window.listenForEnhancedUpdates = () => {
28-
if (!registration) {
29-
window.enhancedPageUpdateCount = 0;
30-
registration = Blazor.registerEnhancedPageUpdateCallback(() => {
31-
window.enhancedPageUpdateCount++;
32-
});
33-
}
30+
window.enhancedPageUpdateCount = 0;
31+
Blazor.addEventListener('enhancedload', onEnhancedLoad);
3432
};
3533
3634
window.stopListeningForEnhancedUpdates = () => {
37-
if (registration) {
38-
registration.dispose();
39-
registration = null;
40-
}
35+
Blazor.removeEventListener('enhancedload', onEnhancedLoad);
4136
};
4237
</script>
4338

0 commit comments

Comments
 (0)