# Scopes - Daten in JSF-Formular anlegen/bearbeiten, Felder vorbelegen



## Tatsu (31. Mrz 2011)

Hallo,
ich möchte ein JSF-Formular benutzen, um einen neuen Datensatz anzulegen oder einen bestehenden anzuzeigen/ zu bearbeiten. Wird ein Datensatz neu angelegt, so sollen einige Felder des Formulars mit Werten vorbelegt werden. Ich möchte dazu Java EE 6 Techiken (JSF, CDI, EJB, JPA) verwenden, also kein Seam, Spring, etc.

*Szenario:*

Ein JSF-Formular soll zum Anlegen, bzw. Bearbeiten eines Datensatzes verwendet werden.
Wird ein neuer Datensatz angelegt, so sollen einige Formularfelder mit Werten vorbelegt werden.
Ändert man einige dieser Werte und kann das Formular nicht gespeichert werden (z.B. wegen fehlerhafter Eingabe) so sollen die eingetragenen Werte erhalten bleiben.
Wird ein bestehender Datensatz aufgerufen, so sollen die Werte aus der Datenbank angezeigt werden. Die Methode zum Vorbelegen der Felder soll also nicht ausgeführt werden.

Ich habe die Annotationen [c]@ManagedBean[/c] in Verbindung mit [c]@RequestScoped[/c] und [c]@SessionScoped[/c] verwendet und bin mit beiden nicht zum Ziel gekommen. Für die Vorbelegung der Felder benutze ich eine Methode die ich mit [c]@PostConstruct[/c] annotiere.

*Konkrete Fragestellung:*

Wie setzt man sowas in EE 6 um (Best Practice)?
Muss man dafür [c]@ConversationScoped[/c] verwenden, oder macht man das besser anders?

*Aufgetretene Probleme mit den Annotationen:*
@ViewScoped
Mit @ViewScoped funktioniert alles richtig beim Anlegen eines neuen Datensatzes. Die mit @PostConstruct annotierten Methode wird beim ersten Aufruf des Formulars aufgerufen und die Felder werden korrekt mit Werten vorbelegt. Ändert man nun einige Werte so bleiben diese auch erhalten, wenn das Formular mehrfach wieder aufgerufen wird (z.B. bei Fehleingaben). 

Wenn ich allerdings in einer Liste einer anderen JSF-Seite einen Datensatz zum Bearbeiten auswähle, dann werden die Daten nicht in das Formular geladen, da ich ja den Seitenkontext gewechselt habe. Das Objekt wird also neu erzeugt. Statt den ausgewählten Datensatz anzuzeigen, wird ein neues Objekt erstellt.

@RequestScoped
Also habe ich es mit der Annotation @RequestScoped versucht. Nun werden die Daten korrekt im Formular angezeigt, wenn ich in der Liste einen Datensatz zum Bearbeiten ausgewählt habe.

Ändere ich nun allerdings einige dieser Werte und es treten Fehler beim Prüfen des Formulars auf (z.B. bei Fehleingaben) wird die mit @PostConstruct annotierte Methode aufgerufen. Diese füllt dann die Felder mit den Standardwerten, was ja nicht sein soll.

Ich bin ratlos wie ich das korrekt umsetzen soll und bin übrigens Java EE Newbie.

Auszug Formular [license.xhtml]:
[XML]
...
                        <hutputLabel value="Erstellt am" for="createdAt" />
                        <h:inputText id="createdAt" value="#{licenseController.license.createdAt}" readonly="true" >
                            <f:convertDateTime type="date"/>
                        </h:inputText>
                        <p:message for="createdAt" />

                        <hutputLabel value="Erstellt von" for="createdBy" />
                        <h:inputText id="createdBy" value="#{licenseController.license.createdBy}" readonly="true" />
                        <p:message for="createdBy" />

                    </hanelGrid>
                </p:fieldset>

                <p:commandButton action="#{licenseController.doCreateLicense}" value="Speichern" update="form" />
[/XML]

Auszug Liste (Edit-Button) [licenses.xhtml]:
[XML]
<p:column id="editButton">
    <p:commandButton id="edit" action="#{licenseController.doEditLicense}" ajax="false" image="ui-icon ui-icon-search">
        <f:setPropertyActionListener value="#{license}" target="#{licenseController.selectedLicense}" /> 
    </p:commandButton>
</p:column>
[/XML]

Auszug LicenseController.java:

```
@ManagedBean
@RequestScoped
public class LicenseController implements Serializable {

    @EJB
    private LicenseEJB licenseEJB;
    private License license = new License();
    private List<License> licenseList = new ArrayList<License>();
    private License selectedLicense;

    @PostConstruct
    public void doConstruct() {
        // DS für Tabelle auslesen
        this.licenseList = licenseEJB.findAll();
        // Formularfelder vorbelegen
        this.license.setRegKey(getRandomKey());
        this.license.setCreatedAt(new Date());
        this.license.setCreatedBy("Captain Kirk");
        this.license.setMaxLicenses(1);
    }

    public String doCreateLicense() {
        this.license = licenseEJB.createLicense(license);
        return "licenses.xhtml?faces-redirect=true";
    }

    public String doEditLicense() {
        this.license = this.selectedLicense;
        return "license";
    }
// ... getter und setter
}
```

Vielen Dank im Voraus für Eure Tipps.

Bastian


----------



## Tatsu (5. Apr 2011)

Hallo alle zusammen,

ich habe jetzt noch mal einige Tage lang gegoogelt, in meinen Büchern nachgeschaut und Beispiele ausprobiert. In den Beispielen wird meist SessionScoped verwendet. Das kann aber eigentlich doch nicht richtig sein, oder?

Ich vermute, das bei Formularen in den meisten Fällen @ViewScoped richtig sein sollte. Beim Anlegen von Daten funktioniert das auch super. Ich bekomme es nur einfach nicht hin, Daten zum Bearbeiten in ein Formular zu laden.

Kann mir jemand von Euch vielleicht ein kleines Beispiel mit einer Managed Bean oder CDI geben, oder einen Tip der mich auf den richtigen Weg bringt?

Ich wäre Euch sehr dankbar, da ich mich seit Tagen im Kreis drehe und überhaupt nicht voran komme.

Die Varianten die ich umsetzen möchte:
1. Neuer Datensatz -> Formular mit einigen vorbelegten Feldern (Standardwerte) zum Anlegen
2. Liste von Datensätzen -> Mausklick auf einen Datensatz lädt -> Formular mit dem Datensatz zum Editieren

Bastian (Java EE Newbie)


----------



## y0dA (6. Apr 2011)

Da ich aktuell nur mit JSF 1.2 inkl. Richfaces arbeite kann ich dir nur bedingt weiterhelfen, dein Problem ist einfach dass wenn du auf der Seite ein submit absetzt die Seite neu geladen wird und hierbei eben die dazugehörige ManagedBean neu gebaut.

Mit Richfaces kann man das Problem mit dem Tag

```
<a4j:keepAlive beanName="managedBean" />
```
umgehen - damit bleibt die Bean so lange "am Leben" bis die Seite verlassen wird.
Alternative wäre noch dass du manuell dein License Objekt in die Session schreibst und aus der Session wieder löschst wenn du die Seite verlässt bzw das Objekt nicht mehr benötigst. Hierbei kannst du dann bequem das Objekt aus der Session holen in der @PostConstruct Methode der ManagedBean.


----------



## Tatsu (27. Apr 2011)

Ich zeige im Folgenden zwei Lösungen für das von mir in diesem Thread beschriebene Problem auf. 


Lösung mit JSF-Boardmitteln
Lösung mit PrettyFaces

Sollte Euch die Lösung gefallen, bzw. weiterhelfen, so würde ich mich über einen _Danke-Klick_ freuen. Es wäre schön, wenn Ihr _Verbesserungsvorschläge_ machen, bzw. Ideen einbringen würdet, wie man das Ganze evtl. noch besser umsetzen könnte.​

*Einleitung:*
Möchte man mit [c]@ViewScoped[/c] annotierten [c]ManagedBeans[/c] arbeiten, so ergibt sich das Problem, dass die ManagedBean zerstört wird, sobald man den Seitenkontext verlässt.

Möchte man z.B die eindeutige [c]ID[/c] eines Datensatzes auf die nächste JSF-Seite retten, so stellt sich die Frage, wie man dies am besten umsetzt.

Szenario:

```
┏━━━━━━━━━━━━━━━━━━━━━━━┓                       ┏━━━━━━━━━━━━━━━━┓
┃Seite-> personen.xhtml                ┃                       ┃Seite-> person.xhtml       ┃
┃┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┃ Bei Klick auf Button  ┃Vorname:    ┆J.T.         ┃
┃Max      ┆Mustermann   ┆ [Anzeigen]  ┃ Person anzeigen       ┃Nachname:   ┆Kirk         ┃
┃Gabi     ┆Glorreich    ┆ [Anzeigen]  ┃ =====================>┃Geburtstag: ┆01.01.2011   ┃
┃J.T.     ┆Kirk         ┆ [Anzeigen]  ┃                       ┃Geburtsort: ┆Erde         ┃
┗━━━━━━━━━━━━━━━━━━━━━━━┛                       ┗━━━━━━━━━━━━━━━━┛
                                        ║                        ║
                                      ┏━━━━━━━━━━━━━━━━┓
                                      ┃ PersonController
                                      ┃ @ManagedBean
                                      ┃ @ViewScoped
                                      ┗━━━━━━━━━━━━━━━━┛
```

*Lösung mit JSF-Boardmitteln:*
Eine Möglichkeit dazu stellt JSF selbst zur Verfügung. Benutzt man den Tag [c]<f:viewParam />[/c] auf der Zielseite [c]person.xhtml[/c], so kann man den benötigten Wert einfach übergeben und dann im Controller auslesen. Die entsprechende Methode kann man auch gleich in der Seite mit [c]<f:event type="javax.faces.event.PreRenderViewEvent" ... />[/c] angeben.

Auszug person.xhtml

```
<f:metadata>
  <f:viewParam name="id" value="#{personController.id}"/>
  <f:event type="javax.faces.event.PreRenderViewEvent" 
           listener="#{personController.prepareForm}"/>
</f:metadata>
```

Auszug personController.java

```
public void prepareForm(ComponentSystemEvent ev) {
        FacesContext ctx = FacesContext.getCurrentInstance();
        if (!ctx.isValidationFailed()) {
            if (id != 0) {
                // Find record by id
                this.person = personEJB.findById(id);
            } else {
                // Set default values for the new record
            }
        }
    }
```

*Lösung mit PrettyFaces:*
Löst man das Ganze mit PrettyFaces so ergibt sich ganz nebenbei noch der Vorteil, dass URls ähnlich wie beim Einsatz einer REST-Architektur genutzt werden können. Der Aufruf der URL [c]/editor/person/50[/c] führt dann dazu, dass die Person mit der id 50 angezeigt wird.



Funktion|Seite|URI|PrettyFaces-Annotation
Personenliste anzeigen|/editor/personen.xhml|/editor/personen|pattern = "/editor/personen/", viewId = "/editor/personen.xhtml"
Person anlegen|/editor/person.xhtml|/editor/person|pattern = "/editor/person/", viewId = "/editor/person.xhtml"
Person anzeigen|/editor/person.xhtml|/editor/person/20|pattern = "/editor/person/#{ id : personController.id }" ...

Wie im letzten Mapping ersichtlich besteht die Möglichkeit Variablen anzugeben die in Attribute der Bean geschrieben werden sollen. Diese Möglichkeit wird dazu genutzt, um die ID an die Bean zu übergeben.

*Auszug personController.java*

```
@ManagedBean
@ViewScoped
@URLMappings(mappings = {
    @URLMapping(id = "listPersons", pattern = "/editor/personen/", viewId = "/editor/personen.xhtml"),
    @URLMapping(id = "newPerson", pattern = "/editor/person/", viewId = "/editor/person.xhtml"),
    @URLMapping(id = "editPerson", pattern = "/editor/person/#{ id : personController.id }", viewId = "/editor/person.xhtml")
})
public class PersonController implements Serializable {
...
}
```

Zum Schluss wird eine Methode mit [c]@URLAction(mappingId = "editPerson", onPostback = false)[/c] annotiert, um den Datensatz der gewünschten Person zu laden.

*Auszug personController.java*

```
@URLAction(mappingId = "editPerson", onPostback = false)
    public String loadEntity() {
        int id = 0;

        FacesContext ctx = FacesContext.getCurrentInstance();
        if (!ctx.isValidationFailed()) {
            // Convert String to integer
            if (rid != null) {
                try {
                    id = Integer.parseInt(rid);
                } catch (Exception E) {
                    // Redirect to create context if rid is not a number
                    ctx.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, "Identifier must be a number.", "Identifier must be a number. Example: 1435"));
                    return "<seiteA>"
                }
            }
            if (id != 0) {
                // Redirect to create context if there is no record with the current rid
                if (getEJB().findById(id) == null) {
                    ctx.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_WARN, "There is no record with the id " + id + ".", ""));
                    return "<seiteA>";
                } else {
                    // Get Record
                    this.entity = (T) getEJB().findById(id);
                }
            } else {
                return "<SeiteA>";
            }
        }
        return null;
}
```

Bei der hier vorgestellten Lösung macht es Sinn, die Bean von einer generischen abstrakten Bean abzuleiten, um sich die Tipparbeit nur einmal zu machen.

Ich hoffe dieser Artikel hilft anderen die das gleiche Problem haben weiter. Über einen Danke-Klick würde ich mich freuen.

Grüße Bastian


----------

