Handling user-submitted data
Subsections
- File Uploads
-
How to upload and handle files using forms
- Form Components
-
Enhanced input fields
- Image Galleries
-
Upload and edit an image gallery
- Multimedia Galleries
-
Create complex image galleries
Description
Forms are used to gather information from users. You could also use plain anchors with request attributes, but forms are way more convenient especially for more than a couple of fields and when performing validation.
The more general kind of form has some input fields that are sent to the server in a POST and received from the @Controller as method parameters. This is a standard Spring Web MVC feature and quite straightforward to implement.
For more complicated forms you’d rather have a "form backing bean" holding all the input fields. This allows the use of the Yada Form Fragments for quickly assembling the HTML of the form.
Creating a java object just to hold the form data might seem overkill when you already have a database @Entity with the same exact fields. With a simple technique you can very easily use the same @Entity classes as form backing beans and save yourself a lot of work.
Plain Forms
The standard plain forms don’t need much explanation: you have some HTML with input fields and a @Controller that receives those input fields:
<form th:action="@{/addBook}">
<input name="bookTitle" th:value="${bookTitle}">
@Controller
public class BookController {
@RequestMapping("/addBook")
public String addBook(String bookTitle) {
Book book = new Book(bookTitle);
The above code doesn’t perform any validation but in a real-world example it should check at least that the title is not empty and go back to the user with an error message. The error handling code would have to be added for any field. Writing it could be quite time-consuming if there are more than a couple of fields. This is a possible solution for error handling:
<form th:action="@{/addBook}">
<input name="bookTitle" th:value="${bookTitle}" th:classappend="${titleError}?'formError':''">
<div th:if="${titleError!=null}" th:text="${titleError}" class="formError">Title Error</div>
@Controller
public class BookController {
@RequestMapping("/addBook")
public String addBook(String bookTitle, Model model) {
if (bookTitle.trim().length()==0) {
model.addAttribute("titleError", "The book title can not be empty");
return "bookForm";
}
Book book = new Book(bookTitle);
Form Backing Beans
TODO
Entity Backing Beans
When the form contains most of the fields that are defined in an existing @Entity it makes sense to use that @Entity as a backing bean, although some may argue that exposing database classes on the front-end layer would compromise layer isolation.
The general idea is that Spring can create an instance of a new @Entity or read it from the database before setting form values on it. The @Controller method handling the form will receive a ready-populated @Entity where any database value has been overwritten by the form value. This is very convenient because you just have to save the object and the form values will be persisted to the database. Even more, you can write a single method to handle both addition and editing of @Entity instances, sharing the common code.
The trick is to add a @ModelAttribute to the @Controller, which is always called before Spring calls the final @RequestMapping method:
<form th:action="@{/addBook}" th:object="${book}">
<input type="hidden" name="bookId" value="*{id}">
<input th:field="*{bookTitle}">
@Controller
public class BookController {
@ModelAttribute("book")
public Book addBook(@RequestParam(value="bookId", required=false) Long id) {
Book toEdit = null;
Exception exception = null;
if (id!=null) {
try {
toEdit = bookRepository.findOne(id);
} catch (Exception e) {
exception = e;
}
if (toEdit==null) {
log.error("Can't find Book with id={} - (creating new)", id, exception);
} else {
log.debug("Book {}-{} fetched from DB as ModelAttribute", id, toEdit.getTitle());
}
}
if (toEdit==null) {
toEdit = new Book();
}
return toEdit;
}
@RequestMapping("/addBook")
public String addBook(Book book, Model model) {
// Validation here
...
bookRepository.save(book);
It’s always better to use a specific name for the @Entity id, like bookId
and not just id
, so that you can handle different @Entity instances in the same @Controller.
Validation can be performed as usual, checking the values already stored in the entity bean, as long as it’s done before saving.
Ajax Forms
To send a form via ajax you just need to add the yadaAjax
class:
<form class="yadaAjax" action="/subscribe">
When a form is added dynamically via custom javascript, you also need to call
yada.enableAjaxForms();
after the change.
Any submit handler that needs to be invoked before form submission has to be listed as a SubmitHandler value:
data-yadaSubmitHandler |
yada:submitHandler |
---|
Both the data- attribute version and the yada: dialect version take a comma-separated list of function names to be called in order. Submission is aborted if any returns false and any following functions are skipped.
Example:
<form class="yadaAjax" action="/subscribe" yada:submitHandler="validateInput">
Submit handlers can be set on the form tag and on any submit button. The "this" object is either the form or the clicked button, depending on where the tag has been placed.
Postprocessing
There are many options to handle the server response, like replacing page content or invoking some handler. See Ajax Links for more details. For example, this form replaces a page element with the returned html:
<form class="yadaAjax" action="/subscribe" yada:updateOnSuccess="#someSection">
Validation in a modal
When an ajax form is inside a modal, the outcome of the submission could be a validation error on some fields, a confirmation message or the closing of the modal. These outcomes can be achieved by the same controller method by returning different results:
-
to show a validation error, return the full modal with the form
-
to show a confirmation message, return a notification
-
to close the modal, return
YadaViews.AJAX_CLOSE_MODAL
There’s no need to set a yada:updateOnSuccess=""
on the form because, whenever a modal is
returned from the backend, the current open modal is closed (unless sticky).
The closing and opening of the modals in case of validation errors is clearly visible.
To avoid that, not the full modal but only the form part should be returned so that
the modal stays open, and the form must be annotated with yada:updateOnSuccess=""
.
Examples:
return "/someModalWithForm";
return yadaNotify.title("Lorem", model).ok().message("Ipsum").add();
return YadaViews.AJAX_CLOSE_MODAL;
Form groups
Multiple forms scattered around the page can be submitted together if they belong to the same "Form Group": when any (ajax or not) form in the group is submitted, the fields from all other forms in the same group are added to the payload. If a field has the same name in more than one form, only the first found is considered.
The submission of a form group can also be triggered by an anchor or any other type of element
with a href
or data-href
attribute (ajax or not). Any request parameter on the url is added to the payload and
overwrites any form fields with the same name.
The syntax to define a form group is the following:
data attribute | yada dialect | value | description |
---|---|---|---|
data-yadaFormGroup |
yada:formGroup |
any name |
Identifies the form as belonging to the given group |
Example with a triggering form:
<form action="/someAction" yada:formGroup="myGroup">
<input name="a" value="1">
<button type="submit">Submit</button>
</form>
<form action="ignored" yada:formGroup="myGroup">
<input name="b" value="2">
</form>
<form action="ignored" yada:formGroup="myGroup">
<input name="c" value="3">
</form>
In the above example, submitting the first form would send "a=1&b=2&c=3"
to /someAction
.
A similar behavior would be obtained by using the "form" attribute on the <input>
tags of child forms.
The use of yada:formGroup
has the following advantages:
-
it is quicker to type on large forms
-
can be used together with (ajax or not) requests from elements other than forms (e.g. ajax links)
-
a future improvement could allow forms to belong to multiple groups, something that can’t be done with the "form" attribute
Example with a triggering anchor in ajax:
<a href="" yada:ajax="/someUrl?a=9" yada:formGroup="myGroup">Click me</a>
<form action="/someAction" yada:formGroup="myGroup">
<input name="a" value="1">
<input name="c" value="3">
<button type="submit">Submit</button>
</form>
<form action="ignored" yada:formGroup="myGroup">
<input name="b" value="2">
</form>
In the above example, clicking on the link would send "a=9&b=2&c=3"
to /someUrl
.
all. Remember that button handlers receive the button itself: function editTaskFormHandler(responseText, responseHtml, form, button) {
TO BE CONTINUED