Send list object from thymeleaf to controller - spring-mvc

I have a big problem about saving an Object list from Thymeleaf to the controller. Object List in thymeleaf is generated by Jquery. but I don't know how to get the data to Controller, that Object list doesn't know the size. Because users can add it anytime.
Please help me to send a list object in thymeleaf to controller.
I’ve Created a new class with 1 properties: ArrayList loaiDoans;
"LoaiDoan" is a Object that i want to save.
And using that class is an object to Save List "LoaiDoan" from thymeleaf to controller.
But List don't know the size first.because that genarated in thymeleaf. The first time i load the Model, the Model contain List is empty,so that list is not display in screen.
This is my class
public class ListLoaiDoan {
private ArrayList<LoaiDoan> loaiDoans;
//Getter Setter
}
My controller bind list object from controller to thymeleaf
#RequestMapping("/luunhieuobject")
public String LoadNhieuObjectCungLuc(Model model) {
ListLoaiDoan listLoaiDoanAAA = new ListLoaiDoan();
model.addAttribute("listLoaiDoan111",listLoaiDoanAAA);
return "/MHtrangchu/LuuNhieuObjCungLuc";
}
//This is the method save list Object from thymeleaf to controller
#PostMapping("/luunhieuobject")
public String processQuery(#ModelAttribute("listLoaiDoan111") ListLoaiDoan listLoaiDoan) {
System.out.println(listLoaiDoan.getLoaiDoans() != null ? listLoaiDoan.getLoaiDoans().size() : "List Empty");
System.out.println("--");
return "/MHtrangchu/LuuNhieuObjCungLuc";
}
LuuNhieuObjCungLuc.html
<form th:object="${listLoaiDoan111}" method="post" th:action="#{/luunhieuobject}">
<!--INPUT FIELDS-->
<div class="row">
<div class="col">
<div id="movieList">
<div class="row">
<div style="margin-left:100px;" class="col-4 form-group">tenloaidoan</div>
<div style="margin-left:100px;" class="col-4 form-group">madoan</div>
</div>
<div class="row item" th:each="row, stat : ${listLoaiDoan111.loaiDoans}">
<div class="col-lg-6 form-group">
<input th:field="*{loaiDoans[__${stat.index}__].tenloaidoan}" type="text" class="form-control"/>
</div>
<div class="col-lg-6 form-group">
<input th:field="*{loaiDoans[__${stat.index}__].madoan}" type="text" class="form-control"/>
</div>
</div>
</div>
</div>
</div>
<!--ADD NEW ROW BUTTON-->
<div class="row">
<div class="col">
<button type="button" class="btn btn-success" onclick="addRow()">Add row</button>
</div>
</div>
<!--SUBMIT FORM BUTTON-->
<div class="row text-right">
<div class="col">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</div>
</form>
That not display annything in the screen, i know that because "listLoaiDoanAAA" is the empty and "th:each" in thymeleaf have nothing to show, how to generate "input" tag and save to controller help me !

I solved this problem !
Set a size for ArrayList before bind it to thymeleaf !
I save my day. thanks Stackoverflow.
i fix my controller like this
#GetMapping("/luunhieuobject")
public String LoadNhieuObjectCungLuc(Model model) {
ListLoaiDoan listLoaiDoanAAA = new ListLoaiDoan();
ArrayList<LoaiDoan> LDD = new ArrayList<LoaiDoan>(10);
listLoaiDoanAAA.setLoaiDoans(LDD);
model.addAttribute("listLoaiDoan111", listLoaiDoanAAA);
return "/MHtrangchu/LuuNhieuObjCungLuc";
}

Related

How to submit an exisitng object from the view via a from in Thymeleaf with Spring MVC?

In my Thymeleaf view, I have some objects shown on the page:
Here's the HTML
<form method="post" action="#" th:action="#{/basket}" th:object="${itemOrder}">
<div class="card-body">
<h4 class="card-title">
item title
</h4>
<h5 th:text="${item.price} + ' €'">item price</h5>
<input type="hidden" th:field="*{sku}"/> <br>
<input type="submit" value="Add to basket" />
</div>
</form>
<div class="container" th:if="${itemOrder != null}">
<h4>Item in the basket:</h4><br>
<h5 th:text="${itemOrder.price} + ' €'">item price</h5>
</div>
Under each element, I have a form to submit this element to the controller.
All Thymeleaf and Spring MVC guides are showing how to submit a form when a user provides data through input fields. Here, I don't need to take in any data from the user, all the data is available already. I just need to wrap existing data and submit it with a form. How can I make it with Thymeleaf and Spring MVC?
Here's the BasketController that should handle the form reu
#PostMapping("/basket")
public String processItemOrder(#ModelAttribute ItemOrder itemOrder, Model model) {
model.addAttribute("itemOrder", itemOrder);
return "basket";
}

How to pass value from drop-down list

I'm working with the Spring MVC + Thymeleaf project, and I have a problem with passing the field value to object.
There are the malt and country entities. In malt form, there is a drop-down list, that are populated from DB - only country names - nothig fancy. I'm able to populate the list, but when I'm clicking "submit" button, there are some errors. Code below (only relevant parts):
Malt entity:
#Setter
#Getter
#NoArgsConstructor
#Entity
#ToString
#Table(name="malt")
public class Malt extends BaseEntity {
#Column(name="malt_name")
private String maltName;
#ManyToOne(fetch=FetchType.EAGER,
cascade= {CascadeType.PERSIST, CascadeType.MERGE,
CascadeType.DETACH, CascadeType.REFRESH})
#JoinColumn(name="producer_id")
private Producer producer;
#Column(name="malt_filling")
private int maltFilling;
#Column(name="malt_ebc")
private int maltEbc;
#Column(name="malt_usage")
private String maltUsage;
#ManyToOne(fetch=FetchType.EAGER,
cascade= {CascadeType.PERSIST, CascadeType.MERGE,
CascadeType.DETACH, CascadeType.REFRESH})
#JoinColumn(name="country_id")
private Country country;
#ManyToMany(mappedBy="malts")
private Set<Batch> batches;
Malt controller:
#Controller
#RequestMapping("/malt")
public class MaltController {
#ModelAttribute("countries")
public Collection<Country> populateCountries() {
return countryService.findAll();
}
#RequestMapping("{id}/update")
public String updateMalt(#PathVariable String id, Model model) {
model.addAttribute("malt", maltService.findById(Long.valueOf(id)));
return "malt-form";
}
#PostMapping
public String saveOrUpdate(#ModelAttribute Malt malt) {
Malt savedMalt = maltService.save(malt);
return "redirect:/malt/" + savedMalt.getId() + "/malt-show";
}
Malt form:
<div class="form-field-input">
<select class="form-control" th:field="*{id}">
<option value="0">Select country</option>
<option
th:each="country : ${countries}"
th:value="${country.id}"
th:text="${country?.countryName}">
</option>
</select>
</div>
<div class="form-field-submit">
<button class="submit-button" type="submit">Submit</button>
</div>
Malt show template:
<div class="wrapper">
<div class="main">
<div class="page-title">
<p th:text="${malt.maltName}">Malt name</p>
</div>
<div class="show">
<div class="form-row">
<div class="form-field-name">
<label>Producer:</label>
</div>
<div class="form-field-input">
<p th:text="${malt.producer.producerName}">Producer name</p>
</div>
</div>
<div class="form-row">
<div class="form-field-name">
<label>Country:</label>
</div>
<div class="form-field-input">
<p th:text="${malt.country.countryName}">Country</p>
</div>
</div>
<div class="form-row">
<div class="form-field-name">
<label>Malt filling:</label>
</div>
<div class="form-field-input">
<p th:text="${malt.maltFilling}">Malt filling</p>
</div>
</div>
<div class="form-row">
<div class="form-field-name">
<label>Malt usage:</label>
</div>
<div class="form-field-input">
<p th:text="${malt.maltUsage}">Malt usage</p>
</div>
</div>
<div class="form-row">
<div class="form-field-name">
<label>Malt EBC:</label>
</div>
<div class="form-field-input">
<p th:text="${malt.maltEbc}">Malt EBC</p>
</div>
</div>
</div>
</div>
</div>
End error that I'm getting:
An error happened during template parsing (template: "class path resource [templates/malt-show.html]")
org.thymeleaf.exceptions.TemplateInputException: An error happened during template parsing (template: "class path resource [templates/malt-show.html]")
.
.
.
Caused by: org.attoparser.ParseException: Exception evaluating SpringEL expression: "malt.country.countryName" (template: "malt-show" - line 44, col 11)
at org.attoparser.MarkupParser.parseDocument(MarkupParser.java:393)
at org.attoparser.MarkupParser.parse(MarkupParser.java:257)
at org.thymeleaf.templateparser.markup.AbstractMarkupTemplateParser.parse(AbstractMarkupTemplateParser.java:230)
... 52 more
Caused by: org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating SpringEL expression: "malt.country.countryName" (template: "malt-show" - line 44, col 11)
at org.thymeleaf.spring5.expression.SPELVariableExpressionEvaluator.evaluate(SPELVariableExpressionEvaluator.java:290)
.
.
.
Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1007E: Property or field 'countryName' cannot be found on null
Link to repo: https://github.com/fangirsan/maruszka-new/tree/malt-form-problem
I've tried a lot of different approaches, but with no result.
As the exception said, problem is ${malt.country.countryName} inside your malt-show form. In the last line of exception stacktrace, I see Property or field 'countryName' cannot be found on null. This means that you are trying to get a property of related model that is null. Probably, the column country_id in your malt table is null. In the other words, country_id not saved with the other fields. With these assumptions, you will find the problem in the malt-form form where you are saving malts. I inspected this form, probably the problem is <select class="form-control" th:field="*{id}">. I think this must be changed to <select class="form-control" th:field="*{country.id}">.
Important Note:
Some of related models can be null, for example assume country in your malt model can be null(it depends on application business logic). In case of nullable relations, accessing the model relation fields in ${model.relation.field} pattern may produce above error. So you should use null checking inside your thymeleaf template in those cases.

ASP.NET Core 2.1 File Upload Form doesn't reach controller

This is my controller
[ValidateAntiForgeryToken]
[HttpPost("Save")]
public async Task<ActionResult> SaveAsync(UploadDocumentViewModel Input)
{
var filePath = $"{this.hostingEnvironment.WebRootPath}/documents";
foreach(var item in Input.Files)
{
var fileName = ContentDispositionHeaderValue.Parse(item.ContentDisposition).FileName;
var fullFilePath = Path.Combine(filePath, fileName);
using(var stream = new FileStream(fullFilePath, FileMode.Create))
{
await item.CopyToAsync(stream);
}
}
return this.Ok();
}
This is the form that I am uploading from
#using (Html.BeginForm("Save", #ViewContext.RouteData.Values["controller"].ToString(), FormMethod.Post, new { enctype = "multipart/form-data",id = "form" }))
{
#Html.AntiForgeryToken()
<div class="col-sm-12">
<div class="row">
<div class="col-sm-12">
<div class="form-group">
#Html.LabelFor(m => m.Categories)
#Html.DropDownListFor(m => m.Categories, (SelectList)Model.Categories, "", new { #class = "form-control col-sm-12" })
#Html.ValidationMessageFor(m => m.Categories)
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<input id="Files" type="file" name="Files" multiple />
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<hr />
</div>
</div>
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<div class="clearfix">
<div class="pull-right">
<input type="submit" id="submit" value="Save" class="btn btn-primary" />
#Html.ActionLink("Cancel", "Index", #ViewContext.RouteData.Values["controller"].ToString(), new { }, new { #class = "btn btn-outline-secondary" })
</div>
</div>
</div>
</div>
</div>
</div>
}
When I am trying to upload my file, I get transferred to a 404 page. However, If I remove the async Task items from my controller, I can reach the controller just fine.
I am trying to follow this Microsoft tutorial but I can't seem to get it to work:
https://learn.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads?view=aspnetcore-2.1
Your form is attempting to generate a route for a "Save" action, but you action name is actually SaveAsync. It worked when you removed the async stuff, because you likely changed the method name to Save as well at that point.
It's not traditional to name actions with the Async suffix, even if they are async (unlike other types of methods). As a result, I'd recommend simply removing the Async part of the method name, and you'll be fine. Otherwise, you need to change the reference in your form from "Save" to "SaveAsync" to match the name of the action.
FWIW, you're killing yourself using the HtmlHelper methods here. Use the tag helpers instead. For example, your from could be as simple as:
<form method="post" asp-action="Save" enctype="multipart/form-data">
...
</form>

Use a helper to set a css class

I need to make reactive a class inside a const (exported from a module).
export const messageControls = '
<div id="controls"">
<i id="idcont" class="{{starred}}"></i>
</div>
'
This class belongs to an HTML block who's inserted as innerHTML of a createElement.
var newElement = document.createElement('div');
newElement.id = i._id;
newElement.className = "single_message";
newElement.innerHTML = messageControls;
document.getElementById('conversation_details').appendChild(newElement);
The {{helper}} below is not rendering anything :
starred: function () {
return 'bob';
},
<i id="idcont" class="{{starred}}"></i> gives {{starred}} in plain text
<i id="idcont" class=" ' + {{starred}} + ' "></i> breaks all
Any idea?
Update - full Blaze template as requested
<template name="inbox">
<div class="searchmessages">
<input type="text" name="searchmessages" id="searchmessages" placeholder="  any word / any date">
</div>
<div class="row">
<div class="col-xs-4 l-O list_messages">
<div id="gridreceived" class="gridmessages">
{{#each incoming_all}}
<div id="{{_id}}" class="item {{readornot}}">
<div class="item-content">
<div class="task_inlist">
<div class="task_from">
{{{from}}}
</div>
<div class="task_date">
{{initialdate}}
</div>
<div class="task_subject">
{{{subject}}}
</div>
<div class="task_content">
{{{htmlbody}}}
</div>
</div>
</div>
</div>
{{/each}}
</div>
<div class="grid_nomatch">{{grid_noresult}}</div>
</div>
<div id="conversation_details" class="col-xs-8" media="print">
<!--
here are each selected message details
-->
</div>
</div>
</template>
You're trying to inject spacebars template markup directly into the DOM but meteor-blaze wants to use spacebars to build the DOM. It doesn't watch the DOM for arbitrary changes and then make template substitutions inside of it!
You can instead use Meteor's reactivity to automatically insert new items into the DOM for you based on changes to the underlying data. In your case it looks like you're trying to show the details of a message that's been clicked on. You probably have a template event handler already to catch the click. In that template handler you can set a Session variable which indicates which message is currently selected and then use that Session variable inside the helper that renders the message details.
For example:
<template name="inbox">
<div class="searchmessages">
<input type="text" name="searchmessages" id="searchmessages" placeholder="  any word / any date">
</div>
<div class="row">
<div class="col-xs-4 l-O list_messages">
<div id="gridreceived" class="gridmessages">
{{#each incoming_all}}
<div id="{{_id}}" class="item {{readornot}}">
// render summary of each message
</div>
{{/each}}
</div>
<div class="grid_nomatch">{{grid_noresult}}</div>
{{#with selectedMessage}}
<div id="conversation_details" class="col-xs-8" media="print">
// render selected message details
</div>
{{/with}}
</div>
</template>
Your js:
Template.inbox.events({
'click .item'(ev) {
Session.set('selectedMessageId',this._id);
}
});
Template.inbox.helpers({
selectedMessage() {
return Messages.findOne(Session.get('selectedMessageId'));
}
});
Now to your follow-up question about how to reactively style an element. Let's say your message* object has aisStarredboolean key. In the message detail section we've set the data context using{{#with currentMessage}}` so any key of the current message can be used directly in our spacebars template. Where you are displaying the message details you can do:
<div id="controls"">
<i id="idcont" class="{{#if isStarred}}starred{{/if}}"></i>
</div>
Depending on whether or not the message is starred this will render as class="" or class="starred".

Spring MCV 3 showErrors doesn't display anything

I try to validate a simple form. The validation is well executed but the result page doesn't display the errors.
I use velocity to render the page.
I've used as example the PetClinic project from spring website.
Here is my controller when I hit the "post form" button:
#Controller
#RequestMapping("/subscription")
public class SubscriptionController {
#RequestMapping(value = "/newCustomer", method = RequestMethod.POST)
public String processSubmit(#ModelAttribute Customer customer, BindingResult result, SessionStatus status) {
new CustomerValidator().validate(customer, result);
if (result.hasErrors()) {
return "subscription";
}
else {
status.setComplete();
return "redirect:/admin";
}
}
}
When I go in debug, I see the errors. I'm successfully redirected on the subscription page but the errors are not displayed.
My webpage (simplified):
...
#springBind("customer")
#springShowErrors("<br/>" "")
<form class="form-horizontal" method="post" action="#springUrl("/subscription/newCustomer/")">
....
<!-- Button -->
<div class="controls">
<button class="btn btn-primary">#springMessage("website.subscription.signup")</button>
</div>
</form>
...
if you need anything else, don't hesitate to tell me. Thanks for your help! I'm stuck on this since several days.
EDIT :
I finally found the error. It was with the springBind tag. I didn't well understand that you need to bind the field to show the associated error. Here is the fixed code for one field for twitter bootstrap framework.
#springBind("customer.name")
<div class="control-group #if(${status.error})error#end">
<!-- Prepended text-->
<label class="control-label">#springMessage("website.subscription.name")</label>
<div class="controls">
<div class="input-prepend">
<span class="add-on"><i class="icon-user"></i></span>
<input class="input-xlarge"
placeholder="John Doe" id="name" name="name" type="text">
</div>
<p class="help-block">
#springShowErrors("<br/>" "")
</p>
</div>
</div>
springShowErrors(...) will show all the errors associated with the field name of the POJO customer.

Resources