2012-03-28 19 views
0

嘗試編寫允許多文本輸入的複合組件。我讀過可以爲複合組件定義一個後備組件,所以我不必編寫渲染器或處理程序。我想不出的是如何將在composite的xhtml中聲明的動作委託給後備組件。我想我還不完全理解這個概念。有人有想法嗎?調用複合組件中的備份組件的ActionListener

我使用Tomcat 7,EL 2.2,春季3,鑽嘴魚科2.1.7

這是我想使用該組件的方式:

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

BackingBean.java持有對象的列表:

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

的複合元件multiInput.xhtml看起來像THI 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); 
    } 

    ... 
} 

現在 - removeRowaddEmptyRow從不叫上多輸入。 ajax請求被觸發,但在某處丟失。爲什麼?

+0

是否有一個'上的複合或任何其父母rendered'屬性?如果是這樣,你是否100%在表單提交期間評估'true'?另請參閱http://stackoverflow.com/questions/2118656/hcommandlink-hcommandbutton-is-not-being-invoked在代碼中,您順便提到了一些紅鯡魚。簡化/重命名時請小心。 – BalusC 2012-03-28 19:00:09

+0

thx @BalusC,我更新了樣本,所以它少了'紅鯡魚'(希望)。是的,我驗證了所有父組件的'rendered'屬性都被評估爲'true'。但是讓我想知道的是[stackoverflow.com/questions/2118656/...](http://stackoverflow.com/questions/2118656/hcommandlink-hcommandbutton-is-not-being-invoked)中的第4點。似乎支持組件不被保留。每次我點擊刪除或添加按鈕'CompositeComponentTagHandler.createComponent'會創建一個新的支持組件'MultiInput'的實例。但爲什麼? – fischermatte 2012-03-29 08:30:42

+0

我已經創建了這樣的組件,他們工作正常。我拷貝了你的確切代碼(爲了簡單起見,我只用'Object'替換了'Foo'和'MultiSelectRow'),它工作的很好。您的具體問題是其他地方引起的,到目前爲止沒有在代碼中顯示。也許是嵌套的形式。也許一個'渲染'屬性評估'false'。誰知道。唯一的區別是我不使用Spring,因此只在bean上使用標準的JSF註釋。 – BalusC 2012-03-29 13:26:07

回答

0

雖然我不明白所有的細節,但我找到了一種使它工作的方法。由於在每個請求上創建了一個支持組件MultiInput的新實例,我必須通過覆蓋saveStaterestoreState來保存該狀態。這樣我可以保留屬性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,但unformat Lucky沒有區別... – fischermatte 2012-03-29 08:17:34

+0

@fischermatte只是一些想法;-) – claudegex 2012-03-29 22:46:49

+0

實際上,當使用myfaces而不是mojarra時,它似乎是必需的。看到我的答案。 – fischermatte 2012-04-02 12:31:30

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> 

單擊第一個按鈕並填寫兩個操作數字段時,結果將被正確計算。但是,在field1中輸入非數字值時,驗證失敗。

使用第二個按鈕時,從不計算動作偵聽器方法。但是,總是提交完整的表單,因此在field1中輸入非數字值也會觸發錯誤。

我也試過p:ajax,其行爲與f:ajax相同。

我真的不知道這裏發生了什麼。希望有更多JSF智慧的人可以幫忙。

+0

thx @Davy。實際上在我的情況下,它正在使用primefaces和標準jsf按鈕。也許你可以嘗試以這種方式調用監聽器:''覆蓋'saveState'和'restoreState'。但這只是一個猜測... – fischermatte 2012-04-02 12:39:18