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

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

窗口[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代码调用notifyAll()函数(目前附加到< p:remoteCommand>的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));
    }
}

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

只是一个注意事项(完全偏离主题):通过JNDI查找获取BeanManager的实例,如前面的代码片段中所做的那样是临时的.具有Weld版本2.2.2 final的GlassFish Server 4.1无法注入CDI事件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(消息);在foreach循环内部向管理员发送关于在实体类别中进行的这些更改的通知(以JSON消息的形式).

如第一个代码片段所示,window [msg]();调用与应用程序作用域bean相关联的action方法(通过< 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组件的id< h:panelGroup>它包含一个简单的CSS菜单,其中包含一堆< ui:repeat> s.

希望这会使情况更加清晰.

更新:

这个问题已经基于<p:remoteCommand>准确回答了here(关于具体问题,唯一的问题是如本问题的介绍部分所述,删除对全局JavaScript变量的依赖).

解决方法:

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

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

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

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

第1部分,JS早早回归

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

Java是多线程的,并行运行多个命令,而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();
}

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

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

第2部分,验证消息

请注意窗口[msg]()的不同之处.如果您不能完全信任推送的消息,则可以认为这有点危险. window [msg]()基本上运行以变量msg的内容命名的任何函数.例如,如果msg碰巧关闭,那么将运行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传回.

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.

因此功能看起来有点像(请跟我一起):

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

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.

第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错误或类似错误,仍然可以正确处理消息.

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.

第5部分:JS的直接调用

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

通过跳过对窗口的调用[jsonMessages [nr] .json.msg]([{name:’msgNr’,value:nr}]); (以及窗口[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),状态= 0:已完成1 =已添加2 =正在运行的是对GUI代码的回调,该代码更新了屏幕上等待处理的列表.设置autoRun = false以停止自动处理,并设置autoRun = true以进行自动处理.

注意:将autoRun从false设置为true后,您需要以最低的nr触发runRemote一次.

标签:jsf-2-2,javascript,jquery,jsf,primefaces
来源: https://codeday.me/bug/20191006/1862514.html