在复合组件中调用后备组件的ActionListener [英] Invoke ActionListener of Backing Component in Composite Component

查看:98
本文介绍了在复合组件中调用后备组件的ActionListener的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

尝试编写允许多文本输入的复合组件.我读到可以为复合组件定义支持组件,因此不必编写渲染器或处理程序.我不知道的是如何将Composite的xhtml中声明的动作委派给支持组件.我想我还不太了解这个概念.有人有主意吗?

try to write a composite component that allows mutltiple text inputs. I read that it is possible to define a backing component for a composite component, so I don't have to write a renderer nor a handler. What I couldn't figure out is how to delegate actions declared in composite's xhtml to the backing component. I guess i did not yet quite understand the concept of this. Does anybody has an Idea?

我正在使用Tomcat 7,EL 2.2,Spring 3,Mojarra 2.1.7

I am using Tomcat 7, EL 2.2, Spring 3, Mojarra 2.1.7

这是我想使用组件的方式:

This is the way i'd like to use the component:

<custom:multiInput value="#{backingBean.inputList}"/>

BackingBean.java 包含对象列表的位置:

Where the BackingBean.java holds a list of objects:

@Component
@Scope(value="view")
public class BackingBean {
    ...
    private List<Foo> inputList;
    ....
}

复合组件 multiInput.xhtml 看起来像这样:

The composite component multiInput.xhtml looks like this:

<cc:interface componentType="MultiInput">
    <cc:attribute name="value" required="true" type="java.util.List" />
</cc:interface>

<cc:implementation>    
    <div id="#{cc.clientId}">
        <h:dataTable value="#{cc.attrs.rows}" var="row">
            <h:column>
                <!-- here will be a selector component in order to select a foo object -->
            </h:column>
            <h:column>
               <h:commandButton value="Remove Row">
                    <f:ajax execute=":#{cc.clientId}" render=":#{cc.clientId}" listener="#{cc.removeRow(row)}" />
                </h:commandButton>
            </h:column>
            <h:column>
                <h:commandButton value="Add Row" rendered="#{cc.lastRow}">
                    <f:ajax execute=":#{cc.clientId}" render=":#{cc.clientId}" listener="#{cc.addEmptyRow()}" />
                </h:commandButton>
            </h:column>
        </h:dataTable>
    </div>    
</cc:implementation>

这里是支持组件 MultiInput.java :

@FacesComponent(value="MultiInput")
public class MultiInput extends UIInput implements NamingContainer, Serializable{

    ...

    @Override
    public String getFamily() {
        return "javax.faces.NamingContainer";
    }

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

    public void removeRow(MultiInputRow row) {
        // why is this method is never reached when clicking remove button?
    }

    public void addEmptyRow() {
        // why is this method is never reached when clicking add button?
    }

    public ListDataModel<MultiSelectRow> getRows() {
        return (ListDataModel<MultiSelectRow>) getStateHelper().eval(PropertyKeys.rows, null);
    }

    private void setRows(ListDataModel<MultiSelectRow> rows) {
        getStateHelper().put(PropertyKeys.rows, rows);
    }

    ...
}

现在-removeRowaddEmptyRow从未在MultiInput上调用.触发了一个ajax请求,但是它在某处丢失了.为什么?

Now - removeRow and addEmptyRow is never called on MultiInput. An ajax request is triggered but it gets lost somewhere. Why?

推荐答案

尽管我不了解所有细节,但我找到了一种使之起作用的方法.由于在每个请求上都会创建支持组件MultiInput的新实例,因此我不得不通过覆盖saveStaterestoreState来保存状态.这样,我可以将属性rows保留为简单属性.我还删除了encodeBegin方法并改写了getSubmittedValue.

Although I don't understand everything in detail, I found a way to make it work. Since on each request a new instance of the backing component MultiInput is created, I had to save the state by overwriting saveState and restoreState. This way I could keep the property rows as a simple property. I also removed the encodeBegin method and overwrote getSubmittedValue.

至少以这种方式,它在Mojarra中工作.当使用默认设置使用MyFaces时,我遇到了一些序列化异常,但是由于我们将继续使用Mojarra,因此我对此没有更深入的了解.而且MyFaces似乎对ajax事件侦听器更为震惊.它在侦听器方法中需要"AjaxBehaviorEvent"参数.

At least this way it is working in Mojarra. When using MyFaces with default settings, I got some serialization exceptions, but I did not get deepter into that since we will stick on Mojarra. Also MyFaces seemed to be more stricked with ajax event listeners. It required "AjaxBehaviorEvent" parameters in listener methods.

这里是完整的支持组件MultInput:

Here the complete backing component MultInput:

@FacesComponent(value = "MultiInput")
public class MultiInput extends UIInput implements NamingContainer, Serializable {

    ListDataModel<MultiInputRow> rows;

    @Override
    public String getFamily() {
        return "javax.faces.NamingContainer";
    }

    @Override
    public Object getSubmittedValue() {
        List<Object> values = new ArrayList<Object>();
        List<MultiInputRow> wrappedData = (List<MultiInputRow>) getRows().getWrappedData();
        for (MultiInputRow row : wrappedData) {
            if (row.getValue() != null) { // only if a valid value was selected
                values.add(row.getValue());
            }
        }
        return values;
    }

    public boolean isLastRow() {
        int row = getRows().getRowIndex();
        int count = getRows().getRowCount();
        return (row + 1) == count;
    }

    public boolean isFirstRow() {
        int row = getRows().getRowIndex();
        return 0 == row;
    }

    public void removeRow(AjaxBehaviorEvent e) {
        List<MultiInputRow> wrappedData = (List<MultiInputRow>) getRows().getWrappedData();
        wrappedData.remove(rows.getRowIndex());
        addRowIfEmptyList();
    }

    public void addEmptyRow(AjaxBehaviorEvent e) {
        List<MultiInputRow> wrappedData = (List<MultiInputRow>) getRows().getWrappedData();
        wrappedData.add(new MultiInputRow(null));
    }

    public ListDataModel<MultiInputRow> getRows() {
        if (rows == null) {
            rows = createRows();
            addRowIfEmptyList();
        }
        return rows;
    }

    public List<Object> getValues() {
        return (List<Object>) super.getValue();
    }

    private ListDataModel<MultiInputRow> createRows() {
        List<MultiInputRow> wrappedData = new ArrayList<MultiInputRow>();
        List<Object> values = getValues();
        if (values != null) {
            for (Object value : values) {
                wrappedData.add(new MultiInputRow(value));
            }
        }
        return new ListDataModel<MultiInputRow>(wrappedData);
    }

    private void addRowIfEmptyList() {
        List<MultiInputRow> wrappedData = (List<MultiInputRow>) rows.getWrappedData();
        if (wrappedData.size() == 0) {
            wrappedData.add(new MultiInputRow(null));
        }
    }

    @Override
    public Object saveState(FacesContext context) {
        if (context == null) {
            throw new NullPointerException();
        }
        Object[] values = new Object[2];
        values[0] = super.saveState(context);
        values[1] = rows != null ? rows.getWrappedData() : null;
        return (values);
    }

    @Override
    public void restoreState(FacesContext context, Object state) {
        if (context == null) {
            throw new NullPointerException();
        }

        if (state == null) {
            return;
        }
        Object[] values = (Object[]) state;
        super.restoreState(context, values[0]);
        rows = values[1] != null ? new ListDataModel<MultiInputRow>((List<MultiInputRow>) values[1]) : null;
    }

    /**
     * Represents an editable row that holds a value that can be edited.
     */
    public class MultiInputRow {

        private Object value;

        MultiInputRow(Object value) {
            this.value = value;
        }

        public Object getValue() {
            return value;
        }

        public void setValue(Object value) {
            this.value = value;
        }
    }
}

这篇关于在复合组件中调用后备组件的ActionListener的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆