2012-03-28 2 views
0

попытайтесь написать составной компонент, который позволяет вводить mutttiple текстовые входы. Я прочитал, что можно определить компонент поддержки для составного компонента, поэтому мне не нужно писать рендерер или обработчик. Я не мог понять, как делегировать действия, объявленные в xhtml в составной компонент компонента. Наверное, я еще не совсем понял концепцию этого. Кто-нибудь имеет Идею?Invoke ActionListener компонента Backing в составном компоненте

Я использую Tomcat 7, EL 2,2, Весна 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 выглядит Тхи s:

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

    ... 
} 

Теперь - removeRow и addEmptyRow никогда не вызывается на MultiInput. Запрос ajax запускается, но где-то он теряется. Зачем?

+0

Есть ли 'rendered' атрибут композит или любой из его родителей? Если да, то вы на 100% оцениваете «истину» во время отправки формы? См. Также http://stackoverflow.com/questions/2118656/hcommandlink-hcommandbutton-is-not-being-invoked У вас кстати довольно красные сельди в коде. Будьте осторожны при упрощении/переименовании. – BalusC

+0

thx @BalusC, я обновил образец, чтобы у него было меньше «красных сельдей» (надеюсь). да, я проверил, что все атрибуты «обработанные» родительские компоненты оцениваются как «истинные». но то, что заставляет меня задаться вопросом, - это пункт 4 в [stackoverflow.com/questions/2118656/...](http://stackoverflow.com/questions/2118656/hcommandlink-hcommandbutton-is-not-being-invoked). кажется, что компонент поддержки не сохраняется. каждый раз, когда я нажимаю кнопку «Удалить» или «Добавить», «CompositeComponentTagHandler.createComponent» создаст новый экземпляр компонента поддержки MultiInput. но почему? – fischermatte

+0

Я создал такие компоненты раньше, и они отлично работают. Я копировал ваш точный код (я просто заменил 'Foo' и' MultiSelectRow' на 'Object' для простоты), и он отлично работает. Ваша конкретная проблема возникает в другом месте, которая пока не показана в коде, опубликованном до сих пор. Возможно, вложенная форма. Возможно, атрибут 'rendered', который оценил' false'. Кто знает. Единственное различие заключается в том, что я не использую Spring и поэтому просто использовал стандартные аннотации JSF для компонента. – BalusC

ответ

0

Хотя я не все понимаю подробно, я нашел способ заставить его работать. Поскольку по каждому запросу создается новый экземпляр компонента поддержки 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; 
     } 
    } 
} 
1

Я думаю, что метод подпись для АЯКС методов слушателя должна включать AjaxBehaviorEvent (непроверенный):

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

и F: Ajax тег должен просто выглядеть (без скобок):

<f:ajax execute=":#{cc.clientId}" render=":#{cc.clientId}" listener="#{cc.addEmptyRow}" /> 
+0

thx @claudegex, но, к сожалению, это не имеет никакого значения ... – fischermatte

+0

@fischermatte просто некоторые мысли ;-) – claudegex

+0

Фактически, когда вы используете myfaces вместо mojarra, это кажется необходимым. см. мой ответ. – fischermatte

0

Я борюсь с той же проблемой здесь: используя <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 может помочь.

+0

thx @Davy. на самом деле в моем случае он работает как с простой, так и с обычной jsf-кнопкой. Возможно, вы могли бы попробовать вызвать слушателя таким образом '' + перезаписать 'saveState' и' restoreState'. Но это всего лишь предположение ... – fischermatte

 Смежные вопросы

  • Нет связанных вопросов^_^