I use spring boot with thymeleaf 3
I try to display a bean
In my controller I have
public String getNewCarsTemplate(Model model) {
model.addAttribute("cars", new Cars());
}
In cars I have
#OneToOne
private Cities cities; // field id and name
#OneToMany(mappedBy = "car")
private List<Locations> locations = new ArrayList<>();
In location I have
private Integer id;
private String name;
#ManyToOne
private Suppliers supplier;
In my thymeleaf fragment
<form id="carsForm" action="#" th:action="#{/template/new/cars}" th:object="${cars}" th:method="post">
...
<input type="hidden" th:field="*{cities.id}" >
<input id="carsCities" class="form-control js-typeahead" type="search" placeholder="Type partial value" th:field="*{cities.name}" autocomplete="off" >
...
<table id="locationsTable" class="table table-striped table-hover responsive">
<thead>
<tr>
<th th:text="#{name}">Name</th>
<th></th>
</tr>
</thead>
<tbody>
<tr th:each="car, stat : ${cars}">
<td>
<input type="hidden" th:id="${'locationId-'+stat.index}" th:field="*{car.locations[__${stat.index}__].id}" />
<input type="text" class="form-control" th:id="${'locationName-'+stat.index}" th:placeholder="#{name.placeholder}" placeholder="Name" th:field="*{car.locations[__${stat.index}__].name}" />
</td>
<td class="align-middle"> <i class="fas fa-trash-alt"></i></td>
</tr>
</tbody>
</table>
</form>
When I try to display this fragment I get
org.attoparser.ParseException: Exception evaluating SpringEL
expression: "cities.id" Caused by:
org.springframework.expression.spel.SpelEvaluationException: EL1008E:
Property or field 'id' cannot be found on object of type
'java.util.ArrayList' - maybe not public or not valid?
Could you try substituting
<input type="hidden" th:field="*{cities.id}" >
with
<input type="hidden" th:field="*{cities?.id}" >
and let me know if this works for you.
The '?' operator will address the problem that in the new Cars() object the cities is null
Related
I really feel like this should be easy but I’m thinking it may have changed with .Net 6. I can pass values to my controller with the input “name=’name’” but for some reason I cannot get any values from my model into my controller. I am trying to POST my row values to the controller. I am using an enumerable. I’m not sure if I should be using a or not. Another thing is how should I be populating my table row from a loop of the model. I thought using #Html. Was for older .net and tag helpers are the new way but I couldn’t get any to work populating my rows.
<form method="post">
<div id="tblPullParts" class="container justify-content-center mt-3">
<table class="table table-striped">
<thead>
<tr>
<th></th>
<th >Order #</th>
<th >Item</th>
<th >Description</th>
<th >Quantity</th>
</tr>
</thead>
<tbody>
#foreach (var p in Model)
{
<tr>
<td><input type="radio" id="radio" name="radio"
value="#Html.DisplayFor(item => p.PartID)" /></td>
#*<td><input asp-for="Selected" type="radio" value="Selected" /></td>*#
<th scope="row">#Html.DisplayFor(item => p.PartID)</th>
<td>#Html.DisplayFor(item => p.Name)</td>
<td>#Html.DisplayFor(item => p.ItemLocation)</td>
<td>#Html.DisplayFor(item => p.PartGroup)</td>
<td>#Html.DisplayFor(item => p.Description)</td>
<td>
<input type="text" asp-for="#p.Name" id="txtNameN" />
</td>
</tr>
}
</tbody>
</table>
#*<input type="text" id="#Model[0].Name" />*#
<input type="text" id="txtName" name="txtName" value="" />
</div>
<div class="text-center">
<button type="submit" class="btn btn-lg btn-success mt-3">Start Pick</button>
</div>
</form>
[HttpPost]
public async Task<IActionResult> Index( PartVM model, string radio, string txtName)
{
if (model?.PartID != 0)
{
return View("UpdatePickQuantity", model);
}
if (!String.IsNullOrWhiteSpace(txtName))
{
}
//Request.QueryString["radio"];
var lstParts = await _ordersService.GetAllParts();
return View(lstParts);
}
#Html.DisplayFor() can only display the value of model, If you want to submit the value, You need to use <input>,Here is a simple demo, I add hidden input in your form to submit the value:
#model List<PartVM>
#{
int i = 0;
}
<form method="post">
<div id="tblPullParts" class="container justify-content-center mt-3">
<table class="table table-striped">
<thead>
<tr>
<th></th>
<th >Order #</th>
<th >Item</th>
<th >Description</th>
<th >Quantity</th>
</tr>
</thead>
<tbody>
#foreach (var p in Model)
{
<tr>
<td><input type="radio" id="radio" name="radio"
value="#Html.DisplayFor(item => p.PartID)" /></td>
#*<td><input asp-for="Selected" type="radio" value="Selected" /></td>*#
<th scope="row">
#Html.DisplayFor(item => p.PartID)
<input type="hidden" asp-for="#p.PartID" name="[#i].PartID">
</th>
<td>
#Html.DisplayFor(item => p.Name)
</td>
<td>
#Html.DisplayFor(item => p.ItemLocation)
<input type="hidden" asp-for="#p.ItemLocation" name="[#i].ItemLocation">
</td>
<td>
#Html.DisplayFor(item => p.PartGroup)
<input type="hidden" asp-for="#p.PartGroup" name="[#i].PartGroup">
</td>
<td>
#Html.DisplayFor(item => p.Description)
<input type="hidden" asp-for="#p.Description" name="[#i].Description">
</td>
<td>
<input type="text" asp-for="#p.Name" name="[#i].Name" id="txtNameN" />
</td>
</tr>
i++;
}
</tbody>
</table>
#*<input type="text" id="#Model[0].Name" />*#
<input type="text" id="txtName" name="txtName" value="" />
</div>
<div class="text-center">
<button type="submit" class="btn btn-lg btn-success mt-3">Start Pick</button>
</div>
</form>
Controller
[HttpPost]
public async Task<IActionResult> Index( List<PartVM> model, string radio, string txtName)
{
//.....
}
Demo:
Your second question can refer to this github issue.
Edit============================
If you want just want to pass the row where radio is selected, you need to js to achieve this. refer to this:
Create a ViewModel
public class PartvmViewModel
{
public int PartID { get; set; }
public string Name { get; set; }
public string ItemLocation { get; set; }
public string PartGroup { get; set; }
public string Description { get; set; }
public string? txtName { get; set; }
}
controller
public IActionResult Display()
{
List<PartvmViewModel> viewmodel = new List<PartvmViewModel>();
//pass data from PartVM to PartvmViewModel
foreach (var item in PartVms)
{
viewmodel.Add(new PartvmViewModel()
{
PartID = item.PartID,
Description = item.Description,
ItemLocation = item.ItemLocation,
Name = item.Name,
PartGroup = item.PartGroup
});
}
return View(viewmodel);
}
[HttpPost]
public IActionResult Display([FromBody]PartvmViewModel model)
{
//because the value of radio is equal to PartID,SO i don't write it as parameter here
//.........
}
View
#model List<PartvmViewModel>
#{
int i = 0;
}
<form method="post">
<div id="tblPullParts" class="container justify-content-center mt-3">
<table class="table table-striped">
<thead>
<tr>
<th></th>
<th >Order #</th>
<th >Item</th>
<th >Description</th>
<th >Quantity</th>
</tr>
</thead>
<tbody>
#foreach (var p in Model)
{
<tr>
<td><input type="radio" name="radio" onclick="Method(this)"
value="#Html.DisplayFor(item => p.PartID)" /></td>
#*<td><input asp-for="Selected" type="radio" value="Selected" /></td>*#
<th scope="row">
#Html.DisplayFor(item => p.PartID)
<input type="hidden" asp-for="#p.PartID" name="[#i].PartID" id="PartID">
</th>
<td>
#Html.DisplayFor(item => p.Name)
</td>
<td>
#Html.DisplayFor(item => p.ItemLocation)
<input type="hidden" asp-for="#p.ItemLocation" name="[#i].ItemLocation" id="ItemLocation">
</td>
<td>
#Html.DisplayFor(item => p.PartGroup)
<input type="hidden" asp-for="#p.PartGroup" name="[#i].PartGroup" id="PartGroup">
</td>
<td>
#Html.DisplayFor(item => p.Description)
<input type="hidden" asp-for="#p.Description" name="[#i].Description" id="Description">
</td>
<td>
<input type="text" asp-for="#p.Name" name="[#i].Name" id="txtNameN" />
</td>
</tr>
i++;
}
</tbody>
</table>
#*<input type="text" id="#Model[0].Name" />*#
<input type="text" id="txtName" name="txtName" value="" />
</div>
<div class="text-center">
<button class="btn btn-lg btn-success mt-3" onclick="Submit()">Start Pick</button>
</div>
</form>
#section Scripts
{
<script>
var Data;
function Method(obj){
this.Data = {
"PartID":$(obj).val(),
"ItemLocation" : $(obj).parent().parent().find('td:eq(2)').find(':input').val(),
"PartGroup" : $(obj).parent().parent().find('td:eq(3)').find(':input').val(),
"Description" : $(obj).parent().parent().find('td:eq(4)').find(':input').val(),
"Name" : $(obj).parent().parent().find('td:eq(4)').find(':input').val(),
}
}
function Submit(){
Data.txtName = $("#txtName").val();
$.ajax({
url : '/Home/Display',
type : 'post',
data : JSON.stringify(Data),
contentType : 'application/json'
});
}
</script>
}
My opinion==========================
Actually, In my opinion, Pass the list of data is ok. Because i notice that you pass the value of radio into the controller, You can just use:
[HttpPost]
public async Task<IActionResult> Index( List<PartVM> model, string radio, string txtName)
{
var item = model.Where(i => i.PartID == int.Parse(radio)).FirstOrDefault();
//.....
}
to get the selected row, It is more easier than using js;
Instead of doing this
<th scope="row">
#Html.DisplayFor(item => p.PartID)
<input type="hidden" asp-for="#p.PartID" name="[#i].PartID" id="PartID">
</th>
I would do something like
<th scope="row">
<p>#p.PartID</p>
<input type="hidden" asp-for="#p.PartID" name="[#i].PartID" id="PartID">
</th>
However, I could be wrong because you are using razor syntax and I am not use to that yet ;)
I am trying to redirect to another local web page using a tag in Thymeleaf and Spring boot but it is
not working. I am redirecting from index.html to addEdit.html which are in the same folder.
Here is my code.
index.html
<div class="container">
<p th:text="${message}"></p>
<a th:href="#{/addEdit.html}" class="btn btn-outline-info">Add Employee</a> //not working
<table class="table table-bordered table-dark">
<thead class="">
<tr>
<th>#</th>
<th>Name</th>
<th>Departmen</th>
<th>Position</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr th:each="employee : ${employees}" >
<th th:text="${employee.id}"></th>
<td th:text="${employee.name}"></td>
<td th:text="${employee.department}"></td>
<td th:text="${employee.position}"></td>
<td>
<form action="delete">
<input type="submit" value="Delete" class="btn btn-outline-warning"/>
</form>
</td>
<td>
<form action="edit">
<input type="submit" value="Edit" class="btn btn-outline-info"/>
</form>
</td>
</tr>
</tbody>
</table>
</div>
my EmployeeController
#Autowired
private employeeRepo repo;
#RequestMapping("/")
public String home(Model model) {
List<Employee> list = new ArrayList<>();
list = repo.findAll();
model.addAttribute("employees",list);
return "index";
}
#PostMapping("/addEmployee")
public void addEmployee(Employee employee,Model model) {
repo.save(employee);
model.addAttribute("message","Add Successfully");
home(model);
}
my addEdit.html
<div class="container bg-light">
<form action="addEmployee">
<input class="form-control form-control-sm" type="text" placeholder="Name" name="name"><br>
<input class="form-control form-control-sm" type="text" placeholder="Department" name="department"><br>
<input class="form-control form-control-sm" type="text" placeholder="Position" name="postion"><br/>
<input type="submit" value="Add Employee" class="btn btn-outline-success btn-lg btn-block">
</form>
</div>
You should not include the .html in your link. The link should point to a URL that your controller exposes. There is currently no controller method that exposes the /addEdit url for example.
So update your controller:
#GetMapping
public String addEmployee(Model model) {
// add model attributes here as needed first
return "addEdit" // This refers to the view, so 'addEdit.html'
}
Now update the link to:
<a th:href="#{/addEdit}" class="btn btn-outline-info">Add Employee</a>
As shown in my post here the GET action method Test(..) works fine when using foreach loop in the corresponding Test.chtml view but the POST action method Test(...) returns null. But, as mentioned by many users, the foreach is not reliable for POST method. So, I decided to use the for loop as shown below. But that returned unexpected results in the view since, according to this post, in a foreachloop type casting is done automatically but in for loop you have to type cast the objects Model[i].BlogID etc to a proper class object type.
So, I decided to type cast the objects Model[i].BlogID etc to a BlogsWithRelatedPostsViewModel class object type as shown in the second version of Test.cshml view below; and this time the Test.cshtml view is displayng the correct records. But although the submit button in the view is sending a a valid model (ModelState.IsValid is true) the Model.Count is 0 that results in no update to database. Why Model.Count is 0 and how to correct it? As you can see below the html page source of the view is showing the name attributes of the tags matching the property values in the View Model.
Note: For complete code, please see this OP. I'm using ASP.NET Core with EF Core and Tag Helpers.
Test.cshtml view with for loop - without type casting the loop objects:
#model IList<ASP_Core_Blogs.Models.BlogPostViewModels.BlogsWithRelatedPostsViewModel>
#using ASP_Core_Blogs.Models.BlogPostViewModels
#{ ViewData["Title"] = "Index"; }
<div class="row">
<div class="col-md-12">
<form asp-controller="Blogs" asp-action="Test" asp-route-returnurl="#ViewData["ReturnUrl"]" method="post">
#{
IEnumerable<SelectListItem> yearsList = (IEnumerable<SelectListItem>)ViewBag.YearsList;
var currentlySelectedIndex = 0; // Currently selected index (usually will come from model)
}
<strong>Select a Post Year</strong>
<h6>Choose a year and a URL to begin:</h6>
<label>Year:</label><select asp-for="#currentlySelectedIndex" asp-items="yearsList"></select><input type="submit" class="btn btn-default" name="GO" value="GO" />
<table class="table">
<thead>
<tr>
<th></th>
<th></th>
<th>Url</th>
<th>Title</th>
<th>Content</th>
</tr>
</thead>
<tbody>
#for (int i=0; i< Model.Count(); i++)
{
<tr>
<td><input type="hidden" asp-for="#Model[i].BlogID" /></td>
<td><input type="hidden" asp-for="#Model[i]).PostID" /></td>
<td>
<input type="text" asp-for="#Model[i].Url" style="border:0;" readonly />
</td>
<td>
<input asp-for="#Model[i].Title" />
</td>
<td>
<input asp-for="#Model[i].Content" />
</td>
</tr>
}
</tbody>
</table>
<button type="submit" class="btn btn-default">Save</button>
</form>
</div>
</div>
Test.cshtml view with for loop objects being casted as BlogsWithRelatedPostsViewModel class objects:
#model IList<ASP_Core_Blogs.Models.BlogPostViewModels.BlogsWithRelatedPostsViewModel>
#using ASP_Core_Blogs.Models.BlogPostViewModels
#{ ViewData["Title"] = "Index"; }
<div class="row">
<div class="col-md-12">
<form asp-controller="Blogs" asp-action="Test" asp-route-returnurl="#ViewData["ReturnUrl"]" method="post">
#{
IEnumerable<SelectListItem> yearsList = (IEnumerable<SelectListItem>)ViewBag.YearsList;
var currentlySelectedIndex = 0; // Currently selected index (usually will come from model)
}
<strong>Select a Post Year</strong>
<h6>Choose a year and a URL to begin:</h6>
<label>Year:</label><select asp-for="#currentlySelectedIndex" asp-items="yearsList"></select><input type="submit" class="btn btn-default" name="GO" value="GO" />
<table class="table">
<thead>
<tr>
<th></th>
<th></th>
<th>Url</th>
<th>Title</th>
<th>Content</th>
</tr>
</thead>
<tbody>
#for (int i=0; i< Model.Count(); i++)
{
<tr>
<td><input type="hidden" asp-for="((BlogsWithRelatedPostsViewModel)#Model[i]).BlogID" /></td>
<td><input type="hidden" asp-for="((BlogsWithRelatedPostsViewModel)#Model[i]).PostID" /></td>
<td>
<input type="text" asp-for="((BlogsWithRelatedPostsViewModel)#Model[i]).Url" style="border:0;" readonly />
</td>
<td>
<input asp-for="((BlogsWithRelatedPostsViewModel)#Model[i]).Title" />
</td>
<td>
<input asp-for="((BlogsWithRelatedPostsViewModel)#Model[i]).Content" />
</td>
</tr>
}
</tbody>
</table>
<button type="submit" class="btn btn-default">Save</button>
</form>
</div>
</div>
A Portion of html generated by View after Submit:
<tr>
<td><input type="hidden" data-val="true" data-val-required="The BlogID field is required." id="BlogID" name="BlogID" value="1" /></td>
<td><input type="hidden" data-val="true" data-val-required="The PostID field is required." id="PostID" name="PostID" value="1" /></td>
<td>
<input type="text" style="border:0;" readonly id="Url" name="Url" value="blog1#test.com" />
</td>
<td>
<input type="text" id="Title" name="Title" value="Title1" />
</td>
<td>
<input type="text" id="Content" name="Content" value="Content1" />
</td>
</tr>
I will create a list with checkboxlist. For those I use following code:
<s:form action="accept" namespace="/manager/course">
<s:checkboxlist list="courseRequests" name="acceptList" listValue="studentNickname" listKey="studentId" theme="checkbox-fix"/>
<s:url action="accept" namespace="/manager/course" var="accList" />
<s:a href="%{accList}"><s:text name="Accept"/> </s:a>
</s:form>
It work fine a create a check box list, that you can see its pic in the following:
and this is html code generated by above code:
<form id="accept" name="accept" action="/ESA/manager/course/accept.action" method="post">
<table class="wwFormTable">
<table class="gradienttable">
<tr>
<th class="row"><p>Row</p></th>
<th style="width: 240px;"><p>Student</p></th>
<th ><p>Accept</p></th>
</tr>
<tr>
<td id="row"><p><label>1</label></p></td>
<td style="width:250px;"><p>
<label for="acceptList-1" class="checkboxLabel">Mansour Barzegar</label>
</p></td>
<td style="text-align:center;"><p>
<input type="checkbox" name="acceptList" value="5" id="acceptList-1" </p></td>
</tr>
<tr>
<td id="row"><p><label>2</label></p></td>
<td style="width:250px;"><p>
<label for="acceptList-2" class="checkboxLabel">Ali Mahmoudi</label>
</p></td>
<td style="text-align:center;"><p>
<input type="checkbox" name="acceptList" value="6" id="acceptList-2" </p></td>
</tr>
<tr>
<td id="row"><p><label>3</label></p></td>
<td style="width:250px;"><p>
<label for="acceptList-3" class="checkboxLabel">Masih Zare</label>
</p></td>
<td style="text-align:center;"><p>
<input type="checkbox" name="acceptList" value="7" id="acceptList-3" </p></td>
</tr>
</table>
Accept
</table>
</form>
In the Action Class I tried to retrive seleced checkbox value by following code:
private int[] acceptList;
public void setAcceptList(int[] acceptList){
this.acceptList=acceptList;
}
and several other code but I all states I get null.
Do I use wrong code?
in your markup, do this:
<input type="checkbox" name="thename" id="checkbox_id1" value="value1" />
<input type="checkbox" name="thename" id="checkbox_id2" value="value2" />
in your action (or object) do this:
// action/object code...
Set<String> cbox = new HashSet();
public void setThename(String[] thenames) {
for (String thename : thenames) {
cbox.add(thename);
}
}
// action/object code...
notice the checkbox name matches the setter name, e.g. element name == someName and method == setSomeName
Same would apply for Set<Integer>, but you use int[] thenames as the argument. You could also use Integer[] thenames for the argument.
to test output:
if (cbox != null) {
for (String s : cbox) {
log.info(s);
}
}
http://struts.apache.org/release/2.2.x/docs/using-checkboxes.html
I have a dynamic forms with checkbox/radio-button lists & matrices:
Following code renders checkbox list:
#foreach (var sq in Model.SubQuestions)
{
<label>
<input type="hidden" name="answerResult.index" value="#sq.Id" />
<input type="checkbox" name="answerResult[#sq.Id].SubQuestionId" value="#sq.Id" />
#sq.Label.Name
</label>
}
radio-button list:
<input type="hidden" name="answerResult.index" value="#Model.Id" />
#foreach (var sq in Model.SubQuestions)
{
<label>
<input type="radio" name="answerResult[#Model.Id].SubQuestionId" value="#sq.Id" />
#sq.Label.Name
</label>
}
My POST-action in controller:
[HttpPost]
public ActionResult PassageSurvey(int surveyId, int surveyPageIndex, IList<AnswerResult> answerResult)
where IList<AnswerResult> is an auto-bound collection from my form. I get only items that were checked/selected. Everything is going well.
Now I need to get the same collection from checkbox/radio-button matrices.
Radio-button matrix:
<table width="100%">
<tr>
<th></th>
#foreach (var av in Model.AnswerVariants)
{
<th style="text-align: center;">
<label>#av.Label.Name</label>
</th>
}
</tr>
#foreach (var sq in Model.SubQuestions)
{
<tr>
<td>
<label>#sq.Label.Name</label>
<input type="hidden" name="answerResult.index" value="#sq.Id" />
<input type="hidden" name="answerResult[#sq.Id].SubQuestionId" value="#sq.Id" />
</td>
#foreach (var av in Model.AnswerVariants)
{
<td align="center">
<input type="radio" name="answerResult[#sq.Id].AnswerVariantId" value="#av.Id" />
</td>
}
</tr>
}
</table>
Checkbox matrix:
<table width="100%">
<tr>
<th></th>
#foreach (var av in Model.AnswerVariants)
{
<th style="text-align: center;">
<label>#av.Label.Name</label>
</th>
}
</tr>
#foreach (var sq in Model.SubQuestions)
{
<tr>
<td>
<label>#sq.Label.Name</label>
</td>
#foreach (var av in Model.AnswerVariants)
{
<td align="center">
<input type="hidden" name="answerResult.index" value="#sq.Id" />
<input type="hidden" name="answerResult[#sq.Id].AnswerVariantId" value="#sq.Id" />
<input type="checkbox" name="answerResult[#sq.Id].SubQuestionId" value="#sq.Id" />
</td>
}
</tr>
}
</table>
POST-action in controller always the same.
Now from radio-button matrix (in current sample 3x3) IList<AnswerResult> gets always 3 items, depending on items that were selected in rows and columns.
But from Checkbox matrix (3x3) IList<AnswerResult> gets always all 9 items (regardless items were checked, hidden-inputs always have values)
But I want to get only items, that were checked. How could I change my checkbox-matrix template to solve this problem?
Finally I decided to handle this problem in my post-action instead. Removing items (checkboxes) that were not selected, and have nulls in answerResult[##].SubQuestionId fields.