jsfprimefacesselectonemenuprimefaces-datatabletablefilter

Primefaces Datatable filtering issue for drop down as data input


I have a drop down (p:selectOneMenu) as the input field in the Primefaces 8.0 datatable row, after I select the value in the drop down, if I sort it the selected value can be kept after ajax submit. However if I input a filter that filter 0 rows, and then I clear the filter, the selected value in the drop down disappear:

updated base on Kukeltje's request for adding input text:

  1. Select the drop down value

enter image description here

  1. input the filter so that all the rows are filter out

enter image description here

  1. clear the filter, the selected value disappear

enter image description here

My backing bean:

package sample;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.enterprise.context.SessionScoped;
import javax.inject.Named;

@Named
@SessionScoped
public class SampleBean implements java.io.Serializable {

    /**
     * 
     */
    private static final long serialVersionUID = 5307652891294044974L;

    private static final Map<String, String> dropDown = new HashMap<>();
    
    static {
        dropDown.put("K1", "K1");
        dropDown.put("K2", "K2");
        dropDown.put("K3", "K3");
    }

    public Map<String, String> getDropDown() {
        return dropDown;
    }

    private List<TableObject> tableObjects = Arrays.asList(new TableObject[] {new TableObject(), new TableObject()});
    
    public List<TableObject> getTableObjects() {
        return tableObjects;
    }

    public void setTableObjects(List<TableObject> tableObjects) {
        this.tableObjects = tableObjects;
    }

    public static class TableObject 
    {
        private String dd;
        private String inputText;

        public String getDd() {
            return dd;
        }

        public void setDd(String dd) {
            this.dd = dd;
        }

        public String getInputText() {
            return inputText;
        }

        public void setInputText(String inputText) {
            this.inputText = inputText;
        }
        
    }
}

My facelet:

<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:o="http://omnifaces.org/ui"
    xmlns:of="http://omnifaces.org/functions">

<h:head>
    <title>Hello World JSF 2.3</title>
</h:head>

<h:body>
    <h:form>
        <p:dataTable var="item" value="#{sampleBean.tableObjects}"
            widgetVar="itemTable">

            <p:ajax event="sort" process="@this" update="@this"
                skipChildren="false" />
            <p:ajax event="page" process="@this" update="@this"
                skipChildren="false" />
            <p:ajax event="filter" process="@this" update="@this" global="false"
                skipChildren="false" />

            <p:column headerText="Dropdown" sortBy="#{item.dd}"
                filterBy="#{item.dd}" filterMatchMode="contains">
                <p:selectOneMenu id="Dropdown" value="#{item.dd}" required="false"
                    label="Dropdown" style="width: 90%">

                    <f:selectItem itemValue="#{null}" itemLabel="" />
                    <f:selectItems value="#{sampleBean.dropDown.entrySet()}"
                        var="entry" itemValue="#{entry.value}" itemLabel="#{entry.key}" />
                </p:selectOneMenu>
            </p:column>
            <p:column id="InputTextHeader" headerText="Input Text"
                sortBy="#{item.inputText}" filterBy="#{item.inputText}"
                filterMatchMode="contains">
                <p:inputText id="InputText" value="#{item.inputText}" />
            </p:column>
        </p:dataTable>
    </h:form>
</h:body>
</html>

I push my testing project to github, in case you want to test it

git clone https://github.com/saycchai/jsf-test.git
cd jsf-test
chmod +x *.sh
./buildAndRun.sh

for payara server: browse: http://localhost:8080/index.xhtml

for other server: http://localhost:8080/jsf-test/index.xhtml


Solution

  • Finally I found a work around solution as follows:

    I added a phase listener, if the filter submit 0 row (pass as a request parameter, please see the onstart part in the facelet), then backup the Dd value to previousDd before the APPLY_REQUEST_VALUES phase and set the backup value previousDd back to Dd before the RENDER_RESPONSE phase, code as follows:

    Backing Bean

    package sample;
    
    import java.util.Arrays;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    import javax.enterprise.context.SessionScoped;
    import javax.inject.Named;
    import javax.persistence.Transient;
    
    @Named
    @SessionScoped
    public class SampleBean implements java.io.Serializable {
    
        /**
         * 
         */
        private static final long serialVersionUID = 5307652891294044974L;
    
        private static final Map<String, String> dropDown = new HashMap<>();
        
        static {
            for(int i=1; i<10; i++) {
                dropDown.put("K"+i, "K"+i);
            }
        }
    
        public Map<String, String> getDropDown() {
            return dropDown;
        }
    
        private List<TableObject> tableObjects = Arrays.asList(new TableObject[] {new TableObject(), new TableObject(), new TableObject(), new TableObject()});
        
        public List<TableObject> getTableObjects() {
            return tableObjects;
        }
    
        public void setTableObjects(List<TableObject> tableObjects) {
            this.tableObjects = tableObjects;
        }
    
        public static class TableObject 
        {
            private String dd;
            private String inputText;
    
            public String getDd() {
                return dd;
            }
    
            public void setDd(String dd) {
                this.dd = dd;
            }
    
            public String getInputText() {
                return inputText;
            }
    
            public void setInputText(String inputText) {
                this.inputText = inputText;
            }
            
            @Transient
            private String previousDd;
            
            public String getPreviousDd() {
                return previousDd;
            }
    
            public void setPreviousDd(String previousDd) {
                this.previousDd = previousDd;
            }
    
        }
    }
    
    

    Facelet

    
    <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" 
    >
    
    <h:head>
        <title>Hello World JSF 2.3</title>
    </h:head>
    
    <h:body>
        <h:form id="form">
            <p:dataTable id="itemTable" var="item" value="#{sampleBean.tableObjects}"
                widgetVar="itemTable"
                paginator="true"
            >
    
                <p:ajax event="sort" process="@this" update="@this"
                    skipChildren="false" />
                <p:ajax event="page" process="@this" update="@this"
                    skipChildren="false"
                />
                <p:ajax event="filter" process="@this" update="@this" global="false"
                    skipChildren="false" 
                    onstart="cfg.ext.params.push({name: 'tableFilterCount', value: PF('itemTable').paginator.cfg.rowCount});"
                />
    
                <p:column id="DropdownHeader" headerText="Dropdown" sortBy="#{item.dd}"
                    filterBy="#{item.dd}" filterMatchMode="contains">
                    <p:selectOneMenu id="Dropdown" value="#{item.dd}" required="false"
                        label="Dropdown" style="width: 90%">
                        
                        <f:selectItem itemValue="#{null}" itemLabel="" />
                        <f:selectItems value="#{sampleBean.dropDown.entrySet()}"
                            var="entry" itemValue="#{entry.value}" itemLabel="#{entry.key}" />
                    </p:selectOneMenu>
                </p:column>
                <p:column id="InputTextHeader" headerText="Input Text"
                    sortBy="#{item.inputText}" filterBy="#{item.inputText}"
                    filterMatchMode="contains">
                    <p:inputText id="InputText" value="#{item.inputText}" >
                        <p:ajax />
                    </p:inputText>
                </p:column>
            </p:dataTable>
        </h:form>
    </h:body>
    </html>
    
    

    Phase Listener

    package sample;
    
    import javax.faces.event.PhaseEvent;
    import javax.faces.event.PhaseId;
    import javax.faces.event.PhaseListener;
    
    import org.primefaces.component.datatable.DataTable;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import sample.SampleBean.TableObject;
    
    public class SamplePhaseListener implements PhaseListener {
    
        /**
         * 
         */
        private static final long serialVersionUID = 5273254619684337785L;
    
        private Logger logger = LoggerFactory.getLogger(this.getClass());
    
        public void afterPhase(PhaseEvent event) {
            
        }
    
        public void beforePhase(PhaseEvent event) {
            
            if(event.getPhaseId() == PhaseId.APPLY_REQUEST_VALUES) {
                
                prettyPrint("----------------start of Before "+event.getPhaseId().getName()+"------------------------------", 0);
                
                String tableFilterCount = event.getFacesContext().getExternalContext().getRequestParameterMap().get("tableFilterCount");
                
                if(tableFilterCount != null && "0".equals(tableFilterCount)) {
                    //backup the previous value first
                    DataTable table = (DataTable) event.getFacesContext().getViewRoot().findComponent("form:itemTable");
                    for (int index = 0; index < table.getRowCount(); index++) {
                        table.setRowIndex(index);
    
                        Object rowData = table.getRowData();
    
                        if(rowData != null) {
                            TableObject tableObject = (TableObject) rowData;
                            logger.info(" before backup to previousDd, dd: {}, previousDd: {}", tableObject.getDd(), tableObject.getPreviousDd());
    
                            tableObject.setPreviousDd(tableObject.getDd());
                            
                            logger.info(" after backup to previousDd, dd: {}, previousDd: {}", tableObject.getDd(), tableObject.getPreviousDd());
                        }
                        
                    }
                }
                
                prettyPrint("----------------end of Before "+event.getPhaseId().getName()+"------------------------------", 0);
            }
            
            
            if(event.getPhaseId() == PhaseId.RENDER_RESPONSE) {
                
                prettyPrint("----------------start of Before " + event.getPhaseId().getName() + "------------------------------", 0);
                
                String tableFilterCount = event.getFacesContext().getExternalContext().getRequestParameterMap().get("tableFilterCount");
                
                if(tableFilterCount != null && "0".equals(tableFilterCount)) {
                    //restore the Dd from previous value
                    DataTable table = (DataTable) event.getFacesContext().getViewRoot().findComponent("form:itemTable");
                    for (int index = 0; index < table.getRowCount(); index++) {
                        table.setRowIndex(index);
    
                        Object rowData = table.getRowData();
    
                        if(rowData != null) {
                            TableObject tableObject = (TableObject) rowData;
                            logger.info(" before restore from previousDd, dd: {}, previousDd: {}", tableObject.getDd(), tableObject.getPreviousDd());
    
                            tableObject.setDd(tableObject.getPreviousDd());
    
                            logger.info(" after restore from previousDd, dd: {}, previousDd: {}", tableObject.getDd(), tableObject.getPreviousDd());
                        }
                        
                    }
                }
                
                
                prettyPrint("----------------end of Before "+event.getPhaseId().getName()+"------------------------------", 0);
            }
        }
    
        public PhaseId getPhaseId() {
            return PhaseId.ANY_PHASE;
        }
    
        public static final String IDENT = "  ";
    
        private void prettyPrint(String str, int depth) {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < depth; i++)
                sb.append(IDENT);
    
            sb.append(str + "\n");
    
            logger.trace(sb.toString());
        }
    }
    
    

    faces-config.xml

    
    <?xml version='1.0' encoding='UTF-8'?>
    <faces-config xmlns="http://xmlns.jcp.org/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
            http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_3.xsd"
        version="2.3">
    
        <lifecycle>
            <phase-listener>sample.SamplePhaseListener</phase-listener>
        </lifecycle>
        
    </faces-config>