I am new to thymeleaf... Can someone please tell how values is passed between thymeleaf html and spring controllers... Please suggest good tutorials for thymeleaf-spring-mvc...
In the below example, please let me know how the user entered value of owner in text field is passed to spring controller so that it validates and returns the results. And viceversa how the results returned by controller, is taken by thymeleaf to display the results.. How the value of LASTNAME is known to controller.. how it get passed to owner object of controller owner.getLastName()..
Find Owners
<form th:object="${owner}" action="ownersList.html" th:action="#{'/owners.html'}" method="get" class="form-horizontal"
id="search-owner-form">
<fieldset>
<div class="control-group" id="lastName">
<label class="control-label">Last name </label>
<input type="text" th:field="*{lastName}" size="30" maxlength="80"/>
<span class="help-inline" th:errors="*{lastName}">[Errors]</span>
</div>
<div class="form-actions">
<button type="submit">Find Owner</button>
</div>
</fieldset>
</form>
#RequestMapping(value = "/owners", method = RequestMethod.GET)
public String processFindForm(Owner owner, BindingResult result, Model model) {
// allow parameterless GET request for /owners to return all records
if (owner.getLastName() == null) {
owner.setLastName(""); // empty string signifies broadest possible search
}
// find owners by last name
Collection<Owner> results = this.clinicService.findOwnerByLastName(owner.getLastName());
if (results.size() < 1) {
// no owners found
result.rejectValue("lastName", "notFound", "not found");
return "owners/findOwners";
}
if (results.size() > 1) {
// multiple owners found
model.addAttribute("selections", results);
return "owners/ownersList";
} else {
// 1 owner found
owner = results.iterator().next();
return "redirect:/owners/" + owner.getId();
}
}
Suppose you have a controller method like:
void method-name(Owner owner){
// ...
}
When the submit button is clicked the values is automatically set to domain class, before that create a new method and set model attribute of object
model.addAttribute("owner",new Owner);
Related
I'm learning Spring boot. I have a list of products with unique ids, and I want to implement a "lookup by id" functionality, but I don't know how to do it, I searched but got totally different stuff.
I already have a #Getmapping method like this:
#Getmapping(/products/{id})
If I manually type in the id in the url I'll get what I what. But I want to have an input box in the HTML page like:
<form>
Look up by id: <input/>
</form>
and after I submit the form it'll redirect to that page. For example, if I enter input of 1, it'll go to localhost:8080/products/1
I've been searching but all I got was stuff about #Postmapping.
Add a #PostMapping to your controller:
#Controller
#RequestMapping("/products")
public class ProductController {
#GetMapping //Controller method for showing the empty form
public String index(Model model) {
model.addAttribute("formData", new SearchFormData()); // Create an empty form data object so Thymeleaf can bind to it
return "index";
}
#PostMapping
public String searchById(SearchFormData formData) {
return "redirect:/products/" + formData.getId(); //Use the value the user entered in the form to do the redirect
}
#GetMapping("/{id}")
public String showProduct(#PathVariable("id") long id) {
...
}
}
With SearchFormData representing the form fields (there is only 1 field in this case):
public class SearchFormData {
private long id;
// getters and setters
And update Thymeleaf template like this:
<form th:action="#{/products}" th:method="post" th:object="${formData}">
<input th:field="*{id}" type="number">
<button type="submit">Search</button>
</form>
Note that the value of th:object needs to match with the name used to add the SearchFormData instance to the model.
See Form handling with Thymeleaf for more info.
The following simple code will direct you to a URL that is generated from a concatenation of the base address of the <form>'s action attribute and the value of its first <input>:
document.querySelector("form").addEventListener("submit",function(ev){
ev.preventDefault();
this.action="/product/"+this.querySelector("input").value;
console.log(this.action);
// in real code: uncomment next line!
// this.submit()
})
<form>
Look up by id: <input type="text" value="123" />
</form>
In the real code you will delete the console.log() und uncomment the following line: this.submit().
Alternatively you could also do:
document.querySelector("form").addEventListener("submit",function(ev){
ev.preventDefault();
location = "/product/"+this.querySelector("input").value;
})
This will redirect you to the page without actually submitting the form.
so simply want to make a button that will call the controller action passing a parameter...
have everything I believe in place but unable to configure/reference the parameter in the actionlink helper...
Yes I will be refactoring my button onclick once I get through this html helper setup...
<h1 style="font-size:30px">Enter The Core-i Product Key (format RST 102A08R EPCA 00007)</h1>
<form action="/action_page.php">
<label for="productKey">Product Key:</label>
<input type="text" id="productKey" name="productKey"><br><br>
</form>
<p>Click the "Get Key" button and a trial key will be generated custom to your IBMi hardware".</p>
<p>
#Html.ActionLink(
"Get Key",
"GetTrialKey", // controller action
"HomeController", // controller
new { productKey }, // IT DOES NOT LIKE PRODUCTKEY (REFERENCED ABOVE)
new { #class = "btn btn-info" }) // html attributes
</p>
<div class="display-2">
<a button class="text-center, btn btn-info form-control text-white" typeof="button" onclick="location.href='#Url.Action("GetTrialKey(productKey)")'">Get Key</button></a>
<p>
<br />
</p>
</div>
refactored to...
view...
<form action="HomeController/getTrialKey" method="POST">
<label for="productKey">Product Key:</label>
<input type="text" name="productKey" maxlength="22" value="xxx xxxxxxx xxxx xxxxx"><br><br>
<input type="submit" value="Get Trial Key" class="btn btn-primary" />
</form>
controller...
[HttpPost]
public async Task<IActionResult> getTrialKey(string productKey)
{
when I run it i get...
This localhost page can’t be foundNo webpage was found for the web address: https://localhost:44346/HomeController/getTrialKey
Referring back to one of the comments:
I didn't discourage you from using HTML Helpers. I just meant the way you constructed the form and you used ActionLink was wrong. And it would be easier to just have an input for the product key inside the form, if that's the only thing you want to post back to the server.
And I would highly recommend you to read through documentations from Microsoft, at least this one: https://dotnet.microsoft.com/apps/aspnet/mvc to understand what a MVC is. From your code sample, I didn't see you used M - Model at all.
Anyway, if you just only want to get the product key the user types in, I would do it like this:
Define a Controller
I dislike the idea of putting everything under /home (i.e., HomeController). Just think about the URL to the page that would make sense to the user.
Now I am guessing what you are trying to do. I saw terms like product keys and trial keys. What about a controller called ProductKeyController:
public class ProductKeyController : Controller
{
// This corresponds to /productkeys, and you can list all the product keys
// on the view it returns.
public ActionResult Index()
{
return View();
}
// This corresponds to /productkeys/create, and you can create a specific product
// key by asking the user to provide a trial key?
// The view this returns might be the page where you build the form
public ActionResult Create()
{
...
return View();
}
// This corresponds the form post.
[HttpPost]
public ActionResult Create(CreateProductKeyViewModel model)
{
...
return View(model);
}
}
The view model
Your MVC controller is responsible to fetch data, if needed, build a view model, and pass it to the view. When you create a product key, if you need to ask the user to enter anything, you can declare a model and properties within it:
public class CreateProductKeyViewModel
{
[Required]
[Display(Name = "Trial Key")]
[MaxLength(22)]
public string TrialKey { get; set; }
}
The View Create.cshtml
Since you know the controller will be passing the view model to the view, you can declare it on top of the view so that everything you do with the view model inside the view is strongly-typed.
#model CreateProductViewModel
#{
Layout = "xxx";
}
<h1>Enter The Core-i Product Key (format RST 102A08R EPCA 00007)</h1>
#using(Html.BeginForm("create", "productKey", new { area = "" }, FormMethod.Post))
{
#Html.LabelFor(x => x.TrialKey)
#Html.TextBoxFor(x => x.TrialKey)
<button type="submit">Create</button>
}
See how everything within the view is strongly-typed? You don't have to manually create the form and the input for asking user for the trial key.
And when the user enters the trial key and presses submit, it will post back to the Post method of Create. Since the view is declared with the view model, and the view model is the parameter of the create method, MVC has already done the model binding for you hence you will get what user entered on the post back.
This is at least something to get you started.
Note: I wrote everything by hand. Not tested.
I have a simple reset password form screen. When the user submits via the Recover button the page posts to http://localhost:8080/api/v1/accountmanagement/user/password_reset . In that endpoint the user's email is validated then a hash is generated that is concatenated into a link which is sent by the system to the user via email. Everything works fine, its just that, what I want to happen is when the user clicks the recover button, I want them to remain on the recover password page. What I plan to do is to just unhide a div saying "Thank you for submitting a recover your password request etc.". Showing the div is not a problem (might be using plain javascript -- I don't have a js framework -- maybe use jQuery), its just that when the user clicks the recover button, the post request goes to the rest api therefore changing the page that is being displayed.
I tried to add target="_blank" in the form, but it didn't work. My front end is written in html and thymeleaf only.
There might be a simple solution out there that I'm just missing.
resetPassword.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
<title>Reset Password</title>
<link href="css/main.css" rel="stylesheet"/>
</head>
<body>
<div>
<div>
<h2>Recover your password</h2>
<p>To recover your password follow the instructions below to reset your password.</p>
</div>
<form th:action="#{/api/v1/accountmanagement/user/password_reset}" method="post"
id="resetPasswordForm" th:object="${account}" target="_blank">
<div id="field_email">
<label for="email">Enter your email address :</label>
<p>Enter the email address with your account.</p>
<div><input type="text" id="email" name="email" th:field="*{email}"/></div>
</div>
<div id="successRecover" style="display: none;">
An email has been sent to your email address. The email contains instructions to reset your password.
</div>
<div th:if="${param.error}" class="login-panel_error error">Error.</div>
<div>
<button type="submit" class="btn-primary">
<i class="fa fa-spin fa-spinner hidden"></i>
<span>Recover</span>
</button>
</div>
</form>
</div>
</body>
</html>
PasswordManagementController.java
#Controller
#RequestMapping("/api/v1/accountmanagement")
public class PasswordManagementController {
#Autowired
private PasswordResetService passwordResetService;
private Logger LOG = LoggerFactory.getLogger(this.getClass());
#RequestMapping(value = "/user/password_reset", method = RequestMethod.POST)
public ResponseEntity processResetPassword(#ModelAttribute(value = "account") Account account,
BindingResult result, HttpServletRequest request) {
LOG.info("account email " + account.getEmail());
// build the hash here for hashUrl
if (account != null) {
if (!StringUtil.isBlank(account.getEmail())) {
try {
passwordResetService.processResetPassword(account, hashUrl);
} catch (AccountConfigException ace) {
// deal with exception here.
// return
} catch (Exception e) {
// deal with exception here.
// return
}
return new ResponseEntity(HttpStatus.OK);
} else {
// deal with an exception here.
// return
}
} else {
// deal with an exception here.
// return
}
}
}
If you're returning a ResponseEntity in the case of a successful request you will indeed be redirected.
I don't know how you handle errors but what you might want to do is changing the return type of your controller method to String. This way you can redirect to the same page in case of a successful response while passing a boolean value to the model that will decide to render your successRecover div, without needing any Javascript.
The new handler:
#RequestMapping(value = "/user/password_reset", method = RequestMethod.POST)
public String processResetPassword(#ModelAttribute(value = "account") Account account, Model model) {
// build the hash here for hashUrl
try {
passwordResetService.processResetPassword(account, hashUrl);
} catch (AccountConfigException ace) {
// deal with exception here.
// return
} catch (Exception e) {
// deal with exception here.
// return
}
model.addAttribute("isOK", true);
return "index";
}
And then in the view:
<div id="successRecover" th:if="${isOK}">
An email has been sent to your email address. The email contains instructions to reset your password.
</div>
I have in trouble with posting json object and mapping it with List object in Spring MVC controller. I have the form that has several checkboxes. When a user checks and submits, data in the form is deserialized, sent to Spring MVC controller and mapped to List type object. When a user checks two or more checkboxes, it works fine. but for just one box, it doesn't.
When a user checks two or more, the data is deserialized like below.
{"users":["137","138"]}
However, when a user checks just one checkbox, it is like
{"users":"138"}
and 400 bad request error is returned.
Is there any workaround or solution for this?
The jQuery codes are:
$(document).ready(function() {
$('#groupusers').submit(function() {
var users = $(this).serializeObject();
$.postJSON("${context}/admin/groups/${group.seq}/users", users, function(result) {
...
});
return false;
});
And the form is:
<form id="groupusers" method="post" accept-charset="UTF-8" action="/" class="edit_group">
...
<div id="users">
<c:forEach var="user" items="${users}">
<label><input id="users" name="users" type="checkbox" value="${user.seq}" /> ${user.firstName} ${user.lastName}</label><br>
</c:forEach>
</div>
</form>
List object mapped to the form data:
public class AssignedUsers {
private List<Long> users;
...
}
Thanks in advance.
What is the implementation of the serializeObject() function? You might need to modify that to always return an array, so even if you only checked one box, the object should be: {"users":["138"]}
The server returned a 400 probably because the Jackson at threw a parsing exception at the backend.
Fojas' jQuery Serialize
This is the solution I have found and since been using which does excatly what you are requesting.
Example
HTML
<form id="myForm">
<input name="data[]" value="some data"/>
<input name="data[]" value="more data"/>
<input name="users[]" value="137"/>
</form>
jQuery
$('#myForm').serializeObject();
Output
{
data: ["some data", "more data"]
users: [137]
}
I am building a WebPages site and have an issue when I try to pass ModelState data to a partial page.
Here is the code for Create.cshtml:
#{
Page.Title = "Create Fund";
var name = "";
if (IsPost) {
name = Request.Form["name"];
if (!name.IsValidStringLength(2, 128)) {
ModelState.AddError("name", "Name must be between 2 and 128 characters long.");
}
}
}
#RenderPage("_fundForm.cshtml", name)
Here is the code for _fundForm.cshtml:
#{
string name = PageData[0];
}
<form method="post" action="" id="subForm">
<fieldset>
#Html.ValidationSummary(true)
<legend>Fund</legend>
<p>
#Html.Label("Name:", "name")
#Html.TextBox("name", name)
#Html.ValidationMessage("name", new { #class = "validation-error" })
</p>
<p>
<input type="submit" name="submit" value="Save" />
</p>
</fieldset>
</form>
The issue I am having is when there is an error for "name", the validation error does not display. Is there a special way to pass ModelState between the two pages?
_fundForm is going to be shared between Create.cshtml and Edit.cshtml.
ModelState is a readonly property of System.Web.WebPages.WebPage class. Its backing field is a private ModelStateDictionary and is initialized at first access. I can't see any way to force ModelState across pages, apart from doing it via reflection as seen in SO question: Can I change a private readonly field in C# using reflection?
Otherwise, you can simply use a third parameter in the invocation, like this:
#RenderPage("_fundForm.cshtml", name, ModelState);
In effect, the first parameter after the page name will become the Model of the new page, so there is enough space (i.e. the next parameter) to pass the ModelState.
In your "_fundForm.cshtml" merge the ModelState received by the calling page with the local one, like this:
#{
//In _fundForm.cshtml
var ms = PageData[1];
ModelState.Merge((ModelStateDictionary)ms);
}