Tin*_*iny 8 javascript jquery jsf primefaces jsf-2.2
注意:虽然这个问题涵盖了大量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();
}
};
}
Run Code Online (Sandbox Code Playgroud)
在上面的代码中,event.data包含一个JSON字符串{"jsonMessage":"updateModel"}.因此,msg将包含一个字符串值updateModel.
在以下代码段中,
if (window[msg]) {
window[msg](); //It is literally interpreted as a JavaScript function - updateModel();
}
Run Code Online (Sandbox Code Playgroud)
window[msg]();导致与a关联的JavaScript函数<p:remoteCommand>被调用(后者又调用actionListener="#{bean.remoteAction}"与之关联的函数<p:remoteCommand>).
<p:remoteCommand name="updateModel"
actionListener="#{bean.remoteAction}"
oncomplete="notifyAll()"
process="@this"
update="@none"/>
Run Code Online (Sandbox Code Playgroud)
update="@none" 不一定需要.
收到此消息后,我需要通知所有关联的客户端有关此更新.我使用以下JavaScript函数来执行此操作,该oncomplete函数与上面的处理程序相关联<p:remoteCommand>.
var jsonMsg;
function notifyAll() {
if(jsonMsg) {
sendMessage(jsonMsg);
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,变量jsonMsg已在第一个片段中分配了一个值 - 它是一个全局变量.sendMessage()是另一个JavaScript函数,它实际上通过WebSockets向所有关联的客户端发送有关此更新的通知,这在此问题中是不需要的.
这很有效,但有一种方法可以在以下条件下做一些魔术
if (window[msg]) {
window[msg]();
//Do something to call notifyAll() on oncomplete of remote command.
}
Run Code Online (Sandbox Code Playgroud)
因此,该notifyAll()功能可以通过一些JavaScript代码直接调用(这是目前连接到oncomplete的<p:remoteCommand>和预期的JavaScript代码(甚至是别的东西)应模拟这个oncomplete),基本上无需依靠一个全局JavaScript变量(jsonMSg)?
例如,当管理员对命名的JPA实体进行一些更改(通过DML操作)时Category,将触发实体侦听器,从而导致如下引发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));
}
}
Run Code Online (Sandbox Code Playgroud)
不用说,实体Category是用注释指定的@EntityListeners(CategoryListener.class).
只是一个注意事项(完全偏离主题):BeanManager通过前面的代码片段中的JNDI查找获取实例是临时的.具有Weld版本2.2.2 final的GlassFish Server 4.1无法注入javax.enterprise.event.Event<T>应该按如下方式注入的CDI事件.
@Inject
private Event<CategoryChangeEvent> event;
Run Code Online (Sandbox Code Playgroud)
然后,参考上面的相关代码片段,可以按如下方式触发事件.
event.fire(new CategoryChangeEvent(category));
Run Code Online (Sandbox Code Playgroud)
在Web项目中观察到此事件如下.
@ApplicationScoped
public class RealTimeUpdate {
public void onCategoryChange(@Observes CategoryChangeEvent event) {
AdminPush.sendAll("updateModel");
}
}
Run Code Online (Sandbox Code Playgroud)
管理员使用自己的终点如下(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);
}
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
这里只允许管理员使用此端点.使用方法中的条件检查阻止所有其他用户创建WebSocket会话onOpen().
session.getAsyncRemote().sendText(message);在foreach循环内部向管理员发送关于在实体中进行的这些更改的通知(以JSON消息的形式)Category.
如第一个代码片段所示,window[msg]();调用<p:remoteCommand>与应用程序作用域bean相关联的action方法(通过如前所示)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.
}
Run Code Online (Sandbox Code Playgroud)
所有其他用户/客户(在不同的面板上 - 除管理面板之外)只应actionListener="#{realTimeMenuManagedBean.remoteAction}"在完全完成时通知- 在操作方法完成之前不得发生 - 应通过oncomplate事件处理程序通知<p:remoteCommand>.这就是为什么采取两个不同的终点的原因.
那些其他用户使用他们自己的终点:
@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);
}
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
带注释的方法@OnMessage来播放,当通过发送消息oncomplete的<p:remoteCommand>,如上所示.
这些客户端使用以下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();
});
}
Run Code Online (Sandbox Code Playgroud)
结合以下内容<p:remoteCommand>.
<p:remoteCommand name="updateModel"
process="@this"
update="parentMenu"/>
Run Code Online (Sandbox Code Playgroud)
其中parentMenu- 由此更新的组件<p:remoteCommand>是id一个容器JSF组件<h:panelGroup>,它包含带有一堆<ui:repeat>s 的普通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();
}
Run Code Online (Sandbox Code Playgroud)
将失败。 dosomethinghere()返回时不会被调用remoteCommand(因为 JS 不想等待某些事件,这可能永远不会发生)。这意味着,dosomethinghere()当 Ajax 请求刚刚打开到远程(Java 应用程序)时,将被调用。
oncomplete要在 Ajax 调用完成后运行某些内容,必须在例程(或)中完成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]();
Run Code Online (Sandbox Code Playgroud)
全局变量有一些缺点。只有一个。如果您碰巧在 WebSocket 上收到另一条 JSON 消息,而前一条消息仍在 中处理remoteCommand,则这将覆盖前一条消息。因此,notifyAll()将看到较新的消息两次,旧的消息将丢失。
经典的竞争条件。您必须做的是,创建一个类似注册表的东西来注册所有消息,然后传递一些值来notifyAll()告诉哪些已注册的消息将被处理。
只需进行一点点更改,您就可以并行(此处)或串行(第 4 部分)处理消息。
首先,创建一个计数器来区分消息。也是一个存储所有消息的对象。我们声明我们期望的所有有效消息(参见第 2 部分):
var jsonMsgNr = 0;
var jsonMessages = {};
var validmsg = { updateModel:1 }
Run Code Online (Sandbox Code Playgroud)
现在,每次我们收到一条消息时都添加一条消息:
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 };
Run Code Online (Sandbox Code Playgroud)
为了能够传递nr给NotifyAll()额外的参数,需要传递给Bean。我们称它为msgNr:
// Following might look a bit different on older PrimeFaces
window[msg]([{name:'msgNr', value:nr}]);
}
}
}
Run Code Online (Sandbox Code Playgroud)
也许可以查看/sf/answers/505510561/以了解有关以这种方式传递值的更多信息。
该remoteActionbean 现在获得一个额外的参数msgNr传递,该参数必须通过 Ajax 传回。
不幸的是,我不知道(抱歉)这在 Java 中是什么样子。因此,请确保您对 AjaxCall 的回答
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);
}
Run Code Online (Sandbox Code Playgroud)
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);
}
Run Code Online (Sandbox Code Playgroud)
这里有些东西有点多余。例如,您可能不需要该
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}]);
}
}
Run Code Online (Sandbox Code Playgroud)
注意: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);
}
Run Code Online (Sandbox Code Playgroud)
这样 AJAX 请求在sendMessage运行时就已经发出了。如果现在dosomething出现 JavaScript 错误或类似错误,消息仍然可以正确处理。
请注意:所有这些都是在未经任何测试的情况下输入的。可能存在语法错误或更糟的情况。对不起,我已经尽力了。如果您发现错误,编辑就是您的朋友。
现在,所有这些都已就位并进行了序列化运行,您始终可以调用之前的notifyAll()using 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}]);
}
}
Run Code Online (Sandbox Code Playgroud)
现在您可以使用 开始处理runRemote(nr)并使用 调用完成函数realNotifyAll(nr)。
函数updateGuiPushList(nr, state)是state=0:finished 1=added 2=running对 GUI 代码的回调,它更新屏幕上等待处理的推送列表。设置autoRun=false停止自动加工和autoRun=true进行自动加工。
注意:设置autoRunfromfalse到后,当然true需要runRemote用最低的 触发一次。nr
| 归档时间: |
|
| 查看次数: |
2636 次 |
| 最近记录: |