从p:remoteCommand的oncomplete处理程序调用JavaScript函数 – 使用一些JavaScript代码模拟相同的函数

注意:虽然这个问题涵盖了大量Java代码片段的长文本信息,但它只是针对JavaScript / jQuery和一些PrimeFaces的东西(只是

),如开头的介绍部分所述。


我收到来自WebSockets(Java EE 7 / JSR 356 WebSocket API)的JSON消息,如下所示。

 if (window.WebSocket) { var ws = new WebSocket("wss://localhost:8181/ContextPath/AdminPush"); ws.onmessage = function (event) { jsonMsg=event.data; var json = JSON.parse(jsonMsg); var msg=json["jsonMessage"]; if (window[msg]) { window[msg](); //It is literally interpreted as a function - updateModel(); } }; } 

在上面的代码中, event.data包含一个JSON字符串{"jsonMessage":"updateModel"} 。 因此, msg将包含一个字符串值,即updateModel

在以下代码段中,

 if (window[msg]) { window[msg](); //It is literally interpreted as a JavaScript function - updateModel(); } 

window[msg](); 导致调用与

相关联的JavaScript函数(后者调用与

关联的actionListener="#{bean.remoteAction}" )。

 

不一定需要update="@none"


收到此消息后,我需要通知所有关联的客户端有关此更新。 我使用以下JavaScript函数来执行此操作,该函数与上述

oncomplete处理程序相关联。

 var jsonMsg; function notifyAll() { if(jsonMsg) { sendMessage(jsonMsg); } } 

请注意,变量jsonMsg已在第一个片段中分配了一个值 – 它是一个全局变量。 sendMessage()是另一个JavaScript函数,它实际上通过WebSockets向所有关联的客户端发送有关此更新的通知,这在此问题中是不需要的。


这很有效,但有一种方法可以在以下条件下做一些魔术

 if (window[msg]) { window[msg](); //Do something to call notifyAll() on oncomplete of remote command. } 

这样可以直接通过一些JavaScript代码调用notifyAll()函数(当前附加到

oncomplete上,预期的JavaScript代码(甚至其他东西)应该模拟这个oncomplete )基本上不需要依赖在全局JavaScript变量( jsonMSg )?


编辑:我想解决的问题(可能被认为是附加信息)。

例如,当管理员对名为Category的JPA实体进行一些更改(通过DML操作)时,将触发实体侦听器,从而导致CDI事件如下引发。

 @ApplicationScoped public class CategoryListener { @PostPersist @PostUpdate @PostRemove public void onChange(Category category) throws NamingException { BeanManager beanManager = (BeanManager) InitialContext.doLookup("java:comp/BeanManager"); beanManager.fireEvent(new CategoryChangeEvent(category)); } } 

不用说实体Category是用注释@EntityListeners(CategoryListener.class)指定的。

只是一个注意事项( 完全偏离主题 ):通过JNDI查找获取BeanManager的实例,如前面的代码片段中所做的那样是临时的。 具有Weld版本2.2.2 final的GlassFish Server 4.1无法注入CDI事件javax.enterprise.event.Event ,它应该按如下方式注入。

 @Inject private Event event; 

然后,参考上面的相关代码片段,可以按如下方式触发事件。

 event.fire(new CategoryChangeEvent(category)); 

在Web项目中观察到此事件如下。

 @ApplicationScoped public class RealTimeUpdate { public void onCategoryChange(@Observes CategoryChangeEvent event) { AdminPush.sendAll("updateModel"); } } 

管理员使用自己的端点如下( AdminPush.sendAll("updateModel");在其中手动调用)。

 @ServerEndpoint(value = "/AdminPush", configurator = ServletAwareConfig.class) public final class AdminPush { private static final Set sessions = new LinkedHashSet(); @OnOpen public void onOpen(Session session, EndpointConfig config) { if (Boolean.valueOf((String) config.getUserProperties().get("isAdmin"))) { sessions.add(session); } } @OnClose public void onClose(Session session) { sessions.remove(session); } private static JsonObject createJsonMessage(String message) { return JsonProvider.provider().createObjectBuilder().add("jsonMessage", message).build(); } public static void sendAll(String text) { synchronized (sessions) { String message = createJsonMessage(text).toString(); for (Session session : sessions) { if (session.isOpen()) { session.getAsyncRemote().sendText(message); } } } } } 

这里只允许管理员使用此端点。 使用onOpen()方法中的条件检查阻止所有其他用户创建WebSocket会话。

session.getAsyncRemote().sendText(message);foreach循环内部向管理员发送关于在实体Category中进行的这些更改的通知(以JSON消息的forms)。

如第一个代码片段所示, window[msg](); 调用与应用程序作用域bean相关联的action方法(通过

,如前所示) – actionListener="#{realTimeMenuManagedBean.remoteAction}"

 @Named @ApplicationScoped public class RealTimeMenuManagedBean { @Inject private ParentMenuBeanLocal service; private List category; private final Map<Long, List> categoryMap = new LinkedHashMap<Long, List>(); // Other lists and maps as and when required for a dynamic CSS menu. public RealTimeMenuManagedBean() {} @PostConstruct private void init() { populate(); } private void populate() { categoryMap.clear(); category = service.getCategoryList(); for (Category c : category) { Long catId = c.getCatId(); categoryMap.put(catId, service.getSubCategoryList(catId)); } } // This method is invoked through the above-mentioned 

. public void remoteAction() { populate(); } // Necessary accessor methods only. }

只有当actionListener="#{realTimeMenuManagedBean.remoteAction}"完全完成时actionListener="#{realTimeMenuManagedBean.remoteAction}"通知所有其他用户/客户(位于不同面板上的用户/客户端) – 在操作方法完成之前不得发生 – 应该是通过

oncomplate事件处理程序通知。 这就是为什么采取两个不同的终点的原因。


那些其他用户使用他们自己的终点:

 @ServerEndpoint("/Push") public final class Push { private static final Set sessions = new LinkedHashSet(); @OnOpen public void onOpen(Session session) { sessions.add(session); } @OnClose public void onClose(Session session) { sessions.remove(session); } @OnMessage public void onMessage(String text) { synchronized (sessions) { for (Session session : sessions) { if (session.isOpen()) { session.getAsyncRemote().sendText(text); } } } } } 

当通过

oncomplete发送消息时,使用@OnMessage注释的方法开始播放,如上所示。

这些客户端使用以下JavaScript代码从上面提到的应用程序作用域bean中获取新值(管理员已经从数据库中充分查询了bean。因此,没有必要每次都可以再次查询它。单独的客户端(管理员除外)。因此,它是一个应用程序范围的bean)。

 if (window.WebSocket) { var ws = new WebSocket("wss://localhost:8181/ContextPath/Push"); ws.onmessage = function (event) { var json = JSON.parse(event.data); var msg = json["jsonMessage"]; if (window[msg]) { window[msg](); } }; $(window).on('beforeunload', function () { ws.close(); }); } 

与以下

结合使用。

 

其中parentMenu – 由

更新的组件是容器JSF组件id ,其中包含带有一堆的简单CSS菜单。

希望这会使场景更清晰。


更新:

这个问题已基于

得到了解答(关于具体问题,唯一的问题是如本问题的介绍部分所述,删除对全局JavaScript变量的依赖)。

我不认为我理解你问题的每个方面,但无论如何我试着帮助一下。 请注意,我不知道PrimeFaces,所以我所做的只是阅读文档。

我的理解是,你试图摆脱全局变量。 但我担心,我不认为这是可能的。

这里的问题是,PrimeFaces不允许你从远程调用的调用透明地传递一些东西到oncomplete调用(除了你把它传递给Bean的Java代码然后再回到UI,这通常是不是你想要的)。

但是,我希望,你可以非常接近它。

第1部分,JS早期回归

还请注意,可能存在一些关于Java和JavaScript的误解。

Java是multithreading的,并行运行多个命令,而JavaScript是单线程的,通常永远不会等待完成某些事情。 为了获得响应式Web-UI,必须以异步方式执行操作。

因此,在调用oncomplete处理程序之前,您的remoteCommand调用(从JS端看)将(通常是异步情况)返回很久。 这意味着,如果window[msg]()返回,您还没有完成remoteCommand

那么你想用以下代码管理什么

 if (window[msg]) { window[msg](); //Do something to call notifyAll() on oncomplete of remote command. dosomethinghere(); } 

将失败。 当remoteCommand返回时,不会调用remoteCommand ()(因为JS不想等待某些事件,这可能永远不会发生)。 这意味着,当Ajax请求刚刚打开到远程(到Java应用程序)时,将调用dosomethinghere() )。

要在Ajax调用完成后运行某些东西,必须在oncomplete例程(或onsuccess )中完成。 这就是为什么它在那里。

第2部分,validationmsg

请注意window[msg]() 。 如果您不能完全信任推送的消息,则可以认为这有点危险。 window[msg]()实际上运行任何以变量msg的内容命名的函数。 例如,如果msg恰好close那么将运行window.close() ,这可能不是你想要的。

你应该确定, msg是一个预期的单词,并拒绝所有其他单词。 示例代码:

 var validmsg = { updateModel:1, rc:1 } [..] if (validmsg[msg] && window[msg]) window[msg](); 

第3部分:如何并行处理多个JSON消息

全局变量有一些缺点。 只有一个。 如果您碰巧在WebSocket上收到另一条JSON消息,而前一条消息仍在remoteCommand处理,则会覆盖以前的消息。 因此, notifyAll()将看到两次较新的消息,旧的消息丢失。

经典的竞争条件。 您必须做的是,创建类似注册表的内容来注册所有消息,然后将一些值传递给notifyAll()以告知应该处理哪些注册消息。

只需稍加改动,您可以并行(此处)或串行(第4部分)处理消息。

首先,创建一个能够区分消息的计数器。 也是存储所有消息的对象。 我们声明了我们期望的所有有效消息(参见第2部分):

 var jsonMsgNr = 0; var jsonMessages = {}; var validmsg = { updateModel:1 } 

现在每次收到一条消息时都会添加一条消息:

 if (window.WebSocket) { var ws = new WebSocket("wss://localhost:8181/ContextPath/AdminPush"); ws.onmessage = function (event) { var jsonMsg = event.data; var json = JSON.parse(jsonMsg); var msg=json["jsonMessage"]; if (validmsg[msg] && window[msg]) { var nr = ++jsonMsgNr; jsonMessages[nr] = { jsonMsg:jsonMsg, json:json }; 

为了能够将nr传递给NotifyAll() ,需要将另一个参数传递给Bean。 我们称之为msgNr

  // Following might look a bit different on older PrimeFaces window[msg]([{name:'msgNr', value:nr}]); } } } 

或许可以查看https://stackoverflow.com/a/7221579/490291 ,了解更多关于以这种方式传递值的信息。

remoteAction bean现在获得传递的附加参数msgNr ,必须通过Ajax传回。

不幸的是,我不知道(对不起)这在Java中的表现如何。 所以请确保,您对AjaxCall的回答会再次将msgNr复制出来。

此外,由于文档对此主题很安静,我不确定如何将参数传递回oncomplete处理程序。 根据JavaScript调试器, notifyAll()获取3个参数: xhdrpayloadpfArgs 。 不幸的是,我无法设置测试用例来了解事情的样子。

因此function看起来有点像(请耐心等待):

 function notifyAll(x, data, pfArgs) { var nr = ???; // find out how to extract msgNr from data var jsonMsg = jsonMessages[nr].jsonMsg; var json = jsonMessages[nr].json; jsonMessages[nr] = null; // free memory sendMessage(jsonMsg); dosomething(json); } 

如果将其拆分为两个函数,则可以从应用程序的其他部分调用notifyAll()

 function notifyAll(x, data, unk) { var nr = ???; // find out how to extract msgNr from data realNotifyAll(nr); } function realNotifyAll(nr) { if (!(nr in jsonMessages)) return; var jsonMsg = jsonMessages[nr].jsonMsg; var json = jsonMessages[nr].json; delete jsonMessages[nr]; // free memory sendMessage(jsonMsg); dosomething(json); } 

这里的一些事情有点多余。 例如,您可能不需要jsonMessagesjson元素,或者想要再次解析json ,以便在json非常大的情况下jsonMessages一些内存。 但是,代码不是最佳的,而是易于根据您的需求进行调整。

第4部分:序列化请求

现在对序列化事物进行更改。 通过添加一些信号量,这很容易。 JavaScript中的信号量只是变量。 这是因为只有一个全局线程。

 var jsonMsgNr = 0; var jsonMessages = {}; var validmsg = { updateModel:1 } var jsonMsgNrLast = 0; // ADDED if (window.WebSocket) { var ws = new WebSocket("wss://localhost:8181/ContextPath/AdminPush"); ws.onmessage = function (event) { var jsonMsg = event.data; var json = JSON.parse(jsonMsg); var msg=json["jsonMessage"]; if (validmsg[msg] && window[msg]) { var nr = ++jsonMsgNr; jsonMessages[nr] = { jsonMsg:jsonMsg, json:json }; if (!jsonMsgNrLast) { // ADDED jsonMsgNrLast = nr; // ADDED window[msg]([{name:'msgNr', value:nr}]); } } } } function realNotifyAll(nr) { if (!(nr in jsonMessages)) return; var jsonMsg = jsonMessages[nr].jsonMsg; var json = jsonMessages[nr].json; delete jsonMessages[nr]; // free memory sendMessage(jsonMsg); dosomething(json); // Following ADDED nr++; jsonMsgNrLast = 0; if (nr in jsonMessages) { jsonMsgNrLast = nr; window[jsonMessages[nr].json.msg]([{name:'msgNr', value:nr}]); } } 

注意: jsonMsgNrLast可能只是一个标志(true / false)。 但是,在变量中使用当前处理的数字可能对其他地方有帮助。

话虽如此,如果sendMessagedosomething故障,则会出现饥饿问题。 所以也许你可以稍微交错一下:

 function realNotifyAll(nr) { if (!(nr in jsonMessages)) return; var jsonMsg = jsonMessages[nr].jsonMsg; var json = jsonMessages[nr].json; delete jsonMessages[nr]; // free memory nr++; jsonMsgNrLast = 0; if (nr in jsonMessages) { jsonMsgNrLast = nr; // Be sure you are async here! window[jsonMessages[nr].json.msg]([{name:'msgNr', value:nr}]); } // Moved, but now must not rely on jsonMsgNrLast: sendMessage(jsonMsg); dosomething(json); } 

这样,当sendMessage运行时,AJAX请求已经发出。 如果现在dosomething有JavaScript错误或类似错误,仍然可以正确处理消息。

请注意:所有这些都是在没有任何测试的情况下输入的。 可能存在语法错误或更糟。 对不起,我尽我所能。 如果您发现了错误,编辑就是您的朋友。

第5部分:JS的直接调用

现在,有了所有这些并且序列化运行,您始终可以使用realNotifyAll(jsonMsgNrLast)调用先前的notifyAll() realNotifyAll(jsonMsgNrLast) 。 或者,您可以在列表中显示jsonMessages并选择任意数字。

通过跳过对window[jsonMessages[nr].json.msg]([{name:'msgNr', value:nr}]);的调用window[jsonMessages[nr].json.msg]([{name:'msgNr', value:nr}]); (以及window[msg]([{name:'msgNr', value:nr}]); )你也可以暂停Bean处理并使用通常的JQuery回调按需运行它。 为此创建一个函数并再次更改代码:

 var jsonMsgNr = 0; var jsonMessages = {}; var validmsg = { updateModel:1 } var jsonMsgNrLast = 0; var autoRun = true; // ADDED, set false control through GUI if (window.WebSocket) { var ws = new WebSocket("wss://localhost:8181/ContextPath/AdminPush"); ws.onmessage = function (event) { var jsonMsg = event.data; var json = JSON.parse(jsonMsg); if (validmsg[msg] && window[msg]) { var nr = ++jsonMsgNr; jsonMessages[nr] = { jsonMsg:jsonMsg, json:json }; updateGuiPushList(nr, 1); if (autoRun && !jsonMsgNrLast) { runRemote(nr); } } } } function realNotifyAll(nr) { if (!(nr in jsonMessages)) return; var jsonMsg = jsonMessages[nr].jsonMsg; var json = jsonMessages[nr].json; delete jsonMessages[nr]; // free memory updateGuiPushList(nr, 0); jsonMsgNrLast = 0; if (autoRun) runRemote(nr+1); // Moved, but now must not rely on jsonMsgNrLast: sendMessage(jsonMsg); dosomething(json); } function runRemote(nr) { if (nr==jsonMsgNrLast) return; if (nr in jsonMessages) { if (jsonMsgNrLast) { alert("Whoopsie! Please wait until processing finished"); return; } jsonMsgNrLast = nr; updateGuiPushList(nr, 2); // Be sure you are async here! window[jsonMessages[nr].json.msg]([{name:'msgNr', value:nr}]); } } 

现在,您可以使用runRemote(nr)开始处理,并使用runRemote(nr)调用完成函数。

函数updateGuiPushList(nr, state)state=0:finished 1=added 2=running是对GUI代码的回调,该代码更新了屏幕上等待处理的列表。 设置autoRun=false以停止自动处理,并设置autoRun=false以进行自动处理。

注意:将autoRunfalse设置为true您需要以最低的nr触发runRemote一次。