Cvičení Stripes: Porovnání verzí

Z FI WIKI
Přejít na: navigace, hledání
(Přijímání a kontrola dat z formuláře)
(Samostatné cvičení)
Řádka 430: Řádka 430:
 
</java>
 
</java>
  
==== Samostatné cvičení ====
+
==== Mazání ====
  
zkuste v tabulce položek přidat odkazy na mazání položek
+
Do index.jsp do tabulky se záznamy je třeba přidat odkaz, který specifikuje volanou metodu a parametr:
 +
<xml>
 +
<td><s:link href="/zaznam.action" event="smaz"><s:param name="zaznam.id"value="${z.id}"/>smazat</s:link></td>
 +
</xml>
 +
a do ActionBeanu je třeba přidat metodu:
 +
<java>
 +
    public Resolution smaz() {
 +
        ListIterator<Zaznam> listIterator = getZaznamy().listIterator();
 +
        while(listIterator.hasNext()) {
 +
            if(listIterator.next().getId()==zaznam.getId()) {
 +
                listIterator.remove();
 +
                break;
 +
            }
 +
        }
 +
        return new RedirectResolution("/index.jsp");
 +
    }
 +
</java>
 +
 
 +
Kód využívá toho, že když Stripes obdrží od prohlížeče parametr s tečkami v názvu, pokusí se
 +
ho převést na volání metod. V tomto případě tedy parametr '''zaznam.id''' způsobí
 +
zavolání <code>getZaznam()</code>, které vrátí null (je to nový request od prohlížeče
 +
a tudíž nová instance ActionBeanu), takže Stripes vytvoří instanci třídy <code>Zaznam</code>,
 +
nastaví ji do proměnné <code>zaznam</code> a pak zavolá <code>getZaznam().setId(id)</code>
 +
kde <code>id</code> bude číslo vzniklé převodem hodnoty parametru.
  
 
=== Editování ===
 
=== Editování ===

Verze z 25. 9. 2008, 17:22


Příprava prostředí

Vytvořte si novou webovou aplikaci jménem WebCviceni5, zvolte server Apache TomCat 6.0.13 a verzi JavaEE 5.


Přidejte knihovny rámce Stripes. Klikněte pravým tlačítkem ve stromu projektu na Libraries - Add Jar/Folder. Zvolte všechny soubory v adresáři

/packages/run/idea-7.0/stripes-1.5/lib

tj.

commons-logging.jar
cos.jar
log4j-1.2.13.jar
stripes.jar

nebo si stáhněte instalaci Stripes z http://www.stripesframework.org/display/stripes/Download

Přidejte knihovny JSTL 1.1. Klikněte pravým tlačítkem na Libraries - Add Library, vyberte položku JSTL 1.1 a přidejte ji.

Zkuste aplikaci spustit, ať vidíte, že jede.

Zajistíme jednotný vzhled stránek

Teď zjistíme, jak se zajištuje jednotný vzhled stránek aplikace.

Vytvořte JSP stránku rozvrh.jsp (klikněte pravým tlačítkem na Web Pages, vyberte New - JSP, zadejte jméno rozvrh. Smažte její obsah a nahraďte ho tímto:

 
<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8" %>
<%@ taglib prefix="s" uri="http://stripes.sourceforge.net/stripes.tld" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="f" uri="http://java.sun.com/jsp/jstl/fmt" %>
 
<s:layout-definition>
<html>
<head>
  <title><c:out value="${nadpis}" /></title>
  <style type="text/css">
      input.error { background-color: yellow; }
      body { font-family: Helvetica, sans-serif; background-color: azure;}
      h1 { text-align: center; text-decoration: underline; text-shadow: aquamarine; text-transform: capitalize; }
      /*  http://www.alistapart.com/articles/taminglists/ */
     #navigace { width: 120px; border-right: 1px solid #000; padding: 0 0 1em 0; margin-bottom: 1em;
               background-color: #90bade;  color: #333;  }
     #navigace ul { list-style: none;  margin: 0;  padding: 0;  border: none; }
     #navigace li { border-bottom: 1px solid #90bade;  margin: 0; }
     #navigace li a { display: block; padding: 5px 5px 5px 0.5em; border-left: 10px solid #1958b7;
         border-right: 10px solid #508fc4; background-color: #2175bc;  color: #fff; text-decoration: none; width: 100%; }
      html>body #navigace li a { width: auto; }
      #navigace li a:hover { border-left: 10px solid #1c64d1;  border-right: 10px solid #5ba3e0;
          background-color: #2586d7;  color: #fff; }
      /*  http://realworldstyle.com/2col.html */
      #navigace { width: 120px; float: left; margin-left: -1px; }
      #obsah { padding: 10px;  margin-left: 130px; }
  </style>
    <s:layout-component name="hlavicka"/>
</head>
<body>
   <h1><c:out value="${nadpis}" /></h1>
   <div id="navigace">
     <ul>
       <li><s:link href="/index.jsp">Domů</s:link></li>
       <li><s:link href="/praha.jsp">do Prahy</s:link></li>
       <li><s:link href="/podoli.jsp">do Podolí</s:link></li>
       <li><s:link href="/lekarna.jsp">do lékárny</s:link></li>  
     </ul>
   </div>
   <div id="obsah">
       
       <s:layout-component name="telo"/>
       
    </div>
</body>
</html>
 
</s:layout-definition>

Tím jsme vytvořili šablonu rozvržení všech stránek, s nadpisem, navigačním menu a místem pro doplnění obsahu jednotlivých stránek.

Nyní změňte obsah stránky index.jsp na následující:

 
<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="f" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="s" uri="http://stripes.sourceforge.net/stripes.tld" %>
 
<s:layout-render name="/rozvrh.jsp" nadpis="Hlavní stránka">
    <s:layout-component name="telo">
 
        Tady zatím nic není. Ale je to úvodní stránka aplikace.
 
    </s:layout-component>
</s:layout-render>

Je vidět, že stránka index.jsp se odkazuje na stránku rozvrh.jsp, a její oblast (tag s:layout-component) jménem telo nahrazuje vlastním obsahem. Zároveň definuje atribut nadpis, který je v rozvrh.jsp použit pro titul stránky a nadpis. Spusťte aplikaci.

samostatné cvičení

Vytvořte několik dalších JSP stránek využívajících stejný vzhled a přidejte je do navigačního menu.

Úloha pro pokročilé: změňte index.jsp tak, aby se přidal další obsah hlavičky stránky, ne do těla. Například další definici CSS nebo JavaScript.

Použití Stripes

Konfigurace

Nejdřív je nutné zkopírovat soubor s lokalizovanými chybovými hlášeními Stripes. Na příkazovém řádku se přepněte do adresáře s třídami a zkopírujte ho, tj.

cd ~/NetBeansProjects/WebApplication5/src/java
cp /packages/run/idea-7.0/stripes-1.5/lib/StripesResources.properties .

V NetBeans se soubor objeví v Source packages - default package. Přidejte mu českou a anglickou variantu stejně jako v minulém cvičení (Add Locale - Language Code).


Otevřete soubor web.xml, a změňte ho na:

 
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" 
xmlns="http://java.sun.com/xml/ns/javaee" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
 
    <!-- JSTL fmt: tagy pouziji lokalizovane texty  -->
    <context-param>
      <param-name>javax.servlet.jsp.jstl.fmt.localizationContext</param-name>
      <param-value>StripesResources</param-value>
    </context-param>
 
    <filter>
        <display-name>Stripes Filter</display-name>
        <filter-name>StripesFilter</filter-name>
        <filter-class>net.sourceforge.stripes.controller.StripesFilter</filter-class>
        <init-param>
            <!-- kde hleda action beans -->
            <param-name>ActionResolver.Packages</param-name>
            <param-value>cz.muni.fi.pa165.cv5</param-value>
        </init-param>
        <init-param>
            <!-- jaka locale aplikace podporuje -->
            <param-name>LocalePicker.Locales</param-name>
            <param-value>cs:utf-8,en:utf-8</param-value>
        </init-param>
    </filter>
 
    <filter-mapping>
        <filter-name>StripesFilter</filter-name>
        <url-pattern>*.jsp</url-pattern>
        <dispatcher>REQUEST</dispatcher>
    </filter-mapping>
 
    <filter-mapping>
        <filter-name>StripesFilter</filter-name>
        <servlet-name>StripesDispatcher</servlet-name>
        <dispatcher>REQUEST</dispatcher>
    </filter-mapping>
 
    <servlet>
        <servlet-name>StripesDispatcher</servlet-name>
        <servlet-class>net.sourceforge.stripes.controller.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
 
    <servlet-mapping>
        <servlet-name>StripesDispatcher</servlet-name>
        <url-pattern>*.action</url-pattern>
    </servlet-mapping>
</web-app>


Ještě je vhodné vytvořit soubor log4j.xml s konfigurací logování, abychom viděli, co se děje. Pravým tlačítkem myši na default package dejte New - XML - XML document

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/'>
    <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%d [%t] %-5p %c - %m%n"/>
        </layout>
    </appender>
    <category name="org.apache.log4j.xml">
        <priority value="info"/>
    </category>
    <category name="net.sourceforge.stripes">
            <priority value="debug"/>
    </category>
    <root>
        <priority value="info"/>
        <appender-ref ref="STDOUT"/>
    </root>
</log4j:configuration>

Přijímání a kontrola dat z formuláře

Nejdříve vytvoříme nějakou třídu představující data, normální JavaBean. Nezapomeňte vytvořit balík cz.muni.fi.pa165.cv5, do kterého budeme třídy umísťovat.

 
package cz.muni.fi.pa165.cv5;
 
public class Zaznam {
    
    public enum Barva { ZELENA, MODRA, CERVENA }
 
    private static long counter;
 
    private long id = counter++;
    private String jmeno;
    private int pocet;
    private boolean vybran;
    private Barva barva;
 
    public long getId() { return id; }
    public void setId(long id) { this.id = id; }
    public String getJmeno() { return jmeno;  }
    public void setJmeno(String jmeno) { this.jmeno = jmeno; }
    public int getPocet() { return pocet; }
    public void setPocet(int pocet) { this.pocet = pocet; }
    public boolean isVybran() { return vybran; }
    public void setVybran(boolean vybran) { this.vybran = vybran; }
    public Barva getBarva() { return barva; }
    public void setBarva(Barva barva) { this.barva = barva;  }
    
}

Pak vytvoříme třídu, která implementuje ActionBean, tedy ve Stripes může obsluhovat požadavky od prohlížeče:

 
package cz.muni.fi.pa165.cv5;
 
import java.util.ArrayList;
import java.util.List;
import net.sourceforge.stripes.action.ActionBean;
import net.sourceforge.stripes.action.ActionBeanContext;
import net.sourceforge.stripes.action.RedirectResolution;
import net.sourceforge.stripes.action.Resolution;
import net.sourceforge.stripes.action.UrlBinding;
import net.sourceforge.stripes.validation.Validate;
import net.sourceforge.stripes.validation.ValidateNestedProperties;
import org.apache.log4j.Logger;
 
@UrlBinding("/zaznam.action")
public class ZaznamActionBean implements ActionBean {
    static Logger log = Logger.getLogger(ZaznamActionBean.class);
    
    private ActionBeanContext ctx;
    public void setContext(ActionBeanContext ctx) { this.ctx = ctx;  }
    public ActionBeanContext getContext() { return ctx;  }
 
    
    public List<Zaznam> getZaznamy() { 
        return (List<Zaznam>) ctx.getRequest().getSession().getAttribute("zaz");
    }
    public void setZaznamy(List<Zaznam> zaznamy) { 
        ctx.getRequest().getSession().setAttribute("zaz", zaznamy);
    }
    
    @ValidateNestedProperties(
        value = {
            @Validate(on = {"pridej","uloz"}, field = "jmeno", required = true),
            @Validate(on = {"pridej","uloz"}, field = "pocet", required = true, minvalue = 1)
        }
    )
    private Zaznam zaznam;
    public Zaznam getZaznam() { return zaznam;  }
    public void setZaznam(Zaznam zaznam) { this.zaznam = zaznam;  }
    
    public Resolution pridej() {
        log.debug("pridej()");
        if(getZaznamy()==null) { setZaznamy(new ArrayList<Zaznam>()); }
        getZaznamy().add(zaznam);
        return new RedirectResolution("/index.jsp");
    }
    
}

Tato třída je mapována na URL končící na /zaznam.action, při vyvolání události pridej zkontroluje, že data v instanci Zaznamu splňují podmínky, a bude vyvolána metoda odpovídající jménu události.


A změníme index.jsp tak, aby vypisovala seznam Zaznamů a obsahovala formulář pro zadání nového.

 
<%@ page pageEncoding="utf-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="f" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="s" uri="http://stripes.sourceforge.net/stripes.tld" %>
 
<s:layout-render name="/rozvrh.jsp" nadpis="Hlavní stránka">
    <s:layout-component name="telo">
 
        <s:useActionBean var="ab" binding="/zaznam.action"/>
        <table border="1">
        <c:forEach var="z" items="${ab.zaznamy}">
            <tr>
                <td><c:out value="${z.jmeno}" /></td>
                <td><c:out value="${z.pocet}" /></td>
                <td><c:out value="${z.vybran?'ano':'ne'}" /></td>
                <td><c:out value="${z.barva}" /></td>
            </tr>    
            </c:forEach>
        </table>
        
        <s:form action="/zaznam.action">
            <fieldset><legend>Nový záznam</legend>
            <s:errors/>
            <table>
                <tr>
                    <td><label for="z1">jméno:</label></td>
                    <td><s:text id="z1" name="zaznam.jmeno" /></td>
                </tr>
                <tr>
                    <td><label for="z2">počet:</label></td>
                    <td><s:text id="z2" name="zaznam.pocet" /></td>
                </tr>
                <tr>
                    <td><label for="z3">vybrán:</label></td>
                    <td><s:checkbox id="z3" name="zaznam.vybran" /></td>
                </tr>
                <tr>
                    <td><label for="z4">barva:</label></td>
                    <td><s:select id="z4" name="zaznam.barva" >
                        <s:options-enumeration enum="cz.muni.fi.pa165.cv5.Zaznam.Barva"/>
                    </s:select></td>
                </tr>
            </table>
            <s:submit name="pridej">Přidej</s:submit>
            </fieldset>
       </s:form>
 
    </s:layout-component>
</s:layout-render>

Spusťte aplikaci a zkuste postupně odeslat formulář s hodnotami, bez hodnot, s textem místo čísla.

Lokalizace do češtiny

Je vidět, že máme tři problémy:

  • chybová hlášení nejsou lokalizovaná do češtiny
  • názvy polí neodpovídají popisům ve stránce
  • názvy barev jsou bez diakritiky, odpovídají identifikátorům enumu

Je proto nutné v souboru StripesResources_cs.properties

  • přeložit chybová hlášení, minimálně ta použitá v našem případě
  • lokalizovat názvy polí formuláře, ve kterých může dojít k chybě
  • definovat lokalizované názvy barev
 #chybova hlaseni
 stripes.errors.header=<div style="color:#b72222; font-weight: bold">Prosím opravte následující chyby:</div><ol>
 validation.required.valueNotPresent=pole {0} musí být vyplněno
 converter.number.invalidNumber=Hodnota "{1}" zadaná do pole {0} musí být číslo
 validation.minvalue.valueBelowMinimum=Minimální hodnota pro {0} musí být {2}

 #nazvy poli
 zaznam.jmeno=jméno
 zaznam.pocet=počet

 #nazvy polozek enumu
 Barva.MODRA=modrá
 Barva.ZELENA=zelená
 Barva.CERVENA=červená
 

Zkuste si spustit aplikaci.

samostatné cvičení

Proveďte lokalizaci do angličtiny a důslednou internacionalizaci. Statické texty nahraďte JSP tagy

 
<%@ taglib prefix="f" uri="http://java.sun.com/jsp/jstl/fmt" %>
...
 <f:message key="klic.textu" />

Obsluha dalších událostí

Obsluhu dalších událostí můžeme do action beanu snadno dodat přidáním dalších metod, které mají následující tvar:

 
 
public Resolution nazevUdalosti() {
    //obsluha udalosti
    ...
    //vraceni RedirectResolution nebo ForwardResolution 
    return new ForwardResolution("/stranka.jsp");
}

událost pak vyvoláme odkazem v JSP s případnými parametry:

 
  <s:link href="/NazevActionBeanu.action" event="nazevUdalosti"> 
     <s:link-param name="p1" value="${p1}" /> 
     <s:link-param name="p2" value="${p2}" /> 
     text odkazu 
  </s:link>

případně submitem ve formuláři se stejným jménem:

 
  <s:submit name="nazevUdalosti"> text tlacitka </s:submit>

Parametry a obsah polí ve formuláři se Stripes pokusí nastavit do stejnojmených properties na action beanu, tj. parametr "p1" se pokusí nastavit např. do:

 
 int p1;
 public int getP1() { return p1; } 
 public void setP1(int i) { p1 = i; }

Mazání

Do index.jsp do tabulky se záznamy je třeba přidat odkaz, který specifikuje volanou metodu a parametr:

 
<td><s:link href="/zaznam.action" event="smaz"><s:param name="zaznam.id"value="${z.id}"/>smazat</s:link></td>

a do ActionBeanu je třeba přidat metodu:

 
    public Resolution smaz() {
        ListIterator<Zaznam> listIterator = getZaznamy().listIterator();
        while(listIterator.hasNext()) {
            if(listIterator.next().getId()==zaznam.getId()) {
                listIterator.remove();
                break;
            }
        }
        return new RedirectResolution("/index.jsp");
    }

Kód využívá toho, že když Stripes obdrží od prohlížeče parametr s tečkami v názvu, pokusí se ho převést na volání metod. V tomto případě tedy parametr zaznam.id způsobí zavolání getZaznam(), které vrátí null (je to nový request od prohlížeče a tudíž nová instance ActionBeanu), takže Stripes vytvoří instanci třídy Zaznam, nastaví ji do proměnné zaznam a pak zavolá getZaznam().setId(id) kde id bude číslo vzniklé převodem hodnoty parametru.

Editování

Smazání je jednoduchá operace. pro editování je třeba data ve formuláři předvyplnit, a při uložení změny promítnout. Je třeba provést následující změny. V index.jsp přidat odkaz:

 
<s:link href="/zaznam.action" event="edit"><s:link-param name="zaznam.id" value="${z.id}"/> edit </s:link>

Vytvořit novou JSP stánku pro editaci edit.jsp:

 
<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="f" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="s" uri="http://stripes.sourceforge.net/stripes.tld" %>
 
<f:message key="index.nadpis" var="nadpis" />
    <s:layout-render name="/rozvrh.jsp" nadpis="${nadpis}">
    <s:layout-component name="telo">
        <s:useActionBean var="ab" binding="/zaznam.action"/>
        
            <s:form action="/zaznam.action">
                <s:hidden name="zaznam.id" />
            <fieldset><legend><f:message key="upravit.zaznam" /></legend>
            <s:errors />
            <table>
                <tr>
                    <td><label for="z1"><f:message key="zaznam.jmeno" />:</label></td>
                    <td><s:text id="z1" name="zaznam.jmeno" /></td>
                </tr>
                <tr>
                    <td><label for="z2"><f:message key="zaznam.pocet" />:</label></td>
                    <td><s:text id="z2" name="zaznam.pocet" /></td>
                </tr>
                <tr>
                    <td><label for="z3"><f:message key="zaznam.vybran" />:</label></td>
                    <td><s:checkbox id="z3" name="zaznam.vybran" /></td>
                </tr>
                <tr>
                    <td><label for="z4"><f:message key="zaznam.barva" />:</label></td>
                    <td><s:select id="z4" name="zaznam.barva" >
                        <s:options-enumeration enum="cz.muni.fi.pa165.cv5.Zaznam.Barva"/>
                    </s:select></td>
                </tr>
            </table>
            <s:submit name="uloz"><f:message key="uloz" /></s:submit>
            </fieldset>
       </s:form>
    </s:layout-component>
</s:layout-render>

s patřičnými texty v StripesResources_cs.properties

edit.nadpis=Zmena zaznamu
upravit.zaznam=Zmenit zaznam
uloz=Uloz


a v actionbeanu je třeba přidat metodu s anotací @Before, která přednastaví záznam podle specifikovaného id do proměnné tak, aby getZaznam() vracelo vybraný záznam. Při odeslání formuláře se změněnými hodnotami se tedy nejdříve natáhne správný záznam do proměnné zaznam, a pak Stripes mapováním obsahu formuláře do něj nastaví nové hodnoty. V metodě uloz() se pak změněné hodnoty musí uložit do databáze. V tomto příkladu to není nutné, protože záznam existuje jen v paměti, a v té už byl změněn.

Všimněte si, že po volání uloz() se provede redirect, protože šlo o odeslání obsahu formuláře a tato akce se nesmí provést podruhé, když uživatel klikne na reload.

 
    @Before(LifecycleStage.BindingAndValidation)
    public void natazeni() {
        String ids = ctx.getRequest().getParameter("zaznam.id");
        if(ids==null) return;
        int id = Integer.parseInt(ids);
        for(Zaznam z:getZaznamy()) {
            if(z.getId()==id)
              setZaznam(z);
        }
    }
    
    public Resolution edit() {
        return new ForwardResolution("/edit.jsp");
    }
    
    public Resolution uloz() {
        //normalne by se tady ulozil zaznam do databaze, ale
        //protoze existuje pouze v pameti, neni to potreba
        return new RedirectResolution("/index.jsp");
    }

Práce s databází

V reálné aplikaci je třeba nějak přistupovat k databázi. ActionBeans jsou objekty existující jen krátce, proto je vhodný postup ten, že si vytvoříte servlet, kterému nastavíte ve web.xml parametr load-on-startup, a do jeho metody init() umístíte vytvoření nějaké třídy pro práci s databází, a tu vložíte do kontextu aplikace, tj:

 
public class Starter extends HttpServlet {
 
    @Override
    public void init() throws ServletException {
        getServletContext().setAttribute("model", new MujModel(....));
    }
  
}

a web.xml:

 
 <servlet>
   <servlet-name>Starter</servlet-name>
   <servlet-class>cz.muni.fi.pa165.cv5.Starter</servlet-class>
   <load-on-startup>1</load-on-startup>
 </servlet>

V action beanech se pak k němu dostanete jednoduše přes kontext aplikace:

 
public Resolution uloz() {
        MujModel model = (MujModel) ctx.getServletContext().getAttribute("model");
        model.uloz(getZaznam());
        return new RedirectResolution("/index.jsp");
    }