Updating components inside a custom ui:component when it is used multiple times on same page

2.7k views Asked by At

I am building a simple component in JSF (Mojarra 2.1.x) where I have to access parent ui components to update them, currently I'm using binding to achieve this, but it only works as long as I don't use the component more than once on the same page.

So I need a solution that would allow me to use the component multiple times on same page.

In the following code I'm updating commandButton with binding="#{msButton}" and panel with binding="#{msPanel}":

<?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">
<ui:component xmlns="http://www.w3.org/1999/xhtml"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:c="http://java.sun.com/jsp/jstl/core"
    xmlns:cc="http://java.sun.com/jsf/composite"
    xmlns:layout="http://sterz.stlrg.gv.at/jsf/layout"
    xmlns:p="http://primefaces.org/ui">

    <cc:interface>
        <cc:attribute name="controller" required="true" />
        <cc:attribute name="converter" required="true" />
    </cc:interface>
    <cc:implementation>
        <p:commandButton id="msButton#{cc.attrs.controller.class.getSimpleName()}" binding="#{msButton}" value="#{msg.mehr} (#{cc.attrs.controller.itemList.size()})" type="button" />
        <p:overlayPanel id="msOverlayPanel" for=":#{msButton.clientId}" hideEffect="fade" my="right top" at="right bottom">
            <p:panel id="msPanel#{cc.attrs.controller.class.getSimpleName()}" binding="#{msPanel}" styleClass="ui-panel-fit">
                <ui:repeat id="repeat" value="#{cc.attrs.controller.itemList}"
                    var="item"> 
                    <p:commandButton id="removeButton"
                        actionListener="#{cc.attrs.controller.removeItem(item)}"
                        icon="ui-icon-trash" update=":#{msPanel.clientId} :#{msButton.clientId}" ajax="true"
                        process="@this" disabled="#{cc.attrs.controller.itemList.size() == 1}"/>
                    <p:selectBooleanButton id="value1" value="#{item.exclude}"
                        offLabel="und" onLabel="und nicht" style="width:80px;">
                        <p:ajax event="change" process="@this" />
                    </p:selectBooleanButton>
                    <p:autoComplete converter="#{cc.attrs.converter}"
                        readonly="#{cc.attrs.readonly}" value="#{item.from}"
                        dropdown="true"
                        completeMethod="#{cc.attrs.controller.autocomplete}" var="gp"
                        itemLabel="#{gp.displayName}" itemValue="#{gp}">
                        <p:ajax event="itemSelect" process="@this" />
                    </p:autoComplete>
                    <h:outputText value=" #{msg.bis} " />
                    <p:autoComplete converter="#{cc.attrs.converter}"
                        readonly="#{cc.attrs.readonly}" value="#{item.to}" dropdown="true"
                        completeMethod="#{cc.attrs.controller.autocomplete}" var="gp"
                        itemLabel="#{gp.displayName}" itemValue="#{gp}">
                        <p:ajax event="itemSelect" process="@this" />
                    </p:autoComplete>
                    <br />
                </ui:repeat>
                <hr />
                <p:commandButton id="addButton" actionListener="#{cc.attrs.controller.addItem}"
                    icon="ui-icon-plus" value="#{msg.zufuegen}" update="@parent :#{msButton.clientId}"
                    ajax="true" process="@this"/>
            </p:panel>
        </p:overlayPanel>
    </cc:implementation>
</ui:component>

Any help is much apprecieted.

3

There are 3 answers

0
danizmax On BEST ANSWER

The solution is to use the faces component bean

@FacesComponent("com.xxx.MultiselectorIdComponent")
public class MultiselectorIdComponent extends UINamingContainer
{

    UIComponent msPanel;

    // getters/settter and other ui compunents    

}

tell the component interface what faces component bean to use

<cc:interface componentType="com.xxx.MultiselectorIdComponent">

bind the JSF component to the one in the faces component bean

<p:panel binding="#{cc.msPanel}"/>

and to access the components, for example to update the component, we use the binding

<p:commandButton value="My Button" update=":#{cc.msPanel.clientId}"/>

ALSO: A good practice is to use a parent container (like <div>) with the following ID

<div id="#{cc.clientId}">

Hope this helps, Regards

4
Predrag Maric On

You could target the components for update by their style class

<p:commandButton styleClass="msButton" ... />

<p:panel styleClass="msPanel" ... />

And I guess you update them from addButton, which would look like this

<p:commandButton id="addButton" update="@(.msButton, .msPanel)" ... />

It should have no problems working with many cc instances on the same page.

UPDATE

You can try with update="@composite", which should refresh the whole custom component. Or, the cumbersome update="@parent @parent:@parent:@parent:@child(1)" which should target panel and button respectively. Or update="@parent @parent:@parent:@previous", which should do the same. Take a look at chapter 4.3 in Primefaces User Guide for more examples and supported keywords.

0
skybber On

I solve similar problem by common bean "bindingBean" stored in ViewContext. BindingBean holds all binded component in internal MAP of component records. The key of hash map is an EL expression - it is factory of the component. EL is used for components creating. Components are hold in records stored in MAP in bind bean.

Example:

<h:panel binding="#{bindBean.get('msPanelFactory.getPanel()').component}"/>

Record:

public class BindingRecord {
    private Object component;
    private String compExpr;

    public BindingRecord(String compExpr) {
        this.compExpr = compExpr;
    }
    public Object getComponent() {
        if (component == null) {
            component = getValueExprValue("#{" + compExpr + "}");
        }
        return component;
    }
    public void setComponent(Object component) {
        this.component = component;
    }
    public Object getStoredComponent() {
        return component;
    }
}

Binding bean:

public class BindingBean {
    private final Map<String, BindingRecord> bindingMap = new HashMap<>();
    public BindingRecord get(String key) {
        BindingRecord result = bindingMap.get(key);
        if (result == null) {
            result = new BindingRecord(key);
            bindingMap.put(key, result);
        }
        return result;
    }
}

In you case you can use:

<h:panel binding="#{bindingBean.get('panelFactory.create()').component}"/>

Inside composite component you can use trick - Pass component name by composite parameter:

<h:panel binding="#{bindingBean.get('panelFactory.create('.concate(cc.attrs.componentName).concate(')')).component}"/>