Reputation: 3422
I'd like to implement the OmniFaces <o:socket> with the <o:commandScript> option as documented here.
My JSF page looks like:
<h:form id="myForm">
<!-- the socket is by default disconnected. It will
connect when the user selects a subChannel.
Setting the subChannel to "null" must disconnect
the socket -->
<o:socket channel="myChannel" scope="session"
connected="#{myManager.subChannel ne null}"
user="#{myManager.subChannel}"
onmessage="someTestScript" onopen="onOpen" onclose="onClose"/>
<o:commandScript name="someTestScript"
actionListener="#{myManager.testPush}"
render=":testValue" />
<!-- dummy rendering to check if everything is working -->
<h:panelGroup id="testValue"
rendered="#{myManager.subChannel ne null}" >
<h:outputText value="#{myManager.bbb} #{myManager.aaa}" />
</h:panelGroup>
<!-- To simplify: I have several buttons which allows
the user to select the socket "user". The buttons
are only displayed when no "user" is selected
(thus, socket is not connected yet as well) -->
<p:dataGrid value=#{...a list...} var="sth"
rendered="#{myManager.subChannel eq null">
<p:ajax event="click"
update="myForm"
listener="#{myManager.setSubChannel(sth.id)}" />
<p:panel>
<h:outputText value=#{sth.text} />
</p:panel>
</p:dataGrid>
<!-- Resetting -->
<p:commandButton value="Reset"
actionListener="#{myManager.setSubChannel(null)"
update="myForm" />
</h:form>
My backing bean:
@Named(value = "myManager")
@SessionScoped
public class MyStuffManager implements Serializable{
private Long subChannel = null;
private Long aaa;
private String bbb;
public void testPush(Long aaa, String bbb){
System.out.println("Text received: " + bbb + ", Number received: " + aaa);
this.aaa = aaa;
this.bbb = bbb;
}
// getter & setter
}
My socket push point:
@Named
@ApplicationScoped
public Class mySocket implements Serializable{
@Inject
@Push(channel = "myChannel")
private PushContext socket;
// Basically, all methods are triggered by CDI events. So
// far, I just want to send an int and a String to see how
// I send parameters.
public void test(@Observes @MyEventQualifier AnEvent event) {
Map<String, Object> input = new HashMap<>();
input.put("aaa", 123456789);
input.put("bbb", "a text");
socket.send(input);
}
}
Currently, my situation is:
However, my open points are:
oncomplete="OmniFaces.Push.open('myChannel')", the subChannel is still resolved to null. Strangely enough, if I execute other JSF stuff (like updating an <h:outputText> field), the subChannel is properly updated and retrieved. How should I have a dynamic "user" attribute depending on a EL when connection is not automatic?
We're using OmniFaces 2.6 as stated by the documentation.
Any hint/advise is obviously welcomed. Thanks.
Upvotes: 2
Views: 1038
Reputation: 3422
I finally fixed it! For those who are interested in the solving, and note for myself later:
1) How to dynamically change sub channel
Option 1: Change the o:socket scope to view so that the HTTP session scope is wider than the socket scope.
<o:socket channel="myChannel" scope="view"
connected="#{myManager.subChannel ne null}"
user="#{myManager.subChannel}"
onmessage="someTestScript" onopen="onOpen" onclose="onClose"/>
When exiting the socket, the socket will properly closes and re-open with the correct subChannel value. For some reasons, it seems than having a "subChannel" as a String works better than Long.
Option 2: Use a "sub channel map". Actually, I haven't stated in my problem than multiple users can connect to the same sub channel. Another option is to have all users connected via their userId <o:socket channel="myChannel" user="#{user.id}" session="scope" /*default value anyway*/ {...}/>
Then, instead of sending to a subchannel, I send to a collection of user id:
Map<String, Set<String>> subChannelUserMap = new HashMap<>();
// ...
public void onUserConnect(User user, String subChannel){
subChannelUserMap.get(subChannel).add(user.getId());
}
// ....
public void sendMessage(Map<K, V> input, String subChannel){
socket.send(input, subChannelUserMap.get(subChannel));
}
2) Call backing bean methods from JavaScript
As stated in my self-comment, I misunderstood <o:commandScript>
usage. Let's consider the backing bean method:
// ...
public class BackingBean{
public void test(){
String aaa = Faces.getRequestParameter("aaa", String.class);
String bbb = Faces.getRequestParameter("aSillyName", String.class);
LOGGER.debug("aaa={}, bbb={}", aaa, bbb);
}
}
with the socket written as <o:socket /*...*/ onmessage=onMessage />
, I'll have:
JSF Page:
<o:commandScript name="aSillyFunctionName" render="..."
actionListener="#{backingBean.test}" ... />
JavaScript file:
function onMessage(message, channel, event){
aSillyFunctionName({aaa: message.aaa, aSillyName: bbb});
}
And here I'm done.
Upvotes: 2