Aufrufen einer JavaScript-Funktion von einem unvollständigen Handler von p: remoteCommand - Simulieren des gleichen mit etwas JavaScript-Code

9

Vorsicht: Obwohl diese Frage lange Textinformationen mit einer Unmenge von Java-Code-Snippets abdeckt, richtet sie sich nur an JavaScript / jQuery und ein bisschen PrimeFaces-Zeug (nur <p:remoteCommand> ) wie eingangs erwähnt.

Ich erhalte eine JSON-Nachricht von WebSockets (Java EE 7 / JSR 356 WebSocket-API) wie folgt.

%Vor%

Im obigen Code enthält event.data eine JSON-Zeichenfolge {"jsonMessage":"updateModel"} . Daher enthält msg einen Zeichenfolgenwert, der updateModel ist.

Im folgenden Codeabschnitt

%Vor%

window[msg](); bewirkt, dass eine JavaScript-Funktion aufgerufen wird, die mit einem <p:remoteCommand> verknüpft ist (was wiederum ein mit dem actionListener="#{bean.remoteAction}" verknüpftes <p:remoteCommand> aufruft).

%Vor%

update="@none" wird nicht unbedingt benötigt.

Nachdem ich diese Nachricht erhalten habe, muss ich alle zugehörigen Clients über dieses Update informieren. Dazu benutze ich die folgende JavaScript-Funktion, die dem oncomplete -Handler des obigen <p:remoteCommand> zugeordnet ist.

%Vor%

Beachten Sie, dass der Variable jsonMsg im ersten Snippet bereits ein Wert zugewiesen ist - es handelt sich um eine globale Variable. sendMessage() ist eine weitere JavaScript-Funktion, die über WebSockets eine Benachrichtigung über dieses Update an alle zugehörigen Clients sendet, die in dieser Frage nicht benötigt wird.

Das funktioniert gut, aber gibt es eine Möglichkeit, Magie unter folgenden Bedingungen zu machen:

%Vor%

, so dass die Funktion notifyAll() direkt über einen JavaScript-Code aufgerufen werden kann (der momentan an oncomplete von <p:remoteCommand> angehängt ist und der erwartete JavaScript-Code (oder sogar etwas anderes) diese oncomplete ) grundsätzlich simuliert Sie müssen nicht mehr auf eine globale JavaScript-Variable ( jsonMSg ) angewiesen sein?

Edit: Das Problem, das ich versuche zu lösen (es kann als zusätzliche Information betrachtet werden).

Wenn ein Administrator beispielsweise einige Änderungen (mittels DML-Operationen) an einer JPA-Entität mit dem Namen Category vornimmt, werden Entity-Listener ausgelöst, was wiederum bewirkt, dass ein CDI-Ereignis wie folgt ausgelöst wird.

%Vor%

Es ist unnötig zu erwähnen, dass die Entität Category mit der Annotation @EntityListeners(CategoryListener.class) bezeichnet ist.

Nur eine Randnotiz ( vollständig aus dem Zweig ): Eine Instanz von BeanManager über eine JNDI-Suche wie im vorherigen Code-Snippet zu erhalten ist temporär. Der GlassFish Server 4.1 mit der Weld-Version 2.2.2 final schlägt das CDI-Ereignis javax.enterprise.event.Event<T> , das wie folgt injiziert werden soll, nicht ein.

%Vor%

Und dann kann das Ereignis wie folgt mit Bezug auf das relevante Code-Snippet ausgelöst werden.

%Vor%

Dieses Ereignis wird im Webprojekt wie folgt beobachtet.

%Vor%

Wo ein Admin seinen eigenen Endpunkt wie folgt verwendet ( AdminPush.sendAll("updateModel"); wird darin manuell aufgerufen).

%Vor%

Hier darf nur ein Administrator diesen Endpunkt verwenden. Alle anderen Benutzer können keine WebSocket-Sitzung mit einer bedingten Prüfung in der Methode onOpen() erstellen.

session.getAsyncRemote().sendText(message); in der foreach -Schleife sendet eine Benachrichtigung (in Form einer JSON-Nachricht) an den Administrator über diese Änderungen in der Entität Category .

Wie im ersten Codefragment gezeigt, ruft window[msg](); eine Aktionsmethode (über <p:remoteCommand> wie zuvor gezeigt) auf, die mit einer Anwendungsbereichs-Bean verknüpft ist - actionListener="#{realTimeMenuManagedBean.remoteAction}" .

%Vor%

Alle anderen Benutzer / Clients (die sich in einem anderen Bereich als dem Admin-Bereich befinden) sollten nur benachrichtigt werden, wenn actionListener="#{realTimeMenuManagedBean.remoteAction}" vollständig abgeschlossen ist - dies darf nicht geschehen, bevor die Aktionsmethode beendet wurde - sollte benachrichtigt werden durch den oncomplate Event-Handler von <p:remoteCommand> . Dies ist der Grund, warum zwei verschiedene Endpunkte genommen wurden.

Diese anderen Benutzer verwenden ihren eigenen Endpunkt:

%Vor%

Die mit @OnMessage annotierte Methode wird wiedergegeben, wenn eine Nachricht über oncomplete von <p:remoteCommand> gesendet wird, wie oben gezeigt.

Diese Clients verwenden den folgenden JavaScript-Code, um einfach die neuen Werte aus dem oben genannten Application-Scoped-Bean zu holen (die Bean wurde bereits vom Administrator aus der Datenbank entsprechend abgefragt. Daher ist es nicht nötig, sie erneut lächerlich abzufragen jeder einzelne Client getrennt (außer dem Admin). Daher ist es eine Anwendungsbereichs Bean).

%Vor%

In Verbindung mit dem folgenden <p:remoteCommand> .

%Vor%

Wo parentMenu - Die von dieser <p:remoteCommand> zu aktualisierende Komponente ist id einer Container-JSF-Komponente <h:panelGroup> , die ein einfaches CSS-Menü mit einer Menge <ui:repeat> s enthält.

Ich hoffe, dadurch wird das Szenario klarer.

Aktualisierung:

Diese Frage wurde genau hier beantwortet, basierend auf <p:remoteCommand> (In Bezug auf die konkrete Frage bestand die einzige Frage darin, eine Abhängigkeit von einer globalen JavaScript-Variablen zu entfernen, wie im einführenden Teil dieser Frage angegeben).

    
Tiny 01.03.2015, 11:37
quelle

1 Antwort

1

Ich glaube nicht, dass ich jeden Aspekt Ihres Problems verstanden habe, aber trotzdem versuche ich ein wenig zu helfen. Beachten Sie, dass ich PrimeFaces nicht kenne, also habe ich nur die Dokumente gelesen.

Was ich verstehe, ist, dass Sie versuchen, die globale Variable loszuwerden. Aber ich fürchte, ich glaube nicht, dass das möglich ist.

Das Problem hier ist, dass PrimeFaces es nicht erlaubt, etwas transparent von Ihrem Aufruf des Remote-Aufrufs weiter an den oncomplete -Aufruf zu übergeben (außer dass Sie es an einen Java-Code der Bean und dann zurück an die Benutzerschnittstelle übergeben) , und das ist normalerweise nicht was du willst).

Aber ich hoffe, Sie können ihm sehr nahe kommen.

Teil 1, JS gibt früh

zurück

Bitte beachten Sie auch, dass es wahrscheinlich ein Missverständnis über Java und JavaScript gibt.

Java ist Multithread und führt mehrere Befehle parallel aus, während JavaScript ein Singlethreading ist und normalerweise niemals auf die Ausführung von etwas wartet. Dinge asynchron zu tun ist zwingend erforderlich, um eine responsive Web-UI zu erhalten.

Daher wird Ihr remoteCommand -Aufruf (von der JS-Seite aus betrachtet) (normalerweise asynchrone Groß- / Kleinschreibung) zurückgegeben, lange bevor der oncomplete -Handler aufgerufen wird. Das heißt, wenn window[msg]() zurückgibt, sind Sie noch nicht mit dem remoteCommand fertig.

Also, was Sie mit dem folgenden Code verwalten möchten

%Vor%

wird fehlschlagen. dosomethinghere() wird nicht aufgerufen, wenn remoteCommand zurückgegeben wird (da JS nicht auf ein Ereignis warten möchte, was niemals passieren könnte). Das heißt, dosomethinghere() wird aufgerufen, wenn die Ajax-Anfrage gerade für den Remote (zur Java-Anwendung) geöffnet wurde.

Um etwas auszuführen, nachdem der Ajax-Aufruf beendet wurde, muss dies in der Routine oncomplete (oder onsuccess ) geschehen. Deshalb ist es da.

Teil 2, validiere msg

Bitte beachten Sie etwas anderes über window[msg]() . Dies kann als etwas gefährlich angesehen werden, wenn Sie der gedrückten Nachricht nicht vollständig vertrauen können. window[msg]() führt im Wesentlichen jede Funktion aus, die mit dem Inhalt der Variablen msg benannt ist. Wenn zum Beispiel msg zufällig close ist, wird window.close() ausgeführt, was wahrscheinlich nicht das ist, was Sie wollen.

Sie sollten sicherstellen, dass msg ein erwartetes Wort ist und alle anderen Wörter ablehnen. Beispielcode hierfür:

%Vor%

Teil 3: Wie man mehrere JSON Nachrichten parallel behandelt

Die globale Variable hat einige Nachteile. Es gibt nur eins. Wenn Sie eine weitere JSON-Nachricht auf dem WebSocket erhalten, während die vorherige Nachricht noch in remoteCommand verarbeitet wird, wird die vorherige Nachricht überschrieben. So wird die notifyAll() die neuere Nachricht zweimal sehen, die alte ist verloren.

Eine klassische Race-Bedingung. Was Sie tun müssen, ist, so etwas wie eine Registrierung zu erstellen, um alle Nachrichten zu registrieren, und dann einen Wert an notifyAll() zu übergeben, um mitzuteilen, welche der registrierten Nachrichten verarbeitet werden sollen.

Mit nur einer kleinen Änderung können Sie entweder parallel (hier) oder seriell (Teil 4) die Nachrichten verarbeiten.

Erstellen Sie zuerst einen Zähler, um die Nachrichten unterscheiden zu können. Auch ein Objekt zum Speichern aller Nachrichten. Und wir erklären alle gültigen Nachrichten, die wir erwarten (siehe Teil 2): ​​

%Vor%

Fügen Sie nun bei jedem Empfang eine Nachricht hinzu:

%Vor%

Damit nr an NotifyAll() übergeben werden kann, muss ein zusätzlicher Parameter an das Bean übergeben werden. Nennen wir es msgNr :

%Vor%

Vielleicht werfen Sie einen Blick in Ссылка , um mehr über das Übergeben von Werten auf diese Weise zu erfahren.

Die remoteAction Bean erhält nun einen zusätzlichen Parameter msgNr passed, der über Ajax zurückgegeben werden muss.

  

Leider habe ich keine Ahnung (sorry) wie das in Java aussieht. Stellen Sie also sicher, dass Ihre Antwort auf den AjaxCall die msgNr erneut kopiert.

     

Auch wenn die Dokumentation zu diesem Thema sehr ruhig ist, bin ich nicht sicher, wie die Parameter an den oncomplete -Handler zurückgegeben werden. Gemäß dem JavaScript-Debugger erhält notifyAll() 3 Parameter: xhdr , payload und pfArgs . Leider konnte ich keinen Testfall erstellen, um herauszufinden, wie die Dinge aussehen.

Daher sieht die Funktion ein bisschen so aus (bitte mit mir tragen):

%Vor%

Wenn Sie dies in zwei Funktionen aufteilen, können Sie notifyAll() von anderen Teilen Ihrer Anwendung aufrufen:

%Vor%
  

Einige Dinge hier sind ein bisschen überflüssig. Zum Beispiel brauchen Sie vielleicht das Element json in jsonMessages nicht oder möchten das json noch einmal parsen, um etwas Speicher zu sparen, falls der json sehr groß ist. Der Code soll jedoch nicht optimal sein, sondern sich einfach an Ihre Bedürfnisse anpassen lassen.

Teil 4: Anfragen serialisieren

Nun zu den Änderungen, um Dinge zu serialisieren. Das ist ziemlich einfach, indem Sie ein Semaphor hinzufügen. Semaphore in JavaScript sind nur Variablen. Dies liegt daran, dass es nur einen globalen Thread gibt.

%Vor%

Hinweis: jsonMsgNrLast könnte nur ein Flag sein (true / false). Die aktuell verarbeitete Zahl in einer Variablen kann jedoch möglicherweise woanders helfen.

Nachdem das gesagt wurde, gibt es ein Problem mit dem Hungern, falls etwas in sendMessage oder dosomething fehlschlägt. Vielleicht können Sie es ein wenig verschachteln:

%Vor%

Auf diese Weise wird die AJAX-Anfrage bereits gesendet, während sendMessage ausgeführt wird. Wenn nun dosomething einen JavaScript-Fehler oder ähnliches hat, werden die Nachrichten weiterhin korrekt verarbeitet.

  

Bitte beachten Sie: All dies wurde ohne Tests eingegeben. Es könnte Syntaxfehler oder Schlimmeres geben. Sorry, ich habe mein Bestes versucht. Wenn Sie einen Fehler finden, ist Bearbeiten Ihr Freund.

Teil 5: Direkter Aufruf von JS

Jetzt, mit all dem an Ort und Stelle und einem serialisierten Lauf, können Sie immer das vorherige notifyAll() mit realNotifyAll(jsonMsgNrLast) aufrufen. Oder Sie können jsonMessages in einer Liste anzeigen und eine beliebige Zahl auswählen.

Indem Sie den Aufruf an window[jsonMessages[nr].json.msg]([{name:'msgNr', value:nr}]); (und über window[msg]([{name:'msgNr', value:nr}]); ) überspringen, können Sie auch die Bean-Verarbeitung anhalten und sie bei Bedarf mit den üblichen JQuery-Callbacks ausführen. Erstellen Sie dazu eine Funktion und ändern Sie den Code erneut:

%Vor%

Jetzt können Sie die Verarbeitung mit runRemote(nr) starten und die Vervollständigungsfunktion mit realNotifyAll(nr) aufrufen.

Die Funktion updateGuiPushList(nr, state) mit state=0:finished 1=added 2=running ist der Callback zu Ihrem GUI-Code, der die Bildschirmliste der wartenden Pushs zur Verarbeitung aktualisiert. Setzen Sie autoRun=false , um die automatische Verarbeitung zu stoppen und autoRun=true für die automatische Verarbeitung.

Hinweis: Nachdem Sie autoRun von false auf true gesetzt haben, müssen Sie runRemote einmal mit der niedrigsten nr auslösen, natürlich.

    
Tino 25.06.2015 04:39
quelle