Skip to content

9. Przykład 07 – Tagi formularza

Stworzymy i wykorzystamy następujący formularz:

Rozszerzymy koncepcję wstrzykiwania parametrów, omówioną w przypadku pola tekstowego, na inne elementy HTML formularza.

9.1. Projekt NetBeans

  • w [1], widoki projektu [Formx.JSP]
  • w [2], plik konfiguracyjny Struts oraz komunikaty zinternacjonalizowane
  • do [3], akcje z [Formx.java] oraz kolejny plik konfiguracyjny Struts.

9.2. Konfiguracja Struts

Plik [struts.xml] ma następującą treść:


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
    "http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
  <constant name="struts.custom.i18n.resources" value="messages" />
  
  <include file="example/example.xml"/>

  <package name="default" namespace="/" extends="struts-default">
    <default-action-ref name="index" />
    <action name="index">
      <result type="redirectAction">
        <param name="actionName">Form</param>
        <param name="namespace">/example</param>
      </result>
    </action>
  </package>
</struts>
  • wiersz 9: zawiera odwołanie do innego pliku konfiguracyjnego [example.xml]. To właśnie ten plik będzie konfigurował akcje.
  • wiersze 11–19: pakiet default. Określa adres przekierowania dla URL /. Zostanie on przekierowany do URL /example/Form.action.

Plik [example.xml], który konfiguruje działania, wygląda następująco:


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
        "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
        "http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
  <package name="example" namespace="/example" extends="struts-default">
    <action name="Form" class="example.Form">
      <result name="success">/example/Form.JSP</result>
    </action>
    <action name="Form1" class="example.Form1">
      <result name="success">/example/Form1.JSP</result>
    </action>
    <action name="Form2" class="example.Form2">
      <result name="success">/example/Form2.JSP</result>
    </action>
  </package>
</struts>
  • wiersze 7–17: definiują akcje /example/Form, /example/Form1, /example/Form2, gdzie /example to przestrzeń nazw (wiersz 7), a Formx to akcje.
  • wiersze 8–10: wykonanie akcji Form spowoduje wyświetlenie widoku /example/Form.JSP.
  • wiersze 11–13: wykonanie akcji Form1 spowoduje wyświetlenie widoku /example/Form1.JSP.
  • wiersze 14–16: wykonanie akcji Form2 spowoduje wyświetlenie widoku /example/Form2.JSP.

9.3. Pliki komunikatów

Nie będziemy się tutaj zatrzymywać na kwestii internacjonalizacji projektu. Omówiliśmy ją już w przykładzie 01. Podajemy zawartość pliku [messages.properties], aby czytelnik mógł zrozumieć kolejne widoki.


Form.francais=Fran\u00E7ais
Form.anglais=Anglais
Form.titre=Struts 2 - les tags de formulaire
Form.message=Struts 2 - les tags de formulaire
Form.langues=langues
Form.textfield=1-textfield
Form.password=2-password
Form.textarea=3-textarea
Form.select1=4-select (multiple=false, size=1)
Form.select1.header=<-- select1 -->
Form.select2=5-select (multiple=false, size=3)
Form.select3=6-select (multiple=true, size=3)
Form.radio=7-radio
Form.checkbox=8-checkbox
Form.checkboxlist=9-checkboxlist
Form.hidden=10-hidden
Form.submitText=Valider
Form.buttonRazText=Raz
Confirmation.message=Confirmation des valeurs saisies
Confirmation.champ=champ
Confirmation.valeur=valeur
Confirmation.textfield=1-textfield
Confirmation.password=2-password
Confirmation.textarea=3-textarea
Confirmation.select1=4-select (multiple=false, size=1)
Confirmation.select2=5-select (multiple=false, size=3)
Confirmation.select3=6-select (multiple=true, size=3)
Confirmation.radio=7-radio
Confirmation.checkbox=8-checkbox
Confirmation.checkboxlist=9-checkboxlist
Confirmation.hidden=10-hidden

9.4. Widok [Form.JSP] – część wprowadzania danych

Wygląda to następująco:

 

Wygląda to następująco:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
  <head>
    <title><s:text name="Form.titre"/></title>
    <s:head/>
  </head>

  <body background="<s:url value="/ressources/standard.jpg"/>">
    <h2><s:text name="Form.message"/></h2>
    <h3><s:text name="Form.langues"/></h3>
    <ul>
      <li>
        <s:url id="URL" action="Form">
          <s:param name="request_locale">en</s:param>
        </s:url>
        <s:a href="%{URL}"><s:text name="Form.anglais"/></s:a>
      </li>
      <li>
        <s:url id="URL" action="Form">
          <s:param name="request_locale">fr</s:param>
        </s:url>
        <s:a href="%{URL}"><s:text name="Form.francais"/></s:a>
      </li>
    </ul>
    <s:form name="formulaire">
      <s:textfield name="textfield" key="Form.textfield" />
      <s:password name="password" key="Form.password"/>
      <s:textarea name="textarea" key="Form.textarea" cols="40" rows="5"/>
      <s:select name="select1" list="select1Values" size="1" key="Form.select1" headerValue="<-- select 1 -->" headerKey="-1" />
      <s:select name="select2" size="3" list="select2Values" key="Form.select2"/>
      <s:select name="select3" size="3" list="select3Values" key="Form.select3" multiple="true"/>
      <s:radio name="radio" list="radioValues" key="Form.radio"/>
      <s:checkbox name="checkbox" key="Form.checkbox"/>
      <s:checkboxlist name="checkboxlist" list="checkboxlistValues" key="Form.checkboxlist"/>
      <s:hidden name="hidden" key="Form.hidden"/>
      <s:submit key="Form.submitText" name="submitText"/>
    </s:form>
    <hr/>
 ...
  </body>
</html>
  • Formularz zaczyna się w wierszu 26, a kończy w wierszu 38. W wierszu 26 brakuje atrybutu action. Atrybut ten określa, do jakiej akcji zostaną przesłane dane z formularza. W przypadku braku atrybutu action dane zostaną przesłane do akcji, która doprowadziła do wyświetlenia widoku, w tym przypadku do akcji [Form].
  • wiersz 27: pole wprowadzania danych. Atrybut key określa jego etykietę. Atrybut name to nazwa parametru, który zostanie przesłany. Ciąg znaków przesłany dla tego pola będzie miał postać:
textfield=texte_saisi

Nazwa parametru odpowiada polu akcji [Form]. Wartość pola wprowadzania danych texte_saisi zostanie tutaj wstawiona do pola textfield akcji [Form]. I odwrotnie, jeśli widok [Form.JSP] zostanie wyświetlony w wyniku działania [Form], wartość pola wprowadzania danych będzie odpowiadać wartości pola textfield w działaniu [Form]. Pole to jest zatem wykorzystywane zarówno do odczytu, jak i zapisu. W tym celu klasa [Form] musi posiadać metody getTextField oraz setTextField.

To rozumowanie ma zastosowanie do wszystkich kolejnych tagów wprowadzania danych. Nie będziemy tego powtarzać.

  • wiersz 28: wprowadzenie hasła. Ciąg znaków wysyłany do akcji [Form] będzie miał postać
password=mot_de_passe_saisi

W akcji [Form] musi istnieć pole password wraz z metodami get i set.

  • wiersz 29: wprowadzenie tekstu wielowierszowego w obszarze o wymiarach 5 wierszy i 40 kolumn. Ciąg znaków wysyłany do akcji [Form] będzie miał postać
textarea=saisie

W akcji [Form] musi istnieć pole textarea wraz z metodami get i set.

  • wiersz 30: lista rozwijana (size=1) z opcją pojedynczego wyboru. Można wybrać tylko jedną wartość z listy. Ciąg znaków wysyłany do akcji [Form] będzie miał postać:
select1=valeur_sélectionnée

W akcji [Form] musi istnieć pole select1 wraz z metodami get i set.

Atrybut key to nazwa listy. Atrybut list określa listę wartości, które mają zostać umieszczone w polu kombi. W tym przypadku wywołana zostanie metoda getSelect1Values akcji [Form] w celu wypełnienia pola kombi. Metoda ta może zwracać kolekcję, słownik lub tablicę. W tym przypadku zwróci ona tablicę ciągów znaków [chaine1, chaine2, ...], która wygeneruje następujący kod HTML:


<option value="chaine1">chaine1</option>
<option value="chaine2">chaine2</option>
...

Tekst pomiędzy znacznikami <option>...</option> zostanie wyświetlony w polu rozwijanym. Atrybut value określa wartość, która ma zostać przesłana, jeśli użytkownik wybierze tę opcję. W tym przypadku atrybut value i tekst wyświetlany w polu rozwijanym są identyczne. Najczęściej jednak tak nie jest. Jeśli wybrana zostanie druga opcja, następujący ciąg znaków zostanie przesłany do akcji [Form]:

select1=chaine2

Pole [Form].select1 otrzyma wówczas wartość chaine2. I odwrotnie, jeśli w momencie wyświetlenia formularza wartość tego pola wynosi chaine3, to wizualnie trzeci element


<option value="chaine3">chaine3</option>

będzie wyglądał na zaznaczony w polu rozwijanym.

Atrybut headerValue określa tekst, który ma być wyświetlany jako pierwsza opcja listy rozwijanej, a headerKey określa wartość, która ma zostać przesłana, jeśli użytkownik wybierze tę opcję.

  • wiersz 31: ponownie lista o rozmiarze 3 (size=3): w przeciwieństwie do poprzedniej listy, na której widoczny jest tylko jeden element, tutaj widoczne będą 3 elementy listy. Sposób zaznaczania pozostaje taki sam. Można zaznaczyć tylko jeden element.
  • wiersz 32: ponownie lista, ale z możliwością wielokrotnego wyboru (multiple=true). W tym przypadku wartość przesłana do akcji [Form] będzie miała postać:
select3=chaine2&select3=chaine5

jeśli wybrano opcje chaine2 i chaine5. Gdy dla tego samego parametru, w tym przypadku select3, księgowanych jest kilka wartości, akcja musi posiadać pole o nazwie parametru i mieć typ tableau. Na przykład akcja [Form] mogłaby mieć pole o następującej postaci:

String[] select3 ;

oraz odpowiadające mu metody get / set. Tablica select3 otrzyma wówczas tablicę ciągów znaków [chaine2,chaine5]. I odwrotnie, podczas wyświetlania widoku [Form.JSP] wartości pola [Form].select3 określają, które opcje z listy select3 będą zaznaczone. Nadal mamy do czynienia z trybem odczytu/zapisu.

  • wiersz 33: grupa przycisków opcji. Nazwa grupy jest podawana przez atrybut key. Nazwy poszczególnych przycisków są podawane przez atrybut list. Wartością tego atrybutu jest nazwa metody akcji [Form], która musi zwracać kolekcję, słownik lub tablicę. W tym przypadku metoda [Form].getRadioValues zwróci tablicę ciągów znaków [chaine1,chaine2,...], która wygeneruje następujące tagi HTML:

<input type="radio" name="radio" ... value="chaine1"/>chaine1
<input type="radio" name="radio" ... value="chaine2"/>chaine2
...

Jeśli przycisk opcji chaine2 jest zaznaczony, wysyłana wartość będzie wynosić

radio=chaine2

i ta wartość zostanie umieszczona w polu [Form].radio.

  • wiersz 34: pole wyboru. Jeśli jest zaznaczone, następujący ciąg parametrów zostanie przesłany do akcji [Form]
checkbox=true

Jeśli pole to nie jest zaznaczone, żaden ciąg parametrów nie zostanie przesłany. Struts 2 posiada wewnętrzny mechanizm, dzięki któremu wszystko przebiega tak, jakby ciąg

checkbox=false

został przesłany.

  • Wiersz 35: lista pól wyboru. Można zaznaczyć wiele z nich. Nazwa grupy jest podawana przez atrybut key. Nazwy poszczególnych pól wyboru są podawane przez atrybut list. Wartością tego atrybutu jest nazwa metody akcji [Form], która musi zwracać kolekcję, słownik lub tablicę. W tym przypadku metoda [Form].getCheckboxlistValues zwróci tablicę ciągów znaków [chaine1,chaine2,...], która wygeneruje następujące tagi HTML:

<input type="checkbox" name="checkboxlist" ... value="chaine1"/>chaine1
<input type="checkbox" name="checkboxlist" ... value="chaine2"/>chaine2
...

Jeśli użytkownik zaznaczy pola o nazwach chaine2 i chaine5, następujący ciąg znaków zostanie przesłany do akcji [Form]:

checkboxlist=chaine2&checkboxlist=chaine5

Tabela [chaine2, chaine5] zostanie przypisana do pola [Form].checkboxlist, które musi zatem być typu tabela ciągów znaków, tak jak w przypadku list z możliwością wielokrotnego wyboru. Z kolei podczas wyświetlania widoku [Form.JSP] wartości pola [Form].checkboxlist określają, które pola wyboru zostaną zaznaczone.

  • wiersz 36: pole ukryte. Pole ukryte to pole, które jest przesyłane bez wprowadzania danych przez użytkownika. Jego wartość jest ustalana przez programistę. W tym przypadku pole ukryte przyjmie wartość z pola [Form].hidden. Ta sama wartość zostanie przesłana w postaci:
hidden=qqchose

i ponownie przypisana do pola [Form].hidden.

  • wiersz 37: przycisk typu submit. Kliknięcie tego przycisku spowoduje przesłanie wszystkich wartości formularza do akcji [Form]. Ciąg przesłanych parametrów będzie wyglądał następująco:
textfield=texte&password=&textarea=ligne1%0D%0Aligne2%0D%0A&select1=z%C3%A9ro&select2=trois&select3=z%C3%A9ro&select3=deux&__multiselect_select3=&radio=bleu&checkbox=true&__checkbox_checkbox=true&checkboxlist=v%C3%A9lo&checkboxlist=bus&__multiselect_checkboxlist=&hidden=initial&submitText=Valider 

9.5. Akcja [Form] – część wprowadzania danych

Jej kod jest następujący:


package example;

import com.opensymphony.xwork2.ActionSupport;

public class Form extends ActionSupport {

  // konstruktor bez parametrów
  public Form() {
  }
  // pola formularza
  private String textfield = "texte";
  private String password = "secret";
  private String textarea = "ligne1\nligne2\n";
  private String select1 = "zéro";
  private String[] select1Values = new String[]{"zéro", "un", "deux"};
  private String select2 = "trois";
  private String[] select2Values = new String[]{"zéro", "un", "deux", "trois", "quatre"};
  private String[] select3 = new String[]{"zéro", "deux"};
  private String[] select3Values = new String[]{"zéro", "un", "deux", "trois", "quatre"};
  private String radio = "bleu";
  private String[] radioValues = new String[]{"bleu", "blanc", "rouge"};
  private Boolean checkbox = false;
  private String[] checkboxlist = new String[]{"vélo", "bus"};
  private String[] checkboxlistValues = new String[]{"voiture", "tram", "vélo", "bus", "métro"};
  private String hidden = "initial";
  private String submitText;

  // wartości wybrane w polu select3
  public String getSelect3SelectedValues() {
    return getValue(select3);
  }

  // wartości wybrane w polu checkboxlist
  public String getCheckboxlistSelectedValues() {
    return getValue(checkboxlist);
  }

  // metoda pomocnicza
  public String getValue(String[] values) {
    String result = "";
    for (String value : values) {
      result += " " + value;
    }
    return result;
  }

// metody pobierające i ustawiające wartości pól
  ....
}
  • wiersz 1: pole powiązane z tagiem textfield w formularzu. Służy zarówno do zainicjowania tego pola wprowadzania danych, jak i do pobrania z niego wartości. W momencie wykonania akcji POST otrzymuje wartość wprowadzoną w tym polu.
  • wiersz 2: pole powiązane z tagiem password w formularzu. W momencie wywołania POST pobiera wartość wprowadzoną w tym polu.
  • wiersz 13: powiązany z tagiem textarea formularza. W momencie wywołania POST pobiera tekst wprowadzony w tym polu.
  • wiersz 14: powiązany z tagiem select1 formularza, lista z jednym elementem do wyboru. Jego wartość początkowa wybiera element z listy rozwijanej. Zostanie wybrany ten element, którego atrybut value jest równy tej wartości. W momencie wykonania POST otrzymuje atrybut value z opcji wybranej w polu rozwijanym.
  • wiersz 15: ciągi znaków, które będą zasilać listę rozwijaną select1.
  • wiersze 16 i 17: to samo dotyczy select1
  • wiersz 18: powiązana z tagiem select1 formularza lista wielokrotnego wyboru. Wartości początkowe w tabeli wybierają odpowiadające im elementy listy. Wybrane zostaną te elementy, których atrybut value jest równy jednemu z tych elementów. W momencie wywołania POST otrzymuje atrybuty value różnych opcji wybranych z listy rozwijanej.
  • wiersz 19: ciągi znaków, które zostaną umieszczone na liście select3
  • wiersz 20: powiązany z tagiem radio formularza. Jego wartość służy do zaznaczenia jednego z przycisków opcji, tego, którego atrybut value jest równy tej wartości. W momencie wywołania POST otrzymuje wartość zaznaczonego przycisku opcji.
  • wiersz 21: różne nazwy i wartości przycisków opcji.
  • wiersz 22: powiązany z tagiem checkbox w formularzu. Jego wartość początkowa służy do zaznaczenia lub odznaczenia pola wyboru. W momencie wywołania POST przyjmuje wartość true, jeśli pole zostało zaznaczone, a w przeciwnym razie – false.
  • wiersz 23: powiązany z tagiem checkboxlist w formularzu. Wartości początkowe w tabeli określają, które pola wyboru mają zostać zaznaczone. Zaznaczone zostaną te pola, których atrybut value jest równy jednej z tych wartości początkowych. W momencie POST otrzymuje atrybuty value z różnych zaznaczonych pól wyboru.
  • wiersz 24: ciągi znaków, które będą stanowić treść etykiet pól w checkboxlist.
  • wiersz 25: powiązany z ukrytym polem hidden. Jego wartość początkowa ustala wartość ukrytego pola. W momencie POST otrzyma tę samą wartość, ponieważ użytkownik nie ma możliwości jej zmiany.
  • wiersz 26: powiązany z tagiem submit. W momencie wywołania POST przyjmuje etykietę przycisku. Można pominąć to pole.
  • Metody w wierszach 29, 34 i 39 zostaną omówione nieco dalej.

9.6. Widok [Form.JSP] – część „Potwierdzenie”

Widok [Form.JSP] zawiera sekcję „Potwierdzenie wprowadzonych danych”, która nie została jeszcze omówiona:

 

Jej kod jest następujący:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
...
    <hr/>
    <h2><s:text name="Confirmation.message"/></h2>
    <table border="1">
      <tr>
        <th><s:text name="Confirmation.champ"/></th>
        <th><s:text name="Confirmation.valeur"/></th>
      </tr>
      <tr>
        <td><s:text name="Form.textfield"/></td>
        <td><s:property value="textfield"/>
      </tr>
      <tr>
        <td><s:text name="Form.password"/></td>
        <td><s:property value="password"/>
      </tr>
      <tr>
        <td><s:text name="Form.textarea"/></td>
        <td><s:property value="textarea"/>
      </tr>
      <tr>
        <td><s:text name="Form.select1"/></td>
        <td><s:property value="select1"/>
      </tr>
      <tr>
        <td><s:text name="Form.select2"/></td>
        <td><s:property value="select2"/>
      </tr>
      <tr>
        <td><s:text name="Form.select3"/></td>
        <td><s:property value="select3SelectedValues"/>
      </tr>
      <tr>
        <td><s:text name="Form.radio"/></td>
        <td><s:property value="radio"/>
      </tr>
      <tr>
        <td><s:text name="Form.checkbox"/></td>
        <td><s:property value="checkbox"/>
      </tr>
      <tr>
        <td><s:text name="Form.checkboxlist"/></td>
        <td><s:property value="checkboxlistSelectedValues"/>
      </tr>
      <tr>
        <td><s:text name="Form.hidden"/></td>
        <td><s:property value="hidden"/>
      </tr>
    </table>
  </body>
</html>
  • wiersz 13: wyświetla wartość pola [Form].textfield
  • wiersz 17: wyświetla wartość pola [Form].password
  • wiersz 21: wyświetla wartość pola [Form].textarea
  • wiersz 25: wyświetla wartość pola [Form].select1
  • wiersz 29: wyświetla wartość pola [Form].select2
  • wiersz 33: wyświetla wartości tablicy [Form].select3. W tym celu należy użyć metody [Form].getSelect3SelectedValues.
  • wiersz 37: wyświetla wartość pola [Form].radio
  • wiersz 41: wyświetla wartość pola [Form].checkbox
  • wiersz 45: wyświetla wartości tablicy [Form].checkboxlist. W tym celu należy użyć metody [Form].getCheckboxlistSelectedValues.
  • wiersz 49: wyświetla wartość pola [Form].hidden

9.7. Akcja [Form] – część potwierdzająca

Wymienione powyżej metody getSelect3SelectedValues i getCheckboxlistSelectedValues są zdefiniowane w klasie [Form]:


  // wartości wybrane w polu select3
  public String getSelect3SelectedValues() {
    return getValue(select3);
  }

  // wartości wybrane w polu checkboxlist
  public String getCheckboxlistSelectedValues() {
    return getValue(checkboxlist);
  }

  // metoda pomocnicza
  public String getValue(String[] values) {
    String result = "";
    for (String value : values) {
      result += " " + value;
    }
    return result;
}

Ograniczają się one do łączenia elementów tablic, które mają wyświetlić.

9.8. Testy

Po uruchomieniu projektu w NetBeans wyświetla się następująca strona startowa:

Wartości wyświetlane w [1] i [2] pochodzą z wartości początkowych pól akcji [Form]. Rozważmy kilka przykładów:


private String textarea = "ligne1\nligne2\n";

Wartość pola textarea wyjaśnia wyświetlane wartości w [1A] i [2A].


private String[] select3 = new String[]{"zéro", "deux"};
private String[] select3Values = new String[]{"zéro", "un", "deux", "trois", "quatre"};

Wartość pola select3Values wyjaśnia zawartość listy w [1B]. Wartość pola select3 wyjaśnia elementy wybrane w polu [1B] i wyświetlone w polu [2B].

Jeśli wprowadzimy inne wartości w formularzu i zatwierdzimy go, wprowadzone wartości zostaną zapisane w odpowiednich polach akcji [Form]. Następnie ponownie wyświetli się widok [Form.JSP]. Mamy więc do czynienia z tą samą sytuacją, co poprzednio, z tą różnicą, że wartości początkowe zostały zastąpione wartościami przesłanymi. Oto przykład:

9.9. Akcja [Form1] i widok [Form1.JSP]

Plik konfiguracyjny [example.xml] konfiguruje następującą akcję [Form1]:


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
        "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
        "http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
  <package name="example" namespace="/example" extends="struts-default">
    <action name="Form" class="example.Form">
      <result name="success">/example/Form.JSP</result>
    </action>
    <action name="Form1" class="example.Form1">
      <result name="success">/example/Form1.JSP</result>
    </action>
    <action name="Form2" class="example.Form2">
      <result name="success">/example/Form2.JSP</result>
    </action>
  </package>
</struts>

W wierszach 11–13 wywołanie akcji [Form1] generuje widok [Form1.JSP].

Akcja [Form1] wygląda następująco:


package example;

import com.opensymphony.xwork2.ActionSupport;

public class Form1 extends ActionSupport{

  // konstruktor bez parametrów
  public Form1() {
  }
  // pola formularza
  private Data data=new Data();

  /**
   * @return the data
   */
  public Data getData() {
    return data;
  }

  /**
   * @param data the data to set
   */
  public void setData(Data data) {
    this.data = data;
  }
}

Akcja [Form1] jest identyczna z akcją [Form], z tą różnicą, że jej część szablonowa została przeniesiona do klasy pomocniczej, klasy [Data], wiersz 11. Klasa [Data] ma następującą postać:


package example;

public class Data {

  // konstruktor bez parametrów
  public Data() {
  }
  
// pola formularza
  private String textfield = "texte";
  private String password = "secret";
  private String textarea = "ligne1\nligne2\n";
  private String select1 = "zéro";
  private String[] select1Values = new String[]{"zéro", "un", "deux"};
  private String select2 = "trois";
  private String[] select2Values = new String[]{"zéro", "un", "deux", "trois", "quatre"};
  private String[] select3 = new String[]{"zéro", "deux"};
  private String[] select3Values = new String[]{"zéro", "un", "deux", "trois", "quatre"};
  private String radio="bleu";
  private String[] radioValues = new String[]{"bleu", "blanc", "rouge"};
  private Boolean checkbox = false;
  private String[] checkboxlist = new String[]{"vélo", "bus"};
  private String[] checkboxlistValues = new String[]{"voiture", "tram", "vélo", "bus", "métro"};
  private String hidden = "initial";
  private String submitText;

  // wartości wybrane w polu select3
  public String getSelect3SelectedValues() {
    return getValue(select3);
  }

  // wartości wybrane w polu checkboxlist
  public String getCheckboxlistSelectedValues() {
    return getValue(checkboxlist);
  }

  // metoda pomocnicza
  public String getValue(String[] values) {
    String result = "";
    for (String value : values) {
      result += " " + value;
    }
    return result;
  }

  // metody pobierające i ustawiające
....

}

W akcji [Form] odnajdujemy pola zadeklarowane wcześniej.

Co jest dość logiczne, widok [Form1.JSP] jest zbliżony do widoku [Form.JSP]:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
  <head>
    <title><s:text name="Form.titre"/></title>
    <s:head/>
  </head>

  <body background="<s:url value="/ressources/standard.jpg"/>">
    <h2><s:text name="Form.message"/></h2>
    <h3><s:text name="Form.langues"/></h3>
    <ul>
      <li>
        <s:url id="URL" action="Form">
          <s:param name="request_locale">en</s:param>
        </s:url>
        <s:a href="%{URL}"><s:text name="Form.anglais"/></s:a>
      </li>
      <li>
        <s:url id="URL" action="Form">
          <s:param name="request_locale">fr</s:param>
        </s:url>
        <s:a href="%{URL}"><s:text name="Form.francais"/></s:a>
      </li>
    </ul>
    <s:form name="formulaire">
      <s:textfield name="data.textfield" key="Form.textfield" />
      <s:password name="data.password" key="Form.password"/>
      <s:textarea name="data.textarea" key="Form.textarea" cols="40" rows="5"/>
      <s:select name="data.select1" list="data.select1Values" size="1" key="Form.select1" headerValue="<-- select 1 -->" headerKey="-1" />
      <s:select name="data.select2" size="3" list="data.select2Values" key="Form.select2"/>
      <s:select name="data.select3" size="3" list="data.select3Values" key="Form.select3" multiple="true"/>
      <s:radio name="data.radio" list="data.radioValues" key="Form.radio"/>
      <s:checkbox name="data.checkbox" key="Form.checkbox"/>
      <s:checkboxlist name="data.checkboxlist" list="data.checkboxlistValues" key="Form.checkboxlist"/>
      <s:hidden name="data.hidden" key="Form.hidden"/>
      <s:submit key="Form.submitText" name="data.submitText"/>
    </s:form>
    <hr/>
    <h2><s:text name="Confirmation.message"/></h2>
    <table border="1">
      <tr>
        <th><s:text name="Confirmation.champ"/></th>
        <th><s:text name="Confirmation.valeur"/></th>
      </tr>
      <tr>
        <td><s:text name="Form.textfield"/></td>
        <td><s:property value="data.textfield"/>
      </tr>
      <tr>
        <td><s:text name="Form.password"/></td>
        <td><s:property value="data.password"/>
      </tr>
      <tr>
        <td><s:text name="Form.textarea"/></td>
        <td><s:property value="data.textarea"/>
      </tr>
      <tr>
        <td><s:text name="Form.select1"/></td>
        <td><s:property value="data.select1"/>
      </tr>
      <tr>
        <td><s:text name="Form.select2"/></td>
        <td><s:property value="data.select2"/>
      </tr>
      <tr>
        <td><s:text name="Form.select3"/></td>
        <td><s:property value="data.select3SelectedValues"/>
      </tr>
      <tr>
        <td><s:text name="Form.radio"/></td>
        <td><s:property value="data.radio"/>
      </tr>
      <tr>
        <td><s:text name="Form.checkbox"/></td>
        <td><s:property value="data.checkbox"/>
      </tr>
      <tr>
        <td><s:text name="Form.checkboxlist"/></td>
        <td><s:property value="data.checkboxlistSelectedValues"/>
      </tr>
      <tr>
        <td><s:text name="Form.hidden"/></td>
        <td><s:property value="data.hidden"/>
      </tr>
    </table>
  </body>
</html>

Różnicę między widokami [Form] i [Form1] można zilustrować na przykładzie wiersza 27 przedstawionego poniżej:


<s:textfield name="data.textfield" key="Form.textfield" />

Pole wprowadzania danych jest powiązane w trybie odczytu i zapisu z polem data.textfield akcji [Form1]. Przypominamy, że po wykonaniu akcji właściwości akcji [Form1] są dostępne dla widoku. W związku z tym poprzedni atrybut name zostanie obliczony jako [Form1].getData().getTextField(). W związku z tym muszą istnieć dwie poprzednie metody o nazwie get.

9.10. Akcja [Form2] i widok [Form2.JSP]

Plik konfiguracyjny [example.xml] konfiguruje następującą akcję [Form2]:


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
        "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
        "http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
  <package name="example" namespace="/example" extends="struts-default">
    <action name="Form" class="example.Form">
      <result name="success">/example/Form.JSP</result>
    </action>
    <action name="Form1" class="example.Form1">
      <result name="success">/example/Form1.JSP</result>
    </action>
    <action name="Form2" class="example.Form2">
      <result name="success">/example/Form2.JSP</result>
    </action>
  </package>
</struts>

W wierszach 14–16 wywołanie akcji [Form2] generuje widok [Form2.JSP].

Akcja [Form2] wygląda następująco:


package example;

import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.ModelDriven;

public class Form2 extends ActionSupport implements ModelDriven {

  // konstruktor bez parametrów
  public Form2() {
  }
  // szablon akcji
  public Object getModel() {
    return new Data();
  }
}
  • wiersz 6: akcja [Form2] implementuje interfejs [ModelDriven]. Należy zaimplementować tylko jedną metodę – metodę getModel z wiersza 12. Metoda ta zwraca instancję klasy [Data], którą omówiono wcześniej.

Fakt, że akcja [Form2] implementuje interfejs [ModelDriven], ma następującą konsekwencję: widok wyświetlany w wyniku akcji [Form2] ma bezpośredni dostęp do właściwości modelu akcji [Form2], który jest zwracany przez metodę getModel(). W związku z tym widok [Form2.JSP] powraca do stanu, w jakim znajdował się przy początkowej akcji [Form]:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
  <head>
    <title><s:text name="Form.titre"/></title>
    <s:head/>
  </head>

  <body background="<s:url value="/ressources/standard.jpg"/>">
    <h2><s:text name="Form.message"/></h2>
    <h3><s:text name="Form.langues"/></h3>
    <ul>
      <li>
        <s:url id="URL" action="Form">
          <s:param name="request_locale">en</s:param>
        </s:url>
        <s:a href="%{URL}"><s:text name="Form.anglais"/></s:a>
      </li>
      <li>
        <s:url id="URL" action="Form">
          <s:param name="request_locale">fr</s:param>
        </s:url>
        <s:a href="%{URL}"><s:text name="Form.francais"/></s:a>
      </li>
    </ul>
    <s:form name="formulaire">
      <s:textfield name="textfield" key="Form.textfield" />
      <s:password name="password" key="Form.password"/>
      <s:textarea name="textarea" key="Form.textarea" cols="40" rows="5"/>
      <s:select name="select1" list="select1Values" size="1" key="Form.select1" headerValue="<-- select 1 -->" headerKey="-1" />
      <s:select name="select2" size="3" list="select2Values" key="Form.select2"/>
      <s:select name="select3" size="3" list="select3Values" key="Form.select3" multiple="true"/>
      <s:radio name="radio" list="radioValues" key="Form.radio"/>
      <s:checkbox name="checkbox" key="Form.checkbox"/>
      <s:checkboxlist name="checkboxlist" list="checkboxlistValues" key="Form.checkboxlist"/>
      <s:hidden name="hidden" key="Form.hidden"/>
      <s:submit key="Form.submitText" name="submitText"/>
    </s:form>
    <hr/>
    <h2><s:text name="Confirmation.message"/></h2>
    <table border="1">
      <tr>
        <th><s:text name="Confirmation.champ"/></th>
        <th><s:text name="Confirmation.valeur"/></th>
      </tr>
      <tr>
        <td><s:text name="Form.textfield"/></td>
        <td><s:property value="textfield"/>
      </tr>
      <tr>
        <td><s:text name="Form.password"/></td>
        <td><s:property value="password"/>
      </tr>
      <tr>
        <td><s:text name="Form.textarea"/></td>
        <td><s:property value="textarea"/>
      </tr>
      <tr>
        <td><s:text name="Form.select1"/></td>
        <td><s:property value="select1"/>
      </tr>
      <tr>
        <td><s:text name="Form.select2"/></td>
        <td><s:property value="select2"/>
      </tr>
      <tr>
        <td><s:text name="Form.select3"/></td>
        <td><s:property value="select3SelectedValues"/>
      </tr>
      <tr>
        <td><s:text name="Form.radio"/></td>
        <td><s:property value="radio"/>
      </tr>
      <tr>
        <td><s:text name="Form.checkbox"/></td>
        <td><s:property value="checkbox"/>
      </tr>
      <tr>
        <td><s:text name="Form.checkboxlist"/></td>
        <td><s:property value="checkboxlistSelectedValues"/>
      </tr>
      <tr>
        <td><s:text name="Form.hidden"/></td>
        <td><s:property value="hidden"/>
      </tr>
    </table>
  </body>
</html>

W ten sposób wiersz 27, który wcześniej brzmiał:


<s:textfield name="data.textfield" key="Form.textfield" />

ponownie przybiera postać:


<s:textfield name="textfield" key="Form.textfield" />

Zaletą umieszczenia modelu poza widokiem jest to, że taka architektura wyraźniej wyznacza funkcje każdego elementu architektury MVC. Model M jest jednoznacznie identyfikowany przez klasę. Ponadto model może istnieć przed akcjami. Jeśli więc aplikacja internetowa zarządza bazą osób, prawdopodobnie istnieje klasa Personne. Jeśli akcja udostępnia formularz do wprowadzenia danych w celu utworzenia nowej osoby, model tej akcji jest oczywisty: jest to klasa Personne.

Widzieliśmy, jak łatwo było przejść od akcji [Form] do akcji [Form2]:

  • szablon M widoku V został zamknięty w osobnej klasie
  • akcja generująca widok wykorzystujący ten model implementuje interfejs ModelDriven wraz z metodą getModel, która zwraca instancję modelu M. Akcję tę można zatem postrzegać jako rozszerzenie kontrolera C w Struts 2, czyli klasy FilterDispatcher.

Czytelnik będzie mógł zastosować to podejście do każdej akcji opisanej poniżej.