2012-03-28 26 views
0

複数のテキスト入力を可能にする複合コンポーネントを作成しようとします。私は、コンポジットコンポーネントのバッキングコンポーネントを定義することが可能であるので、レンダラーやハンドラを記述する必要はありません。私が理解できなかったことは、複合コンポーネントのxhtmlで宣言されたアクションをバッキングコンポーネントに委譲する方法です。私はまだこれの概念をかなり理解していないと思う。誰にもアイディアがありますか?複合コンポーネントのバッキングコンポーネントのActionListenerを呼び出す

私はクロサギ科2.1.7、Tomcatの7、EL 2.2、春3を使用しています

これは私がコンポーネントを使用したい方法です:

<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%ですか? http://stackoverflow.com/questions/2118656/hcommandlink-hcommandbutton-is-not-being-invokedも参照してください。ちょっとした赤い文字列がコード内にあります。簡略化/名前を変更するときは注意してください。 – BalusC

+0

thx @BalusC、私はサンプルを更新しました。はい、私は、すべての親コンポーネントの 'rendered'属性が' true'に評価されていることを確認しました。私が不思議に思うのはポイント4です。[stackoverflow.com/questions/2118656/...](http://stackoverflow.com/questions/2118656/hcommandlink-hcommandbutton-is-not-being-invoked)バッキングコンポーネントが保持されていないようです。削除または追加ボタン「CompositeComponentTagHandler.createComponent」をクリックするたびに、バッキングコンポーネント 'MultiInput'の新しいインスタンスが作成されます。しかし、なぜ? – fischermatte

+0

私はこれまでのようなコンポーネントを作成しており、うまく動作します。あなたの正確なコードをコピーしました(単純化のために 'Foo'と' MultiSelectRow'を 'Object'で置き換えただけです)、うまく動作します。あなたの具体的な問題は、これまでに投稿されたコードには表示されていません。たぶんネストされたフォーム。 'false'を評価する' rendered'属性かもしれません。知るか。唯一の違いは、私はSpringを使用していないため、Bean上で標準のJSFアノテーションを使用したことです。 – BalusC

答えて

0

私は細部のすべてを理解していないが、私はそれを動作させるための方法を発見しました。各リクエストでバッキングコンポーネントMultiInputの新しいインスタンスが作成されるので、saveStaterestoreStateを上書きして状態を保存しなければなりませんでした。このようにして、プロパティrowsを単純なプロパティとして保持することができます。私もencodeBeginメソッドを削除し、getSubmittedValueを上書きしました。

少なくともこの方法では、Mojarraで働いています。 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

私はAjaxのリスナーメソッドのメソッドシグネチャがAjaxBehaviorEvent(未確認)を含めるべきだと思う:

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

と、f:AJAXタグはちょうど(括弧なし)のようになります。

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

THXの@claudegex、しかしunformatunatelyそれは違いはありません代わりにクロサギ科のMyFacesのを使用している場合... – fischermatte

+0

@fischermatteだけでいくつかの考え;-) – claudegex

+0

実際に、必要としているようです。私の答えを見てください。 – 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> 

最初のボタンをクリックして2つのオペランドフィールドを入力すると、結果が正しく計算されます。ただし、field1に数値以外の値を入力すると、検証に失敗します。第二のボタンを使用している場合

、アクションリスナーメソッドが計算されることはありません。しかし、完全なフォームは常に提出されるので、field1に数値以外の値を入力するとエラーが発生します。

また、と同じように動作したp:ajaxを試しました。

私は実際に何が起こっているのか分かりません。より多くのJSF知恵のある方が助けてくれることを願っています。

+0

thx @Davy。実際は私の場合は、プライムフェイスと標準のjsfボタンの両方で動作しています。おそらく、この方法でリスナーを呼び出すことができます。 '+ 'saveState'と' restoreState'を上書きします。しかし、これはちょうど推測です... – fischermatte

関連する問題