![](/img/trans.png)
[英]Invoking a p:remoteCommand via a JavaScript function passing a message local to that function to another function through the “oncomplete” handler
[英]Invoking a JavaScript function from oncomplete handler of p:remoteCommand - simulating the same using some JavaScript code
注意:尽管此问题涵盖了很长的文本信息以及<p:remoteCommand>
Java代码片段,但它仅针对JavaScript / jQuery和一些PrimeFaces东西(只是<p:remoteCommand>
),如开头部分的介绍中所述。
我从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]();
导致与<p:remoteCommand>
关联的JavaScript函数被调用(进而调用与<p:remoteCommand>
关联的actionListener="#{bean.remoteAction}"
)。
<p:remoteCommand name="updateModel"
actionListener="#{bean.remoteAction}"
oncomplete="notifyAll()"
process="@this"
update="@none"/>
不一定需要update="@none"
。
收到此消息后,我需要通知所有相关的客户端此更新。 我使用以下JavaScript函数来实现此功能,该功能与上述<p:remoteCommand>
的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代码(当前附加到<p:remoteCommand>
oncomplete
上的预期代码,而预期的JavaScript代码(或其他内容)应该模拟此oncomplete
)直接调用notifyAll()
,从而基本上无需依赖在全局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最终版本的GlassFish Server 4.1无法注入CDI事件javax.enterprise.event.Event<T>
,该事件javax.enterprise.event.Event<T>
如下方式注入。
@Inject
private Event<CategoryChangeEvent> 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<Session> sessions = new LinkedHashSet<Session>();
@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消息的形式)。
如第一个代码段所示, window[msg]();
调用与应用程序作用域Bean相关联的操作方法(通过<p:remoteCommand>
(如前所示) actionListener="#{realTimeMenuManagedBean.remoteAction}"
。
@Named
@ApplicationScoped
public class RealTimeMenuManagedBean {
@Inject
private ParentMenuBeanLocal service;
private List<Category> category;
private final Map<Long, List<SubCategory>> categoryMap = new LinkedHashMap<Long, List<SubCategory>>();
// 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 <p:remoteCommand>.
public void remoteAction() {
populate();
}
// Necessary accessor methods only.
}
仅当actionListener="#{realTimeMenuManagedBean.remoteAction}"
完全完成时,才应通知所有其他用户/客户端(在其他面板上,而不在管理面板上)-应该在操作方法完成之前发生-通过<p:remoteCommand>
的oncomplate
事件处理程序通知。 这就是为什么要采用两个不同的端点的原因。
这些其他用户使用他们自己的端点:
@ServerEndpoint("/Push")
public final class Push {
private static final Set<Session> sessions = new LinkedHashSet<Session>();
@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);
}
}
}
}
}
当通过<p:remoteCommand>
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();
});
}
结合以下<p:remoteCommand>
。
<p:remoteCommand name="updateModel"
process="@this"
update="parentMenu"/>
其中parentMenu
要由此<p:remoteCommand>
更新的组件是容器JSF组件<h:panelGroup>
的id
,该组件包含带有一堆<ui:repeat>
的纯CSS菜单。
希望这可以使情况更加清晰。
这个问题已经被准确地回答了在这里根据<p:remoteCommand>
至于具体问题,唯一的问题是,在一个全球性的JavaScript变量删除的依赖在这个问题的介绍部分所述)。
我认为我不了解您问题的各个方面,但是无论如何,我都会尽力帮助您。 请注意,我不了解PrimeFaces,所以我所做的只是阅读文档。
我了解的是,您尝试摆脱全局变量。 但是,恐怕我认为这是不可能的。
这里的问题是,PrimeFaces不允许您透明地将某些内容从远程调用进一步传递给oncomplete
调用(除非您将其传递给Bean的Java代码,然后再传递给UI,通常是不是您想要的)。
但是,我希望您可以非常接近它。
另请注意,关于Java和JavaScript可能存在一些误解。
Java是多线程的,可以并行运行多个命令,而JavaScript是单线程的,通常从不等待完成。 为了获得响应式Web UI,必须异步执行操作。
因此,您的remoteCommand
调用(从JS端看到)将(通常是异步情况)返回很久,然后才会调用oncomplete
处理程序。 这意味着,如果window[msg]()
返回,则说明还没有完成remoteCommand
。
因此,您要使用以下代码进行管理
if (window[msg]) {
window[msg]();
//Do something to call notifyAll() on oncomplete of remote command.
dosomethinghere();
}
将失败。 当remoteCommand
返回时, remoteCommand
dosomethinghere()
将不会被调用(因为JS不想等待某些事件,这可能永远不会发生)。 这意味着,当Ajax请求刚刚向远程(向Java应用程序)打开时,将调用dosomethinghere()
)。
要在Ajax调用完成后运行某些东西,必须在oncomplete
例程(或onsuccess
)中完成。 这就是为什么它在那里。
msg
请注意关于window[msg]()
一些不同之处。 如果您不能完全信任所推送的消息,这将被认为有些危险。 window[msg]()
本质上运行任何以msg
变量的内容命名的函数。 例如,如果msg
恰好close
则将运行window.close()
,这可能不是您想要的。
您应该确保msg
是一个期望的词,并拒绝所有其他词。 示例代码:
var validmsg = { updateModel:1, rc:1 }
[..]
if (validmsg[msg] && window[msg])
window[msg]();
全局变量有一些缺点。 只有一个。 如果在上一条消息仍在remoteCommand
处理的同时,您恰巧在WebSocket上收到另一条JSON消息,这将覆盖上一条消息。 因此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
再次将msgNr复制出来。另外,由于文档对此主题比较安静,因此我不确定如何将参数传递回
oncomplete
处理程序。 根据JavaScript调试器的信息,notifyAll()
获得3个参数:xhdr
,payload
和pfArgs
。 不幸的是,我无法设置测试用例来了解情况。
因此,该函数看起来有点像(请与我同在):
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);
}
这里有些东西有点多余。 例如,你也许并不需要
json
的元素jsonMessages
或想解析json
再节省一些内存在的情况下JSON是非常大的。 但是,该代码不是最佳代码,而是易于调整以适应您的需求。
现在进行更改以序列化事物。 通过添加一些信号量,这很容易。 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
可能只是一个标志(是/ jsonMsgNrLast
)。 但是,将当前处理过的数字放在变量中可能会有所帮助。
话虽如此,如果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错误或类似错误,则消息仍会正确处理。
请注意:所有这些都是未经测试输入的。 可能存在语法错误或更严重的错误。 抱歉,我已经尽力了。 如果发现错误,请编辑为您的朋友。
现在,有了所有这些并进行了序列化运行,您始终可以使用realNotifyAll(jsonMsgNrLast)
调用先前的notifyAll()
realNotifyAll(jsonMsgNrLast)
。 或者,您可以在列表中显示jsonMessages
并选择任意数字。
通过跳过对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)
并调用完成函数realNotifyAll(nr)
updateGuiPushList(nr, state)
为state=0:finished 1=added 2=running
的函数updateGuiPushList(nr, state)
state=0:finished 1=added 2=running
是对您的GUI代码的回调,该GUI代码将更新屏幕上等待处理的推送列表。 设置autoRun=false
停止自动处理,设置autoRun=true
进行自动处理。
注意:将autoRun
从false
设置为true
您当然需要以最低nr
触发一次runRemote
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.