La gestione dei form con Spring MVC

Con questo articolo iniziamo ad esaminare alcune tematiche leggermente più avanzate, relativamente allo sviluppo di applicazioni web con Spring MVC.

Partiamo con la gestione dei form (o delle form, a seconda dei gusti), componenti di fatto onnipresenti in ogni web application che si rispetti.

Per l’occasione cominciamo lo sviluppo di un nuovo semplice progetto che, molto modestamente e senza alcuna intenzione velleitaria, chiamerò: SuperRubricaTelefonica! :-D

Definiamo le dipendenze

Ovviamente useremo Spring Boot e per creare la prima struttura del progetto ci sarà utile il tool Spring Initializr.

Per il momento ci basterà includere Thymeleaf come unica dipendenza:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.davioooh</groupId>
  <artifactId>super-rubrica-telefonica</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>SuperRubricaTelefonica</name>
  <description>Una rubrica telefonica spettacolare!</description>

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.7.RELEASE</version>
    <relativePath/>
  </parent>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>

</project>

Il pom.xml risultante dovrebbe somigliare a quello appena riportato.

Aggiungiamo un Controller

In questa prima fase, andremo a simulare il salvataggio di un nuovo contatto nella nostra rubrica. Dico simulare perché per il momento non andremo a inserire queste informazioni in nessuna base dati.

Parleremo di data access layer e Repository in uno dei futuri post di questa serie.

Iniziamo quindi con un Controller piuttosto semplice:

@Controller
@RequestMapping("/contacts")
public class ContactController {

  @GetMapping("/new")
  public String contactForm(Model model) {
    model.addAttribute(new ContactForm());
    return "contact-form";
  }

  @PostMapping("/new")
  public String submitNewContact(@ModelAttribute ContactForm contactForm, Model model) {
    ContactDetails details = new ContactDetails();
    details.setFirstName(contactForm.getFirstName());
    details.setLastName(contactForm.getLastName());
    details.setPhone(contactForm.getPhone());

    model.addAttribute("contact", details);
    return "contact-details";
  }
}

Come vedete, abbiamo una classe ContactController che si occuperà di gestire tutte le richieste relative ai contatti.

Per il momento abbiamo solamente due metodi associati alla creazione di un nuovo contatto.

Il primo handler, contactForm, mostrerà la pagina contenente il form per l’inserimento della nuova voce in rubrica. L’oggetto di tipo ContactForm, aggiunto al Model, servirà a inizializzare i campi del form e fungerà da contenitore per i valori inviati in fase di submission.

Il metodo submitNewContact, invece, intercetterà la submission del form, recuperando i dati inviati tramite POST e visualizzandoli in una pagina di riepilogo.

Esaminando meglio il secondo metodo ci accorgiamo dell’annotazione @ModelAttribute, già incontrata quando abbiamo parlato del passaggio dei dati alle View. In questo caso però la utilizziamo in maniera differente…

Quando effettuiamo l’invio del form al server, l’oggetto “contenitore” ContactForm viene popolato con i valori dei campi input. Possiamo recuperare questi valori nel nostro handler sfruttando l’annotazione @ModelAttribute che (tramite il cosiddetto data binding) permette di associare l’oggetto nel Model con il parametro contactForm del nostro metodo.

Per esporre le informazioni nel Model userò un oggetto di tipo ContactDetails. Avremmo potuto utilizzare lo stesso oggetto contactForm restituitoci dalla POST, ma personalmente preferisco definire degli oggetti specifici (DTO) per ogni funzionalità e/o pagina prevista dall’applicazione.

Creiamo le pagine

A questo punto ci occupiamo delle View… e in questo ci tornerà molto utile Thymeleaf.

Creeremo due pagine: la prima servirà ad inserire i dati del nuovo contatto, la seconda mostrerà un riepilogo degli stessi dati, una volta inviati al server.

Tutto molto semplice :-)

Il form

La prima pagina, contact-form.html, è, per l’appunto, quella contenente il form d’inserimento dati:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
  xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8" />
<title>Nuovo contatto</title>
...
</head>
<body>
  <form th:action="@{/contacts/new}" th:object="${contactForm}" method="post">
    <h2>Nuovo contatto</h2>
    <div class="form-group">
      <label>Nome</label> <input th:field="*{firstName}" type="text" class="form-control" />
    </div>
    <div class="form-group">
      <label>Cognome</label> <input th:field="*{lastName}" type="text" class="form-control" />
    </div>
    <div class="form-group">
      <label>Telefono</label> <input th:field="*{phone}" type="text" class="form-control" />
    </div>
    <div class="form-group">
      <input class="btn btn-primary" type="submit" value="Salva" />
    </div>
  </form>
</body>
</html>

Se la cosa vi può interessare, ho utilizzato una spruzzatina di Bootstrap per rendere le mie pagine più gradevoli alla vista, ma ovviamente questo è del tutto secondario…

Esaminiamo invece alcuni elementi interessanti della pagina.

  • L’attributo th:action, di Thymeleaf, permette di definire la URL a cui verranno inviati i dati del modulo. In Thymeleaf le URL possono essere dichiarate tramite l’operatore @{...}, che consente di risolvere il path in base al contesto.

  • Con th:object invece si definisce quale oggetto presente nel Model sarà associato al form. In questo caso sfruttiamo il data binding per valorizzare i campi input con i valori dei corrispondenti campi del backing object. Se fate attenzione, riconoscerete lo stesso oggetto aggiunto al Model precedentemente, nel nostro Controller.

  • L’associazione dei campi agli input fields avviene, infine, tramite l’attributo th:field. Utilizzando l’operatore *{...} possiamo specificare il nome del campo nel backing object da “collegare” a un certo input.

Tutto quadra, no?

La nostra pagina di riepilogo è ancora più semplice:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
  xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8" />
<title th:text="${'Contatto - ' + contact.firstName + ' ' + contact.lastName}"></title>
...
</head>
<body>
  <div class="dtl-body">
    <h2 th:text="${contact.firstName + ' ' + contact.lastName}"></h2>
    <div>
      <strong>Telefono: </strong> <span th:text="${contact.phone}"></span>
    </div>
  </div>
</body>
</html>

I dati inviati al server vengono semplicemente visualizzati in modo “carino” tramite questo semplice template.

In questo caso ho utilizzato l’attributo th:text che consente di valorizzare un tag HTML con l’output di un’espressione. Ad esempio, ho deciso di mostrare il nome completo del nuovo contatto concatenando nome e cognome con questa semplice espressione:

${contact.firstName + ' ' + contact.lastName}

Ecco fatto!

Lanciando l’applicazione e puntando il browser su localhost:8080/contacts/new visualizzeremo il nostro fantastico form:

Form nuovo contatto

Compilando i campi e inviando il modulo otterremo invece qualcosa di simile:

Riepilogo contatto

In conclusione

La gestione dei form è un aspetto fondamentale e piuttosto ricorrente quando si lavora ad un’applicazione web, dalla più semplice alla più complessa.

Spring MVC e Thymeleaf permettono d’implementare il tutto in modo piuttosto semplice ed elegante.

Nella prossima puntata vedremo come validare i dati inviati dal form e come mostrare gli eventuali errori nella pagina.

A presto,

David