JSF2.2 组件包装重复

JSF2.2 Component to wrap a repeat

我正在使用 JSF 2.2 / Mojarra 2.2.8

在我的模型中有 java.util.Set,我想编辑那些 Set

public class MyModel {
    private Set<Foo> fooSet;
    private Set<Bar> barSet;
    // getters and setters
}

public class Foo {
    private String label;
    //getter and setter
}

public class Bar {
    private String name;
    // getter and setter
}

我为此使用复合组件

<h:form>
    <ez:editFooSet myModel="#{someBean.myModel}"/>
    <ez:editBarSet myModel="#{someBean.myModel}"/>
    <!-- ... -->
</h:form>

我的想法是将 ui:repeat 所需的列表存储在 JSF ManagedBean 中,并使用 @FacesComponentSet 转换为 [=25] 中的 List =] 和 ListSetupdateModel()

editFooSet.xhtml :

<cc:interface componentType="my.app.component.FooSetComponent">
    <cc:attribute name="myModel" type="my.app.model.MyModel" required="true"/>
</cc:interface>
<cc:implementation>
    <ui:repeat value="#{fooSetBean.value}" var="item">
        <h:outputLabel value="Foo label: "/>
        <h:inputText value="#{item.label}"/>
        <h:commandButton value="remove" action="#{fooSetBean.remove(item)}"/>
    </ui:repeat>
    <h:commandButton value="add" action="#{fooSetBean.add()}"/>
</cc:implementation>

FooSetBean.java

@Named
@ViewScoped
public class FooSetBean {
    private List<Foo> value;
    // getter and setter
    puvlic void remove(Foo foo) {
        fooList.remove(foo);
    }
    public void add() {
        fooList.add(new Foo());
    }
}

和 FooSetComponent.java :

@FacesComponent("my.app.component.FooSetComponent")
public class FooSetComponent extends UIInput implements NamingContainer {

    @Override
    public String getFamily() {
        return UINamingContainer.COMPONENT_FAMILY;
    }

    @Override
    public Object getSubmittedValue() {
        return null;
    }

    @Override
    public void encodeBegin(FacesContext context) throws IOException {
        MyModel model = (MyModel) super.getAttributes().get("myModel");
        Collection<Foo> foos = model.getFooSet();
        List<Foo> fooList = new ArrayList<>(foos);
        FooSetBean bean = context.getApplication().evaluateExpressionGet(context, "#{fooSetBean}", FooSetBean.class) ;
        bean.setValue(fooList);
        super.encodeBegin(context);
    }

    @Override
    public void updateModel(FacesContext context) {
        MyModel model = (MyModel) super.getAttributes().get("myModel");
        FooSetBean bean = context.getApplication().evaluateExpressionGet(context, "#{fooSetBean}", FooSetBean.class) ;
        Collection<Foo> newValue = bean.getValue();
        model.setFooSet(new HashSet<>(newValue));
    }
}

editBarSet.xhtml、BarSetBean.java 和 BarSetComponent.java

也一样

而且该解决方案正在发挥作用

我的问题是我有很多这样的 Set 我想分解这段代码

我想要这样的东西:

<h:form>
    <ez:editRepeat value="#{someBean.myModel.fooSet}" itemClass="#{Foo.class}">
        <h:outputLabel value="Foo label: "/>
        <h:inputText value="#{item.label"/>
    </ez:editRepeat>
    <ez:editRepeat value="#{someBean.myModel.barSet}" itemClass="#{Bar.class}">
        <h:outputLabel value="Bar name: "/>
        <h:inputText value="#{item.name}"/>
    </ez:editRepeat>
    <!-- ... -->
</h:form>

与 editRepeat.xhtml :

<cc:interface componentType="my.app.component.EditRepeatComponent">
    <cc:attribute name="value" type="java.util.collection" required="true"/>
    <cc:attribute name="itemClass" type="java.lang.Class" required="true"/>
</cc:interface>
<cc:implementation>
    <ui:repeat value="#{fooSetBean.value}" var="item" id="repeat">
        <cc:insertChildren/>
        <h:commandButton value="remove" action="#{cc.remove(item)}"/>
    </ui:repeat>
    <h:commandButton value="add" action="#{cc.add()}"/>
</cc:implementation>

和 EditRepeatComponent.java

@FacesComponent("my.app.component.EditRepeatComponent")
public class EditRepeatComponent extends UIInput implements NamingContainer {

    @Override
    public String getFamily() {
        return UINamingContainer.COMPONENT_FAMILY;
    }

    @Override
    public void encodeBegin(FacesContext context) throws IOException {
        Collection value = (Collection) super.getAttributes().get("value");
        List<Foo> list = new ArrayList<>(value);
        setList(list);
        super.encodeBegin(context);
    }

    public List getList() {
        return (List) getStateHelper().get("list");
    }

    public void setList(List list) {
        getStateHelper().put("list", list);
    }

    public void add() {
        try {
            Class itemClass = (Class) super.getAttributes().get("itemClass");
            Object newItem = itemClass.newInstance();
            getList().add(newItem);
        } catch (InstantiationException | IllegalAccessException ex) {
            throw new RuntimeException(ex);
        }
    }

    public void remove(Object item) {
        getList().remove(item);
    }

    @Override
    public void updateModel(FacesContext context) {
        // ???
    }

    @Override
    public Object getSubmittedValue() {
        // ???
    }
}

但这不起作用 几秒钟后(系统在 1 秒内工作)我有一个异常:

java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
at java.util.ArrayList.rangeCheck(ArrayList.java:653)
at java.util.ArrayList.get(ArrayList.java:429)
at javax.faces.component.AttachedObjectListHolder.restoreState(AttachedObjectListHolder.java:166)
at javax.faces.component.UIComponentBase.restoreState(UIComponentBase.java:1611)
at com.sun.faces.application.view.FaceletPartialStateManagementStrategy.visit(FaceletPartialStateManagementStrategy.java:380)
at com.sun.faces.component.visit.FullVisitContext.invokeVisitCallback(FullVisitContext.java:151)
at javax.faces.component.UIComponent.visitTree(UIComponent.java:1689)
at javax.faces.component.UIComponent.visitTree(UIComponent.java:1700)
at javax.faces.component.UIComponent.visitTree(UIComponent.java:1700)
at com.sun.faces.application.view.FaceletPartialStateManagementStrategy.restoreView(FaceletPartialStateManagementStrategy.java:367)
at com.sun.faces.application.StateManagerImpl.restoreView(StateManagerImpl.java:138)
at com.sun.faces.application.view.ViewHandlingStrategy.restoreView(ViewHandlingStrategy.java:123)
at com.sun.faces.application.view.FaceletViewHandlingStrategy.restoreView(FaceletViewHandlingStrategy.java:585)
at com.sun.faces.application.view.MultiViewHandler.restoreView(MultiViewHandler.java:150)
at javax.faces.application.ViewHandlerWrapper.restoreView(ViewHandlerWrapper.java:353)
at javax.faces.application.ViewHandlerWrapper.restoreView(ViewHandlerWrapper.java:353)
at javax.faces.application.ViewHandlerWrapper.restoreView(ViewHandlerWrapper.java:353)
at org.omnifaces.viewhandler.RestorableViewHandler.restoreView(RestorableViewHandler.java:86)
at com.sun.faces.lifecycle.RestoreViewPhase.execute(RestoreViewPhase.java:197)
at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:101)
at com.sun.faces.lifecycle.RestoreViewPhase.doPhase(RestoreViewPhase.java:121)
at com.sun.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:198)
at javax.faces.webapp.FacesServlet.service(FacesServlet.java:646)
at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:85)
at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:61)
at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)
at org.wildfly.extension.undertow.security.SecurityContextAssociationHandler.handleRequest(SecurityContextAssociationHandler.java:78)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:131)
at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:56)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:45)
at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:63)
at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:58)
at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:70)
at io.undertow.security.handlers.SecurityInitialHandler.handleRequest(SecurityInitialHandler.java:76)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at org.wildfly.extension.undertow.security.jacc.JACCContextIdHandler.handleRequest(JACCContextIdHandler.java:61)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:261)
at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:247)
at io.undertow.servlet.handlers.ServletInitialHandler.access[=18=]0(ServletInitialHandler.java:76)
at io.undertow.servlet.handlers.ServletInitialHandler.handleRequest(ServletInitialHandler.java:166)
at io.undertow.server.Connectors.executeRootHandler(Connectors.java:197)
at io.undertow.server.HttpServerExchange.run(HttpServerExchange.java:759)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)

我不明白为什么

而且我还不知道如何实施 updateModel()getSubmittedValue() 以使所有系统正常工作

2天后我终于成功了

Omnifaces 文档 http://showcase.omnifaces.org/functions/Converters 给了我处理 ui:repeat 的解决方案:使用 toArray()

    <h:form>
        <ez:editRepeat value="#{someBean.myModel.fooSet}" itemClass="my.app.model.Foo">
            <h:outputLabel value="Foo label: "/>
            <h:inputText value="#{item.label}"/>
        </ez:editRepeat>
        <ez:editRepeat value="#{someBean.myModel.barSet}" itemClass="my.app.model.Bar">
            <h:outputLabel value="Bar name: "/>
            <h:inputText value="#{item.name}"/>
        </ez:editRepeat>
        <!-- ... -->
    </h:form>

editRepeat.xtml(我使用 primefaces p:commanButton 来指定 updateprocess 属性,这样我就不会丢失未提交的输入并且我不会提交所有表格)

<cc:interface>
    <cc:attribute name="value" type="java.util.Collection" required="true"/>
    <cc:attribute name="itemClass" type="java.lang.String" required="true"/>
</cc:interface>
<cc:implementation>
    <h:panelGroup style="display: block; background-color:  rgba(200, 200, 200, 0.5); padding: 12px;">
        <ui:repeat value="#{cc.attrs.value.toArray()}" var="item">
            <h:panelGroup style="background-color: rgba(200, 200, 200, 0.5); margin-left: 12px; margin-bottom: 12px; display: block; padding: 12px;">
                <cc:insertChildren/>
                <p:commandButton value="remove" action="#{editRepeatBean.remove(cc.attrs.value, item)}"
                                 update="@parent:@parent:@parent" process="@parent:@parent:@parent"
                                 style="margin-left: 12px;"/>
            </h:panelGroup>
        </ui:repeat>
        <p:commandButton value="add" action="#{editRepeatBean.add(cc.attrs.value, cc.attrs.itemClass)}" update="@parent" process="@parent"/>
    </h:panelGroup>
</cc:implementation>

EditRepeatBean.java

@Named
@RequestScoped
public class EditRepeatBean {

    public void add(Collection collection, String itemClassName) {
        try {
            Class itemClass = Class.forName(itemClassName);
            Object item = itemClass.newInstance();
            collection.add(item);
        } catch (InstantiationException | IllegalAccessException | ClassNotFoundException ex) {
            throw new RuntimeException(ex);
        }
    }

    public void remove(Collection collection, Object item) {
        collection.remove(item);
    }

}

如果你有:

public class MyModel {
    private Set<Foo> fooSet;
    // getter and setter
}

public class Foo {
    private String label;
    private Set<Bar> barSet;
    // getters and setters
}

public class Bar {
    private String name;
    // getter and setter
}

你可以做到

    <h:form>
        <ez:editRepeat value="#{someBean.myModel.fooSet}" itemClass="my.app.model.Foo">
            <h:outputLabel value="Foo label: "/>
            <h:inputText value="#{item.label}"/>
            <ez:editRepeat value="#{item.barSet}" itemClass="my.app.model.Bar">
                <h:outputLabel value="Bar name: "/>
                <h:inputText value="#{item.name}"/>
            </ez:editRepeat>
        </ez:editRepeat>
        <!-- ... -->
    </h:form>

它也在工作

还有一个问题:Set不能为空,如果找到解决方案我会编辑


编辑:null Collection 的解决方案 只需更改 editRepeat.xhtml 接口以添加一个 componentType 以便在 encodeBegin() 方法中初始化集合并添加一个 cc:attribute 以确保 [=28= 的实现] 默认值为 HashSet

<cc:interface componentType="my.app.component.EditRepeatComponent">
    <cc:attribute name="value" type="java.util.Collection" required="true"/>
    <cc:attribute name="itemClass" type="java.lang.String" required="true"/>
    <cc:attribute name="collectionImpl" type="java.lang.String" default="java.util.HashSet"/>
</cc:interface>

和EditRepeatComponent.java

@FacesComponent("my.app.component.EditRepeatComponent")
public class EditRepeatComponent extends UIInput implements NamingContainer {

    @Override
    public String getFamily() {
        return UINamingContainer.COMPONENT_FAMILY;
    }

    @Override
    public void encodeBegin(FacesContext context) throws IOException {
        ELContext elContext = context.getELContext();
        ValueExpression valueExpression = super.getValueExpression("value");
        if (valueExpression.getValue(elContext) == null) {
            try {
                String collectionImpl = (String) super.getAttributes().get("collectionImpl");
                Class<? extends Collection> collectionClass = (Class<? extends Collection>) Class.forName(collectionImpl);
                Collection collection = collectionClass.newInstance();
                valueExpression.setValue(elContext, collection);
            } catch (InstantiationException | IllegalAccessException | ClassNotFoundException ex) {
                throw new RuntimeException(ex);
            }
        }
        super.encodeBegin(context);
    }

}

还有一个问题...当 ez:editRepeat 在另一个 ez:editRepeat 中时,删除内部 ez:editRepeat 不起作用

Caused by: javax.el.PropertyNotFoundException: The class 'my.app.model.Bar' does not have the property 'barSet'

编辑:最终解决方案。

之前的解决方案,嵌套<editRepeat>有问题,在processValidators()阶段,内部<repeat>个组件的var为null,导致Exception 我不知道为什么,这可能是一个错误...

解决方法是@OverrideprocessValidators()重新设置repeat.var.

这是经过一些改进的完整解决方案:

  • 组件在另一个组件中变形,因此父组件的更新只会更新组件
  • EditRepeatBean的所有代码已移至EditRepeatComponent
  • 添加 var 属性
  • 重命名属性以保持一致性
  • update/render 和 process/execute 以编程方式完成

    <h:form>
        <ez:editRepeat value="#{someBean.myModel.fooSet}"
                itemType="my.app.model.Foo"
                var="foo">
            <h:outputLabel value="Foo label: "/>
            <h:inputText value="#{foo.label}"/>
            <ez:editRepeat value="#{foo.barSet}"
                    itemType="my.app.model.Bar"
                    var="bar">
                <h:outputLabel value="Bar name: "/>
                <h:inputText value="#{bar.name}"/>
            </ez:editRepeat>
        </ez:editRepeat>
        <!-- ... -->
    </h:form>
    

editRepeat.xhtml(包装器):

<cc:interface>
    <cc:attribute name="value" type="java.util.Collection" required="true"/>
    <cc:attribute name="itemType" type="java.lang.String" required="true"/>
    <cc:attribute name="collectionType" type="java.lang.String" default="java.util.HashSet"/>
    <cc:attribute name="var" type="java.lang.String" required="true"/>
</cc:interface>

<cc:implementation>
    <h:panelGroup id="#{cc.id}Wrapper">
        <ez:editRepeatWrapped value="#{cc.attrs.value}" var="#{cc.attrs.var}"
                       itemType="#{cc.attrs.itemType}"
                       collectionType="#{cc.attrs.collectionType}"
                       id="#{cc.id}Wrapped">
            <cc:insertChildren/>
        </ez:editRepeatWrapped>
    </h:panelGroup>
</cc:implementation>

editRepeatWrapped.xhtml :

<cc:interface componentType="my.app.component.EditRepeatComponent">
    <cc:attribute name="value" type="java.util.Collection" required="true"/>
    <cc:attribute name="itemType" type="java.lang.String" required="true"/>
    <cc:attribute name="collectionType" type="java.lang.String" default="java.util.HashSet"/>
    <cc:attribute name="var" type="java.lang.String" required="true"/>
</cc:interface>

<cc:implementation>
    <h:panelGroup id="itemsGroup" style="display: block; background-color:  rgba(0, 255, 0, 0.20); padding: 6px; margin: 6px;">
        <ui:repeat value="#{cc.attrs.value.toArray()}" var="#{cc.attrs.var}"
                id="#{cc.attrs.id}Repeat">
            <h:panelGroup id="itemGroup" style="background-color: rgba(0, 255, 0, 0.2); margin-left: 12px; margin: 6px; display: block; padding: 6px;">
                <cc:insertChildren/>
                <p:commandButton value="remove" action="#{cc.remove()}"
                                 style="margin-left: 12px;"/>
            </h:panelGroup>
        </ui:repeat>
        <p:commandButton value="add" action="#{cc.add()}"/>
    </h:panelGroup>
</cc:implementation>

EditeRepeatComponent.java :

@FacesComponent("my.app.component.EditRepeatComponent")
public class EditRepeatComponent extends UIInput implements NamingContainer {

    @Override
    public String getFamily() {
        return UINamingContainer.COMPONENT_FAMILY;
    }

    @Override
    public void processValidators(FacesContext context) {
        initVar(); // because repeat.var is null at this stage
        super.processValidators(context);
    }

    @Override
    public void encodeBegin(FacesContext context) throws IOException {
        initValue(context);
        initVar();
        super.encodeBegin(context);
    }

    /**
     * set var of the repeat component
     */
    private void initVar() {
        String idRepeatComponent = ((String) super.getAttributes().get("id")) + "Repeat";
        String var = (String) getAttributes().get("var");
        UIRepeat repeatConponent = (UIRepeat) super.findComponent(idRepeatComponent);
        repeatConponent.setVar(var);
    }

    /**
     * if the value is null then initialize the collection with the collection type attribute
     */
    private void initValue(FacesContext context) {
        ELContext elContext = context.getELContext();
        ValueExpression valueExpression = super.getValueExpression("value");
        Collection collection = (Collection) valueExpression.getValue(elContext);
        if (collection == null) {
            try {
                String collectionType = (String) getAttributes().get("collectionType");
                Class<? extends Collection> collectionClass = (Class<? extends Collection>) Class.forName(collectionType);
                collection = collectionClass.newInstance();
                valueExpression.setValue(elContext, collection);
            } catch (InstantiationException | IllegalAccessException | ClassNotFoundException ex) {
                throw new RuntimeException(ex);
            }
        }
    }

    public void remove() {
        String var = (String) getAttributes().get("var");
        Object item = evaluate(var);
        Collection collection = (Collection) getAttributes().get("value");
        collection.remove(item);
        updateView();
    }

    private Object evaluate(String var) {
        FacesContext facesContext = getFacesContext();
        ELContext elContext = facesContext.getELContext();
        Application application = facesContext.getApplication();
        ExpressionFactory expressionFactory = application.getExpressionFactory();
        ValueExpression expression = expressionFactory.createValueExpression(elContext, "#{" + var + "}", Object.class);
        Object item = expression.getValue(elContext);
        return item;
    }

    public void add() {
        try {
            Collection collection = (Collection) getAttributes().get("value");
            String itemType = (String) getAttributes().get("itemType");
            Class itemClass = Class.forName(itemType);
            Object item = itemClass.newInstance();
            collection.add(item);
            updateView();
        } catch (InstantiationException | IllegalAccessException | ClassNotFoundException ex) {
            throw new RuntimeException(ex);
        }
    }

    /**
     * render/update and execute/process the wrapper of the component
     */
    private void updateView() {
        PartialViewContext context = getFacesContext().getPartialViewContext();
        String parentId = this.getParent().getClientId();
        context.getRenderIds().add(parentId);
        context.getExecuteIds().add(parentId);
    }

}

并不是说 <ui:repeat ... var="#{cc.attrs.var}" ...> 没用,var 不是那样设置的(我不知道为什么...),它是在 EditRepeatComponent.initVar() 中设置的 encodeBegin()processValidators() 我只是把 var="#{cc.attrs.var}" 放在理解