Spring Boot (4) A dynamic form that calls a batch process and displays information of the process

0. Introduction

These elements are needed:

  1. A generic field definition
  2. An anchor element that points to the URL passing the generic field definition
  3. A controller of this URL
  4. A Thymeleaf template that displays the fields and calls the process
  5. A WebSocket communication

1. The generic field definition (java class)

Defines a field (value, label, component type..)

package ximo.sockets.model;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter @Setter @NoArgsConstructor @AllArgsConstructor
public class FieldEdu {
  private String name;
  private String label="LABEL";
    
  private String component="INPUT"; //"SELECT INPUT RADIO CHECK"
    
  //In case the component is of tyoe "select"
  private String optionValues=""; //"1,2,3,4"
  private String optionText=""; //"Valencia,Castella,Angles,Frances"
    
  private String value="";
  private String type="STRING"; //"INT STRING BOOLEAN"
}

2. An anchor to call it

Using Thymeleaf to define the anchor

<a th:if="${!menu.hasChildren()}" th:text="${menu.name}" class="dropdown-item" th:href="${menu.href}">
  procedure
</a>

Where menu.href is

href: '/process04?process=Process01&field={"name":"myname", "value":"myvalue"}&field={"name":"year","value":"2023","component":"PASSWORD"}'

In this 3 parameters are passed to the controller "process" and 2 field definitions


3. The controller class

That responds to the  URL "/process04" and passes the received parameters to the model that can be accessed by the Thymeleaf template. Note that the fields need to be parsed from json format into a class of type FieldEdu.

package ximo.controllers;

import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

import com.google.gson.Gson;
import ximo.sockets.model.FieldEdu;

@Controller
public class Proces04Controller {

    /**
     * The call: '/process04?process=Process01&field={"name":"myname",
     * "value":"myvalue"}&field={"name":"year","value":"2023","component":"PASSWORD"}'
     * 
     * @param process The process to call
     * @param lField  The fields to show as parameters to the process
     * @param model   The sructure that is passed to the Thymeleaf template
     * @return
     */
    //
    @GetMapping("/process04")
    public String process04(@RequestParam("process") String process, @RequestParam("field") List<String> lField,
            Model model) {

        Gson gson = new Gson();
        String strMap = gson.toJson(lField);
        System.out.println("map=" + strMap);
        List<FieldEdu> lFldEdu = new ArrayList<FieldEdu>();
        for (String s : lField) {
            FieldEdu fldEdu = gson.fromJson(s, FieldEdu.class);
            lFldEdu.add(fldEdu);
        }

        // The class to execute;
        model.addAttribute("klassName", process);
        model.addAttribute("fields", lFldEdu);

        return "process03_page";
    }

}

4. The proces03_page.html Thymeleaf template

This template is used to create the dynamic fields passed in the mode. Note also the websocket.edu.js javascript file that manages the submit form actions

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
  <head>
    <title>Hello WebSocket</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        
    <link th:rel="stylesheet" th:href="@{/webjars/bootstrap/5.2.3/css/bootstrap.min.css}" />
    <script th:src="@{/webjars/bootstrap/js/bootstrap.min.js}"></script>
        
    <script src="/webjars/sockjs-client/sockjs.min.js"></script>
    <script src="/webjars/jquery/jquery.min.js"></script>
    <script src="/webjars/stomp-websocket/stomp.min.js"></script>
    <script src="/js/edu/websocket.edu.js"></script>
  </head>    
  <body>
    <div id="main-content">
      <div>
        <form id=form01>
            <!-- NON DYNAMIC FIELDS -->
            <div class="mb-3">
            <label for="name" class="form-label">What is your name?</label>
            <input type="text" name="name" id="name" class="form-control" placeholder="Your name here...">
          </div>
          <div>
            <label for="text" class="form-label">What is your message?</label>
            <input type="text" name="text" id="text" class="form-control" placeholder="Your message here...">
          </div>
          <input type="hidden" name="klassName" th:value="${klassName}" id="klassName">
          <!-- CREATING DYNAMIC FIELDS -->
          <div th:each="field : ${fields}" class="mb-3">
            <label th:for="${field.name}" class="form-label" th:text="${field.name}">What is your name?</label>
            <div th:switch="${field.component}">
              <input th:case="'INPUT'" type="text" th:value="${field.value}" th:name="${field.name}" th:id="${field.name}" class="form-control" placeholder="Your name here...">
              <input th:case="'PASSWORD'" type="password" th:value="${field.value}" th:name="${field.name}" th:id="${field.name}" class="form-control" placeholder="Your name here...">
            </div>
          </div>
          <button id="send" type="submit">Send</button>
        </form>
      </div>
    </div>
    <div class="mb-3">
      <label>Message from server: </label><span id="message"></span>
      <textarea class="form-control" id="mytextarea" rows="5"></textarea>
      <!-- <textarea id="mytextarea" rows="4" cols="30">1234</textarea>-->
    </div>
    
  </body>
</html>        

5. The WebSocket communication

This is the javascript code that calls the WebSocket controller which is connected to "/hello"

var randNum =  (Math.floor(Math.random() * 1000000000)).toString(); 


$(document).ready(function() {
  connect();
});

function connect() {
    var socket = new SockJS('/hello');
    
    stompClient = Stomp.over(socket);
    stompClient.connect({}, function() {
        console.log('Web Socket is connected');
        stompClient.subscribe('/queue/reply/'+randNum, function(message) {    
            document.getElementById("mytextarea").value +="\n"+message.body;
        });
    });
}
 
$(function() {
    $("form").on('submit', function(e) {
        e.preventDefault();
    });
    $("#send").click(function() {
        const formElement = document.querySelector("form");
        const formData = new FormData(formElement);
        var objFormData=Object.fromEntries(formData);
        objFormData.idSession = randNum; // add the attirbute idSession to formData
        alert(JSON.stringify(objFormData));
        stompClient.send("/app/hello", {}, 
            JSON.stringify(objFormData));
    });
});

The details of the controller that is connected to the "/hello" and the process class is in a previous post.












Comentarios

Entradas populares de este blog

SpringBoot (14) Let's start (2/10). Defining users

SpringBoot (6) Spring Data JPA (1)