Power Pages React CustomEvents: Suchergebnisse entkoppeln
Power Pages React CustomEvents trennen Suchzustand und Ergebnisse. JavaScript, jQuery und React konsumieren geprüfte Verträge ohne gemeinsamen App State.
Inhalt
- Warum Power Pages Suchergebnisse nicht im React Widget gerendert werden
- Welche CustomEvents braucht eine Power Pages React Suche?
- Was enthält das search-changed Event in Power Pages?
- Warum wird der Power Pages Suchzustand vor dem Ergebnis veröffentlicht?
- Was enthält das search-results Event in React?
- Wie verhindert React veraltete Suchergebnisse bei CustomEvents?
- Warum besitzen Browser CustomEvents kein Replay?
- Wie liest Vanilla JavaScript Power Pages CustomEvents?
- Wie liest jQuery das detail-Feld eines CustomEvents?
- Wie registriert React einen CustomEvent Listener korrekt?
- Warum dürfen Consumer den CustomEvent Payload nicht verändern?
- Wie beeinflussen langsame Listener die CustomEvent-Auslieferung?
- Wie versionieren Power Pages Event-Verträge?
- Wann sind Browser CustomEvents in Power Pages sinnvoll?
Warum Power Pages Suchergebnisse nicht im React Widget gerendert werden
Eine komplexe Power Pages Suche kann schnell zu einer einzigen großen React Anwendung werden.
Das Suchformular kennt dann die Ergebnisliste. Die Ergebnisliste greift auf denselben React Context zu. Eine Kartenansicht benötigt dieselben Hooks. Bestehendes JavaScript muss entweder ersetzt oder in das Bundle eingebaut werden.
Diese Kopplung funktioniert, solange ein Team alle Oberflächen gemeinsam ausliefert.
In Power Pages ist das oft nicht der reale Projektkontext. Web Templates, bestehende jQuery Komponenten und neue React Anwendungen entwickeln sich unabhängig. Sie können unterschiedliche Release Zyklen besitzen.
Ich lasse das Suchwidget deshalb zwei Browser Events veröffentlichen. Das erste beschreibt einen akzeptierten Suchzustand. Das zweite liefert eine geparste Suchantwort.
Die Architekturübersicht der Power Pages Enterprise-Suche ordnet diese Event Grenze in den vollständigen Weg von React über Server Logic bis Dataverse ein.
Welche CustomEvents braucht eine Power Pages React Suche?
Ein einziges Event wäre zu ungenau.
Die Anwendung muss unterscheiden können, ob ein neuer Suchzustand akzeptiert wurde oder ob das Backend bereits eine Antwort geliefert hat.
Ich verwende dafür zwei generalisierte Namen:
const SEARCH_CHANGED_EVENT_NAME = 'portal:search-changed';
const SEARCH_RESULTS_EVENT_NAME = 'portal:search-results';search-changed wird nach einer URL Aktualisierung oder Reparatur und in jedem Fall vor dem vollständigen Search Request ausgelöst.
search-results folgt erst, wenn der aktuelle Request eine Antwort geliefert hat und der Laufzeitparser sie akzeptiert hat.
Die Namen im produktiven Portal bleiben projektspezifisch. Der Vertrag ist wichtiger als das Präfix.
Der Zustands Event gehört nur zum Ergebnisseiten Modus update-url. Die Landingpage im Modus navigate wechselt direkt zur Zielseite und veröffentlicht kein search-changed Event.
Auch die Auswahl eines Vorschlags veröffentlicht kein Zustands Event. Der Beitrag über robustes Auto-Suggest in Power Pages beschreibt, warum die Auswahl nur den Suchtext setzt.
Aspekt | portal:search-changed | portal:search-results |
|---|---|---|
Zeitpunkt | Nach akzeptierter URL Aktualisierung oder Reparatur | Nach Antwort und erfolgreicher Laufzeitprüfung |
Payload | Ursache, Filter, Query String und URL | Geprüfte Custom API Antwort |
Bedeutung | Neuer Suchzustand wurde angenommen | Aktueller Request lieferte eine strukturell akzeptierte Antwort |
Event Name | Über Host Konfiguration anpassbar | In aktueller Implementierung fest |
Landingpage | Wird nicht veröffentlicht | Wird nicht veröffentlicht |
Was enthält das search-changed Event in Power Pages?
Das Zustands Event enthält keine HTTP Antwort:
type SearchChangedCause = 'initial-load' | 'submit' | 'popstate' | 'reset';
interface SearchFilters {
readonly searchText: string;
readonly placeTypes: readonly number[];
readonly areaMin: number | null;
readonly areaMax: number | null;
}
interface SearchChangedEventDetail {
readonly version: 1;
readonly cause: SearchChangedCause;
readonly filters: SearchFilters;
readonly queryString: string;
readonly url: string;
}Die Ursache macht den Auslöser sichtbar:
Ursache | Bedeutung |
|---|---|
initial-load | Ergebnisseite hat ihren Zustand aus der URL hergestellt |
submit | Portalnutzer hat eine neue Suche abgesendet |
popstate | Vor oder Zurück hat einen früheren URL Zustand aktiviert |
reset | Externes Reset Signal hat die angewendeten Filter zurückgesetzt |
Das Feld version: 1 gehört zum Event Vertrag. Consumer können unbekannte Versionen ablehnen, bevor sie Felder interpretieren.
Die sichtbare Reset Aktion innerhalb des Widgets nutzt aktuell den normalen Submit Ablauf. Die Ursache reset kennzeichnet nur das externe Reset Signal.
Der Beitrag über URL Zustand und Browserhistorie zeigt, wie initial-load, submit und popstate den Suchzustand vorbereiten.
Warum wird der Power Pages Suchzustand vor dem Ergebnis veröffentlicht?
Eine neue Suche auf der Ergebnisseite läuft in dieser Reihenfolge:
function executeSearch(nextFilters: SearchFilters): void {
const preparation = controller.prepare(nextFilters);
if (!preparation.isValid) {
showValidationError(preparation.validationErrors[0] ?? 'Die Suchfilter sind ungültig.');
return;
}
const url = updateCurrentSearchUrl(preparation.prepared.serializedSearch.queryString);
dispatchSearchChanged(
SEARCH_CHANGED_EVENT_NAME,
'submit',
preparation.prepared.filters,
preparation.prepared.serializedSearch.queryString,
url
);
runPreparedSearch(preparation.prepared);
}Ein Consumer kann dadurch sofort einen Ladezustand anzeigen, die aktuelle URL dokumentieren oder alte Ergebnisdetails schließen.
Das Event verspricht noch keinen Erfolg.
Wenn Server Logic, Dataverse oder der Parser später einen Fehler liefern, wurde search-changed bereits ausgelöst. Es wird nicht zurückgenommen. Ein Consumer darf das Event deshalb nicht als erfolgreiche Ergebnislieferung interpretieren.
Der Name des Zustands Events kann über die JSON Konfiguration des zentralen React Hosts angepasst werden. Das Ergebnis Event verwendet in der aktuellen Implementierung dagegen einen festen Namen.
Was enthält das search-results Event in React?
Der Ergebnisvertrag ist eine generalisierte Form der bereits geprüften Custom API Antwort:
interface SearchResultItem {
readonly id: string;
readonly name: string;
}
interface SearchResponse {
readonly lcid: number;
readonly createdAt: string;
readonly objects: readonly SearchResultItem[];
}
interface SearchApiData<TContent> {
readonly IsSucceeded: boolean;
readonly Content: TContent;
}
type SearchResultsEventDetail = SearchApiData<SearchResponse>;Das Widget veröffentlicht die Antwort nach dem Parser:
function dispatchSearchResults(eventName: string, response: SearchResultsEventDetail): void {
window.dispatchEvent(
new CustomEvent<SearchResultsEventDetail>(eventName, {
detail: response,
})
);
}Die CustomEvent API stellt den anwendungsspezifischen Payload über detail bereit.
Das Widget ruft diese Funktion immer mit SEARCH_RESULTS_EVENT_NAME auf. Anders als der Name des Zustands Events kommt dieser Wert nicht aus der Laufzeitkonfiguration.
Der Event bedeutet, dass Transport und Parser eine strukturell akzeptierte Antwort geliefert haben. Er bedeutet nicht automatisch, dass IsSucceeded den Wert true besitzt. Consumer prüfen dieses Feld weiterhin.
Der Beitrag über typisierte React und Dataverse Verträge zeigt, welche Parser vor dieser Übergabe liegen.
Wie verhindert React veraltete Suchergebnisse bei CustomEvents?
Ein Benutzer kann eine neue Suche starten, während eine ältere Anfrage noch läuft.
Wenn die ältere Antwort später eintrifft, darf sie nicht die aktuelle Ergebnisansicht überschreiben.
Das Widget vergibt deshalb eine Sequenznummer:
const requestSequence = { current: 0 };
function runPreparedSearch(preparedSearch: PreparedSearch): void {
const requestId = requestSequence.current + 1;
requestSequence.current = requestId;
controller.submitPrepared(preparedSearch).then(
(result) => {
if (requestSequence.current !== requestId) {
return;
}
dispatchSearchResults(SEARCH_RESULTS_EVENT_NAME, result.response);
notifyLocalConsumer(result.filters);
},
(error: unknown) => {
if (requestSequence.current !== requestId) {
return;
}
showSearchError(error);
}
);
}Eine überholte Antwort erzeugt kein Ergebnis Event. Dasselbe gilt für einen fehlgeschlagenen Request oder eine vom Parser abgelehnte Antwort.
Diese Regel schützt nicht nur React State. Sie schützt jeden Vanilla JavaScript, jQuery oder React Consumer, der das Ergebnis Event abonniert.
Warum besitzen Browser CustomEvents kein Replay?
Ein DOM Event existiert nur während seiner Auslieferung.
Ein Listener, der nach dem initialen search-results Event registriert wird, erhält die vorhandene Antwort nicht nachträglich.
Ich registriere Consumer deshalb vor dem Start des Suchwidgets:
<script>
function handleSearchResults(event) {
window.latestSearchResponse = event.detail;
}
window.addEventListener('portal:search-results', handleSearchResults);
</script>
<script src="/assets/search-widget.js" defer></script>Diese Reihenfolge ist besonders für den initialen Request wichtig. Das Widget startet ihn nach dem Mounten auf der Ergebnisseite.
Wenn ein später geladener Consumer den aktuellen Zustand ebenfalls benötigt, reicht ein Event Bus ohne Replay nicht aus. Dann braucht die Seite einen eigenen Store, einen expliziten Request oder einen anderen Wiederherstellungsmechanismus.
Wie liest Vanilla JavaScript Power Pages CustomEvents?
Ein Power Pages Web Template benötigt keine React Abhängigkeit:
function handleSearchResults(event) {
const response = event.detail;
if (response.IsSucceeded !== true) {
renderSearchError();
return;
}
renderResultCards(response.Content.objects);
}
window.addEventListener('portal:search-results', handleSearchResults);
function disposeSearchResultsConsumer() {
window.removeEventListener('portal:search-results', handleSearchResults);
}Die Referenz auf handleSearchResults muss beim Entfernen identisch sein. Eine neue anonyme Funktion würde den registrierten Listener nicht entfernen.
Wie liest jQuery das detail-Feld eines CustomEvents?
jQuery verpackt das native Browser Event:
function handleSearchResults(event) {
const response = event.originalEvent?.detail;
if (response?.IsSucceeded !== true) {
renderSearchError();
return;
}
renderResultCards(response.Content.objects);
}
$(window).on('portal:search-results', handleSearchResults);
function disposeSearchResultsConsumer() {
$(window).off('portal:search-results', handleSearchResults);
}Ich lese den Payload über originalEvent.detail. Dadurch greift der Consumer ausdrücklich auf das native Browser Event zu und bleibt unabhängig davon, welche Eigenschaften die eingesetzte jQuery Version auf ihr Wrapper Event kopiert.
Wie registriert React einen CustomEvent Listener korrekt?
Eine getrennte React Anwendung kann den Vertrag abonnieren, ohne Context oder Hooks des Suchwidgets zu importieren:
import { useEffect, useState } from 'react';
export function SearchResultsConsumer(): React.JSX.Element {
const [response, setResponse] = useState<SearchResultsEventDetail | null>(null);
useEffect(() => {
const handleSearchResults = (event: Event): void => {
const customEvent = event as CustomEvent<SearchResultsEventDetail>;
setResponse(customEvent.detail);
};
window.addEventListener(SEARCH_RESULTS_EVENT_NAME, handleSearchResults);
return () => {
window.removeEventListener(SEARCH_RESULTS_EVENT_NAME, handleSearchResults);
};
}, []);
return <ResultList response={response} />;
}Das Cleanup verhindert doppelte Listener nach einem Remount.
Die TypeScript Assertion validiert keine Laufzeitdaten. Sie beschreibt nur den erwarteten Vertrag. In dieser Architektur hat der Producer die Backend Antwort bereits geparst. Andere Skripte im selben Fenster können trotzdem ein Event mit demselben Namen auslösen.
Ein Event ist deshalb keine Sicherheitsgrenze.
Consumer | Payload Zugriff | Registrierung | Cleanup |
|---|---|---|---|
JavaScript | event.detail | window.addEventListener | Dieselbe Handler Referenz mit |
jQuery | event.originalEvent?.detail | $(window).on | Dieselbe Handler Referenz mit |
React | Assertion auf |
| Rückgabefunktion des Effekts entfernt Listener |
Warum dürfen Consumer den CustomEvent Payload nicht verändern?
CustomEvent.detail wird nicht automatisch tief kopiert.
Alle Listener erhalten dieselbe Objektreferenz. TypeScript readonly verhindert Mutationen nur während der Kompilierung.
Ich behandle den Payload deshalb als unveränderlichen Vertrag. Ein Consumer erstellt eigene View Models, wenn er Daten sortieren, gruppieren oder ergänzen muss.
Das verhindert, dass ein Listener durch eine Mutation das Verhalten eines später ausgeführten Listeners verändert.
Wie beeinflussen langsame Listener die CustomEvent-Auslieferung?
window.dispatchEvent ruft die registrierten Listener synchron auf.
Das bedeutet nicht, dass der Backend Request synchron wird. search-results wird erst im asynchronen Promise Erfolg ausgelöst. Die Auslieferung dieses einzelnen Events läuft danach synchron durch alle Listener.
Ein Consumer sollte deshalb keine teure synchrone Berechnung im Handler ausführen. Er kann State setzen, eine Render Pipeline anstoßen oder umfangreiche Arbeit geplant fortsetzen.
Die Dokumentation zu addEventListener zeigt auch, warum dieselbe Listener Referenz für die spätere Entfernung benötigt wird.
Wie versionieren Power Pages Event-Verträge?
search-changed besitzt bereits version: 1.
Der aktuelle search-results Payload ist dagegen direkt die API Antwort. Er besitzt kein eigenes Event Versionsfeld.
Ich stelle diesen Vertrag deshalb nicht als bereits vollständig versioniert dar.
Vor einer inkompatiblen Änderung gibt es zwei saubere Optionen:
- Das Ergebnis Event erhält einen eigenen versionierten Envelope.
- Eine neue Hauptversion erhält einen neuen Event Namen.
Eine stille Änderung bestehender Felder würde unabhängige Consumer gleichzeitig brechen.
Wann sind Browser CustomEvents in Power Pages sinnvoll?
CustomEvents passen, wenn Producer und Consumer im selben Browserfenster laufen und einen kleinen dokumentierten Vertrag teilen.
Sie eignen sich für Power Pages Seiten, auf denen bestehende Web Templates, jQuery und getrennte React Bundles zusammenarbeiten.
Sie ersetzen keinen globalen Application Store, wenn Consumer Replay, persistierten Zustand oder komplexe bidirektionale Kommunikation benötigen. Sie ersetzen auch keinen Cross-Frame Message Bus und keine serverseitige Integration.
In der ECE Fallstudie zur Power Pages Enterprise-Suche zeige ich den Projektkontext dieser Entkopplung.
Du planst eine Power Pages Oberfläche mit mehreren Frontend Technologien und getrennten Release Zyklen? Der Bereich Power Pages Pro-Code zeigt, wie ich solche Verträge und Integrationsgrenzen aufbaue.