Web UI Framework

How to Create a N-Level Select Widget

Updated: October 16, 2020

In this page we provide samples to have a selection widget with 3 levels by contributing a "template widget" in Studio. Each sample code below is the sample for one widget that has a different behavior (described before the code).

To create and use a new widget:

  1. Copy-paste the sample of your choice in a file that you call, for instance, "3_level_select_widget.xhtml".

    Of course you can modify the samples below to add other levels, or other behaviors. You just need to be familiar with facelets and JSF.

  2. Upload this file in the Resources > Widgets section. The widget is created. You can now use it on layouts and forms.
  3. In the layout of the document type where you want to use the widget, drag and drop the Template widget from the Advanced Widgets category on the right.
  4. Edit the properties of the widget. Here are the properties specific to custom widgets that you need to fill in.
    • Template: choose the XHTML file you have just uploaded.
    • Fields: add one field and put the XPath of the field you want to update. For instance, "dc:coverage".
    • Custom properties configuration: in our sample, you can (should) add those three properties labels and put the value you need:
      • localize
      • required
      • directoryName (should be the name of the vocabulary that holds the values that are displayed).

Widget Samples

Mono-Select 3-Level Widget

Sample example on 3 levels with widget property directoryName filled with the directory name (mono select):

<f:subview
  xmlns="http://www.w3.org/1999/xhtml"
  xmlns:f="http://java.sun.com/jsf/core"
  xmlns:c="http://java.sun.com/jstl/core"
  xmlns:h="http://java.sun.com/jsf/html"
  xmlns:a4j="http://richfaces.org/a4j"
  xmlns:fn="http://java.sun.com/jsp/jstl/functions"
  xmlns:nxu="http://nuxeo.org/nxweb/util"
  xmlns:nxdir="http://nuxeo.org/nxdirectory"
  xmlns:nxl="http://nuxeo.org/nxforms/layout"
  xmlns:nxp="http://nuxeo.org/nxweb/pdf"
  id="#{widget.id}">
<c:if test="#{nxl:isLikeViewMode(widget.mode)}">

  <nxdir:chainSelect id="#{widget.id}_viewselect" size="3" value="#{field}"
    displayValueOnly="true" defaultRootKey="">
    <nxdir:chainSelectListbox index="0" size="0" directoryName="#{widget.properties['directoryName']}"
      localize="true" id="#{widget.id}_parent" displayObsoleteEntries="true" />
    <nxdir:chainSelectListbox index="1" size="0" directoryName="#{widget.properties['directoryName']}"
      localize="true" id="#{widget.id}_parent2" displayObsoleteEntries="true" />
    <nxdir:chainSelectListbox index="2" size="0" directoryName="#{widget.properties['directoryName']}"
      localize="true" id="#{widget.id}_child" displayObsoleteEntries="true" />
    <nxdir:chainSelectStatus display="value" id="#{widget.id}_status" />
  </nxdir:chainSelect>

</c:if>
<c:if test="#{widget.mode == 'edit'}">

  <nxdir:chainSelect size="3" value="#{field}"
    id="#{widget.id}_editselect" multiSelect="false"
    multiParentSelect="false"
    allowBranchSelection="#{widgetProperty_allowBranchSelection}"
    defaultRootKey="" required="#{widgetProperty_required}">
    <a4j:region id="#{widget.id}_region">
      <nxdir:chainSelectListbox index="0" size="1"
        directoryName="#{widget.properties['directoryName']}"
        localize="#{widget.properties['localize']}"
        id="#{widget.id}_parent" ordering="label">
        <f:ajax event="change"
          render="#{widget.id}_parent2 #{widget.id}_child #{widget.id}_message"
          execute="@this" />
      </nxdir:chainSelectListbox>
      <nxdir:chainSelectListbox index="1" size="1"
        directoryName="#{widget.properties['directoryName']}"
        localize="#{widget.properties['localize']}"
        id="#{widget.id}_parent2" ordering="label">
        <a4j:ajax event="change"
          render="#{widget.id}_child #{widget.id}_message"
          execute="@region" />
      </nxdir:chainSelectListbox>
    </a4j:region>
    <nxdir:chainSelectListbox size="1" index="2"
      directoryName="#{widget.properties['directoryName']}"
      localize="#{widget.properties['localize']}"
      id="#{widget.id}_child" ordering="label" />
  </nxdir:chainSelect>
  <h:message styleClass="errorMessage" for="#{widget.id}_editselect"
    id="#{widget.id}_message" />

</c:if>
</f:subview>

Multi-Select 3-Level Widget

Sample example on 3 levels with widget property directoryName filled with the directory name (multi select):

<f:subview
  xmlns="http://www.w3.org/1999/xhtml"
  xmlns:f="http://java.sun.com/jsf/core"
  xmlns:c="http://java.sun.com/jstl/core"
  xmlns:h="http://java.sun.com/jsf/html"
  xmlns:a4j="http://richfaces.org/a4j"
  xmlns:fn="http://java.sun.com/jsp/jstl/functions"
  xmlns:nxu="http://nuxeo.org/nxweb/util"
  xmlns:nxdir="http://nuxeo.org/nxdirectory"
  xmlns:nxl="http://nuxeo.org/nxforms/layout"
  xmlns:nxp="http://nuxeo.org/nxweb/pdf"
  id="#{widget.id}">
<c:if test="#{nxl:isLikeViewMode(widget.mode)}">

  <nxdir:chainSelect id="#{widget.id}_viewselect" size="3"
    value="#{field}" displayValueOnly="true" multiSelect="true"
    defaultRootKey="">
    <nxdir:chainSelectListbox index="0" size="4"
    directoryName="#{widget.properties['directoryName']}"
    localize="#{widget.properties['localize']}"
    id="#{widget.id}_parent" />
    <nxdir:chainSelectListbox size="4"
      directoryName="#{widget.properties['directoryName']}" index="1"
      localize="#{widget.properties['localize']}"
      id="#{widget.id}_parent2" />
    <nxdir:chainSelectListbox size="4"
      directoryName="#{widget.properties['directoryName']}" index="2"
      localize="#{widget.properties['localize']}"
      id="#{widget.id}_child" />
    <nxdir:chainSelectStatus display="value" id="#{widget.id}_status" />
  </nxdir:chainSelect>

</c:if>
<c:if test="#{widget.mode == 'edit'}">

  <a4j:region id="#{widget.id}_region" renderRegionOnly="true">
    <nxdir:chainSelect size="3" value="#{field}"
      id="#{widget.id}_editselect" multiSelect="true"
      multiParentSelect="true"
      allowBranchSelection="#{widgetProperty_allowBranchSelection}"
      defaultRootKey="" required="#{widgetProperty_required}">
      <nxdir:chainSelectListbox index="0" size="4"
        directoryName="#{widget.properties['directoryName']}"
        localize="#{widget.properties['localize']}"
        id="#{widget.id}_parent" ordering="label">
        <a4j:ajax event="change"
          render="#{widget.id}_parent2 #{widget.id}_child #{widget.id}_message"
          execute="@this" />
      </nxdir:chainSelectListbox>
      <nxdir:chainSelectListbox index="1" size="4"
        directoryName="#{widget.properties['directoryName']}"
        localize="#{widget.properties['localize']}"
        id="#{widget.id}_parent2" ordering="label">
        <a4j:ajax event="change"
          render="#{widget.id}_child #{widget.id}_message"
          immediate="true" />
      </nxdir:chainSelectListbox>
      <nxdir:chainSelectListbox size="4" index="2"
        directoryName="#{widget.properties['directoryName']}"
        localize="#{widget.properties['localize']}"
        id="#{widget.id}_child" ordering="label" />
      <a4j:commandButton value="#{messages['command.add']}"
        styleClass="button" immediate="true"
        actionListener="#{chainSelectActions.add}"
        render="#{widget.id}_status #{widget.id}_message"
        id="#{widget.id}_add" />
      <br />
      <nxdir:chainSelectStatus display="value"
        entryCssStyle="background-color: #DDEEFF"
        label="#{messages['label.chainSelect.selection']}"
        id="#{widget.id}_status">
        <f:facet name="removeButton">
          <a4j:commandButton
            actionListener="#{chainSelectActions.delete}"
            execute="@this" render="#{widget.id}_status"
            image="/icons/toggle_minus.png"
            id="#{widget.id}_delete" />
        </f:facet>
      </nxdir:chainSelectStatus>
    </nxdir:chainSelect>
  </a4j:region>
  <h:message styleClass="errorMessage" for="#{widget.id}_editselect"
    id="#{widget.id}_message" />

</c:if>
</f:subview>

Complete Examples with CSV (Plain) and PDF Rendering

Mono-Select Widget

<f:subview
  xmlns="http://www.w3.org/1999/xhtml"
  xmlns:f="http://java.sun.com/jsf/core"
  xmlns:c="http://java.sun.com/jstl/core"
  xmlns:h="http://java.sun.com/jsf/html"
  xmlns:a4j="http://richfaces.org/a4j"
  xmlns:fn="http://java.sun.com/jsp/jstl/functions"
  xmlns:nxu="http://nuxeo.org/nxweb/util"
  xmlns:nxdir="http://nuxeo.org/nxdirectory"
  xmlns:nxl="http://nuxeo.org/nxforms/layout"
  xmlns:nxp="http://nuxeo.org/nxweb/pdf"
  id="#{widget.id}">
<c:if test="#{nxl:isLikePlainMode(widget.mode)}">
<f:subview rendered="#{not empty field}"><nxdir:directoryEntryOutput
  directoryName="#{widget.properties['directoryName']}"
    value="#{fn:split(field, '/')[0]}"
    localize="#{widget.properties['localize']}" />/<nxdir:directoryEntryOutput
    directoryName="#{widget.properties['directoryName']}"
    value="#{fn:split(field, '/')[1]}"
    localize="#{widget.properties['localize']}" />/<nxdir:directoryEntryOutput
    directoryName="#{widget.properties['directoryName']}"
    value="#{field}"
    localize="#{widget.properties['localize']}"
    keySeparator="/" />
</f:subview>
</c:if>
<c:if test="#{widget.mode == 'pdf'}">
  <nxp:html>
   <nxdir:chainSelect id="#{widget.id}_viewselect" size="3" value="#{field}"
    displayValueOnly="true" defaultRootKey="">
     <nxdir:chainSelectListbox index="0" size="0" directoryName="#{widget.properties['directoryName']}"
       localize="true" id="#{widget.id}_parent" displayObsoleteEntries="true" />
     <nxdir:chainSelectListbox index="1" size="0" directoryName="#{widget.properties['directoryName']}"
       localize="true" id="#{widget.id}_parent2" displayObsoleteEntries="true" />
     <nxdir:chainSelectListbox index="2" size="0" directoryName="#{widget.properties['directoryName']}"
       localize="true" id="#{widget.id}_child" displayObsoleteEntries="true" />
     <nxdir:chainSelectStatus display="value" id="#{widget.id}_status" />
   </nxdir:chainSelect>
  </nxp:html>
</c:if>
<c:if test="#{nxl:isLikeViewMode(widget.mode)}">

  <nxdir:chainSelect id="#{widget.id}_viewselect" size="3" value="#{field}"
    displayValueOnly="true" defaultRootKey="">
    <nxdir:chainSelectListbox index="0" size="0" directoryName="#{widget.properties['directoryName']}"
      localize="true" id="#{widget.id}_parent" displayObsoleteEntries="true" />
    <nxdir:chainSelectListbox index="1" size="0" directoryName="#{widget.properties['directoryName']}"
      localize="true" id="#{widget.id}_parent2" displayObsoleteEntries="true" />
    <nxdir:chainSelectListbox index="2" size="0" directoryName="#{widget.properties['directoryName']}"
      localize="true" id="#{widget.id}_child" displayObsoleteEntries="true" />
    <nxdir:chainSelectStatus display="value" id="#{widget.id}_status" />
  </nxdir:chainSelect>

</c:if>
<c:if test="#{widget.mode == 'edit'}">

  <nxdir:chainSelect size="3" value="#{field}"
    id="#{widget.id}_editselect" multiSelect="false"
    multiParentSelect="false"
    allowBranchSelection="#{widgetProperty_allowBranchSelection}"
    defaultRootKey="" required="#{widgetProperty_required}">
    <a4j:region id="#{widget.id}_region">
      <nxdir:chainSelectListbox index="0" size="1"
        directoryName="#{widget.properties['directoryName']}"
        localize="#{widget.properties['localize']}"
        id="#{widget.id}_parent" ordering="label">
        <f:ajax event="change"
          render="#{widget.id}_parent2 #{widget.id}_child #{widget.id}_message"
          execute="@this" />
      </nxdir:chainSelectListbox>
      <nxdir:chainSelectListbox index="1" size="1"
        directoryName="#{widget.properties['directoryName']}"
        localize="#{widget.properties['localize']}"
        id="#{widget.id}_parent2" ordering="label">
        <a4j:ajax event="change"
          render="#{widget.id}_child #{widget.id}_message"
          execute="@region" />
      </nxdir:chainSelectListbox>
    </a4j:region>
    <nxdir:chainSelectListbox size="1" index="2"
      directoryName="#{widget.properties['directoryName']}"
      localize="#{widget.properties['localize']}"
      id="#{widget.id}_child" ordering="label" />
  </nxdir:chainSelect>
  <h:message styleClass="errorMessage" for="#{widget.id}_editselect"
    id="#{widget.id}_message" />

</c:if>
</f:subview>

Multi-Select Widget

<f:subview
  xmlns="http://www.w3.org/1999/xhtml"
  xmlns:f="http://java.sun.com/jsf/core"
  xmlns:c="http://java.sun.com/jstl/core"
  xmlns:h="http://java.sun.com/jsf/html"
  xmlns:a4j="http://richfaces.org/a4j"
  xmlns:fn="http://java.sun.com/jsp/jstl/functions"
  xmlns:nxu="http://nuxeo.org/nxweb/util"
  xmlns:nxdir="http://nuxeo.org/nxdirectory"
  xmlns:nxl="http://nuxeo.org/nxforms/layout"
  xmlns:nxp="http://nuxeo.org/nxweb/pdf"
  id="#{widget.id}">
<c:if test="#{nxl:isLikePlainMode(widget.mode)}"><nxu:inputList
  value="#{field}" model="chainModel"><nxdir:directoryEntryOutput
  directoryName="#{widget.properties['directoryName']}"
    value="#{fn:split(chainModel.rowData, '/')[0]}"
    localize="#{widget.properties['localize']}" />/<nxdir:directoryEntryOutput
    directoryName="#{widget.properties['directoryName']}"
    value="#{fn:split(chainModel.rowData, '/')[1]}"
    localize="#{widget.properties['localize']}" />/<nxdir:directoryEntryOutput
    directoryName="#{widget.properties['directoryName']}"
    value="#{chainModel.rowData}"
    localize="#{widget.properties['localize']}"
    keySeparator="/" /><h:outputText
  rendered="#{chainModel.rowIndex != chainModel.rowCount -1}" value=", " /></nxu:inputList>
</c:if>
<c:if test="#{widget.mode == 'pdf'}">
  <nxp:html>
    <nxdir:chainSelect id="#{widget.id}_viewselect" size="3"
      value="#{field}" displayValueOnly="true" multiSelect="true"
      defaultRootKey="">
      <nxdir:chainSelectListbox index="0" size="4"
      directoryName="#{widget.properties['directoryName']}"
      localize="#{widget.properties['localize']}"
      id="#{widget.id}_parent" />
      <nxdir:chainSelectListbox size="4"
        directoryName="#{widget.properties['directoryName']}" index="1"
        localize="#{widget.properties['localize']}"
        id="#{widget.id}_parent2" />
      <nxdir:chainSelectListbox size="4"
        directoryName="#{widget.properties['directoryName']}" index="2"
        localize="#{widget.properties['localize']}"
        id="#{widget.id}_child" />
      <nxdir:chainSelectStatus display="value" id="#{widget.id}_status" />
    </nxdir:chainSelect>
    </nxp:html>
</c:if>
<c:if test="#{nxl:isLikeViewMode(widget.mode)}">

  <nxdir:chainSelect id="#{widget.id}_viewselect" size="3"
    value="#{field}" displayValueOnly="true" multiSelect="true"
    defaultRootKey="">
    <nxdir:chainSelectListbox index="0" size="4"
    directoryName="#{widget.properties['directoryName']}"
    localize="#{widget.properties['localize']}"
    id="#{widget.id}_parent" />
    <nxdir:chainSelectListbox size="4"
      directoryName="#{widget.properties['directoryName']}" index="1"
      localize="#{widget.properties['localize']}"
      id="#{widget.id}_parent2" />
    <nxdir:chainSelectListbox size="4"
      directoryName="#{widget.properties['directoryName']}" index="2"
      localize="#{widget.properties['localize']}"
      id="#{widget.id}_child" />
    <nxdir:chainSelectStatus display="value" id="#{widget.id}_status" />
  </nxdir:chainSelect>

</c:if>
<c:if test="#{widget.mode == 'edit'}">

  <a4j:region id="#{widget.id}_region" renderRegionOnly="true">
    <nxdir:chainSelect size="3" value="#{field}"
      id="#{widget.id}_editselect" multiSelect="true"
      multiParentSelect="true"
      allowBranchSelection="#{widgetProperty_allowBranchSelection}"
      defaultRootKey="" required="#{widgetProperty_required}">
      <nxdir:chainSelectListbox index="0" size="4"
        directoryName="#{widget.properties['directoryName']}"
        localize="#{widget.properties['localize']}"
        id="#{widget.id}_parent" ordering="label">
        <a4j:ajax event="change"
          render="#{widget.id}_parent2 #{widget.id}_child #{widget.id}_message"
          execute="@this" />
      </nxdir:chainSelectListbox>
      <nxdir:chainSelectListbox index="1" size="4"
        directoryName="#{widget.properties['directoryName']}"
        localize="#{widget.properties['localize']}"
        id="#{widget.id}_parent2" ordering="label">
        <a4j:ajax event="change"
          render="#{widget.id}_child #{widget.id}_message"
          immediate="true" />
      </nxdir:chainSelectListbox>
      <nxdir:chainSelectListbox size="4" index="2"
        directoryName="#{widget.properties['directoryName']}"
        localize="#{widget.properties['localize']}"
        id="#{widget.id}_child" ordering="label" />
      <a4j:commandButton value="#{messages['command.add']}"
        styleClass="button" immediate="true"
        actionListener="#{chainSelectActions.add}"
        render="#{widget.id}_status #{widget.id}_message"
        id="#{widget.id}_add" />
      <br />
      <nxdir:chainSelectStatus display="value"
        entryCssStyle="background-color: #DDEEFF"
        label="#{messages['label.chainSelect.selection']}"
        id="#{widget.id}_status">
        <f:facet name="removeButton">
          <a4j:commandButton
            actionListener="#{chainSelectActions.delete}"
            execute="@this" render="#{widget.id}_status"
            image="/icons/toggle_minus.png"
            id="#{widget.id}_delete" />
        </f:facet>
      </nxdir:chainSelectStatus>
    </nxdir:chainSelect>
  </a4j:region>
  <h:message styleClass="errorMessage" for="#{widget.id}_editselect"
    id="#{widget.id}_message" />

</c:if>
</f:subview>