0
votes

I'm working on a theme for an app I'm making with PrimeFaces 6.2 (community edition) and I'd like to get my simulated DAO objects working before I proceed with my css templating.

I've got an issue I came across in the past and I can't find the right answer for it again. Would someone point out an error I've made somewhere in my code?

Details:

I've made a somewhat complex DataTable using PrimeFaces LazyDataModel with little help from PrimeFaces Showcase pages. My main issue is, when I write something in the filter fields or click on any column headers to do data sorting or even click on pagination buttons I get an unexpexted data rendering issue.

Filtered, sorted and paginated results get displayed in a single concatenated row what I don't want. I've posted images and code further below for insight.

Also, I'd like to point out:

  • No exceptions in JS console.
  • No exceptions in Java console.
  • don't mind the missing pagination icons (text-indent: 0;)

Before filtering

After filtering

XHTML:

<h:form id="input-form-dt2">
    <H4>DATA TABLE - LAZY MODEL</H4>
    <div class="flex-container">
        <p:outputPanel id="dev-input-panel-13">
            <p:dataTable var="DOTable" value="#{dtModelLazy.DOTList}" paginator="true" rows="10" rowKey="#{DOTable.userID}" 
                         paginatorTemplate="{RowsPerPageDropdown} {FirstPageLink} {PreviousPageLink} {CurrentPageReport} {NextPageLink} {LastPageLink}"
                         rowsPerPageTemplate="5,10,15,25,50" selectionMode="single" selection="#{dtModelLazy.selectedObj}" id="DTModelB" lazy="true">
                <p:ajax event="rowSelect" listener="#{dtModelLazy.onRowSelect}" update="input-form-dt2:dlgDTOObjDetail" oncomplete="PF('DTObjDialog').show()" />
                <p:column headerText="User ID" sortBy="#{DOTable.userID}" filterBy="#{DOTable.userID}" filterMatchMode="contains">
                    <h:outputText value="#{DOTable.userID}" />
                </p:column>
                <p:column headerText="Name" sortBy="#{DOTable.name}" filterBy="#{DOTable.name}" filterMatchMode="contains">
                    <h:outputText value="#{DOTable.name}" />
                </p:column>
                <p:column headerText="Surname" sortBy="#{DOTable.surname}" filterBy="#{DOTable.surname}" filterMatchMode="contains">
                    <h:outputText value="#{DOTable.surname}" />
                </p:column>
                <p:column headerText="Age" sortBy="#{DOTable.age}" filterBy="#{DOTable.age}" filterMatchMode="contains">
                    <h:outputText value="#{DOTable.age}" />
                </p:column>
                <p:column headerText="Address" sortBy="#{DOTable.address}" filterBy="#{DOTable.address}" filterMatchMode="contains">
                    <h:outputText value="#{DOTable.address}" />
                </p:column>
                <p:column headerText="City" sortBy="#{DOTable.city}" filterBy="#{DOTable.city}" filterMatchMode="contains">
                    <h:outputText value="#{DOTable.city}" />
                </p:column>
                <p:column headerText="Post code" sortBy="#{DOTable.postCode}" filterBy="#{DOTable.postCode}" filterMatchMode="contains">
                    <h:outputText value="#{DOTable.postCode}" />
                </p:column>
                <p:column headerText="Country code" sortBy="#{DOTable.countryCode}" filterBy="#{DOTable.countryCode}" filterMatchMode="contains">
                    <h:outputText value="#{DOTable.countryCode}" />
                </p:column>
                <p:column headerText="Phone number" sortBy="#{DOTable.phoneNumber}" filterBy="#{DOTable.phoneNumber}" filterMatchMode="contains">
                    <h:outputText value="#{DOTable.phoneNumber}" />
                </p:column>
                <p:column headerText="Avatar hash" sortBy="#{DOTable.photoID}" filterBy="#{DOTable.photoID}" filterMatchMode="contains">
                    <h:outputText value="#{DOTable.photoID}" />
                </p:column>
            </p:dataTable>
            <p:dialog id="dlgDTOObjDetail" header="DataTable Object Detail" widgetVar="DTObjDialog" modal="true" showEffect="fade" hideEffect="fade" resizable="false">
                <p:outputPanel id="DTObjDetail">
                    <p:panelGrid  columns="2" rendered="#{not empty dtModelLazy.selectedObj}" columnClasses="label,value">
                        <h:outputText value="User ID: " />
                        <h:outputText value="#{dtModelLazy.selectedObj.userID}" />
                        <h:outputText value="Name: " />
                        <h:outputText value="#{dtModelLazy.selectedObj.name}" />
                        <h:outputText value="Surname: " />
                        <h:outputText value="#{dtModelLazy.selectedObj.surname}" />
                        <h:outputText value="Age: " />
                        <h:outputText value="#{dtModelLazy.selectedObj.age}" />
                        <h:outputText value="Address: " />
                        <h:outputText value="#{dtModelLazy.selectedObj.address}" />
                        <h:outputText value="City: " />
                        <h:outputText value="#{dtModelLazy.selectedObj.city}" />
                        <h:outputText value="Post code: " />
                        <h:outputText value="#{dtModelLazy.selectedObj.postCode}" />
                        <h:outputText value="Country code: " />
                        <h:outputText value="#{dtModelLazy.selectedObj.countryCode}" />
                        <h:outputText value="Phone number: " />
                        <h:outputText value="#{dtModelLazy.selectedObj.phoneNumber}" />
                        <h:outputText value="Photo hash: " />
                        <h:outputText value="#{dtModelLazy.selectedObj.photoID}" /> 
                    </p:panelGrid>
                </p:outputPanel>
            </p:dialog>
        </p:outputPanel>
    </div>
    <hr></hr>
</h:form>

LAZY MODEL:

public class DataTableModelLazy extends LazyDataModel<DODataTable> {
    private static final long serialVersionUID = -2647349397077805782L;

    private List<DODataTable> datasource;

    public DataTableModelLazy(List<DODataTable> datasource) {
        this.datasource = datasource;
    }

    @Override
    public DODataTable getRowData(String rowKey) {
        for(DODataTable dtObj : datasource) {
            if(dtObj.getUserID().equals(rowKey))
                return dtObj;
        }

        return null;
    }

    @Override
    public Object getRowKey(DODataTable dtObj) {
        return dtObj.getUserID();
    }

    @Override
    public List<DODataTable> load(int first, int pageSize, String sortField, SortOrder sortOrder, Map<String,Object> filters) {
        List<DODataTable> data = new ArrayList<DODataTable>();

        //filter
        for(DODataTable dtObj : datasource) {
            boolean match = true;

            if(filters != null) {
                for (Iterator<String> it = filters.keySet().iterator(); it.hasNext();) {
                    try {
                        String filterProperty = it.next();
                        Object filterValue = filters.get(filterProperty);
                        Field field = dtObj.getClass().getDeclaredField(filterProperty);
                        field.setAccessible(true);
                        String fieldValue = String.valueOf(field.get(dtObj));
                        field.setAccessible(false);
                        if(filterValue == null || fieldValue.startsWith(filterValue.toString())) {
                            match = true;
                        } else {
                            match = false;
                            break;
                        }
                    } catch(Exception e) {
                        match = false;
                    }
                }
            } 
            if(match) {
                data.add(dtObj);
            }
        }

        //sort
        if(sortField != null) {
            Collections.sort(data, new DataTableModelLazySorter(sortField, sortOrder));
        }

        //rowCount
        int dataSize = data.size();
        this.setRowCount(dataSize);

        //paginate
        if(dataSize > pageSize) {
            try {
                return data.subList(first, first + pageSize);
            } catch(IndexOutOfBoundsException e) {
                return data.subList(first, first + (dataSize % pageSize));
            }
        } else {
            return data;
        }
    }
}

VIEW BEAN:

@Named("dtModelLazy")
@ViewScoped
public class DataGeneratorBeanLazy implements Serializable {
    private static final long serialVersionUID = -5918527333909822277L;

    private LazyDataModel<DODataTable> DOTList;
    private DODataTable selectedObj;

    @Inject
    private DataGeneratorBean dataGen;

    @PostConstruct
    public void init() {
        DOTList = new DataTableModelLazy(dataGen.createDTObjects(1500));
    }

    public LazyDataModel<DODataTable> getDOTList() {
        return DOTList;
    }

    public void setDOTList(LazyDataModel<DODataTable> dOTList) {
        DOTList = dOTList;
    }

    public void onRowSelect(SelectEvent event) {
        FacesMessage msg = new FacesMessage("DataTable object selected!", ((DODataTable) event.getObject()).getUserID());
        FacesContext.getCurrentInstance().addMessage(null, msg);
    }

    public DODataTable getSelectedObj() {
        return selectedObj;
    }

    public void setSelectedObj(DODataTable selectedObj) {
        this.selectedObj = selectedObj;
    }

}

Update 1

I have modified the update property as update="input-form-dt2:dlgDTOObjDetail"to meet the suggestion provided. Also, I added the id property for the dialog. The issue still remains.

Update 2

I've changed my approach and started with the basic DataTable first. Also, I've stripped the .xhtml to a bare minimum. It contains only a form with the DataTable inside like so:

<ui:composition xmlns="http://www.w3.org/1999/xhtml"   
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:f="http://xmlns.jcp.org/jsf/core"
    xmlns:h="http://xmlns.jcp.org/jsf/html"
    xmlns:p="http://primefaces.org/ui">
    <div>
        UI components design
    </div>

    <h:form id="input-form-dt1">
        <h4>DATA TABLE - BASIC</h4>
        <p:dataTable id="DTableA" var="dataObject" value="#{dataTableBean.objectList}" paginator="true" rows="10" rowKey="#{dataObject.id}" 
                     paginatorTemplate="{RowsPerPageDropdown} {FirstPageLink} {PreviousPageLink} {CurrentPageReport} {NextPageLink} {LastPageLink}" 
                     rowsPerPageTemplate="5,10,15,25,50">
            <p:column headerText="User ID" sortBy="#{dataObject.userID}" filterBy="#{dataObject.userID}" filterMatchMode="contains">
                <h:outputText value="#{dataObject.userID}" />
            </p:column>
            <p:column headerText="Name" sortBy="#{dataObject.name}" filterBy="#{dataObject.name}" filterMatchMode="contains" >
                <h:outputText value="#{dataObject.name}" />
            </p:column>
            <p:column headerText="Surname" sortBy="#{dataObject.surname}" filterBy="#{dataObject.surname}" filterMatchMode="contains" >
                <h:outputText value="#{dataObject.surname}" />
            </p:column>
            <p:column headerText="Age" sortBy="#{dataObject.age}" filterBy="#{dataObject.age}" filterMatchMode="contains" >
                <h:outputText value="#{dataObject.age}" />
            </p:column>
            <p:column headerText="Address" sortBy="#{dataObject.address}" filterBy="#{dataObject.address}" filterMatchMode="contains" >
                <h:outputText value="#{dataObject.address}" />
            </p:column>
            <p:column headerText="City" sortBy="#{dataObject.city}" filterBy="#{dataObject.city}" filterMatchMode="contains" >
                <h:outputText value="#{dataObject.city}" />
            </p:column>
            <p:column headerText="Post code" sortBy="#{dataObject.postCode}" filterBy="#{dataObject.postCode}" filterMatchMode="contains" >
                <h:outputText value="#{DOTable.postCode}" />
            </p:column>
            <p:column headerText="Country code" sortBy="#{dataObject.countryCode}" filterBy="#{dataObject.countryCode}" filterMatchMode="contains" >
                <h:outputText value="#{dataObject.countryCode}" />
            </p:column>
            <p:column headerText="Phone number" sortBy="#{dataObject.phoneNumber}" filterBy="#{dataObject.phoneNumber}" filterMatchMode="contains" >
                <h:outputText value="#{dataObject.phoneNumber}" />
            </p:column>
            <p:column headerText="Avatar hash" sortBy="#{dataObject.photoID}" filterBy="#{dataObject.photoID}" filterMatchMode="contains">
                <h:outputText value="#{dataObject.photoID}" />
            </p:column>
        </p:dataTable>
    </h:form>
</ui:composition>

As you can see I've also removed all event listeners. I've added a new field to my data object (id of type Integer) and bound DataTables rowKey to it (previously bound to userID of type String - not a good idea). My DataTable backing bean is now as basic as it can be:

@Named("dataTableBean")
@ViewScoped
public class DataTableBean implements Serializable {
    private static final long serialVersionUID = -1662465661106141910L;

    private List<DTObject> objectList;

    @Inject
    private DataGeneratorBean dataGen;

    @PostConstruct
    public void init() {
        setObjectList(dataGen.createDTObjects(1500));
    }

    public List<DTObject> getObjectList() {
        if (objectList == null) {
            return new ArrayList<>();
        } else {
            return objectList;
        }
    }

    public void setObjectList(List<DTObject> objectList) {
        this.objectList = objectList;
    }

}

Now, there are no custom filters, sorters or paginators of any type, not even a data model, just raw data objects in a simple list. The output result is exactly the same as before when paginator buttons are clicked or data gets filtered. All resulting data still gets displayed in a single concatenated line.

ANSWER:

As Kukeltje pointed out in the comments I've made a complete nonsense in my main container and added an autoupdate component to it. That component messed up my data table events, loading data without a table to hold it. Once I've removed the component from my main container, everything worked out. Here is the code for my main container (commented out the troublemaker).

<div id="content-window">
    <p:outputPanel id="content-panel">
        <ui:include src="#{contentLoaderBean.mainContent}" />
        <!-- <p:autoUpdate /> -->
    </p:outputPanel>
</div>
1
In the rowSelect you also update the full form which contains the datatable as well. That is bad practice and can result in what you see. But there are no filter ajax events etc. Sure they are not also present in what you really do and just got left out in the code above?Kukeltje
Ok, helps narrowing things down. Still, what is outside the composite? Where is it included? Try making a real minimal reproducible example.Kukeltje
Oh my god. Nailed it Kukeltje. I've made a dynamic content loader for my main content and completely forgot that it held an autoupdate component (complete nonsense I don't know why I've put it there in the first place) that messed my Data Tables. You warned me about updating my data tables like that and I was 100% sure that my main container had nothing else in it but the ui:include component. Will update my post. Thank you so much.c00ki3s
Next time, always, always, always create a real 100% minimal reproducible example and you will find this yourself. It's called root cayse analysis, important part of debugging.Kukeltje

1 Answers

1
votes

The only situations I've seen this happening is when the datatable is fully updated in addtion to a partial update of the datatable via an event (page or filter or sort or...) In the rowSelect you do seem to update the full form which contains the datatable as well. That is bad practice and can as mentioned result in what you seem so remove that.

But...in your question there are no filter ajax events that explicitly update the full datatable so that cannot cause it. Yet with 99% vertainty there is something fully updates the datatable. Three options

  • there is something in your live code you left out of the datatable in your question
  • There is something else outside the code you posted that plays havoc (an autoupdate e.g),
  • an update from the server side is being done in a method