0

I spent a lot of time already trying to achieve same functionality as shown in Showcase > Dependent Dropdowns, but instead of dropdown (p:selectOneMenu) I want to use autoComplete.

I made a bit more complex as I found it is not as simple - instead of "in page" I'm trying to achieve the same in dialog + I want city autoComplete to be disabled when there is no country selected.

So initial simple approach looks like:

Page

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://xmlns.jcp.org/jsf/core" xmlns:h="http://java.sun.com/jsf/html" xmlns:p="http://primefaces.org/ui"
    xmlns:sec="http://www.springframework.org/security/tags" xmlns:ui="http://java.sun.com/jsf/facelets">

<h:head>
</h:head>

<h:body>

    <h:form>
        <p:commandButton value="Open dialog" id="show" actionListener="#{dependentAutocompletesDialogView.showDialog}" icon="ui-icon-info" update="dialog" />
    </h:form>

    <p:dialog id="dialog" widgetVar="dialog" modal="true" dynamic="true" >
        <h:form prependId="false" id="form">
            <p:panel header="Select a Location" style="margin-bottom:10px;">
                <h:panelGrid columns="2" cellpadding="5">   
                    <p:outputLabel for="country" value="Country: " />
                    <p:autoComplete id="country" value="#{dependentAutocompletesDialogView.country}" completeMethod="#{dependentAutocompletesDialogView.countriesComplete}" 
                        dropdown="true" required="true">
                        <p:ajax event="itemSelect" update="city" />
                    </p:autoComplete>

                    <p:outputLabel for="city" value="City: " />
                    <p:autoComplete id="city" value="#{dependentAutocompletesDialogView.city}" 
                        completeMethod="#{dependentAutocompletesDialogView.citiesComplete(dependentAutocompletesDialogView.country)}"
                        dropdown="true" widgetVar="city" required="true" disabled="#{dependentAutocompletesDialogView.country == null}">
                    </p:autoComplete>
                </h:panelGrid>
            </p:panel>

            <p:commandButton value="Submit" id="submit" actionListener="#{dependentAutocompletesDialogView.dialogSubmit}" icon="ui-icon-check" process="dialog" update="form"/>

        </h:form>
    </p:dialog>

</h:body>
</html>

Bean

package com.codenotfound.primefaces.model;

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

import javax.annotation.PostConstruct;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.faces.event.AjaxBehaviorEvent;
import javax.faces.view.ViewScoped;
import javax.inject.Named;

import org.primefaces.PrimeFaces;
import org.primefaces.context.RequestContext;

@Named
@ViewScoped
public class DependentAutocompletesDialogView {


    private Map<String,List<String>> data = new HashMap<String, List<String>>();

    private String country;
    private String city;  
    private List<String> countries;
    private List<String> cities;

    public List<String> countriesComplete(String filter) {
        return countries;
    }

    public List<String> citiesComplete(String filter) {
        return data.get(country);
    }

    @PostConstruct
    public void init() {
        countries  = new LinkedList<>();
        countries.add("USA");
        countries.add("Germany");
        countries.add("Brazil");

        List<String> list;

        list = new LinkedList<>();
        list.add("New York");
        list.add("San Francisco");
        list.add("Denver");
        data.put("USA", list);

        list = new LinkedList<>();
        list.add("Berlin");
        list.add("Munich");
        list.add("Frankfurt");
        data.put("Germany", list);

        list = new LinkedList<>();
        list.add("Sao Paolo");
        list.add("Rio de Janerio");
        list.add("Salvador");
        data.put("Brazil", list);
    }

    public String getCountry() {
        return country;
    }

    public void setCountry(String country) {
        this.country = country;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public List<String> getCountries() {
        return countries;
    }

    public List<String> getCities() {
        return cities;
    }

    public void displayLocation() {
        FacesMessage msg;
        if(city != null && country != null)
            msg = new FacesMessage("Selected", city + " of " + country);
        else
            msg = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Invalid", "City is not selected."); 

        FacesContext.getCurrentInstance().addMessage(null, msg);
    }

    public void onChange(AjaxBehaviorEvent event) {
        System.out.println();
    }

    public void showDialog() {
        country = null;
        city = null;
        RequestContext.getCurrentInstance().getScriptsToExecute().add("PF('dialog').show();");
        PrimeFaces.current().ajax().update("form");
    }

    public void dialogSubmit() {
        RequestContext.getCurrentInstance().getScriptsToExecute().add("PF('dialog').hide();");
        displayLocation();
    }

}

This seems as working on a first sight:

  • when country is selected, cities autoComplete is enabled
  • required fields are validated
  • when there are no error from validation, expected message is shown

BUT

  • it doesn't work, when country is unselected (I mean city autoComplete is not disabled again)
  • when dialog is closed after validation errors, on re-open, there is old data and also red borders as shown after validation (despite country and city setting to null in showDialog()

I tried to solve deletion using JavaScript by adding:

<p:ajax process="@this"
    oncomplete="if ( PF('country').input.val() == '' ) { PF('city').input.val(''); PF('city').hinput.val(''); PF('city').disable(); }" 
    update="city" />

But it is just deleting city autoComplete, but on submit it is not validated as missing = it's not propagated to bean...

Betlista
  • 10,327
  • 13
  • 69
  • 110
  • 1
    Please, add only the Mínimum code to make the question more readable. [Check this link](https://stackoverflow.com/help/mcve) Are you sending the null country value to backing bean when you unselect the country auto-complete? – Javier Oct 22 '18 at 13:26
  • It's pretty minimal (still Spring boot and Maven config is missing to reproduce easily, but I believe it's not a problem for those familiar with PF). I do not know, how to handle "unSelect" properly... – Betlista Oct 22 '18 at 13:34
  • Possible duplicate of [UIForm with prependId="false" breaks ](https://stackoverflow.com/questions/7415230/uiform-with-prependid-false-breaks-fajax-render) – Kukeltje Oct 22 '18 at 13:56
  • @Kukeltje and which problem it should solve? Removing `prependId="false"` is not fixing any issue I listed above... – Betlista Oct 22 '18 at 14:07
  • It looked like it was strongly related. If it is not, you at least improved your code base ;-) – Kukeltje Oct 22 '18 at 14:13

1 Answers1

0

According my tests, in PF version 6.2, onchange event on autoComplete is generated only when user deletes the filter/selected value from autoComplete...

I used this to solve it:

Page

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://xmlns.jcp.org/jsf/core" xmlns:h="http://java.sun.com/jsf/html" xmlns:p="http://primefaces.org/ui"
    xmlns:sec="http://www.springframework.org/security/tags" xmlns:ui="http://java.sun.com/jsf/facelets">

<h:head>
</h:head>

<h:body>

    <p:growl id="msgs" showDetail="true" />

    <h:form>
        <p:commandButton value="Open dialog" id="show" actionListener="#{dependentAutocompletesDialogChangeView.showDialog}" icon="ui-icon-info" update="dialog" />
    </h:form>

    <p:dialog id="dialog" widgetVar="dialog" modal="true" dynamic="true" >
        <p:ajax event="close" listener="#{dependentAutocompletesDialogChangeView.handleClose}" update="@this" resetValues="true" />
        <h:form id="form">

            <p:remoteCommand name="clearCountry" actionListener="#{dependentAutocompletesDialogChangeView.clearCountryAndCity}" immediate="true" update="city" />

            <p:panel header="Select a Location" style="margin-bottom:10px;">
                <h:panelGrid columns="2" cellpadding="5">   
                    <p:outputLabel for="country" value="Country: " />
                    <p:autoComplete id="country" widgetVar="country" value="#{dependentAutocompletesDialogChangeView.country}"
                        completeMethod="#{dependentAutocompletesDialogChangeView.countriesComplete}" process="@this" 
                        dropdown="true" required="true" emptyMessage="No countries matching filter" onchange="clearCountry()">

                        <p:ajax event="itemSelect" update="city" />

                    </p:autoComplete>

                    <p:outputLabel for="city" value="City: " />
                    <p:autoComplete id="city" value="#{dependentAutocompletesDialogChangeView.city}" 
                        completeMethod="#{dependentAutocompletesDialogChangeView.citiesComplete(dependentAutocompletesDialogChangeView.country)}"
                        dropdown="true" widgetVar="city" required="true" disabled="#{dependentAutocompletesDialogChangeView.country == null}">
                    </p:autoComplete>
                </h:panelGrid>
            </p:panel>

            <p:commandButton value="Submit" id="submit" actionListener="#{dependentAutocompletesDialogChangeView.dialogSubmit}" icon="ui-icon-check" process="dialog" update="form, msgs" />
        </h:form>

    </p:dialog>

</h:body>
</html>

Bean

package com.codenotfound.primefaces.model;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import javax.annotation.PostConstruct;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.faces.view.ViewScoped;
import javax.inject.Named;

import org.primefaces.PrimeFaces;
import org.primefaces.context.RequestContext;
import org.primefaces.event.CloseEvent;

@Named
@ViewScoped
public class DependentAutocompletesDialogChangeView {


    private Map<String,List<String>> data = new HashMap<String, List<String>>();

    private String country;
    private String city;  
    private List<String> countries;
    private List<String> cities;

    public List<String> countriesComplete(String filter) {
        return countries.stream().filter(item -> match(item, filter)).collect(Collectors.<String>toList());
    }

    private boolean match(String item, String filter) {
        return item.contains(filter);
    }

    public List<String> citiesComplete(String filter) {
        return data.get(country);
    }

    @PostConstruct
    public void init() {
        countries  = new LinkedList<>();
        countries.add("USA");
        countries.add("Germany");
        countries.add("Brazil");

        List<String> list;

        list = new LinkedList<>();
        list.add("New York");
        list.add("San Francisco");
        list.add("Denver");
        data.put("USA", list);

        list = new LinkedList<>();
        list.add("Berlin");
        list.add("Munich");
        list.add("Frankfurt");
        data.put("Germany", list);

        list = new LinkedList<>();
        list.add("Sao Paolo");
        list.add("Rio de Janerio");
        list.add("Salvador");
        data.put("Brazil", list);
    }

    public String getCountry() {
        return country;
    }

    public void setCountry(String country) {
        this.country = country;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public List<String> getCountries() {
        return countries;
    }

    public List<String> getCities() {
        return cities;
    }

    public void displayLocation() {
        FacesMessage msg;
        if(city != null && country != null)
            msg = new FacesMessage("Selected", city + " of " + country);
        else
            msg = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Invalid", "City is not selected."); 

        FacesContext.getCurrentInstance().addMessage(null, msg);
    }

    public void showDialog() {
        country = null;
        city = null;
        RequestContext.getCurrentInstance().getScriptsToExecute().add("PF('dialog').show();");
        PrimeFaces.current().ajax().update("form");
    }

    public void dialogSubmit() {
        RequestContext.getCurrentInstance().getScriptsToExecute().add("PF('dialog').hide();");
        displayLocation();
    }

    public void clearCountryAndCity() {
        country = null;
        city = null;

        PrimeFaces.current().executeScript("PF('country').input.val('')");
        PrimeFaces.current().executeScript("PF('city').input.val('')");
        PrimeFaces.current().ajax().update("country", "city");
    }

    public void handleClose(CloseEvent event) {
        clearCountryAndCity();
    }
}
Betlista
  • 10,327
  • 13
  • 69
  • 110