Thymeleaf - Selecting multiple options from a pickList - thymeleaf

Here is my code
<div class="panel-body">
<div id="pickList">
<div class="row">
<div class="col-sm-5">
<select class="form-control pickListSelect pickListResult"
style="width: 100%" multiple="multiple" id="existingAdminGroupMembers" th:field="*{adminGroup.members}">
<!-- <option
th:each="currentUser : ${add_user.adminGroup.currentMembers}"
th:value="${currentUser}" th:text="${currentUser}"></option> -->
</select>
</div>
<div class="col-sm-2 pickListButtons">
<button class="pAdd btn btn-primary btn-sm">></button>
<button class="pAddAll btn btn-primary btn-sm">>></button>
<button class="pRemove btn btn-primary btn-sm"><</button>
<button class="pRemoveAll btn btn-primary btn-sm"><<</button>
</div>
<div class="col-sm-5">
<select class="form-control pickListSelect pickData"
style="width: 100%" multiple="multiple" id="availableMembersForAdminGrp">
<option
th:each="adminUser : ${add_user.adminGroup.availableMembers}"
th:value="${adminUser.userName}"
th:text="${adminUser.userName}"></option>
</select>
</div>
</div>
</div>
</div>
public class Group {
private String groupName;
private List<User> availableMembers = new ArrayList<User>();
private List<String> currentMembers = new ArrayList<String>();
private String[] members;
}
public class AddUser {
private String clusterName;
private String projectName;
private Group adminGroup;
private Group userGroup;
}
Controller Class :
model.addAttribute("add_user", addUser);
When I am trying to return model attribute back to the controller class using Post method, I am not able to retrieve data of the pickListResult from the th:field adminGroup.members .can you tell me what's the problem in the above code.

Assuming yout model attribute is like this from controller side
model.addAttribute("add_user", group );
<select class="form-control pickListSelect pickData" style="width: 100%" multiple="multiple" id="admins" th:field="*{adminUser.userName}">
<option th:each="adminUser : ${add_user.admins.members}"
th:value="${adminUser.userName}"
th:text="${adminUser.userName}"></option>
</select>
Refer this post for more detail

Related

my tag helper Drop Down Select Will Not render properly in .NET core

<div class="form-group">
<label asp-for="roles" class="control-label"></label>
<select asp-for="roles" class="custom-select" asp-items="#(new Select List(ViewBag.Roles))">
<option value="">Assign Role</option>
</select>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">State</label>
<div class="col-sm-10">
<select asp-for="roles" class="form-control custom-select" style="height:20px" asp-items="#(new Select List(ViewBag.Roles,"Id","Name"))">
<option value="">Large select</option>
</select>
</div>
</div>
my drop down in not responsive any help
how to make drop down responsive
It is possible that the value of ViewBag.Roles is assigned incorrectly. Here is an example.
In Index.cshtml
#{
ViewBag.Roles = new List<MyModel>
{
new MyModel{Id=1, Name="thisname"},
new MyModel{Id=2, Name="thisname2"},
};
}
<div class="form-group row">
<label class="col-sm-2 col-form-label">State</label>
<div class="col-sm-10">
<select asp-for="roles" class="form-control custom-select" style="height:20px"
asp-items="#(new SelectList(ViewBag.Roles,"Id","Name"))">
<option value="">Large select</option>
</select>
</div>
Here is the model.
public class MyModel
{
public int Id { get; set; }
public string Name { get; set; }
}
Result.

Is it possible to generate new instances of the same form by clicking a button?

I'm working on a use case in my animal shelter web application where customers are able to register one or more animals at the same time. Ideally I'd like a button on the bottom left that generates another instance of the same form when clicked, so that multiple animal registrations can be saved to the database at once.
NewAnimalRegistration.cshtml:
#model NewAnimalRegistrationViewModel
<html>
<head>
<title>Register an animal</title>
<link rel="stylesheet" href="~/css/style.css"/>
</head>
<body>
<div class="container py-5">
<div class=" row">
<div class="col-md-10" mx-auto>
<div asp-validation-summary="All"></div>
<h1>Animal registration</h1>
<p>
We are happy to hear that you are interested in placing your animal in our shelter. Please fill in the fields below and our system will
check if there is room for your animal.
</p>
<form asp-action="RegistrationForm" method="post">
<div class="form-group row mt-5">
<div class="col-sm-6">
<label asp-for="Name">Name</label>
<input asp-for="Name" class="form-control"/>
</div>
</div>
<div class="form-group row mt-5">
<div class="col-sm-4">
<label asp-for="Gender" class="mr-3">Gender</label>
<select class="form-group" asp-for="Gender" asp-items="#ViewBag.Genders"></select>
</div>
<div class="col-sm-4">
<label asp-for="Type" class="mr-3">Animal type</label>
<select class="form-group" asp-for="Type" asp-items="#ViewBag.AnimalTypes"></select>
</div>
<div class="col-sm-4">
<label>Neutered</label>
<div class="form-check">
<input asp-for="IsNeutered" class="form-check-input" type="radio" value="true">
<label class="form-check-label" asp-for="IsNeutered">
Yes
</label>
</div>
<div class="form-check">
<input asp-for="IsNeutered" class="form-check-input" type="radio" value="false">
<label class="form-check-label" asp-for="IsNeutered">
No
</label>
</div>
</div>
</div>
<div class="form-group mt-5">
<label asp-for="Reason">Why are you deciding to put this animal up for adoption?</label>
<textarea class="form-control" asp-for="Reason" rows="6"></textarea>
</div>
<div class="float-right">
<a asp-controller="Home" asp-action="Index" class="btn btn-primary px-4">Cancel</a>
<button class="btn btn-primary px-4">Save</button>
</div>
</form>
</div>
</div>
</div>
</body>
</html>
Is there a way to do this in .NET Core MVC? If yes, will I simply receive a list of all animal registrations through which I can simply loop and add them all to the database?
I made a demo based on your description, you can refer to it:
Model:
public class NewAnimalRegistrationViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public string Gender { get; set; }
public string Type { get; set; }
public bool IsNeutered { get; set; }
public string Reason { get; set; }
}
Index.cshtml:
#model NewAnimalRegistrationViewModel
<html>
<head>
<title>Register an animal</title>
</head>
<body>
<div class="container py-5">
<div class=" row">
<div class="col-md-10" mx-auto>
<div asp-validation-summary="All"></div>
<h1>Animal registration</h1>
<p>
We are happy to hear that you are interested in placing your animal in our shelter. Please fill in the fields below and our system will
check if there is room for your animal.
</p>
<form asp-action="RegistrationForm" method="post">
<div class="float-right">
<a asp-controller="Home" asp-action="Index" class="btn btn-primary px-4">Cancel</a>
<button class="btn btn-primary px-4">Save</button>
</div>
</form>
<a id="add" href='#' class="text-danger">register another animal</a>
</div>
</div>
</div>
</body>
</html>
#section scripts{
<script>
var count = 0;
$(function () {
var actionUrl = "/Home/AddRegistrationForm?count=" + count;
$.get(actionUrl).done(function (data) {
$('body').find('.float-right').before(data);
});
})
$("#add").on("click", function (e) {
e.preventDefault();
count++;
var actionUrl = "/Home/AddRegistrationForm?count=" + count;
$.get(actionUrl).done(function (data) {
$('body').find('.float-right').before(data);
});
})
</script>
}
_RegisterPartial.cshtml:
#model NewAnimalRegistrationViewModel
#{
int i = ViewBag.Count;
}
<h3>Anaimal #i</h3>
<div class="form-group row mt-5">
<div class="col-sm-6">
<label asp-for="Name">Name</label>
<input asp-for="Name" name="[#i].Name" class="form-control" />
</div>
</div>
<div class="form-group row mt-5">
<div class="col-sm-4">
<label asp-for="Gender" class="mr-3">Gender</label>
<select class="form-group" asp-for="Gender" name="[#i].Gender" asp-items="#ViewBag.Genders"></select>
</div>
<div class="col-sm-4">
<label asp-for="Type" class="mr-3">Animal type</label>
<select class="form-group" asp-for="Type" name="[#i].Type" asp-items="#ViewBag.AnimalTypes"></select>
</div>
<div class="col-sm-4">
<label>Neutered</label>
<div class="form-check">
<input asp-for="IsNeutered" name="[#i].IsNeutered" class="form-check-input" type="radio" value="true">
<label class="form-check-label" asp-for="IsNeutered">
Yes
</label>
</div>
<div class="form-check">
<input asp-for="IsNeutered" name="[#i].IsNeutered" class="form-check-input" type="radio" value="false">
<label class="form-check-label" asp-for="IsNeutered">
No
</label>
</div>
</div>
</div>
<div class="form-group mt-5">
<label asp-for="Reason">Why are you deciding to put this animal up for adoption?</label>
<textarea class="form-control" asp-for="Reason" name="[#i].Reason" rows="6"></textarea>
</div>
Controller:
public IActionResult Index()
{
return View();
}
[HttpGet]
public IActionResult AddRegistrationForm(int count)
{
ViewBag.Count = count;
ViewBag.Genders = new List<SelectListItem>
{
new SelectListItem{ Text = "Female", Value="Female"},
new SelectListItem{ Text = "Male", Value="Male"}
};
ViewBag.AnimalTypes = new List<SelectListItem>
{
new SelectListItem{ Text = "Cat", Value="Cat"},
new SelectListItem{ Text = "Dog", Value="Dog"}
};
return PartialView("_RegisterPartial");
}
[HttpPost]
public IActionResult RegistrationForm(List<NewAnimalRegistrationViewModel> model)
{
return View();
}
Result:

ASP.NET Core MVC validate not required fields

My page is validating a field that is not required when I submit, even though there is no validation configured for this field.
Create.cshtml
#model Lawtech.App.ViewModels.ProcessoViewModel
#{
ViewData["Title"] = "Novo processo";
}
<h3 style="padding-top: 10px">#ViewData["Title"] </h3>
<hr />
<div class="row">
<div class="col-md-12">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="row">
<div class="form-group col-md-4">
<label asp-for="Numero" class="control-label"></label>
<input asp-for="Numero" class="form-control" />
<span asp-validation-for="Numero" class="text-danger"></span>
</div>
<div class="form-group col-sm-4">
<label asp-for="IdArea" class="control-label"></label>
<div class="input-group">
<select id="slcArea" asp-for="IdArea" class="form-control select2"></select>
<div class="input-group-btn">
<a asp-action="CreateArea" class="btn btn-info" style="border-radius:0 0.25rem 0.25rem 0" data-modal="">
<span class="fa fa-plus"></span>
</a>
</div>
</div>
</div>
</div>
<div class="row">
<div class="form-group col-md-6 mt-4">
<input type="submit" value="Cadastrar" class="btn btn-sm btn-primary" />
<a class="btn btn-sm btn-info" asp-action="Index">Voltar</a>
</div>
</div>
</form>
</div>
</div>
<div id="myModal" class="modal fade in">
<div class="modal-dialog">
<div class="modal-content">
<div id="myModalContent"></div>
</div>
</div>
</div>
ViewModel
public class ProcessoViewModel
{
[Key]
public int Id { get; set; }
[DisplayName("Número")]
[Required(ErrorMessage = "O campo número é obrigatório")]
public string Numero { get; set; }
[DisplayName("Área")]
public int IdArea { get; set; }
}
Controller
In Controller Create method, nothing happens, because all validation takes place on the client side.
[Route("novo-processo")]
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(ProcessoViewModel processoViewModel)
{
try
{
if (!ModelState.IsValid) return View(processoViewModel);
await _processoBLL.Insert(_mapper.Map<ProcessoDTO>(processoViewModel));
if (!ValidOperation()) return View(processoViewModel);
return RedirectToAction(nameof(Index));
}
catch
{
return View();
}
}
Inspecting in Chrome I see this generated html for the field that I didn't require validation, I don't know if it could be something related to Jquery.Unobtrusive but I can't remove it either because other fields will be validated.
<select id="slcArea" class="form-control select2 select2-hidden-accessible input-validation-error" data-val="true" data-val-required="The Área field is required." name="IdArea" data-select2-id="slcArea" tabindex="-1" aria-hidden="true" aria-describedby="slcArea-error" aria-invalid="true"></select>
Why is this validation taking place that I have not defined the field as required?
Not nullable properties (that is properties with value types) are always required. Use nullable types (reference types) for properties if they should not be required - eg. int?.
You can always use formnovalidate on any input you don't want validated.
<input asp-for="Numero" formnovalidate="formnovalidate" class="form-control" />
This way there is no need to change your model. This is demonstrated at W3Schools

Cloning div in angular 2 the select 2 is not working properly

I am using Angular's 2 Reactive forms "Form Array's property" to make a copy of elements in a div on '+' button click, and remove that div form '-'. Button cloning is working fine but when I clone, it destroys the select 2 and select 2 is not cloning in its original form.
Here is the Component Code:
import { Component, OnChanges, AfterViewInit } from '#angular/core';
import { FormArray, FormBuilder, FormGroup} from '#angular/forms';
import { ProductPrice, OrderProduct } from './product-price-model';
declare var $:any;
#Component({
selector: 'app-order-product',
templateUrl: './order-product.component.html',
styleUrls: ['./order-product.component.css']
})
export class OrderProductComponent implements OnChanges,AfterViewInit {
myNav = 'Home';
secondNav = 'Product';
thirdNav = 'Order Products';
isThird = 'true';
OrderProductForm: FormGroup;
OrderProd : OrderProduct;
ngOnInit() {
}
ngAfterViewInit() {
$('.select-search').select2();
}
constructor(private fb: FormBuilder){
this.createForm();
}
createForm() {
this.OrderProductForm = this.fb.group({
// Vendor: '',
// OrderDateTime: '',
// DeliveryDate: '',
productPriceDiv: this.fb.array([]),
});
this.productPriceDiv.push(this.fb.group(new ProductPrice()));
}
ngOnChanges(){
this.setProductPrice(this.OrderProd.ProPrice);
alert("asd");
}
get productPriceDiv(): FormArray {
return this.OrderProductForm.get('productPriceDiv') as FormArray;
};
setProductPrice(ProdPrice: ProductPrice[]) {
const PpFGs = ProdPrice.map(product_price => this.fb.group(product_price));
const PpFormArray = this.fb.array(PpFGs);
this.OrderProductForm.setControl('productPriceDiv', PpFormArray);
}
addProductPriceDiv() {
this.productPriceDiv.push(this.fb.group(new ProductPrice()));
}
removeProductPriceDiv(index) {
this.productPriceDiv.controls.splice(index, 1);
}
}
Here is the HTML:
<form [formGroup]="OrderProductForm" (ngSubmit)="onSubmit()">
<div class="panel-body">
<div class="row">
<div class="col-md-12">
<fieldset class="text-semibold">
<legend><i class="icon-reading position-left"></i> General</legend>
<div class="row">
<div class="col-md-4">
<div class="form-group">
<label>Select Vendor </label>
<select class="select-search">
<optgroup label="Mountain Time Zone">
<option value="AZ">Arizona</option>
<option value="CO">Colorado</option>
<option value="ID">Idaho</option>
<option value="WY">Wyoming</option>
</optgroup>
<optgroup label="Central Time Zone">
<option value="AL">Alabama</option>
<option value="IA">Iowa</option>
<option value="KS">Kansas</option>
<option value="KY">Kentucky</option>
</optgroup>
<optgroup label="Eastern Time Zone">
<option value="CT">Connecticut</option>
<option value="FL">Florida</option>
<option value="MA">Massachusetts</option>
<option value="WV">West Virginia</option>
</optgroup>
</select>
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label>Order DateTime</label>
<div class="input-group">
<span class="input-group-addon"><i class="icon-calendar3"></i></span>
<input type="text" class="form-control" id="anytime-both" value="June 4th 08:47">
</div>
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label>Delivery Date</label>
<div class="input-group">
<span class="input-group-addon"><i class="icon-calendar22"></i></span>
<input type="text" class="form-control daterange-single" value="03/18/2013">
</div>
</div>
</div>
</div>
</fieldset>
</div>
<div formArrayName="productPriceDiv" class="col-md-12">
<fieldset>
<legend class="text-semibold"><i class="icon-truck position-left"></i> Product details</legend>
<div *ngFor="let product_price of productPriceDiv.controls; let i=index" [formGroupName]="i" class="row">
<div class="col-md-4">
<div class="form-group">
<label>Product</label>
<select class="select-search" formControlName="Product">
<optgroup label="Mountain Time Zone">
<option value="AZ">Arizona</option>
<option value="CO">Colorado</option>
<option value="ID">Idaho</option>
<option value="WY">Wyoming</option>
</optgroup>
<optgroup label="Central Time Zone">
<option value="AL">Alabama</option>
<option value="IA">Iowa</option>
<option value="KS">Kansas</option>
<option value="KY">Kentucky</option>
</optgroup>
<optgroup label="Eastern Time Zone">
<option value="CT">Connecticut</option>
<option value="FL">Florida</option>
<option value="MA">Massachusetts</option>
<option value="WV">West Virginia</option>
</optgroup>
</select>
</div>
</div>
<div class="col-md-2">
<div class="form-group">
<label>Unit Cost</label>
<input type="text" class="form-control" formControlName="UnitCost" readonly>
</div>
</div>
<div class="col-md-2">
<div class="form-group">
<label>Quantity</label>
<input type="text" class="form-control" formControlName="Quantity" >
</div>
</div>
<div class="col-md-2">
<div class="form-group">
<label>Cost:</label>
<input type="text" class="form-control" formControlName="Cost" >
</div>
</div>
<div class="col-md-2">
<label></label>
<div class="form-group">
<button type="button" (click)="addProductPriceDiv()" class="btn border-success text-success btn-flat btn-rounded btn-icon btn-xs"><i class="icon-add" ></i></button>
<button type="button" (click)="removeProductPriceDiv(i)" class="btn border-success text-success btn-flat btn-rounded btn-icon btn-xs"><i class="icon-dash" ></i></button>
</div>
</div>
</div>
</fieldset>
</div>
</div>
<div class="text-right">
<button type="submit" class="btn btn-primary">Submit form <i class="icon-arrow-right14 position-right"></i></button>
</div>
</div>
</form>
Try methods in this way, hope your problem get solved
addProductPrice() {
return this._fb.group({
Product: [''],
UnitCost: [''],
Quantity: [''],
Cost: ['']
});
};
createForm() {
this.OrderProductForm = this._fb.group({
ProductPrice: this._fb.array([
this.addProductPrice()
])
});
}
addProductPriceDiv() {
const control = <FormArray>this.OrderProductForm.controls['productPriceDiv'];
control.push(this.addProductPrice());
}
removeProductPriceDiv(index) {
const control = <FormArray>this.OrderProductForm.controls['productPriceDiv'];
control.removeAt(index);
};

How to fill out fields in one form for multiple objects in Thymeleaf?

I have a following models (without getters and setters for readability):
#Entity
public class Recipe extends BaseEntity {
private String name;
private String description;
private Category category;
#OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL)
private List<Ingredient> ingredients;
#OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL)
private List<Instruction> instructions;
#ManyToMany
private List<User> administrators;
private int preparationTime;
private int cookTime;
public Recipe(){
super();
ingredients = new ArrayList<>();
instructions = new ArrayList<>();
administrators = new ArrayList<>();
}
public Recipe(String name, String description, Category category, int preparationTime, int cookTime) {
this();
this.name = name;
this.description = description;
this.category = category;
this.preparationTime = preparationTime;
this.cookTime = cookTime;
}
*
#Entity
public class Ingredient extends BaseEntity {
private String name;
private String condition;
private double quantity;
private Measurement measurement;
#ManyToOne
private Recipe recipe;
public Ingredient(){
super();
}
public Ingredient(String name, String condition, double quantity, Measurement measurement) {
this();
this.name = name;
this.condition = condition;
this.quantity = quantity;
this.measurement = measurement;
}
*
#Entity
public class Instruction extends BaseEntity {
private String name;
private String description;
#ManyToOne
private Recipe recipe;
public Instruction(){
super();
}
public Instruction(String name, String description) {
this();
this.name = name;
this.description = description;
}
What i need to do is to fill out fields for each object in one Thymeleaf form and POST it. I know how to do it with a single object. Please explain how to set up the from and controller for multiple objects, so in the end ill have recipe posted with ingredients and instructions list. Thanks!
EDITED:
Here is a controller methods:
#RequestMapping("/recipes/add")
public String formNewRecipe(Model model) {
Recipe recipe = new Recipe();
if (!model.containsAttribute("recipe")) {
model.addAttribute("recipe", recipe);
}
model.addAttribute("action", "/recipes");
model.addAttribute("heading", "New Recipe");
model.addAttribute("submit", "Save");
model.addAttribute("categories", Category.values());
model.addAttribute("measurements", Measurement.values());
return "edit";
}
#RequestMapping(value = "/recipes", method = RequestMethod.POST)
public String addRecipe(#Valid Recipe recipe,
BindingResult result,
RedirectAttributes redirectAttributes) {
if (result.hasErrors()) {
redirectAttributes
.addFlashAttribute("org.springframework.validation.BindingResult.recipe", result);
redirectAttributes.addFlashAttribute("recipe", recipe);
return "redirect:/recipes/add";
}
recipes.save(recipe);
redirectAttributes.addFlashAttribute("flash",
new FlashMessage("New Recipe Created!!!", FlashMessage.Status.SUCCESS));
return "redirect:/recipes/" + recipe.getId();
}
and Thymeleaf form:
<form th:action="#{${action}}" method="post" th:object="${recipe}">
<div class="grid-100 row controls">
<div class="grid-50">
<h2 th:text="${heading}"></h2>
</div>
<div class="grid-50">
<div class="flush-right">
<input class="button" type="submit" th:value="${submit}"/>
<a th:href="#{|/recipes|}" class="secondary">
<button class="secondary">Cancel</button>
</a>
</div>
</div>
</div>
<div class="clear"></div>
<div class="grid-100 row">
<div class="grid-20">
<p class="label-spacing">
<label> Name </label>
</p>
</div>
<div class="grid-40">
<p><input type="text" th:field="*{name}"/>
<div class="error-message"
th:if="${#fields.hasErrors('name')}"
th:errors="*{recipe.name}">
</div>
</p>
</div>
</div>
<div class="clear"></div>
<div class="grid-100 row">
<div class="grid-20">
<p class="label-spacing">
<label> Description </label>
</p>
</div>
<div class="grid-40">
<p><textarea rows="4" th:field="*{description}"></textarea>
<div class="error-message"
th:if="${#fields.hasErrors('description')}"
th:errors="*{recipe.description}">
</div>
</p>
</div>
</div>
<div class="clear"></div>
<div class="grid-100 row">
<div class="grid-20">
<p class="label-spacing">
<label> Category </label>
</p>
</div>
<div class="grid-30">
<p>
<select th:field="*{category}">
<option value="" disabled="disabled">Recipe Category</option>
<option th:each="c : ${categories}"
th:value="${c.name}"
th:text="${c.name}">All Categories</option>
</select>
</p>
</div>
</div>
<div class="clear"></div>
<div class="grid-100 row">
<div class="grid-20">
<p class="label-spacing">
<label> Prep Time </label>
</p>
</div>
<div class="grid-20">
<p>
<input type="number" th:field="*{preparationTime}"/>
<div class="error-message"
th:if="${#fields.hasErrors('preparationTime')}"
th:errors="*{preparationTime}"></div>
</p>
</div>
</div>
<div class="clear"></div>
<div class="grid-100 row">
<div class="grid-20">
<p class="label-spacing">
<label> Cook Time </label>
</p>
</div>
<div class="grid-20">
<p>
<input type="number" th:field="*{cookTime}"/>
<div class="error-message"
th:if="${#fields.hasErrors('cookTime')}"
th:errors="*{cookTime}"></div>
</p>
</div>
</div>
<div class="clear"></div>
<div class="grid-100 row">
<div class="grid-20">
<p class="label-spacing">
<label> Ingredients </label>
</p>
</div>
<div class="grid-20">
<p class="label-spacing">
<label> Item </label>
</p>
</div>
<div class="grid-20">
<p class="label-spacing">
<label> Condition </label>
</p>
</div>
<div class="grid-15">
<p class="label-spacing">
<label> Quantity </label>
</p>
</div>
<div class="grid-20">
<p class="label-spacing">
<label> Measurement </label>
</p>
</div>
<div class="ingredient-row">
<div class="prefix-20 grid-20">
<p>
<input type="text" th:field="*{ingredients[0].name}"/>
<div class="error-message"
th:if="${#fields.hasErrors('ingredients[0].name')}"
th:errors="*{ingredients[0].name}"></div>
</p>
</div>
<div class="grid-20">
<p>
<input type="text" th:field="*{ingredients[0].condition}"/>
<div class="error-message"
th:if="${#fields.hasErrors('ingredients[0].condition')}"
th:errors="*{ingredients[0].condition}"></div>
</p>
</div>
<div class="grid-15">
<p>
<input type="number" th:field="*{ingredients[0].quantity}"/>
<div class="error-message"
th:if="${#fields.hasErrors('ingredients[0].quantity')}"
th:errors="*{ingredients[0].quantity}"></div>
</p>
</div>
<div class="grid-20">
<p>
<select th:field="*{ingredients[0].measurement}">
<option value="" disabled="disabled">Measurement</option>
<option th:each="i : ${measurements}"
th:value="${i.name}"
th:text="${i.name}">Unknown
</option>
</select>
</p>
</div>
</div>
<div class="prefix-20 grid-80 add-row">
<p>
<button>+ Add Another Ingredient</button>
</p>
</div>
</div>
<div class="clear"></div>
<div class="grid-100 row">
<div class="grid-20">
<p class="label-spacing">
<label> Instructions </label>
</p>
</div>
<div class="grid-20">
<p class="label-spacing">
<label> Steps </label>
</p>
</div>
<div class="grid-60">
<p class="label-spacing">
<label> Description </label>
</p>
</div>
<div class="instruction-row">
<div class="prefix-20 grid-20">
<p>
<input type="text" th:field="*{instructions[0].name}"/>
<div class="error-message"
th:if="${#fields.hasErrors('instructions[0].name')}"
th:errors="*{instructions[0].name}"></div>
</p>
</div>
</div>
<div class="instruction-row">
<div class="grid-50">
<p>
<input type="text" th:field="*{instructions[0].description}"/>
<div class="error-message"
th:if="${#fields.hasErrors('instructions[0].description')}"
th:errors="*{instructions[0].description}"></div>
</p>
</div>
</div>
<div class="prefix-20 grid-80 add-row">
<p>
<button>+ Add Another Step</button>
</p>
</div>
</div>
<div class="clear"></div>
<div class="row"> </div>
</form>
It looks something like this:
<form th:object="${recipe}">
<input type="text" th:field="*{ingredients[0].name}" />
<input type="text" th:field="*{ingredients[0].description}" />
<input type="text" th:field="*{instructions[1].name}" />
<input type="text" th:field="*{instructions[1].description}" />
</form>
If you have a dynamic amount of ingredients, the th:each might look like this:
<th:block th:each="ingredient,i : ${recipe.ingredients}">
<input type="text" th:field="*{ingredients[__${i.index}__].name}" /><br />
<input type="text" th:field="*{ingredients[__${i.index}__].condition}" /><br />
<input type="text" th:field="*{ingredients[__${i.index}__].quantity}" /><br />
<input type="text" th:field="*{ingredients[__${i.index}__].measurement.anotherField}" /><br />
</th:block>
Dynamically adding another ingredient to the form is kind of painful... you either have to submit the form and modify the Recipe object in a controller (adding an ingredient, then redirecting back to the form). Or you can use javascript to copy the fields, making sure the name/id/etc match the others, with the index incremented.

Resources