Search code examples
jsfcheckboxprimefacesgrid-layout

PrimeFaces check box grid using 2 Maps or Lists with Row and Column Heading?


I am using 2 Maps of Strings.

  Map<String, String> offices;
  Map<String, String> services;

One Map contains "offices" and one map contains "services provided". Each of these are dynamic as new offices or services can be added or removed. My problem is how to successfully illustrate which offices support each services, and ultimately, how to capture that.

This is what I would like it to look like:

enter image description here

My first problem is I cannot figure out how to represent this using PrimeFaces or JSF vanilla. I have been looking at different things like DataTable, but they all seem to illustrate a single "heading" and not the both the column AND row heading I need. Or they illustrate how to display data complexly, all coming from a SINGLE List or Map. I need to illustrate 2 Maps converging. I believe I need to combine the two Maps into a single intermediate List, but am fumbling on the specifics.

My second problem is how do I capture the boolean values coming back? I am figuring I need to capture indexes, but the specifics of it allude me.

EDIT: CODE AND CLARIFICATION:

  private Map<String, String> offices;

  public Map<String, String> getOffices() {
  return offices;
  }

//constructor
offices.put("PV001", "Pleasantville");
offices.put("GV001", "Green Valley");

and then in the JSF / XHTML markup:

<p:selectManyCheckbox>
    <f:selectItems value="#{myOffices.offices.entrySet()}"
        var="entry" itemLabel="#{entry.value}" itemValue="#{entry.key}" />
</p:selectManyCheckbox>

So how would I CROSS 2 Maps like that and get the relevant check marks. Normally I would just create an office class with a bodyWork, paint, oilChange properties, but unfortunately I need to be able to dynamically adjust the number of these, which is why I am using a Map.


Solution

  • As to layout, just use plain HTML with help of <ui:repeat>. This is indeed not possible using regular JSF table components.

    <table>
        <thead>
            <tr>
                <th>Office</th>
                <ui:repeat value="#{bean.services.values().toArray()}" var="service">
                    <th>#{service}</th>
                </ui:repeat>
            </tr>
        </thead>
        <tbody>
            <ui:repeat value="#{bean.offices.entrySet().toArray()}" var="office">
                <tr>
                    <td>#{office.value}</td>
                    <ui:repeat value="#{bean.services.keySet().toArray()}" var="service">
                        <td><p:selectBooleanCheckbox value="#{bean.officeServices[office.key][service]}" /></td>
                    </ui:repeat>
                </tr>
            </ui:repeat>
        </tbody>
    </table>
    

    To fix the look'n'feel, just throw in some CSS the usual way. Apply if necessary the same CSS class names as PrimeFaces is using for its data table/grid components, such as ui-datatable and brothers.

    As to capturing the submitted values, use a bunch of <p:selectBooleanCheckbox> components on a Map<String, Map<String, Boolean>> property which is keyed by office ID and service ID. Here's how the appropriate bean look like:

    private Map<String, String> offices;
    private Map<String, String> services;
    private Map<String, Map<String, Boolean>> officeServices;
    
    @PostConstruct
    public void init() {
        offices = new LinkedHashMap<>();
        offices.put("o1", "office1");
        offices.put("o2", "office2");
        offices.put("o3", "office3");
        services = new LinkedHashMap<>();
        services.put("s1", "service1");
        services.put("s2", "service2");
        services.put("s3", "service3");
        officeServices = new HashMap<>();
        for (String office : offices.keySet()) {
            officeServices.put(office, new HashMap<String, Boolean>());
        }
    
    }
    
    public void submit() {
        for (Entry<String, Map<String, Boolean>> entry : officeServices.entrySet()) {
            String office = entry.getKey();
            Map<String, Boolean> services = entry.getValue();
            System.out.println(office + ": " + services);
        }
    }
    
    // 3 getters. No setters necessary.
    

    The <p:selectManyCheckbox> is not an option as you cannot control its rendering on a per-cell basis.

    Make sure that you use latest Mojarra. Older versions are known to have bugs related to nested <ui:repeat> and ajax. If I am correct, it's fixed somewhere around 2.1.14~2.1.17.