2

Parents :

<p:selectOneMenu id="parentList"
                 value="#{bean.selectedParent}"
                 converter="#{parentConverter}"
                 required="true">

        <f:selectItem itemLabel="Select" itemValue="#{null}"/>

        <f:selectItems var="parent"
                       value="#{bean.parentList}"
                       itemLabel="#{parent.parentName}"
                       itemValue="#{parent}"/>

        <p:ajax update="childrenList" listener="#{bean.setchildren}"/>
</p:selectOneMenu>

Children :

<p:selectOneMenu id="childrenList"
                     value="#{bean.selectedchild}"
                     converter="#{childConverter}"
                     required="true">

        <f:selectItem itemLabel="Select" itemValue="#{null}"/>

        <f:selectItems var="child"
                       value="#{bean.childrenList}"
                       itemLabel="#{child.childName}"
                       itemValue="#{child}"/>
</p:selectOneMenu>

The managed bean :

@Named
@ViewScoped
public class Bean implements Serializable {

    @Inject
    private Service service;

    private Parent selectedParent;
    private Child selectedChild;
    private List<Parent> parentList;
    private List<Child> childrenList;

    private static final long serialVersionUID = 1L;

    public Bean() {}

    @PostConstruct
    private void init() {
        parentList = service.getParentList();

        // Not necessary unless selectedParent is already available in edit mode.
        if(selectedParent != null) {
            childrenList = service.getChildrenListByParent(selectedParent);
        }
    }

    public void setChildren() {
        if(selectedParent != null) {
            childrenList = service.getChildrenListByParent(selectedParent);
        } else {
            childrenList = null;
        }
    }

    // Getters and setters.
}

The children list is to be populated based on their parent i.e. the children list should only contain children associated with a particular parent.

When the first parent in the parent list is selected, the children list should be reset to empty i.e. children should not be visible without their parent.

Since the parent list has a required field validator, it causes validation. When the first item in the parent list is selected, the children list will be prevented from being updated because of required="true". There is technically nothing wrong but existence of children without their parent may give end-users a bad experience.

What should happen is, when the first item in the parent list is selected, it should not cause validation i.e. the validation is to be skipped conditionally.

One way of doing this is to check either the selectedChild or childrenList itself is null/empty. Such as,

required="#{empty selectedChild or empty childrenList}"

But this does not appear to be a canonical way of skipping validation conditionally in such cases.

Does there exist a better way of skipping validation, when the first item in the parent list is selected so that the children list can be emptied as well along with the parent list (The validation should cause in all other cases. For example, when the form itself is submitted either synchronously or asynchronously)?

BalusC
  • 992,635
  • 352
  • 3,478
  • 3,452
Tiny
  • 24,933
  • 92
  • 299
  • 571

1 Answers1

3

Basically, you want action-dependent validation. I.e. skip validation when that specific <p:ajax> action is invoked, and not on other actions.

This is unfortunately indeed not trivial to declare in the view. There are several tricks/workarounds for that. Most used one is to just check if the particular action is (not) invoked.

E.g. check if the desired save button is invoked by determining the presence of its client ID in the HTTP request parameter map as available by implicit EL object #{param}:

<h:form>
    <p:selectOneMenu ... required="#{not empty param[save.clientId]}">
        ...
        <p:ajax ... />
    </p:selectOneMenu>
    <p:selectOneMenu ... required="true">
        ...
    </p:selectOneMenu>
    <p:commandButton binding="#{save}" ... />
</h:form>

Or check if component's own <p:ajax> is not invoked by determining if component's own client ID does not equal the HTTP request parameter with predefined name javax.faces.source representing the source of the ajax request (the #{component} below is an implicit EL variable representing the current UIComponent):

<h:form>
    <p:selectOneMenu ... required="#{param['javax.faces.source'] ne component.clientId}">
        ...
        <p:ajax ... />
    </p:selectOneMenu>
    <p:selectOneMenu ... required="true">
        ...
    </p:selectOneMenu>
    <p:commandButton ... />
</h:form>

Or check if the parent form is submitted by UIForm#isSubmitted(), which would only evaluate true when a "full form submit" is used as in process="@form" (the <p:ajax process> defaults to @this which wouldn't trigger a "full form submit", and the <p:commandButton process> defaults to @form, which would thus trigger a "full form submit"):

<h:form binding="#{form}">
    <p:selectOneMenu ... required="#{form.submitted}">
        ...
        <p:ajax ... />
    </p:selectOneMenu>
    <p:selectOneMenu ... required="true">
        ...
    </p:selectOneMenu>
    <p:commandButton ... />
</h:form>

Or without binding the form by referencing the form via UIComponent#getNamingContainer() (if you know the position in the component tree; if the form is e.g. 2 naming container parents back, then use #{component.namingContainer.parent.namingContainer.submitted}):

<h:form>
    <p:selectOneMenu ... required="#{component.namingContainer.submitted}">
        ...
        <p:ajax ... />
    </p:selectOneMenu>
    <p:selectOneMenu ... required="true">
        ...
    </p:selectOneMenu>
    <p:commandButton ... />
</h:form>

Take your pick. The first solution has been offered several times before as it's most easy to understand and tweak by starters.

See also:

Community
  • 1
  • 1
BalusC
  • 992,635
  • 352
  • 3,478
  • 3,452