Вызвать ActionListener компонента поддержки в составном компоненте - PullRequest
0 голосов
/ 28 марта 2012

попробуйте написать составной компонент, который позволяет вводить несколько текстов.Я прочитал, что для составного компонента можно определить вспомогательный компонент, поэтому мне не нужно писать ни средство визуализации, ни обработчик.Что я не мог понять, так это как делегировать действия, объявленные в составном xhtml, компоненту поддержки.Я думаю, что я еще не совсем понял концепцию этого.У кого-нибудь есть идея?

Я использую Tomcat 7, EL 2.2, Spring 3, Mojarra 2.1.7

Вот как я хотел бы использовать компонент:

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

Где BackingBean.java содержит список объектов:

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

Составной компонент multiInput.xhtml выглядит так:

<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);
    }

    ...
}

Now - removeRow и addEmptyRow никогда не вызывается на MultiInput.Запрос Ajax срабатывает, но он где-то теряется.Почему?

Ответы [ 3 ]

1 голос
/ 28 марта 2012

Я думаю, что сигнатура метода для методов прослушивателя ajax должна включать AjaxBehaviorEvent (не проверено):

public void addEmptyRow(AjaxBehaviorEvent event) { ... }

, а тег f: ajax должен выглядеть так (без скобок):

<f:ajax execute=":#{cc.clientId}" render=":#{cc.clientId}" listener="#{cc.addEmptyRow}" />
0 голосов
/ 02 апреля 2012

Хотя я не все подробно понимаю, я нашел способ заставить его работать.Поскольку при каждом запросе создается новый экземпляр компонента поддержки MultiInput, мне приходилось сохранять состояние, перезаписывая saveState и restoreState.Таким образом, я могу сохранить свойство rows как простое свойство.Я также удалил метод encodeBegin и переписал getSubmittedValue.

По крайней мере, так работает в Мохарре.При использовании MyFaces с настройками по умолчанию я получил некоторые исключения сериализации, но я не стал углубляться в это, так как мы будем придерживаться Mojarra.Кроме того, MyFaces, похоже, был более поражен слушателями событий ajax.Требуются параметры «AjaxBehaviorEvent» в методах слушателя.

Здесь полный компонент поддержки 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;
        }
    }
}
0 голосов
/ 29 марта 2012

Я борюсь с той же проблемой: при использовании <f:ajax> методы слушателя действия в компоненте поддержки составного компонента не выполняются.

Частично работает при использовании Primefaces <p:commandButton>: слушатель действияметод правильно вызывается в этом случае.Однако значение атрибута 'process' в этом случае, похоже, игнорируется: все поля формы отправляются, что в моем случае вызывает ошибку проверки.Если это не проблема для вас, вы можете попробовать это.

Я создал несколько тестовых классов, которые воспроизводят проблему:

Файл составного компонента testComponent.xhtml:

<html xmlns="http://www.w3c.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core"
    xmlns:h="http://java.sun.com/jsf/html" 
    xmlns:p="http://primefaces.org/xmlns:ui="http://java.sun.com/jsf/facelets" 
    xmlns:composite="http://java.sun.com/jsf/composite">

<composite:interface componentType="testComponent">
</composite:interface>

<composite:implementation>
    <div id="#{cc.clientId}">
        <h:panelGroup id="addPanel">
            <h:inputText id="operand1" value="#{cc.operand1}"/>
            <h:outputText value=" + " />
            <h:inputText id="operand2" value="#{cc.operand2}"/>
            <h:outputText value=" = " />
            <h:outputText id="result" value="#{cc.result}" />
            <br />
            <p:commandButton id="testButton1" value="Primefaces CommandButton"
                actionListener="#{cc.add()}" process="addPanel" update="addPanel"/>
            <h:commandButton id="testButton2" value="f:ajax CommandButton">
                <f:ajax execute="addPanel" render="addPanel" listener="#{cc.add()}" />
            </h:commandButton>
        </h:panelGroup>
    </div>
</composite:implementation>
</html>

Класс компонента поддержки:

package be.solidfrog.pngwin;

import javax.faces.component.FacesComponent;
import javax.faces.component.UINamingContainer;
import javax.faces.event.ActionEvent;

@FacesComponent("testComponent")
public class TestComponent extends UINamingContainer {

    private Integer operand1, operand2, result;

    public void add() {
        System.err.println("Adding " + operand1 + " and " + operand2);
        result = operand1 + operand2;
    }

    public Integer getOperand1() { return operand1; }
    public void setOperand1(Integer operand1) { this.operand1 = operand1; }
    public Integer getOperand2() { return operand2; }
    public void setOperand2(Integer operand2) { this.operand2 = operand2; }
    public Integer getResult() { return result; }
    public void setResult(Integer result) { this.result = result; }
}

И страница использования test.xhtml:

<!DOCTYPE html>
<html xmlns="http://www.w3c.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core"
    xmlns:h="http://java.sun.com/jsf/html" xmlns:p="http://primefaces.org/ui"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:sf="http://java.sun.com/jsf/composite/solidfrog">
<h:body>
    <h:messages />
    <h:form id="testForm">
        <h:outputLabel for="field1" value="Integer field: "/>
        <h:inputText id="field1" value="#{testBean.field1}" />
        <hr/>
        <sf:testComponent id="testComponent" />
    </h:form>
</h:body>
</html>

При нажатии первой кнопки и заполнении двух полей операндов результатправильно рассчитан.Однако, когда в поле 1 вводится нечисловое значение, происходит сбой проверки.

При использовании второй кнопки метод прослушивателя действий никогда не рассчитывается.Однако заполненная форма всегда отправляется, поэтому при вводе нецифрового значения в поле 1 также возникает ошибка.

Я также пытался p:ajax, который вел себя так же, как и f:ajax.

Я действительно понятия не имею, что здесь происходит.Надеюсь, кто-то с большей мудростью JSF может помочь.

...