2012-03-28 11 views
0

versuchen Sie, eine zusammengesetzte Komponente zu schreiben, die mehrere Texteingaben zulässt. Ich habe gelesen, dass es möglich ist, eine Backing-Komponente für eine Composite-Komponente zu definieren, so dass ich weder einen Renderer noch einen Handler schreiben muss. Was ich nicht herausfinden konnte, ist, wie Aktionen, die in der xhtml-Datei von composite deklariert sind, an die unterstützende Komponente delegiert werden. Ich glaube, ich habe das Konzept noch nicht verstanden. Hat jemand eine Idee?Aufrufen des ActionListener der Backing-Komponente in der Composite-Komponente

Ich benutze Tomcat 7, EL 2.2, Frühling 3, Mojarra 2.1.7

Dies ist der Weg Ich mag würde die Komponente verwenden:

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

Wo die BackingBean.java hält eine Liste von Objekten:

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

Verbundbauteil multiInput.xhtml wie thi aussieht 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> 

Und hier die Trägerkomponente 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); 
    } 

    ... 
} 

jetzt - removeRow und addEmptyRow wird nie auf Multiinput genannt. Eine Ajax-Anfrage wird ausgelöst, aber sie geht irgendwo verloren. Warum?

+0

Gibt es ein 'rendered' Attribut auf dem Verbund oder eine ihrer Eltern? Wenn ja, sind Sie zu 100% der Meinung, dass "wahr" während der Übermittlung des Formulars ausgewertet wird? Siehe auch http://stackoverflow.com/questions/2118656/hcommandlink-hcommandbutton-is-not-being-invoiced Sie haben übrigens ziemlich viele rote Heringe im Code. Bitte seien Sie vorsichtig beim Vereinfachen/Umbenennen. – BalusC

+0

thx @BalusC, ich habe das Sample aktualisiert, so dass es weniger "rote Heringe" (hoffentlich) hat. Ja, ich habe überprüft, dass alle gerenderten Attribute der Elternkomponente zu "wahr" ausgewertet werden. aber was mich wundern lässt, ist Punkt 4 in [stackoverflow.com/questions/2118656/...](http://stackoverflow.com/questions/2118656/hcommandlink-hcommandbutton-is-not-being-invoked). Es scheint, dass die Hintergrundkomponente nicht erhalten bleibt. Jedes Mal, wenn ich auf die Schaltfläche zum Entfernen oder Hinzufügen klicke, wird 'CompositeComponentTagHandler.createComponent' eine neue Instanz der Backing-Komponente' MultiInput' erzeugen. aber warum? – fischermatte

+0

Ich habe solche Komponenten schon einmal erstellt und sie funktionieren gut. Ich kopierte den genauen Code (ich habe 'Foo' und' MultiSelectRow' nur durch 'Object' ersetzt) ​​und es funktioniert gut. Ihr konkretes Problem wird an anderer Stelle verursacht, was in dem bisher veröffentlichten Code nicht gezeigt wird. Vielleicht eine verschachtelte Form. Vielleicht ein "gerendertes" Attribut, das "falsch" auswertet. Wer weiß. Der einzige Unterschied ist, dass ich Spring nicht verwende und daher nur Standard-JSF-Annotationen für die Bean verwende. – BalusC

Antwort

0

Obwohl ich nicht alles im Detail verstehe, habe ich einen Weg gefunden, damit es funktioniert. Da bei jeder Anfrage eine neue Instanz der Backing-Komponente MultiInput erstellt wird, musste ich den Zustand durch Überschreiben saveState und restoreState speichern. Auf diese Weise konnte ich die Eigenschaft rows als eine einfache Eigenschaft behalten. Ich entfernte auch die encodeBegin Methode und überschrieb getSubmittedValue.

Zumindest so funktioniert es in Mojarra. Wenn ich MyFaces mit den Standardeinstellungen benutze, habe ich einige Serialisierungsausnahmen, aber ich bin nicht tiefer in das Thema eingedrungen, da wir Mojarra behalten werden. Auch MyFaces schien mit Ajax-Event-Listener besser ausgestattet zu sein. Es erforderte "AjaxBehaviorEvent" -Parameter in Listener-Methoden.

die komplette Trägerkomponente 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

Ich denke, die Methodensignatur für Ajax-Listener-Methoden sollten die AjaxBehaviorEvent (ungeprüfter) schließen ein:

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

und f: ajax-Tag sollte nur wie folgt aussehen (ohne Klammern):

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

thx @claudegex, aber unformell macht es keinen Unterschied ... – fischermatte

+0

@fischermatte nur ein paar Gedanken ;-) – claudegex

+0

eigentlich bei Verwendung von myfaces statt mojarra scheint es erforderlich zu sein. Sieh meine Antwort. – fischermatte

0

Ich habe hier mit dem gleichen Problem zu kämpfen: Verwendung von <f:ajax>, Aktion Listener-Methoden in der Composite-Komponente Backing-Komponente werden nicht ausgeführt.

Es funktioniert teilweise bei der Verwendung von Primefaces <p:commandButton>: Die Aktion Listener-Methode wird in diesem Fall korrekt aufgerufen. Der Wert des Attributs 'process' scheint in diesem Fall jedoch ignoriert zu werden: Alle Formularfelder werden übergeben, was in meinem Fall zu einem Validierungsfehler führt. Wenn dies kein Problem für Sie ist, können Sie dies versuchen.

ich einige Testklassen erstellt haben, das Problem zu reproduzieren:

Verbundkomponentendatei 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> 

Die Trägerkomponentenklasse:

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

Und die Verwendung Seite 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> 

Wenn Sie auf die erste Schaltfläche klicken und die beiden Operandenfelder ausfüllen, wird das Ergebnis korrekt berechnet. Wenn jedoch ein nicht-numerischer Wert in Feld 1 eingegeben wird, ist die Überprüfung fehlgeschlagen.

Bei Verwendung der zweiten Schaltfläche wird die Aktion Listener-Methode nie berechnet. Das vollständige Formular wird jedoch immer übergeben. Daher wird der Fehler auch durch Eingabe eines nicht numerischen Werts in Feld1 ausgelöst.

Ich probierte auch p:ajax, die sich genauso verhielten wie f:ajax.

Ich habe wirklich keine Ahnung, was hier passiert. Hoffentlich kann jemand mit mehr JSF Weisheit helfen.

+0

thx @Davy. tatsächlich in meinem Fall arbeitet es mit beiden, Hauptgesichtern und Standard-jsf-Knopf. Vielleicht könnten Sie versuchen, den Listener auf diese Weise aufzurufen '' + überschreiben 'saveState' und' restoreState'. Aber das ist nur eine Vermutung ... – fischermatte

Verwandte Themen