从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个参数:xhdr
,payload
和pfArgs
。 不幸的是,我无法设置测试用例来了解事情的样子。
因此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); }
这里的一些事情有点多余。 例如,您可能不需要
jsonMessages
的json
元素,或者想要再次解析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)。 但是,在变量中使用当前处理的数字可能对其他地方有帮助。
话虽如此,如果sendMessage
或dosomething
故障,则会出现饥饿问题。 所以也许你可以稍微交错一下:
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
以进行自动处理。
注意:将autoRun
从false
设置为true
您需要以最低的nr
触发runRemote
一次。