I am having big difficulties following Play! 2.2.x documentation and i am currently stuck on how to display error from my form validation.
This is my code:
Route
GET /account/create controllers.Account.create()
POST /account/create controllers.Account.createAccount()
Model
public static UserAccount create(UserAccount data){
UserAccount account = data;
String salt = BCrypt.gensalt();
account.email = data.email;
account.salt = salt;
account.hash = BCrypt.hashpw(data.hash, salt);
account.save();
return account;
}
Controller
// handles POST method
public static Result createAccount(){
Form<UserAccount> userForm = form(UserAccount.class).bindFromRequest();
if(userForm.hasErrors()){
return badRequest();
}else{
UserAccount.create(userForm.get());
Logger.info("Username is: " + userForm.get().email);
return ok("ok, I recived POST data. That's all...");
}
}
// Handles GET method
public static Result create(){
return ok(
views.html.account.form.render()
);
}
Views
#if(form.hasGlobalErrors) {
<p class="error">
#form.globalError.message
</p>
}
#helper.form(action = routes.Account.createAccount()) {
<input type="text" name="email" placeholder="Your Email Address"/><br/>
<input type="password" name="password" placeholder="Your Password"/><br/>
<input type="text" name="fname" placeholder="Your First Name"/><br/>
<input type="text" name="midname" placeholder="Your Middle Name"/><br/>
<input type="text" name="lname" placeholder="Your Last Name"/><br/>
<input type="text" name="dob" placeholder="Your Birthday"/><br/>
<select name="gender" id="gender">
<option value="1">Male</option>
<option value="2">Female</option>
<option value="3">Undecided</option>
</select><br/>
<input type="submit" value="Login" />
}
Error Message
value hasGlobalErrors is not a member of object views.html.account.form
Can anyone tell me what's wrong with my code? I am frustrated with the given example.
EDIT #1:
This is what i have done so far:
Models:
public static UserAccount create(UserAccount data){
UserAccount account = data;
String salt = BCrypt.gensalt();
account.email = data.email;
account.salt = salt;
account.hash = BCrypt.hashpw(data.hash, salt);
account.save();
return account;
}
Controllers:
// HANDLES GET REQUEST
public static Result create(){
return ok(
views.html.account.form.render(userForm)
);
}
// HANDLES POST REQUEST
public static Result createAccount(){
Form<UserAccount> userForm = form(UserAccount.class).bindFromRequest();
if(userForm.hasErrors()){
return badRequest(views.html.account.form.render(userForm));
}else{
// UserAccount.create(userForm.get());
// Logger.info("Username is: " + userForm.get().email);
UserAccount data = userForm.get();
return ok(data.email);
}
}
VIEWS/TEMPLATE
#(form: Form[UserAccount])
#if(form.hasGlobalErrors) {
<h1>Please fix the following error first</h1>
<p>
#form.globalError.message
</p>
<ul>
#for(error <- form.globalErrors) {
<li>#error.message</li>
}
</ul>
}
#helper.form(action = routes.Account.createAccount()) {
<input type="text" name="email" placeholder="Your Email Address"/><br/>
<input type="password" name="password" placeholder="Your Password"/><br/>
<input type="text" name="fname" placeholder="Your First Name"/><br/>
<input type="text" name="midname" placeholder="Your Middle Name"/><br/>
<input type="text" name="lname" placeholder="Your Last Name"/><br/>
<input type="text" name="dob" placeholder="Your Birthday"/><br/>
<select name="gender" id="gender">
<option value="1">Male</option>
<option value="2">Female</option>
<option value="3">Undecided</option>
</select><br/>
<input type="submit" value="Login" />
}
So far, according to firebug when i deliberately put errors on the form the server will return badrequest. However, no error is displayed by the template.
If i change the controller as such:
public static Result createAccount(){
Form<UserAccount> userForm = form(UserAccount.class).bindFromRequest();
if(userForm.hasErrors()){
return ok(userForm.errorsAsJson().toString());
}else{
// UserAccount.create(userForm.get());
// Logger.info("Username is: " + userForm.get().email);
UserAccount data = userForm.get();
return ok("ok, I received POST data. That's all...");
}
}
Or if i do this on my View/Template
<pre>#form.errorsAsJson.toString()</pre>
It works, and errors are printed accordingly. Do anyone know what i am missing here?
EDIT #2:
The best thing that works for me to output the error is by doing this on my View/Template
#(form: Form[UserAccount])
#if(form.hasErrors) {
<h1>Please fix the following error first</h1>
<ul>
#for(error <- form.errors) {
<li>#error.toString</li>
}
</ul>
}
Which outputs this:
(email,[ValidationError(email,error.required,[])])
(hash,[ValidationError(hash,error.required,[])])
Since I am trying to display an appropriate message to user, the message is rather useless.
After looking deeper at your code, there are two things to note:
In your template you need to use the helper tags for your form fields. The helpers will display field specific errors for you: http://www.playframework.com/documentation/2.2.x/JavaFormHelpers
I don't think your form has any global errors, which is why that code isn't displaying anything.
Try the helpers and see if that works for you.
You can use the flash scope:
Controller:
public Result create(){
flash("error", "error msg");
return badRequest(view.render());
}
View:
#flash.get("error")
The problem is that your form doesn't have "global errors", it has errors for the fields email and hash. It's different.
You can check the error for the fields using:
#if(form.error("email") != null) { }
And you can fire global error as belor on controller:
form.reject("global error");
More info:
http://www.playframework.com/documentation/2.0/JavaSessionFlash
I need to see your full template, controller, and routes file. I think the problem is that you don't have a method signature at the top of your template file, something like this:
#(userForm: Form[User])
Once you fix the template, you have another problem that you are not passing the form to the view template in the "badRequest()" flow. Try changing your controller action to look like this:
public static Result createAccount(){
Form<UserAccount> userForm = form(UserAccount.class).bindFromRequest();
if(userForm.hasErrors()){
return badRequest(views.html.createUserTemplate.render(userForm));
}else{
UserAccount.create(userForm.get());
Logger.info("Username is: " + userForm.get().email);
return ok("ok, I recived POST data. That's all...");
}
}
Your global errors code is mostly correct, it should look like this:
#if(userForm.hasGlobalErrors) {
<ul>
#for(error <- userForm.globalErrors) {
<li>#error.message</li>
}
</ul>
}
Related
I have a controller that renders a chores.html page with various sorts of chores.
#GetMapping("/chores")
public String getDueChores(Model model)
{
var tomorrow = LocalDate.now().plusDays(1);
var chores = choreRepository.findByDueBefore(tomorrow);
model.addAttribute("choresDue", chores);
model.addAttribute("allChores", choreRepository.findAll());
model.addAttribute("chore", new Chore());
model.addAttribute("chores", new ArrayList<Chore>());
return "chores";
}
The same page also has a form for adding a new chore. Here's the controller method:
#PostMapping("/chores")
public String addNewChore(#ModelAttribute #Valid Chore chore)
{
chore.setDue(LocalDate.now().plusDays(chore.getDaysBetween()));
choreRepository.save(chore);
return "redirect:/chores";
}
Now I want to display the errors if the new chore is invalid.
Attempt 1:
#PostMapping("/chores")
public String addNewChore(#ModelAttribute #Valid Chore chore,
Errors errors)
{
if (errors.hasErrors())
{
return "chores";
}
chore.setDue(LocalDate.now().plusDays(chore.getDaysBetween()));
choreRepository.save(chore);
return "redirect:/chores";
}
This shows the error message, but sense it's not going through the logic in the GET controller method, all the other chores on the page don't get populated.
Attempt 2:
#PostMapping("/chores")
public String addNewChore(#ModelAttribute #Valid Chore chore,
Errors errors)
{
if (errors.hasErrors())
{
return "redirect:/chores";
}
chore.setDue(LocalDate.now().plusDays(chore.getDaysBetween()));
choreRepository.save(chore);
return "redirect:/chores";
}
This doesn't work because the error information is lost on the redirect, and the errors aren't displayed.
Could anyone point me in the right direction, please?
Here's chores.html, if it's relevant:
<body>
<h1>Due today...</h1>
<form method="post" th:action="#{/chore}" th:object="${chore}">
<ul>
<li th:each="chore: ${choresDue}">
<input type="checkbox" name="choreIds" th:value="${chore.id}"/>
<label th:text="${chore.name}"></label>
</li>
</ul>
<input type="submit" value="Mark Chores Complete">
</form>
<form method="post" action="#" th:action="#{/chores}" th:object="${chore}">
<input type="text" th:field="*{name}" placeholder="Chore name">
<span class="validationError"
th:if="${#fields.hasErrors('name')}"
th:errors="*{name}">Chore name is invalid</span>
<br>
<input type="text" th:field="*{daysBetween}" placeholder="Do chore every # days">
<span class="validationError"
th:if="${#fields.hasErrors('daysBetween')}"
th:errors="*{daysBetween}">Chore name is invalid</span>
<br>
<input type="submit" value="Add chore">
</form>
<hr>
<h1>All Chores</h1>
<form th:method="delete" th:action="#{/deleteChore}" th:object="${chore}">
<ul>
<li th:each="chore: ${allChores}">
<input type="checkbox" name="choreIds" th:value="${chore.id}"/>
<label th:text="${chore.name} + ' every ' + ${chore.daysBetween} + ' days'"></label>
</li>
</ul>
<input type="submit" value="Delete selected chores">
</form>
</body>
Solution is to add Errors to the Model.
#PostMapping("/chores")
public String addNewChore(#ModelAttribute #Valid Chore chore,
Errors errors,
Model model)
{
if (errors.hasErrors())
{
model.addAttribute("error", errors);
var tomorrow = LocalDate.now().plusDays(1);
var chores = choreRepository.findByDueBefore(tomorrow);
model.addAttribute("choresDue", chores);
model.addAttribute("allChores", choreRepository.findAll());
return "chores";
}
chore.setDue(LocalDate.now().plusDays(chore.getDaysBetween()));
choreRepository.save(chore);
return "redirect:/chores";
}
In my application I do not use ajax to post data into server. I get the data with #FormParam annotations. When I insert data or when I get an error, response message is shown in a blank page. What I want to do is I want to show this message on the same page. But I don't know how to do it. When I try to use ajax to send data I always get error. That is why I did not use it.
This is my form:
<form action="Rest/hastaService/insertHasta" method="post"
name="myForm">
<input class="form-control" type="input" id="tcKimlik"
name="tcKimlik" size="11" maxlength="11" required
pattern="[0-9]{11}">
<input class="form-control" type="text" name="isim" id="isim">
<input class="form-control" type="text" name="soyisim" id="soyisim">
<input class="form-control" type="date" name="dogum" id="dogum">
<input class="form-control" type="text" name="meslek" id="meslek">
<input type="submit" class="btn btn-primary" value="Kaydet" />
</form>
Server Side :
#POST
#Path("/insertHasta")
#Consumes("application/x-www-form-urlencoded")
#Produces("application/json")
public Response createHastalar(#FormParam("tcKimlik") long tcKimlik, #FormParam("isim") String isim,
#FormParam("soyisim") String soyIsim, #FormParam("dogum") String dogumTarih,
#FormParam("meslek") String meslek) throws SQLException {
Hasta hasta = new Hasta();
List<Hasta> hastaList = new ArrayList<Hasta>();
hasta.setTcKimlik(tcKimlik);
hasta.setIsim(isim);
hasta.setSoyIsim(soyIsim);
hasta.setDogumTarih(dogumTarih);
hasta.setMeslek(meslek);
/*Check for hasta if they have appointment*/
if(access.randevuAra(tcKimlik) == true){
if(access.hastaAra(tcKimlik) == false){
hastaList.add(hasta);
if(access.saveHasta(hastaList) == true){
return Response.ok("Basariyla Kaydedildi!", MediaType.TEXT_PLAIN).build();
}else{
return Response.status(Status.BAD_REQUEST).entity("Hata!").build();
}
}else{
return Response.status(Status.BAD_REQUEST).entity("Hasta kaydı zaten var!").build();
}
}else{
return Response.status(Status.BAD_REQUEST).entity("Hasta Randevusu Bulunmamaktadir. Lutfen Randevu Aliniz!").build();
}
}
I'm trying to implement LTI in our education platform to connect with QTIWoks in Java.
I have a simple tool consumer that generates the following HTML Form:
<html>
<head> </head>
<body>
<form action="http://192.168.0.114:8080/qtiworks-engine/lti/domainlaunch" name="ltiLaunchForm" id="ltiLaunchForm" method="post" target="basicltiLaunchFrame" enctype="application/x-www-form-urlencoded" style="display: block;">
<input type="hidden" name="context_id" value="cid-00113">
<input type="hidden" name="context_label" value="SI106">
<input type="hidden" name="context_title" value="Design of Personal Environments 1">
<input type="hidden" name="ext_note" value="Instructor from first course">
<input type="hidden" name="launch_presentation_locale" value="en_us">
<input type="hidden" name="lis_person_contact_email_primary" value="sian#imscert.org">
<input type="hidden" name="lis_person_name_family" value="Instructor">
<input type="hidden" name="lis_person_name_given" value="Siân">
<input type="hidden" name="lis_person_sourcedid" value="school.edu:user">
<input type="hidden" name="resource_link_description" value="This learning space is private">
<input type="hidden" name="resource_link_id" value="res-0012612">
<input type="hidden" name="resource_link_title" value="My Weekly Wiki">
<input type="hidden" name="roles" value="Instructor">
<input type="hidden" name="tool_consumer_info_product_family_code" value="sakai-unit">
<input type="hidden" name="tool_consumer_info_version" value="0.9">
<input type="hidden" name="tool_consumer_instance_description" value="University of School (LMSng)">
<input type="hidden" name="tool_consumer_instance_guid" value="lmsng.school.edu">
<input type="hidden" name="user_id" value="user-0016">
<input type="hidden" name="oauth_callback" value="about:blank">
<input type="hidden" name="custom_simple_key" value="custom_simple_value">
<input type="hidden" name="custom_complex____________key" value="Complex!##$^*(){}[]½Value">
<input type="hidden" name="lti_version" value="LTI-1p0">
<input type="hidden" name="lti_message_type" value="basic-lti-launch-request">
<input type="hidden" name="oauth_version" value="1.0">
<input type="hidden" name="oauth_nonce" value="Z2WVNEPUZkzsolOe4hRvKbkXtOSmYiyw">
<input type="hidden" name="oauth_timestamp" value="1429793062">
<input type="hidden" name="oauth_consumer_key" value="feras">
<input type="hidden" name="oauth_signature_method" value="HMAC-SHA1">
<input type="hidden" name="oauth_signature" value="g908qTtVGh8MsOgVUdarVlSBmC0=">
<input type="hidden" name="ext_submit" value="Finish Launch">
</form><iframe name="basicltiLaunchFrame" id="basicltiLaunchFrame" src="" width="100%" height="900" scrolling="auto" frameborder="1" transparency="">
</iframe>
<script type="text/javascript">
document.getElementById("ltiLaunchForm").style.display = "none";
nei = document.createElement('input');
nei.setAttribute('type', 'hidden');
nei.setAttribute('name', 'ext_submit');
nei.setAttribute('value', 'FinishLaunch');
document.getElementById("ltiLaunchForm").appendChild(nei);
document.ltiLaunchForm.submit();
</script>
</body>
</html>
then it calls the dominlaunch from qtiworks
#RequestMapping(value="/domainlaunch", method=RequestMethod.POST)
public String ltiDomainLevelLaunch(final HttpSession httpSession, final HttpServletRequest request,
final HttpServletResponse response)
throws IOException {
/* Decode LTI launch request, and bail out on error */
final DecodedLtiLaunch decodedLtiLaunch = ltiLaunchService.decodeLtiLaunchData(request, LtiLaunchType.DOMAIN);
if (decodedLtiLaunch.isError()) {
response.sendError(decodedLtiLaunch.getErrorCode(), decodedLtiLaunch.getErrorMessage());
return null;
}
final LtiLaunchData ltiLaunchData = decodedLtiLaunch.getLtiLaunchData();
/* Make sure this is a domain launch */
final LtiUser ltiUser = decodedLtiLaunch.getLtiUser();
final LtiDomain ltiDomain = ltiUser.getLtiDomain();
if (ltiDomain==null) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "The tool consumer has attempted a domain-level launch using a link-level key");
return null;
}
/* Extract/create the corresponding LtiResource for this launch */
final LtiResource ltiResource = ltiLaunchService.provideLtiResource(decodedLtiLaunch); /* (May be null for candidates) */
final UserRole userRole = ltiUser.getUserRole();
if (userRole==UserRole.INSTRUCTOR) {
/* If user is an instructor, we'll forward to the LTI instructor MVC after
* "authenticating" the user by creating and storing an LtiDomainTicket
* in the session */
final LtiAuthenticationTicket ltiDomainTicket = new LtiAuthenticationTicket(ltiUser.getId(), ltiResource.getId(),ltiLaunchData.getLaunchPresentationReturnUrl());
LtiResourceAuthenticationFilter.authenticateUserForResource(httpSession, ltiDomainTicket);
return "redirect:/lti/resource/" + ltiResource.getId();
}
else if (userRole==UserRole.CANDIDATE) {
/* If user is a candidate, then we'll launch/reuse a candidate session */
if (ltiResource==null) {
return "candidateLaunchError";
}
/* Extract relevant data */
final String returnUrl = ltiLaunchData.getLaunchPresentationReturnUrl();
final String lisOutcomeServiceUrl = ltiLaunchData.getLisOutcomeServiceUrl();
final String lisResultSourcedid = ltiLaunchData.getLisResultSourcedid();
/* Launch and redirect to session */
try {
final CandidateSessionTicket candidateSessionTicket = candidateSessionLaunchService.launchDomainLevelLtiCandidateSession(httpSession,
ltiUser, ltiResource, returnUrl, lisOutcomeServiceUrl, lisResultSourcedid);
return GlobalRouter.buildSessionStartRedirect(candidateSessionTicket);
}
catch (final CandidateException e) {
return "candidateLaunchError";
}
}
else {
throw new QtiWorksLogicException("Unexpected LTI userRole " + userRole);
}
}
the problem is decodedLtiLaunch.isError() returns true and my request is not performed.
I debugged the problem and found that HttpServletRequest request has no item in parameterMap
however, it's working well when the request comes from moodle (request's parameterMap has the passed params)
how can I solve this please?
thanks in advance.
Without knowing what the exact error code you are seeing is, I am going to make a very strong guess that your signature is bad
Since you are using JAVA I would suggest that you utilize the basiclti-util library that IMSGlobal provides to generate a signature
Add the following dependency
<dependency>
<groupId>org.imsglobal</groupId>
<artifactId>basiclti-util</artifactId>
<version>1.1.2</version>
</dependency>
Then use the following code to generate the signature
Map<String, String> signedParameters = new LtiOauthSigner()
.signParameters(parameters, key, secret, url, "POST");
Then take all of the key-value pairs in the signedParameters map, and use that to construct the form input tags you have in your example.
I am trying to write freemarker template but could not able to parse with my object class.
My POJO is
public class Metrix {
#Id
String _id;
String loginId;
Date date;
List<MatrixDetail> headers;
//All getters and setters
}
public class MatrixDetail {
String header;
int time;
String detail;
//All getters and setters
}
//Controller after saving form
#RequestMapping(value = "/matrix/save", method = RequestMethod.POST)
public View saveMatrix(#ModelAttribute Metrix matrix, ModelMap model) {
System.out.println("Reachecd in matrix save" );
return new RedirectView("/TrackerApplication/header.html");
}
FTL template form part
<form name="matrix" action="matrix/save.html" method="post">
<table class="datatable" align:"center">
<tr>
<th>Login Id:</th> <th> <input type="text" name="loginId" value= ${matrixList.loginId} required /> </th>
</tr>
<tr> <td></td><td></td><td></td></tr>
<tr>
<th>Header</th> <th>Time</th> <th>Details</th>
</tr>
**// I am not getting how this nested object which is of type List<MatrixDetail>
// will get parse in my form.**
<#list matrixList.headers as header>
<spring:bind path = "MatrixDetail">
<tr>
<td> <input name = "header" value = ${header.header} /> </td>
<td> <input name = "time" value = ${header.time} /> </td>
<td> <input name = "detail" value = ${header.detail} /></td></tr>
</#list>
</table>
<input type="submit" value="Save" />
</form>
How can we write freemarker template for form processing of such kind of nested object?
I am getting issues in form submission.
I would strongly advise against this.
Forms might be displayable in email in some cases, but they may not always work in the email client, not to mention those that only ever read emails in text-only form won't be able to use them whatsoever.
If you need users to enter a form, link to a page on your site and have the form there instead.
I have 2 jsp-page. In first jsp-page I use combobox who choosing subject, several radio button for action. On servlet this page I get request.getParameter("subjectID").
Better If I show servlets and jsp
<form action="/TutorWebApp/controller" method="POST" name="editTestForm">
<p>
Choose subject
<select name='subject'>
<c:forEach items="${subjects}" var="subject" >
<option value="${subject.key}">
${subject.value.getName()}
</option>
</c:forEach>
</select>
</p>
<input type="radio" name="command" value="add_test">
Add test <br />
<input type="radio" name="command" value="add_subject">
Add subject <br />
<input type="submit" value="OK"/>
</form>
In this page I choose subject from combobox. And choose "Add test". After I go to servlet where
class AddTestCommand implements Command {
private static final String PARAM_TEST_NAME = "testName";
private static final String PARAM_SUBJECT = "subject";
#Override
public String execute(HttpServletRequest request) throws ServletException, IOException {
String page = " ";
String message = " ";
String testName = request.getParameter(PARAM_TEST_NAME);
if (testName != null && (!"".equals(testName))) {
HttpSession session = request.getSession(true);
Integer userID = (Integer) session.getAttribute("userID");
Integer subjectId =
Integer.valueOf(request.getParameter(PARAM_SUBJECT));
if(AddTestLogic.addTest(userID, subjectId, testName)){
message = "Success";
} else{
message = "This test already exist";
}
request.setAttribute("result", message);
}
page = ResourceBuilder.getPropertyManager(PropertyEnum.JSP_PAGE).
getProperty("path.page.addtest");
return page;
}
}
There I can get value of subject as request.getParameter("subject"); near with testName before if(){} And next step - go to next jsp
<form action="/TutorWebApp/controller" method="POST" name="addTestForm">
<input type="hidden" name="command" value="add_test" />
Name of new test:
<input type="text" name="testName" value=""/>
<input type="submit" value="Add test"/>
</form>
An after input data in jsp I go to the same servlet again. But I lose value request.getParameter("subject").
I try to use HttpSession but on first page I send Map. And get with request just choosen subjectID from Map.
I don't know how resolve this problem.
Thanks
You can retain request parameters for the next request with a hidden field. Request parameters are available by the ${param} map in EL. So, this should do:
<input type="hidden" name="subject" value="${fn:escapeXml(param.subject)}" />
Note that I'm using JSTL fn:escapeXml() to escape HTML entities; this will prevent possible XSS attacks.