Developing Web applications

Experimental introduction

The first impression is very important: curve app can sell the house before the buyer really enters the house; If a car is painted cherry, its paint will be more eye-catching than its engine; Literature is full of stories of love at first sight. The inside is very important, but the outside, that is, what you see at first sight, is also very important.

The applications we build with Spring can do all kinds of things, including processing data, reading information from the database and interacting with other applications. However, the user's first impression of the application comes from the user interface. In many applications, UI is presented in the form of Web application in browser.

In Chapter 1, we created the first Spring MVC controller to present the home page of the application. However, Spring MVC can do many things, not limited to displaying static content. In this chapter, we will develop the first main function of Taco Cloud: the ability to design and customize taco. In this process, we will delve into Spring MVC and see how to present model data and process form input.

Knowledge points

  • Display model data in browser

  • Process and verify form input

  • Select view template library

Show information

Fundamentally speaking, Taco Cloud is a place where you can order taco online. However, in addition, Taco Cloud allows customers to show their creativity, allowing them to design their own taco through rich ingredient s.

Therefore, Taco Cloud needs a page to show taco artists what ingredients they can choose. Optional ingredients can change at any time, so they cannot be hard coded into HTML pages. We should get the available ingredients from the database and pass them to the page to show them to the customer.

In Spring Web applications, acquiring and processing data is the task of the controller, while rendering the data into HTML and displaying it in the browser is the task of the view. In order to support the creation page of taco, we need to build the following components.

  • Domain class used to define taco ingredient properties.
  • The Spring MVC controller class used to get the batching information and pass it to the view.
  • A view template used to render the list of ingredients in the user's browser

The relationship between these components is shown in the figure below.

Because this chapter mainly focuses on the Web framework of Spring, we will explain the contents related to database in Chapter 3. Now the controller is only responsible for providing ingredients to the view. In Chapter 3, we will reinvent the controller so that it can work with the repository to obtain batching data from the database.

Before writing the controller and view, we first determine the domain type used to represent the ingredients, which will lay the foundation for our development of Web components.

Building domain classes

The field of application refers to the scope of the subject it is to solve: that is, the ideas and concepts that will affect the understanding of application [1].
In Tao Cloud applications, domain objects include taco designs, ingredients that make up these designs, customers and orders placed by customers. As a start, we first focus on the ingredients of Taco.

In our field, taco ingredients are very simple objects. Each Ingredient has a name and type to facilitate its visual classification (protein, cheese, sauce, etc.). Each Ingredient also has an ID, so that its reference can be very easy and clear. The following progressive class defines the domain objects we need.

In src/main/java/tacos directory, create a new increment Java file, the code is as follows.

package tacos;

import lombok.Data;
import lombok.RequiredArgsConstructor;

@Data
@RequiredArgsConstructor
public class Ingredient {

  private final String id;
  private final String name;
  private final Type type;

  public static enum Type {
    WRAP, PROTEIN, VEGGIES, CHEESE, SAUCE
  }

}

We can see that this is a very common Java domain class, which defines three properties required to describe ingredients. In the above code, the most unusual thing about the progressive class is that it seems to lack common getter and setter methods, as well as methods such as equals(), hashCode(), toString().

These methods are not in the code, partly because of space savings, but also because we use a library called Lombok (a great library that can generate these methods dynamically at run time). In fact, the class level @ Data annotation is provided by Lombok, which will tell Lombok to generate all missing methods and all constructors with final attribute as parameter. By using Lombok, we can make the code of progressive concise.

Then create taco. Com under src/main/java/tacos package Java file, the code is as follows.

package tacos;
import java.util.List;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import lombok.Data;

@Data
public class Taco {

  private String name;

  private List<String> ingredients;
}

Create an order. Under the src/main/java/tacos package Java file, the code is as follows.

package tacos;
import javax.validation.constraints.Digits;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;

import org.hibernate.validator.constraints.CreditCardNumber;

import lombok.Data;

@Data
public class Order {

  private String name;

  private String street;

  private String city;

  private String state;

  private String zip;

  private String ccNumber;

  private String ccExpiration;

  private String ccCVV;

}

Lombok is not a Spring library, but it is very useful. I find it difficult to develop without it. When I need to write short and concise code examples in my book, it has become my Savior.

To use Lombok, you first add it to your project as a dependency. In POM XML is manually added through the following entries:

<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <optional>true</optional>
</dependency>

This dependency will provide you with Lombok annotations (such as @ Data) during the development phase, and will generate automatic methods at runtime. See the Lombok project page to see how to install Lombok on the IDE of your choice.

I'm sure you'll find Lombok very useful, but you also need to know that it's optional. When developing Spring applications, it is not necessary, so if you don't want to use it, you can write these missing methods manually. When you're done, we'll add some controllers to the application to handle Web requests.

Create controller class

In the Spring MVC framework, the controller is an important participant. Their main responsibility is to process HTTP requests, either pass the request to the view to render HTML (browser presentation), or write the data directly to the response body (RESTful). In this chapter, we will focus on controllers that use views to generate content for Web browsers. In Chapter 6, we'll see how to write controllers in the form of rest APIs to handle requests.

For Taco Cloud applications, we need a simple controller to complete the following functions.

  • Process HTTP GET requests with path / design.
  • Build a list of ingredients.
  • Process the request, pass the batching data to the view template to be rendered as HTML, and send it to the Web browser that initiated the request.

The DesignTacoController class in the following code addresses these requirements.

Create a new designtacocontroller under src/main/java/tacos/web package (if not created) Java file, the code is as follows.

package tacos.web;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import javax.validation.Valid;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import lombok.extern.slf4j.Slf4j;
import tacos.Taco;
import tacos.Ingredient;
import tacos.Ingredient.Type;

@Slf4j
@Controller
@RequestMapping("/design")
public class DesignTacoController {

    @ModelAttribute
    public void addIngredientsToModel(Model model) {
        List<Ingredient> ingredients = Arrays.asList(
          new Ingredient("FLTO", "Flour Tortilla", Type.WRAP),
          new Ingredient("COTO", "Corn Tortilla", Type.WRAP),
          new Ingredient("GRBF", "Ground Beef", Type.PROTEIN),
          new Ingredient("CARN", "Carnitas", Type.PROTEIN),
          new Ingredient("TMTO", "Diced Tomatoes", Type.VEGGIES),
          new Ingredient("LETC", "Lettuce", Type.VEGGIES),
          new Ingredient("CHED", "Cheddar", Type.CHEESE),
          new Ingredient("JACK", "Monterrey Jack", Type.CHEESE),
          new Ingredient("SLSA", "Salsa", Type.SAUCE),
          new Ingredient("SRCR", "Sour Cream", Type.SAUCE)
        );

        Type[] types = Ingredient.Type.values();
        for (Type type : types) {
          model.addAttribute(type.toString().toLowerCase(),
              filterByType(ingredients, type));
        }
    }

    @GetMapping
    public String showDesignForm(Model model) {
      model.addAttribute("design", new Taco());
      return "design";
    }

    private List<Ingredient> filterByType(
        List<Ingredient> ingredients, Type type) {
        return ingredients
           .stream()
           .filter(x -> x.getType().equals(type))
           .collect(Collectors.toList());
    }

}

For DesignTacoController, we should first pay attention to the annotations applied at the class level. The first is @ SLF4J, which is the annotation provided by Lombok. At run time, it will automatically generate an SLF4J (Simple Logging Facade for Java) Logger in this class. This simple annotation has the same effect as the explicit declaration in the class through the following code:

private static final org.slf4j.Logger log =
    org.slf4j.LoggerFactory.getLogger(DesignTacoController.class);

Later, we will use this Logger.

The next annotation used by DesignTacoController is @ Controller. This annotation will identify this class as a Controller and make it a candidate for component scanning, so Spring will find it and automatically create an instance of DesignTacoController, which will be used as a bean in the Spring application context.

DesignTacoController is also annotated with @ RequestMapping. When the @ RequestMapping annotation is used at the class level, it can specify the type of request processed by the controller. In this case, it specifies that DesignTacoController will process requests whose path starts with / design.

Processing GET requests

The @ GetMapping annotation that modifies the showDesignForm() method refines the class level @ RequestMapping@ GetMapping, combined with @ RequestMapping at class level, indicates that showDesignForm() will be called to process the request when an HTTP GET request for / design is received.

@GetMapping is a relatively new annotation, which was introduced in Spring 4.3. Before Spring 4.3, you may need to use the @ RequestMapping annotation at the method level as an alternative:

@RequestMapping(method=RequestMethod.GET)

Obviously, @ GetMapping is more concise and indicates its target HTTP method@ GetMapping is just one of many request mapping annotations. The following table lists all the available request mapping annotations in Spring MVC.

Make the right thing easier

When declaring request mappings for controller methods, the more specific the better. This means at least declaring the path (or inheriting a path from @ RequestMapping at the class level) and the HTTP method it handles.

However, the longer @ RequestMapping(method=RequestMethod.GET) annotation makes it easy for developers to take a lazy approach, that is, ignore the method attribute. Thanks to the new annotation of Spring 4.3, the right thing is easier and we have less input.

The new request mapping annotations have exactly the same properties as @ RequestMapping, so we can use them anywhere we use @ RequestMapping.

In general, I prefer to use @ RequestMapping only at the class level to specify the base path. On each processor method, I will use more specific @ GetMapping, @ PostMapping and other annotations.

Now that we know that the showDesignForm() method will handle requests, let's take a look at the lower body of the method to see what it does. This method builds a list of progressive objects. Now, the list is hard coded. When we study Chapter 3, we will get the available taco ingredients from the database and put them in the list.

After the ingredient list is ready, the next few lines of code of the showDesignForm() method will filter the list according to the ingredient type. The list of ingredient types will be added to the Model object as an attribute, which is passed to the showDesignForm() method as a parameter. The Model object is responsible for passing data between the controller and the view that presents the data. In fact, the data put into the Model attribute will be copied into the Servlet Response attribute, so that the view can find them here. The showDesignForm() method finally returns design, which is the logical name of the view and will be used to render the Model to the view.

Our DesignTacoController has taken shape. If you run the application now and access the / design path on the browser, DesignTacoController's showDesignForm() will be called. It will get data from the repository and put it into the model, and then pass the request to the view. However, we haven't defined the view yet. The request will encounter a very bad problem, that is, HTTP 404 (Not Found). To solve this problem, we will switch our attention to the view, where the data will be decorated with HTML to be displayed in the user's Web browser.

Design view

After the controller completes its work, it's time for the view to appear. Spring provides a variety of ways to define views, including JavaServer Pages (JSP), Thymeleaf, FreeMarker, Mustache, and Groovy based templates. For now, we will use Thymeleaf, which is also our choice when starting this project in Chapter 1.

At runtime, the automatic configuration function of Spring Boot will find Thymeleaf in the classpath, so it will create bean s supporting the Thymeleaf view for Spring MVC.

View libraries like Thymeleaf are designed to be decoupled from specific Web frameworks. In this way, they cannot perceive the Model abstraction of Spring, so they cannot work with the data put into the Model by the controller. However, they can work with the request attribute of the Servlet. Therefore, before Spring transfers the request to the view, it will copy the Model data into the request attribute, which can be accessed by Thymeleaf and other view template schemes.

Thymeleaf template is HTML that adds some additional element attributes, which can guide the template how to render request data. For example, if the key of a request attribute is message, we want to use thymeleaf to render it to an HTML file

In the tag, then in the Thymeleaf template, we can write as follows:

<p th:text="${message}">placeholder message</p>

When the template is rendered as HTML,

The element body will be replaced with the attribute value with key as message in Servlet Request. th:text is an attribute in the Thymeleaf namespace, which performs this replacement process$ {} will tell it to use the value in a request attribute (in this case, message).

Thymeleaf also provides an attribute th:each, which iterates over a collection of elements and renders HTML for each entry in the collection. This is very convenient when we show the list of ingredients in the model in the design view. For example, if we only want to render the list of wrap ingredients, we can use the following HTML fragment:

<h3>Designate your wrap:</h3>
<div th:each="ingredient : ${wrap}">
  <input name="ingredients" type="checkbox" th:value="${ingredient.id}" />
  <span th:text="${ingredient.name}">INGREDIENT</span><br />
</div>

Here, we use the th:each attribute in the < div > tag, so that we can render < div > repeatedly for each element in the set corresponding to the wrap request attribute. At each iteration, the ingredient element is bound to a Thymeleaf variable named ingredient.

In the < div > element, there is a < input > check box element and a < span > element that provides a label for the check box. The check box uses Thymeleaf's th:value to set the value attribute for the rendered < input > element, which will be set as the id attribute of the found ingredient. Element uses th:text to replace the placeholder text of ingredient with the name attribute of ingredient.

When rendering with actual model data, the rendering results of one < div > iteration may be as follows:

<div>
  <input name="ingredients" type="checkbox" value="FLTO" />
  <span>Flour Tortilla</span><br />
</div>

Finally, the above Thymeleaf fragment will become part of a large HTML form through which our taco artist users will submit their delicious works. The complete Thymeleaf template will include all ingredient types.

Create a design. In the src/main/resources/templates / directory HTML file, the code is as follows.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
  <head>
    <title>Taco Cloud</title>
    <link rel="stylesheet" th:href="@{/styles.css}" />
  </head>

  <body>
    <h1>Design your taco!</h1>
    <img th:src="@{/images/TacoCloud.png}" />

    <form
      method="POST"
      th:object="${taco}"
      th:action="@{/design}"
      id="tacoForm"
    >
      <div class="grid">
        <div class="ingredient-group" id="wraps">
          <h3>Designate your wrap:</h3>
          <div th:each="ingredient : ${wrap}">
            <input
              name="ingredients"
              type="checkbox"
              th:value="${ingredient.id}"
            />
            <span th:text="${ingredient.name}">INGREDIENT</span><br />
          </div>
        </div>

        <div class="ingredient-group" id="proteins">
          <h3>Pick your protein:</h3>
          <div th:each="ingredient : ${protein}">
            <input
              name="ingredients"
              type="checkbox"
              th:value="${ingredient.id}"
            />
            <span th:text="${ingredient.name}">INGREDIENT</span><br />
          </div>
        </div>

        <div class="ingredient-group" id="cheeses">
          <h3>Choose your cheese:</h3>
          <div th:each="ingredient : ${cheese}">
            <input
              name="ingredients"
              type="checkbox"
              th:value="${ingredient.id}"
            />
            <span th:text="${ingredient.name}">INGREDIENT</span><br />
          </div>
        </div>

        <div class="ingredient-group" id="veggies">
          <h3>Determine your veggies:</h3>
          <div th:each="ingredient : ${veggies}">
            <input
              name="ingredients"
              type="checkbox"
              th:value="${ingredient.id}"
            />
            <span th:text="${ingredient.name}">INGREDIENT</span><br />
          </div>
        </div>

        <div class="ingredient-group" id="sauces">
          <h3>Select your sauce:</h3>
          <div th:each="ingredient : ${sauce}">
            <input
              name="ingredients"
              type="checkbox"
              th:value="${ingredient.id}"
            />
            <span th:text="${ingredient.name}">INGREDIENT</span><br />
          </div>
        </div>
      </div>

      <div>
        <h3>Name your taco creation:</h3>
        <input type="text"/>
        <br />

        <button>Submit your taco</button>
      </div>
    </form>
  </body>
</html>

As you can see, we will repeat the definition for various types of ingredients

Fragment. In addition, we also include a Submit button and an input field for users to define the name of their work.

It is worth noting that the complete template contains a logo image of Taco Cloud and a reference to the style sheet. The content of the style has nothing to do with our discussion. It just includes the style that allows the ingredients to be displayed in two columns to avoid a long list of ingredients.

Create new styles in src/main/resources/static / CSS, write the following code.

div.ingredient-group:nth-child(odd) {
  float: left;
  padding-right: 20px;
}

div.ingredient-group:nth-child(even) {
  float: left;
  padding-right: 0;
}

div.ingredient-group {
  width: 50%;
}

.grid:after {
  content: '';
  display: table;
  clear: both;
}

*,
*:after,
*:before {
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  box-sizing: border-box;
}

span.validationError {
  color: red;
}

In both scenarios, Thymeleaf's @{} operator is used to generate a path relative to the context, so as to reference the static artifact s we need. As we learned in Chapter 1, in Spring Boot applications, static content should be placed in the / static directory of the root path.

Our controller and view have been completed. Now we can start the application and take a look at the results of our work.

Execute the following commands in the WebIDE of the experimental building to run the program.

# Enter the project root directory
cd /home/project/taco-cloud
# Run program
mvn clean spring-boot:run

After startup, open the Web service and add / design at the end of the address for access. You will see a page as shown in the figure below.

To the source code

But this sentence will report an error

So I deleted it

That's it

It looks very good! Taco artists visiting your site can see a form that contains various taco ingredients that they can use to create their own masterpieces. But what happens when they click the Submit Your Taco button?

Our DesignTacoController is not ready to receive the request to create taco. If the design form is submitted, the user will encounter an error (specifically, it will be an HTTP 405 error: Request Method "POST" Not Supported). As shown in the figure below.

Next, we fix this error by writing some controller code to handle form submission.

Posted by aquarius on Wed, 04 May 2022 07:52:39 +0300