Thymeleaf handling null object in Asterisk - spring

I have a Spring boot App with a Thymeleaf Template in which I define a th:object="${guest}" and I try to access it with the Asterisk syntax like th:value="*{lastName}".
If th:object="${guest} is null it produces an Error: Property or field 'firstName' cannot be found on null. I understand why the error appears. But what is the best way to handle it?
Since I am using th:object="${guest} to prefill values in an form. My current working solution is:
<form th:if="${guest}" th:object="${guest}" ... >
<form th:unless="${guest} ... >
and this is the part I do not understand why can't Thmyleafe workout in the Asterisk that the object is null? But the th:if / th:unless statment gets that th:object is null. There is no case in which accessing an attribute of a null Object is suitable.
And if the object is not there at all it is fine.
Is there a better way with less code?
I also tried:
th:value="*{lastName}?:_" - same error.
th:object="${guest}:?_" - this cant be parsed at all.
Is there a way without the th:if / th:unless. The optimal solution would be to do it in one line so everything stays clean. Can I somehow parse an if in the th:object${}
I could not find something in the documentation or else on the web. Only if Asterisk or ${..} is null
Full Code:
The Fragment:
<form th:fragment="guest_form">
<input type="text" id="firstName" name="firstName" placeholder="Vorname" th:value="*{firstName}" required />
<input type="text" id="lastName" name="lastName" placeholder="Nachname" th:value="*{lastName}" required />
<input type="tel" id="phone" name="phone" placeholder="Telefon Nummer" th:value="*{phone}" />
<input type="email" id="email" name="email" placeholder="Emailadresse" th:value="*{email}" />
<input type="street" id="street" name="street" placeholder="Straße" th:value="*{street}" />
<input type="city" id="city" name="city" placeholder="Stadt" th:value="*{city}" />
<input type="zip" id="zip" name="zip" placeholder="Postleitzahl" th:value="*{zip}" />
<input type="hidden" name="code" th:value="${code}" />
<input th:replace="::csrf_token" />
<input type="submit" id="submitButton" value="Bestätigen" />
</form>
The fragment use:
<form th:include=" fragments/fragments.html::guest_form" th:if="${guest}" th:object="${guest}" action="/register" method="POST"></form>
<form th:include="fragments/fragments.html::guest_form" th:unless="${guest}" action="/register" method="POST"></form>
The function:
#PostMapping("/register")
public String register(#RequestParam String code, Model model, HttpSession session,
RedirectAttributes redirectAttributes, #ModelAttribute("guest") #Valid Guest g,
BindingResult bindingResult) {
model.addAttribute("guest", g);
model.addAttribute("code", code);
// form is not fine
if (bindingResult.hasErrors()) {
return "guest/register";
}
// form is not fine
guestRepo.save(g);
Booking booking = new Booking(g, invRepo.findByCode(code).get());
bookingRepo.save(booking);
session.setAttribute(saBooking, booking);
return "redirect:/ticket";
}
g can be null because you can get redirect to this url - to fill out the form - in this moment g is null. I think I could check if g == null an then Guest g = new Guest(); but this doesnt target my question...

Related

Thymeleaf form array with default values

I'm using Spring+Thymeleaf to see and modify the users in a database. I would like to set the input fields to the actual values of an original user but I've tried with different styles and it doesn't work.
With the present configuration I can update information of users and see the id of original user (it's not in a input field) but I can't show the actual configuration in input field as default.
CONTROLLER:
#GetMapping(value = {"/", ""})
public String subusersPage(HttpSession session, Model model) {
String idUser = BaseController.getLoggedUser(session);
UserDTO userDTO = userService.getUserById(idUser);
model.addAttribute("subusersDTO", userService.getSubusersDTO(userDTO.getSubusers()));
model.addAttribute("populations", userDTO.getPopulations());
model.addAttribute("configurations", userDTO.getConfigurations());
model.addAttribute("attributes", userDTO.getAttributes());
model.addAttribute("subuserDTO", new SubuserDTO());
return "subusers";
}
HTML:
<th:block th:each="subuserDTO_original : ${subusersDTO}">
<hr>
<form action="#" th:action="#{/subusers/__${subuserDTO_original.id}__}" th:object="${subuserDTO}" method="post">
<div>
<p th:text="${'Id: ' + subuserDTO_original.id}"></p>
<p>Name: <input type="text" th:field="*{name}" th:name="name" th:value="${subuserDTO_original.name}"/></p>
<p>Population: <input type="text" th:field="*{population}" th:name="population" th:value="${subuserDTO_original.population}"/></p>
<p>Configuration: <input type="text" th:field="*{configuration}" th:name="configuration" th:value="${subuserDTO_original.configuration}"/></p>
<p>Attributes: <input type="text" th:field="*{attributes}" th:name="attributes" th:value="${subuserDTO_original.attributes}"/></p>
<p>
<button type="submit" th:name="action" th:value="update">Update</button>
<button type="submit" th:name="action" th:value="delete">Delete</button>
<button type="reset" th:name="action" th:value="clear">Clear</button>
</p>
</div>
</form>
<form action="#" th:action="#{/subusers/__${subuserDTO_original.id}__}" method="get">
<button type="submit">Default</button>
</form>
</th:block>
Any help will be very appreciated, thank you!
If you want to edit an existing user, then your th:object (which is ${subuserDTO} in this case) needs to be populated with the values of the original user. This is because when you use the attribute th:field="*{name}", it actually overwrites the name, id, and value of the html tag (which is why th:value="${subuserDTO_original.name}" isn't working.
Two other options you could do:
You could also set name="name" and use th:value instead.
Or another option, you could use ${subuserDTO_original} as your th:object.

How to set default value in thymeleaf th:field

I have a form and I want to set default value in the field below but it's not working.
<span>ID User:</span>
<input type="text" th:value="${session.name}" th:field="*{userid}" th:errorclass="field-error" />
</div>
<div>
<span class="name-in">ID Room:</span>
<input type="text" th:value="${id}" th:field="*{room}" th:errorclass="field-error" />
</div>
I read some topic about this problem and I try to change as given below
th:attr="value = ${session.name}"
But It's still not working. Field ID User is empty. I don't know how to solve this problem.
Although your question contain less information, but i think you want to put default value for all field. If you like to do so change
`<input type="text" th:value="${session.name}" th:field="*{userid}" th:errorclass="field-error" />`
to
<input type="text" name="userid" value="as your wish" th:errorclass="field-error" />
Instead of changing the html, you should instead set the value of *{userid} in your controller. That way you can keep your html the same:
// Controller
modelObject.setUserId(session.name);
// HTML
<input type="text" th:field="*{userid}" th:errorclass="field-error" />

Controller Not receiving value from span in HTML using Spring boot and Thymeleaf

I have the following content in my HTML which is using Thymeleaf
<form action="#" th:action="#{/shutDown}" th:object="${ddata}" method="post">
<span>Domain</span>
<span th:text="${domain}" th:field="*{domain}">domain</span>
<input type="Submit" value="close" />
</form>
And I have the following in my Controller which is using Sprint Boot
#RequestMapping(value = "/shutDown", method = RequestMethod.POST)
public ModelAndView shutDownPage(ModelAndView modelAndView, Authentication authentication,
#ModelAttribute("ddata") DInputBean dInputBean) {
String domain = dInputBean.getdomain();
return modelAndView;
}
I'm hoping I'd get value of domain from the HTML in the Controller but it's always null. DInputBean has getters and setters for "domain" field.
The th:field attribute can be used on <input>, <select>, or, <textarea>.
A solution you could possibly replacing you second <span> with a hidden input element.
<form action="#" th:action="#{/shutDown}" th:object="${ddata}" method="post">
<span>Domain</span>
<input type="hidden" th:field="*{domain}" th:value="${domain}" />
<input type="Submit" value="close" />
</form>
If you wanted to keep the second div, just place the <input type="hidden"> inside the second <span> and remove the th:field attribute from the second <span>.
Edit:
If you wanted to add the value of domain in a span.
<form action="#" th:action="#{/shutDown}" th:object="${ddata}" method="post">
<span>Domain</span>
<span th:text="${domain}">domain<span>
<input type="hidden" th:field="*{domain}" th:value="${domain}" />
<input type="Submit" value="close" />
</form>
http://www.thymeleaf.org/doc/tutorials/3.0/thymeleafspring.html#inputs
An option is to use a read-only input field:
<input type="text" th:field="*{domain}" th:value="${domain}" readonly="readonly"/>
This both displays the value and sends it on submit.
The key is to add the value of the domain variable to the form:
#GetMapping("/shutDownPage")
public String shutDownPage(Model model) {
model.addAttribute("ddata" new Ddata()); //or however you create your bean
String username = ... //however you get your username
String domain = myRepositoryService.findDomainByUsername(username);
model.addAttribute("domain", domain);
return "shutDownPage";
}
Include an HTML page in the action so that when you open the HTML page in a browser without a server/container, the button will still appear to work:
<form action="confirmationPage.html" th:action="#{/shutDown}" th:object="${ddata}" method="post">
<!-- You can benefit from using a conditional expression -->
<span th:text="${domain != null ? domain : 'No domain supplied'}">[domain]</span>
<input type="hidden" th:field="*{domain}" th:value="${domain}"/>
<input type="Submit" value="close"/>
</form>
And your post method:
#PostMapping("/shutDown") //use shorthand
public String shutDownPagePost(#ModelAttribute("ddata") DInputBean dInputBean {
String domain = dInputBean.getDomain();
//do whatever with it
return "confirmationPage";
}

Override generated checkbox-name (via asp-for)

I want to change the name of a view-inputfield that is generated by an "asp-for" tag. This works when I add a "name=newname" with input fields of type 'text' or 'number'. But not when the inputfield is of type 'checkbox'. Then the "name=newname" is ignored. Any ideas why this is and how to solve it when using asp-for? I'm using AspNetCore.Mvc 1.0.0-rc2-final.
<input type="number" asp-for="#item.StatementSeqNr" name="StatementSeqNr" class="form-control" /> produces correct name:
<input name="StatementSeqNr" class="form-control" type="number" data-val="true" id="item_StatementSeqNr" value="5" />
<input type="checkbox" asp-for="#item.StatementActive" name="StatementActive" /> produces incorrect name:
<input checked="checked" data-val="true" id="item_StatementActive" name="item.StatementActive" type="checkbox" value="true" />
This is currently not possible with the new TagHelpers.
You can achieve this by using the HtmlHelpers instead of the TagHelpers. They are still fully supported in asp.net core:
#Html.EditorFor(item => item.StatementActive, null, "StatementActive")

binding to list property of an object returns nothing mvc3

Context: ASP MVC 3/4, VB.net
I am trying to bind to an object that has a list property of complex type.
Problem statement: upon post back I get values in all the properties other than the complex type property, it is returned as nothing. I am doing something wrong because of which default model binder is not able to bind my property which is a list of complex type.
What am i doing wrong?
My View Model classes:
Public Class Customer
Public Property ID As Integer
Public Property FirstName As String
Public Property LastName As String
Public Appointments As IList(Of Appointment)
End Class
Public Class Appointment
Public Property ClientName As String
Public Property [Date] As DateTime
Public Property TermsAccepted As Boolean
End Class
My action methods in controller look like this
Function CreateCusotmerWithMultipleBookings() As ActionResult
Dim Modeldata As New Customer With {.ID = 1, .FirstName = "First Name", .LastName = "Last Name"}
Modeldata.Appointments = New List(Of Appointment) From {New Appointment, New Appointment, New Appointment}
Return View(Modeldata)
End Function
<HttpPost()> _
Function CreateCusotmerWithMultipleBookings(FormData As Customer) As ActionResult
Return View(FormData)
End Function
My view looks like this:
#ModelType Customer
#Using Html.BeginForm
#Html.EditorForModel
If Model.Appointments IsNot Nothing AndAlso Model.Appointments.Count > 0 Then
For i As Integer = 0 To Model.Appointments.Count - 1
#:<b >Appointment #i</b><br />
#Html.TextBoxFor(Function(x) x.Appointments(i).ClientName) #:<br />
#Html.TextBoxFor(Function(x) x.Appointments(i).Date) #:<br />
#Html.TextBoxFor(Function(x) x.Appointments(i).TermsAccepted)#:<br />
Next
End If
End Using
HTML that gets generated from this view is something like: (I have removed unnecessary mark up to ease reading)
<body>
<form action="/testarea/Appointment/CreateCusotmerWithMultipleBookings" method="post">
<input id="ID" name="ID" type="number" value="1" />
<input class="text-box single-line" id="FirstName" name="FirstName" type="text" value="First Name" />
<input class="text-box single-line" id="LastName" name="LastName" type="text" value="Last Name" />
<input id="Appointments_0__ClientName" name="Appointments[0].ClientName" type="text" value="" />
<input id="Appointments_0__Date" name="Appointments[0].Date" type="text" value="1/1/0001 12:00:00 AM" />
<input id="Appointments_0__TermsAccepted" name="Appointments[0].TermsAccepted" type="text" value="False" />
<input id="Appointments_1__ClientName" name="Appointments[1].ClientName" type="text" value="" />
<input id="Appointments_1__Date" name="Appointments[1].Date" type="text" value="1/1/0001 12:00:00 AM" />
<input id="Appointments_1__TermsAccepted" name="Appointments[1].TermsAccepted" type="text" value="False" />
<input id="Appointments_2__ClientName" name="Appointments[2].ClientName" type="text" value="" />
<input id="Appointments_2__Date" name="Appointments[2].Date" type="text" value="1/1/0001 12:00:00 AM" />
<input id="Appointments_2__TermsAccepted" name="Appointments[2].TermsAccepted" type="text" value="False" />
<input type="submit" value="Create customer with multiple appointments" />
</form>
Question again:
Why does the formdata.Appointments in following controller has nothing value in it?
<HttpPost()> _
Function CreateCusotmerWithMultipleBookings(FormData As Customer) As ActionResult
Return View(FormData)
End Function

Resources