0

I have a jsf-2.2 primefaces datatable with paging. One column displays a status of a network component and on table loading I asynchronously query an external service over a resource adapter. When the data table is loaded the status cells are displayed with "Status unknown". Occasionally I will receive status packets from single network components as json in a Message Driven Bean. I then want to send this status via a websocket to the Browser to update the table cells. The json status packets contain the primary database keys of the network components, but on the javascript side in the browser, I need the clientIds of the data table cells. The clientIds have the form "switchTable:swths:2:switchActive" and only differ by the index in the middle.

My first idea was to write a facelet and overwrite the id with the primary key of the network component, but I think this is not the way to go.

Is there a recommended way to map the clientIds to the individual primary keys? This mapping would need to include the session, as there are multiple sessions with the same clientId. I want to update the table cell, that I found in DOM with document.getElementById immediately with the status text.

Jochen Buchholz
  • 370
  • 1
  • 16
  • Do you need selecting it by id or is a jquery selector also 'allowed'? – Kukeltje Jul 03 '18 at 18:25
  • On the server side I have the primary keys, but on the client side I have only the jsf generated clientIds. I think the best way are the use of html5 data attributes f:passThroughAttribute. (Missing html5 knowledge on my side ;-) ) – Jochen Buchholz Jul 04 '18 at 13:48
  • Yes, that is indeed sort of what I was thinking of too. PrimeFaces does the same for row-seletion. Look at the generated html. If you are sure you don't introduce other risks, you can add your primary keys to each row this way and use it in the selectors. If you create a solution, please create an answer since I think it is a very generic issue and helpful to others – Kukeltje Jul 04 '18 at 15:18
  • I'm now in research on ajax / CDI events. At the moment I propagate the async status events as CDI events from the MDB to an observer method in the websocket server endpoint class and send it through the websocket. I'm not sure if there is a possibility to do it like a value change listener with ajax events. In this case the websocket is not needed. I will post the solution here when the problem is solved. – Jochen Buchholz Jul 04 '18 at 17:11
  • No you can't use valuechangelisteners for this. – Kukeltje Jul 04 '18 at 17:28
  • I know, I said 'like a value change listener'. I found an article that discuss this problem and they use ajax listeners. Now I want to test if that solution solve my problems. [Server side action methods on JSF ValueChange events using AJAX listeners](http://www.ocpsoft.org/java/jsf2-java/server-side-action-methods-on-jsf-valuechange-events-using-ajax-listeners/) – Jochen Buchholz Jul 05 '18 at 08:17
  • No, this article does not discuss this problem. It is a 'feature' that can be used when a change in an input that initiated on the client-side. The 'mapping' is not even the issue, you could keep track of that server-side in a list. You are not in a jsf context when you want to 'push' the update, so you can only send the value to the client-side and update it there with javascript, or send a signal to the client that some updates are waiting, use a remoteCommand to call a server-side method and in that method (that IS in a facesContext) update the components that need updating. – Kukeltje Jul 05 '18 at 09:20
  • The former is simple for plain content (with very little markup), the latter updates the JSF components fully but you need to keep track of a 'maping' between the row and PK (and ofcourse you need to know the columnid) – Kukeltje Jul 05 '18 at 09:21

1 Answers1

1

I came up with 2 solutions of this interesting problem.

Solution 1

  • defining hidden table column with 'ID' text in it,
  • finding data table row and cell using javascript by iterating through all rows and matching 'value' of ID column cell

Solution 2

  • utilizing JSF2 namespace xmlns:pt="http://xmlns.jcp.org/jsf/passthrough" to 'programatically' set html id attribute of column cell elements,
  • finding cell element with javascript's document.getElementById function

Example

xhtml page

<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:p="http://primefaces.org/ui" 
    xmlns:pt="http://xmlns.jcp.org/jsf/passthrough" >

    <f:view contentType="text/html">
        <h:head>
            <h:outputScript library="primefaces" name="jquery/jquery.js" />
        </h:head>

        <h:body>
            <p:dataTable id="dataTable" widgetVar="dataTableWidget" value="#{switchController.switches}" var="switch" paginator="true" rows="5">
                <p:column headerText="id" style="display:none">
                    <h:outputText value="#{switch.id}"/>
                </p:column>
                <p:column headerText="Name">
                    <h:outputText value="#{switch.name}"/>
                </p:column>
                <p:column headerText="status">
                    <h:outputText value="#{switch.status}" pt:id="dataTable:switch:#{switch.id}:status"/>
                </p:column>
            </p:dataTable>

            <p:commandButton value="Change status" type="button" onclick="changeStatusExample()"/>
        </h:body>
    </f:view>
</html>

Backing Bean (just for the purpose of this example)

public class SwitchController {

    List<Switch> switches;

    @PostConstruct
    public void init() {
        switches = new ArrayList<>();
        for (int i = 1; i < 11; i++) {
            switches.add(new Switch(i, "Switch " + i, "STATUS_UNKNOWN"));
        }
    }

    public List<Switch> getSwitches() {
        return switches;
    }

    public void setSwitches(List<Switch> switches) {
        this.switches = switches;
    }
}

where Switch is POJO with id, name and status fields.

Javascript

// SOLUTION 1
function getTableCellByIdVer1(switchId, colNumber) {
    //get table rows
    var tableRows = PF('dataTableWidget').tbody[0].childNodes;
    //loop through rows    
    for (i = 0; i < tableRows.length; i++) {
        //get cells of current row
        var cells = tableRows[i].cells;
        //get value of hidden ID column cell
        var id = cells[0].innerText;
        if (id === switchId) {
            return tableRows[i].cells[colNumber];
        }
    }
    return null;
}

// SOLUTION 1
function changeSwitchStatusVer1(changedSwitch) {
    var statusCell = getTableCellByIdVer1(changedSwitch.id, 2);
    if (statusCell) {
        //row exists...now we can change the status
        statusCell.innerText = changedSwitch.status;
    } else {
        console.log('Row with switch id=' + changedSwitch.id + ' not found');
    }
}

// SOLUTION 2
function changeSwitchStatusVer2(changedSwitch) {
    //find cell element by html ID attribute given in xhtml
    var elementId='dataTable:switch:' + changedSwitch.id + ':status';
    var statusCell = document.getElementById(elementId);
    if (statusCell) {
        statusCell.innerText = changedSwitch.status;
    } else {
        console.log('Element with id=' + elementId + ' not found');
    }
}

// EXAMPLE
function changeStatusExample() {
    //simulating situation when websocket pushes info about changed switch to browser page
    // SOLUTION 1
    var changedSwitch = {id: '2', status: 'STATUS_ON'};
    changeSwitchStatusVer1(changedSwitch);

    // SOLUTION 2
    //another switch status changed..using another approach to update table cell
    changedSwitch = {id: '4', status: 'STATUS_OFF'};
    changeSwitchStatusVer2(changedSwitch);
}

IMPORTANT: Note that both solutions will only work if (changed switch) ID's are part of currently 'visible' data table page.

Dusan Kovacevic
  • 1,377
  • 1
  • 13
  • 19
  • My favourite solution is passthrough, but I use the `' Tag from `http://java.sun.com/jsf/core`. It works perfectly like your second solution. – Jochen Buchholz Jul 20 '18 at 13:08