从 p:remoteCommand 的 oncomplete 处理程序调用 JavaScript 函数 - 使用一些 JavaScript 代码模拟相同的函数 [英] Invoking a JavaScript function from oncomplete handler of p:remoteCommand - simulating the same using some JavaScript code

查看:12
本文介绍了从 p:remoteCommand 的 oncomplete 处理程序调用 JavaScript 函数 - 使用一些 JavaScript 代码模拟相同的函数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

注意:虽然这个问题涵盖了带有大量 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 = 函数(事件){jsonMsg=event.data;var json = JSON.parse(jsonMsg);var msg=json["jsonMessage"];如果(窗口[味精]){窗口[味精]();//从字面上理解为函数——updateModel();}};}

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

在以下代码段中,

if (window[msg]) {窗口[味精]();//它从字面上解释为一个JavaScript函数——updateModel();}

window[msg](); 导致与 关联的 JavaScript 函数被调用(依次调用 actionListener="#{bean.remoteAction}" 相关联).

<p:remoteCommand name="updateModel"actionListener="#{bean.remoteAction}"oncomplete="notifyAll()"进程="@this"更新="@none"/>

update="@none" 不一定需要.

<小时>

收到此消息后,我需要将此更新通知所有关联的客户端.我使用以下 JavaScript 函数来执行此操作,该函数与上述 <p:remoteCommand>oncomplete 处理程序相关联.

var jsonMsg;函数 notifyAll() {如果(jsonMsg){发送消息(jsonMsg);}}

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

<小时>

这很有效,但有没有办法在以下条件下发挥作用

if (window[msg]) {窗口[味精]();//在远程命令完成时做一些事情来调用notifyAll().}

这样notifyAll()函数就可以通过一些JavaScript代码直接调用(目前附在oncomplete 和预期的 JavaScript 代码(或什至其他东西)应该模拟这个 oncomplete)基本上消除了依赖全局 JavaScript 变量(jsonMSg)的需要?><小时>

我试图解决的问题(它可能被认为是附加信息).

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

@ApplicationScoped公共类 CategoryListener {@PostPersist@PostUpdate@PostRemovepublic void onChange(Category category) 抛出 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私人事件事件;

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

event.fire(new CategoryChangeEvent(category));

<小时>

在 web 项目中观察到的这个事件如下.

@ApplicationScoped公共类实时更新{public void onCategoryChange(@Observes CategoryChangeEvent 事件) {AdminPush.sendAll("updateModel");}}

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

@ServerEndpoint(value = "/AdminPush", configurator = ServletAwareConfig.class)公共最终类 AdminPush {私有静态最终集<Session>session = new LinkedHashSet();@OnOpenpublic void onOpen(会话会话,EndpointConfig 配置){if (Boolean.valueOf((String) config.getUserProperties().get("isAdmin"))) {会话.添加(会话);}}@OnClose公共无效 onClose(会话会话){session.remove(session);}私有静态 JsonObject createJsonMessage(String message) {返回 JsonProvider.provider().createObjectBuilder().add("jsonMessage", message).build();}公共静态无效sendAll(字符串文本){同步(会话){String message = createJsonMessage(text).toString();对于(会话会话:会话){如果(会话.isOpen()){session.getAsyncRemote().sendText(message);}}}}}

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

session.getAsyncRemote().sendText(message);foreach 循环内向管理员发送有关这些更改的通知(以 JSON 消息的形式)在实体 Category 中制作.

如第一个代码片段所示,window[msg](); 调用一个动作方法(通过前面显示的 )关联使用应用程序作用域 bean - actionListener="#{realTimeMenuManagedBean.remoteAction}".

@Named@ApplicationScoped公共类 RealTimeMenuManagedBean {@注入私有 ParentMenuBean 本地服务;私人列表<类别>类别;私人最终地图<长,列表<子类别>>categoryMap = new LinkedHashMap>();//动态 CSS 菜单需要时的其他列表和映射.public RealTimeMenuManagedBean() {}@PostConstruct私有无效初始化(){填充();}私有无效填充(){categoryMap.clear();category = service.getCategoryList();对于(类别c:类别){Long catId = c.getCatId();categoryMap.put(catId, service.getSubCategoryList(catId));}}//这个方法是通过上面提到的<p:remoteCommand>调用的.公共无效远程操作(){填充();}//仅必要的访问器方法.}

只有在 actionListener="#{realTimeMenuManagedBean.remoteAction}" 完成其完全 - 不能在操作方法完成之前发生 - 应该通过 oncomplate 事件处理程序通知. 这就是为什么取了两个不同的终点.

<小时>

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

@ServerEndpoint("/Push")公共最终类推送{私有静态最终集<Session>session = new LinkedHashSet();@OnOpenpublic void onOpen(会话会话){会话.添加(会话);}@OnClose公共无效 onClose(会话会话){session.remove(session);}@OnMessage公共无效 onMessage(字符串文本){同步(会话){对于(会话会话:会话){如果(会话.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 = 函数(事件){var json = JSON.parse(event.data);var msg = json["jsonMessage"];如果(窗口[味精]){窗口[味精]();}};$(window).on('beforeunload', function () {ws.close();});}

结合以下.

<p:remoteCommand name="updateModel"进程="@this"更新=父菜单"/>

其中 parentMenu - 由该 更新的组件是容器 JSF 组件 id><h:panelGroup> 包含一个带有一堆 的普通 CSS 菜单.

希望这能让场景更清晰.

<小时>

更新:

根据此处准确回答/showcase/ui/ajax/remoteCommand.xhtml" rel="nofollow noreferrer"> (至于具体问题,唯一的问题是删除依赖在此问题的介绍部分中所述的全局 JavaScript 变量上).

解决方案

我不认为我理解您问题的方方面面,但无论如何我都会尝试提供一些帮助.请注意,我不知道 PrimeFaces,所以我所做的只是阅读文档.

我的理解是,您试图摆脱全局变量.但恐怕,我不认为这是可能的.

这里的问题是,PrimeFaces 不允许您将某些内容从远程调用的调用透明地传递到 oncomplete 调用(除非您将它传递给 Bean 的 Java 代码和然后回到用户界面,这通常不是你想要的).

但是,我希望你能非常接近它.

第 1 部分,JS 提前返回

另请注意,您可能对 Java 和 JavaScript 存在一些误解.

Java 是多线程的,可以并行运行多个命令,而 JavaScript 是单线程的,通常从不等待某事完成.异步操作是获得响应式 Web 用户界面所必需的.

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

那么你想用下面的代码管理什么

if (window[msg]) {窗口[味精]();//在远程命令完成时做一些事情来调用notifyAll().在这里做点什么();}

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

要在 Ajax 调用完成后运行一些东西,这必须在 oncomplete 例程(或 onsuccess)中完成.这就是它存在的原因.

第 2 部分,验证 msg

请注意window[msg]() 的一些不同之处.如果您不能完全信任推送的消息,这会被认为有点危险.window[msg]() 本质上运行任何以变量 msg 的内容命名的函数.例如,如果 msg 碰巧是 close 那么 window.close() 将被运行,这可能不是你想要的.

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

var validmsg = { updateModel:1, rc:1 }[..]if (validmsg[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 = 函数(事件){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:

//以下在旧的 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 notifyAll(x, data, pfArgs) {var nr = ???;//找出如何从数据中提取 msgNrvar jsonMsg = jsonMessages[nr].jsonMsg;var json = jsonMessages[nr].json;jsonMessages[nr] = null;//空闲内存发送消息(jsonMsg);做某事(json);}

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

function notifyAll(x, data, unk) {var nr = ???;//找出如何从数据中提取 msgNrrealNotifyAll(nr);}函数 realNotifyAll(nr) {if (!(nr in jsonMessages)) 返回;var jsonMsg = jsonMessages[nr].jsonMsg;var json = jsonMessages[nr].json;删除 jsonMessages[nr];//空闲内存发送消息(jsonMsg);做某事(json);}

<块引用>

这里有些东西有点多余.例如,您可能不需要 jsonMessages 中的 json 元素,或者想要再次解析 json 以节省一些内存,以防 json 非常大的.然而,代码并不意味着是最佳的,而是要易于根据您的需要进行调整.

第 4 部分:序列化请求

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

var jsonMsgNr = 0;var jsonMessages = {};var validmsg = { updateModel:1 }var jsonMsgNrLast = 0;//添加如果(window.WebSocket){var ws = new WebSocket("wss://localhost:8181/ContextPath/AdminPush");ws.onmessage = 函数(事件){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) {//添加jsonMsgNrLast = nr;//添加window[msg]([{name:'msgNr', value:nr}]);}}}}函数 realNotifyAll(nr) {if (!(nr in jsonMessages)) 返回;var jsonMsg = jsonMessages[nr].jsonMsg;var json = jsonMessages[nr].json;删除 jsonMessages[nr];//空闲内存发送消息(jsonMsg);做某事(json);//以下添加nr++;jsonMsgNrLast = 0;if (nr in jsonMessages){jsonMsgNrLast = nr;window[jsonMessages[nr].json.msg]([{name:'msgNr', value:nr}]);}}

注意:jsonMsgNrLast 可能只是一个标志(真/假).然而,将当前处理的数字放在一个变量中也许可以帮助其他地方.

话虽如此,但如果 sendMessagedosomething 出现问题,就会出现饥饿问题.所以也许你可以稍微交错一下:

function realNotifyAll(nr) {if (!(nr in jsonMessages)) 返回;var jsonMsg = jsonMessages[nr].jsonMsg;var json = jsonMessages[nr].json;删除 jsonMessages[nr];//空闲内存nr++;jsonMsgNrLast = 0;if (nr in jsonMessages){jsonMsgNrLast = nr;//确保这里是异步的!window[jsonMessages[nr].json.msg]([{name:'msgNr', value:nr}]);}//移动了,但现在不能依赖 jsonMsgNrLast:发送消息(jsonMsg);做某事(json);}

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

<块引用>

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

第 5 部分:从 JS 直接调用

现在,有了所有这些和序列化的运行,您可以始终使用 realNotifyAll(jsonMsgNrLast) 调用之前的 notifyAll().或者您可以在列表中显示 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,通过GUI设置假控制如果(window.WebSocket){var ws = new WebSocket("wss://localhost:8181/ContextPath/AdminPush");ws.onmessage = 函数(事件){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);如果(自动运行&& !jsonMsgNrLast){运行远程(nr);}}}}函数 realNotifyAll(nr) {if (!(nr in jsonMessages)) 返回;var jsonMsg = jsonMessages[nr].jsonMsg;var json = jsonMessages[nr].json;删除 jsonMessages[nr];//空闲内存updateGuiPushList(nr, 0);jsonMsgNrLast = 0;如果(自动运行)运行远程(nr+1);//移动了,但现在不能依赖 jsonMsgNrLast:发送消息(jsonMsg);做某事(json);}功能运行远程(nr){如果 (nr==jsonMsgNrLast) 返回;if (nr in jsonMessages){if (jsonMsgNrLast) { alert("哎呀!请等到处理完成");返回;}jsonMsgNrLast = nr;updateGuiPushList(nr, 2);//确保这里是异步的!window[jsonMessages[nr].json.msg]([{name:'msgNr', value:nr}]);}}

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

函数 updateGuiPushList(nr, state)state=0:finished 1= added 2=running 是对更新屏幕上的 GUI 代码的回调等待处理的推送列表.设置autoRun=false 停止自动处理,autoRun=true 自动处理.

注意:将 autoRunfalse 设置为 true 后,您需要使用最低的 runRemote 触发一次 runRemotecode>nr,当然.

Caution : Although this question covers long textual information with a mess of Java code snippets, it is merely targeted to JavaScript/jQuery and a bit of PrimeFaces stuff (just <p:remoteCommand>) as mentioned in the introductory part in the beginning.


I am receiving a JSON message from WebSockets (Java EE 7 / JSR 356 WebSocket API) as follows.

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();
        }
    };
}

In the above code, event.data contains a JSON string {"jsonMessage":"updateModel"}. Thus, msg will contain a string value which is updateModel.

In the following segment of code,

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

window[msg](); causes a JavaScript function associated with a <p:remoteCommand> to be invoked (which in turn invokes an actionListener="#{bean.remoteAction}" associated with the <p:remoteCommand>).

<p:remoteCommand name="updateModel"
                 actionListener="#{bean.remoteAction}" 
                 oncomplete="notifyAll()"
                 process="@this"
                 update="@none"/>

update="@none" is not necessarily needed.


After receiving this message, I need to notify all the associated clients about this update. I use the following JavaScript function to do so which is associated with the oncomplete handler of the above <p:remoteCommand>.

var jsonMsg;

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

Notice that the variable jsonMsg is already assigned a value in the first snippet - it is a global variable. sendMessage() is another JavaScript function that actually sends a notification about this update to all the associated clients through WebSockets which is not needed in this question.


This works well but is there a way to do some magic in the following condition

if (window[msg]) {
    window[msg]();

    //Do something to call notifyAll() on oncomplete of remote command.
}

so that the notifyAll() function can be invoked through some JavaScript code directly (which is currently attached to oncomplete of <p:remoteCommand> and the expected JavaScript code (or even something else) should simulate this oncomplete) basically eliminating the need to depend upon a global JavaScript variable (jsonMSg)?


Edit : The problem I am trying to solve (it may be considered to be additional information).

When an admin for example, makes some changes (by means of DML operations) to a JPA entity named Category, entity listeners are triggered which in turn causes a CDI event to be raised as follows.

@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));
    }
}

Needless to say that the entity Category is designated with the annotation @EntityListeners(CategoryListener.class).

Just one side note (completely off topic) : Getting an instance of BeanManager through a JNDI look-up as done in the preceding code snippet is temporary. The GlassFish Server 4.1 having the Weld version 2.2.2 final fails to inject the CDI event javax.enterprise.event.Event<T> which is supposed to be injected as follows.

@Inject
private Event<CategoryChangeEvent> event;

And then, the event can be fired as follows with reference to the relevant code snippet above.

event.fire(new CategoryChangeEvent(category));


This event is observed in the web project as follows.

@ApplicationScoped
public class RealTimeUpdate {

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

Where an admin uses his own end-point as follows (AdminPush.sendAll("updateModel"); is invoked manually therein).

@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);
                }
            }
        }
    }
}

Here only an admin is allowed to use this end-point. All other users are prevented from creating a WebSocket session using a conditional check in the onOpen() method.

session.getAsyncRemote().sendText(message); inside the foreach loop sends a notification (in the form of a JSON message) to the admin about these changes made in the entity Category.

As shown in the first code snippet, window[msg](); invokes an action method (through a <p:remoteCommand> as shown earlier) associated with an application scoped bean - 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.
}

All other users/clients (who are on a different panel - other than the admin panel) should only be notified when actionListener="#{realTimeMenuManagedBean.remoteAction}" finishes in its entirely - must not happen before the action method finishes - should be notified through the oncomplate event handler of <p:remoteCommand>. This is the reason why two different end-points have been taken.


Those other users use their own end-point:

@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);
                }
            }
        }
    }
}

The method annotated with @OnMessage comes to play, when a message is sent through oncomplete of <p:remoteCommand> as shown above.

Those clients use the following JavaScript code to just fetch the new values from the above-mentioned application scoped bean (the bean was already queried adequately by the admin from the database. Thus, there is no need to ridiculously query it again by each and every individual client separately (other than the admin). Hence, it is an application scoped 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();
    });
}

In conjunction with the following <p:remoteCommand>.

<p:remoteCommand name="updateModel"
                 process="@this"
                 update="parentMenu"/>

Where parentMenu - the component to be updated by this <p:remoteCommand> is an id of a container JSF component <h:panelGroup> which contains a plain CSS menu with a bunch of <ui:repeat>s.

Hope this makes the scenario clearer.


Update :

This question has been answered precisely here based on <p:remoteCommand> (As to the concrete question, the sole question was to remove a dependency upon a global JavaScript variable as stated in the introductory part of this question).

解决方案

I don't think I understood every aspect of your problem, but anyway I try to help a bit. Note that I do not know PrimeFaces, so all I did was reading the docs.

What I understand is, that you try to get rid of the global variable. But I am afraid, I do not think this is possible.

The problem here is, that PrimeFaces does not allow you to pass something transparently from your invocation of the remote call further to the oncomplete call (except you pass it to a Java code of the Bean and then back to the UI, and this usually is not what you want).

However, I hope, you can come very close to it.

Part 1, JS returns early

Please also note that there probably is some misconception about Java and JavaScript.

Java is multithreaded and runs several commands in parallel, while JavaScript is singlethreaded and usually never waits for something to complete. Doing things asychronously is mandatory to get a responsive Web-UI.

Hence your remoteCommand invocation (seen from the JS side) will (usually, async case) return long before the oncomplete handler will be invoked. That means, if window[msg]() returns, you are not finished with the remoteCommand yet.

So what you want to manage with following code

if (window[msg]) {
    window[msg]();

    //Do something to call notifyAll() on oncomplete of remote command.
    dosomethinghere();
}

will fail. dosomethinghere() will not be invoked when the remoteCommand returned (as JS does not want to wait for some event, which might never happen). This means, dosomethinghere() will be invoked when the Ajax-request was just opened to the remote (to the Java application).

To run something after the Ajax call finished, this must be done in the oncomplete routine (or onsuccess). This is why it's there.

Part 2, validate msg

Please note something different about window[msg](). This can be considered a bit dangerous if you cannot trust the pushed message completely. window[msg]() essentially runs any function named with the contents of the variable msg. For example if msg happen to be close then window.close() will be run, which probably is not what you want.

You should make sure, msg is one expected word, and decline all other words. Example code for this:

var validmsg = { updateModel:1, rc:1 }

[..]

if (validmsg[msg] && window[msg])
  window[msg]();

Part 3: How to handle multiple JSON messages in parallel

The global variable has some drawback. There is only one. If you happen to receive another JSON message on the WebSocket while the previous message still is processing in the remoteCommand, this will overwrite the previous message. So the notifyAll() will see the newer message twice, the old one is lost.

A classical race condition. What you must do is, to create something like a registry to register all the messages, and then pass some value to notifyAll() to tell, which of the registered messages shall be processed.

With only a little change, you can either parallely (here) or serially (Part 4) process the messages.

First, create a counter to be able to distinguish the messages. Also an object to store all the messages. And we declare all valid messages we expect (see Part 2):

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

Now add a message each time we receive one:

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 };

To be able to pass the nr to NotifyAll() an additional parameter needs to be passed to the Bean. Let's call it msgNr:

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

Perhaps have a look into https://stackoverflow.com/a/7221579/490291 for more on passing values this way.

The remoteAction bean now gets an additional parameter msgNr passed, which must be passed back via Ajax.

Unfortunately I have no idea (sorry) how this looks in Java. So make sure, your answer to the AjaxCall copies the msgNr out again.

Also, as the documentation is quiet about this subject, I am not sure how the parameters are passed back to the oncomplete handler. According to the JavaScript debugger, notifyAll() gets 3 parameters: xhdr, payload, and pfArgs. Unfortunately I was not able to setup a test case to find out how things look like.

Hence the function looks a bit like (bear with me, please):

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);
}

If you split this into two functions, then you can invoke the notifyAll() from other parts in your application:

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);
}

Some things here are a bit redundant. For example you perhaps do not need the json element in jsonMessages or want to parse the json again to spare some memory in case the json is very big. However the code is meant not to be optimal but to be easy to adjust to your needs.

Part 4: serialize requests

Now to the changes to serialize things. That's quite easy by adding some semaphore. Semaphores in JavaScript are just variables. This is because there is only one global thread.

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}]);
    }
}

Note: jsonMsgNrLast could be just a flag (true/false). However having the current processed number in a variable perhaps can help somewhere else.

Having said that, there is a starvation problem in case something fails in sendMessage or dosomething. So perhaps you can interleave it a bit:

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);
}

This way the AJAX request is already send out while sendMessage is running. If now dosomething has a JavaScript error or similar, the messages are still processed correctly.

Please note: All this was typed in without any tests. There might be syntax errors or worse. Sorry, I tried my best. If you find a bug, edit is your friend.

Part 5: Direct Invocation from JS

Now, with all this in place and a serialized Run, you can always invoke the previous notifyAll() using realNotifyAll(jsonMsgNrLast). Or you can display the jsonMessages in a list and choose any arbitrary number.

By skipping the call to window[jsonMessages[nr].json.msg]([{name:'msgNr', value:nr}]); (and above window[msg]([{name:'msgNr', value:nr}]);) you also can halt the Bean processing and run it on-demand using the usual JQuery callbacks. For this create a function and change the code a bit again:

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}]);
    }
}

Now you can start the processing with runRemote(nr) and invoke the completion function with realNotifyAll(nr).

The function updateGuiPushList(nr, state) with state=0:finished 1=added 2=running is the callback to your GUI code which updates the on-screen list of waiting pushes to process. Set autoRun=false to stop automatic processing and autoRun=true for automatic processing.

Note: After setting autoRun from false to true you need to trigger runRemote once with the lowest nr, of course.

这篇关于从 p:remoteCommand 的 oncomplete 处理程序调用 JavaScript 函数 - 使用一些 JavaScript 代码模拟相同的函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆