Spring Boot cannot render global error on multipart form submit - spring-boot

Currently I am trying to upload a CSV file containing records and this part is working fine.
However on submission of this form, if a data is not valid or missing, or if there is an import failure, I want to return a simple error message without refreshing the page.
Is it possible to return an error message on the same popup form, or is there any alternative way to do this?
Please find the code snippet below.
Form/Page:
<form id="uploadrecordform" method="POST" th:action="#{/import-record-file}" enctype="multipart/form-data">
<div class="form">
<h2>upload</h2>
<div class="form-element">
<label for="file">Upload record file</label>
<input type="file" name="file" class="form-control-file" id="file" accept=".csv" required>
</div>
<div class="form-element">
<button type="submit">
<p th:text="#{submit_text}"></p>
</button>
</div>
<div role="alert" th:if="${globalError}">
<strong>Error:</strong>
<span th:text="${globalError}"></span>
</div>
</div>
</form>
Note: on the page, there is an "add record" button and while clicking on the button, it opens the new form as a popup.
API sample code:
#RequestMapping(value = "/import-record-file", method = RequestMethod.POST)
#ResponseBody
public String importUserRecordCsvFile( #Valid #RequestParam("file") MultipartFile file, BindingResult result) {
final String username = principal.getName();
// validate file
if (file.isEmpty()) {
System.out.println("message Please select a CSV file to upload.");
ObjectError error = new ObjectError("globalError", "this is test error");
result.addError(error);
if (result.hasErrors()) {
return "errors/import-record-file";
}
}
return "empty";
}

I believe you are trying to make the controller validate the form and return the form back to the same pop up if there is an error.
You can do so using ajax. Let say your form is in a pop up and the pop up has the id popup.
<div id="popup">
<form>
<!-- form details -->
</form>
</div>
You can submit your form via ajax and have the result displayed in the pop up itself without refreshing the whole page.
The ajax function will do something as below,
$.ajax({
type: "POST",
data: formData,
url: url,
success: function (data) {
$('#popup').html(data); // data is always a simple html view
}
});
The endpoint handling the form submit (in your case, the endpoint /import-record-file) will function as follows, in case of error, the whole form is displayed back in the popup and in case of success, a simple html success message can be returned which will be displayed in the same popup.
So basically we are just overriding the content of the popup using ajax.

Related

ASP.NET Core How to dynamically remove list element?

I want a user to be able to edit the order and delete some items. To do this, for each item I added a delete button (you can see it in the code below).
Partial view Views/Shared/EditorTemplates/Item.cshtml (Attribute data-id is hardcoded, because I have no idea how to pass the current OrderItem Id):
#model TimeTracker.Models.OrderItem
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Product" class="control-label"></label>
<input asp-for="Product" class="form-control" />
<span asp-validation-for="Product" class="text-danger"></span>
<input class="btn btn-default" type="button" id="btnDel" data-id="1" value="Remove" />
</div>
In ajax call comes the error in the console, which refers to the controller method. Here I am assuming I filled in the parameter data in ajax call incorrectly. I really don't know how to fill it in correctly in my case.
Failed to load resource: the server responded with a status of 405 ()
:5001/Orders/RemoveItem:1
Ajax call:
$("#btnDel").on('click', function () {
$.ajax({
async: true,
data: { order: $('#form').serialize(), orderItemId: $('#btnDel').data('id') },
type: "DELETE",
url: '/TaskTypes/RemoveItem',
success: function (partialView) {
console.log("partialView: " + partialView);
$('#itemsContainer').html(partialView);
}
});
});
Controller method:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> RemoveItem([Bind("OrderItems")] Order order, int orderItemId)
{
order.OrderItems.RemoveAll(c => c.Id == orderItemId);
return PartialView("Items", order);
}
When you use ValidateAntiForgeryToken You have to pass RequestVerificationToken in your ajax header to validate your request.
Please visit https://learn.microsoft.com/en-us/aspnet/core/security/anti-request-forgery?view=aspnetcore-3.1 for more details about prevent cross site request.

Play framework write Action with Ok(...) that doesn't load new page

Play framework 2.4.x. A button is pressed on my home page that executes some code via Ajax, and returns its results beneath the button without loading a new page. The results wait for a user to input some text in a field and press "submit". Those results Look like this:
<li class="item">
<div>
<h3>Email: </h3>
<a>#email.tail.init</a>
<h3>Name: </h3>
<a>#name</a>
</div>
<div>
<h3>Linkedin: </h3>
<form class="linkedinForm" action="#routes.Application.createLinkedin" method="POST">
<input type="number" class="id" name="id" value="#id" readonly>
<input type="text" class="email" name="email" value="#email" />
<input type="text" class="emailsecondary" name="emailsecondary" value="" />
<input type="text" class="name" name="email" value="#name" />
<input type="text" class="linkedin" name="linkedin" value="" />
<input type="submit" value="submit" class="hideme"/>
</form>
</div>
<div>
<form action="#routes.Application.delete(id)" method="POST">
<input type="submit" value="delete" />
</form>
</div>
</li>
Along with some jquery that slides up a li after submission:
$(document).ready(function(){
$(".hideme").click(function(){
$(this).closest('li.item').slideUp();
});
});
However, since a form POST goes inside an Action that must a return an Ok(...) or Redirect(...) I can't get the page to not reload or redirect. Right now my Action looks like this (which doesn't compile):
newLinkedinForm.bindFromRequest.fold(
errors => {
Ok("didnt work" +errors)
},
linkedin => {
addLinkedin(linkedin.id, linkedin.url, linkedin.email, linkedin.emailsecondary, linkedin.name)
if (checkURL(linkedin.url)) {
linkedinParse ! Linkedin(linkedin.id, linkedin.url, linkedin.email, linkedin.emailsecondary, linkedin.name)
Ok(views.html.index)
}else{
Ok(views.html.index)
}
}
)
Is it possible to return Ok(...) without redirecting or reloading? If not how would you do a form POST while staying on the same page?
EDIT: Here is my attempt at handling form submission with jquery so far:
$(document).ready(function(){
$(".linkedinForm").submit(function( event ) {
var formData = {
'id' : $('input[name=id]').val(),
'name' : $('input[name=name]').val(),
'email' : $('input[name=email']).val(),
'emailsecondary' : $('input[name=emailsecondary]').val(),
'url' : $('input[name=url]').val()
};
jsRoutes.controllers.Application.createLinkedin.ajax({
type :'POST',
data : formData
})
.done(function(data) {
console.log(data);
});
.fail(function(data) {
console.log(data);
});
event.preventDefault();
};
});
This is an issue with the browser's behavior on form submission, not any of Play's doing. You can get around it by changing the behavior of the form when the user clicks submit.
You will first want to attach a listener to the form's submission. You can use jQuery for this. Then, in that handler, post the data yourself and call .preventDefault() on the event. Since your javascript is now in charge of the POST, you can process the data yourself and update your page's HTML rather than reloading the page.
What you need is use ajax to submit a form, check this: Submitting HTML form using Jquery AJAX
In your case, you can get the form object via var form = $(this), and then start a ajax with data from the form by form.serialize()
$.ajax({
type: form.attr('method'),
url: form.attr('action'),
data: form.serialize(),
success: function (data) {
alert('ok');
}
});
In order to accomplish this task, i had to use play's javascriptRouting
This question's answer helped a lot.
I'm not experienced with jquery so writing that correctly was difficult. For those that find this, here is my final jquery that worked:
$(document).ready(function(){
$("div#results").on("click", ".hideme", function(event) {
var $form = $(this).closest("form");
var id = $form.find("input[name='id']").val();
var name = $form.find("input[name='name']").val();
var email = $form.find("input[name='email']").val();
var emailsecondary = $form.find("input[name='emailsecondary']").val();
var url = $form.find("input[name='url']").val();
$.ajax(jsRoutes.controllers.Application.createLinkedin(id, name, email, emailsecondary, url))
.done(function(data) {
console.log(data);
$form.closest('li.item').slideUp()
})
.fail(function(data) {
console.log(data);
});
});
});
Note that my submit button was class="hideme", the div that gets filled with results from the DB was div#results and the forms were contained within li's that were class="item". So what this jquery is doing is attaching a listener to the static div that is always there:
<div id="results">
It waits for an element with class="hideme" to get clicked. When it gets clicked it grabs the data from the closest form element then sends that data to my controller via ajax. If the send is successful, it takes that form, looks for the closest li and does a .slideUp()
Hope this helps

How do I use an Angular.JS generated form to upload a file (and other fields) through AJAX to Symfony2?

I'm trying to send a form generated by Angular.js through an AJAX call to a Symfony controller action in order to be saved. The form is generated in an ng-repeat after receiving some JSON data (including an entry ID). The code looks as follows.
<div ng-repeat="job in sc.careers">
<div>
<h2>{[{job.title}]}</h2>
<span ng-if="job.super">{[{job.super}]}</span>
<div>
<h4>Role</h4>
<span ng-bind-html="job.role"></span>
</div>
<div class="col-md-4">
<h4>Required Skills</h4>
<span ng-bind-html="job.skills"></span>
</div>
<div class="col-md-4">
<h4>Media</h4>
Some other content \ media
</div>
</div>
<hr/>
<div class="join-us">
<button ng-click="sc.joinUs[$index] = true" ng-hide="sc.joinUs[$index]">Apply for this position</button>
<div ng-show="sc.joinUs[$index]">
<h4>Join Us</h4>
<span>Some random text</span>
<form>
<input type="hidden" name="job_id" value="{[{job.id}]}" />
<label for="email">E-Mail</label>
<input type="email" id="email" name="email" />
<label for="motivation">Cover Letter & CV link</label>
<textarea id="motivation" name="motivation"></textarea>
<label for="resume">Upload your resume</label>
<input type="file" name="resume">
<button type="submit">Send your application</button>
</form>
</div>
</div>
</div>
* most class names have been redacted to keep the code as relevant to the question as possible
I have not yet placed an ng-click -> submit directive. Also, my Angular is configured for the use of the "{[{" and "}]}" delimiters so as not to interfere with TWIG.
I have searched the internet for possible answers but they all pertain to Symfony generated forms (which include validation tokens). Other answers (such as this one) don't quite describe the Angular side of things or don't describe sending the entire form.
In the end, I'm not exactly sure how to approach this. If it's too complicated I'd even settle for not using AJAX at all and submitting directly to Symfony. (actually, the only reason I want to use AJAX is to make the site feel more "snappy").
For what it's worth I'm using the latest stable versions of PHP, Symfony and Angular.JS at the time of writing.
Update
So i managed to send the data back to the controller by using an Agular JS module that enables the use of ng-model on file inputs and by using FormData like so:
var formObject = new FormData;
formObject.append('email', self.careers[index].application.email);
formObject.append('motivation', self.careers[index].application.motivation);
formObject.append('resume', self.careers[index].application.file);
formObject.append('jobID', self.careers[index].id);
$http.post('/app_dev.php/jobs/apply', formObject, {
transformRequest: angular.identity,
headers: { 'Content-Type': undefined } // Allows angular to choose the proper Content-Type
})
The only problem left now is that Symfony does not recognize my form data as being valid. The controller action looks like this (for now).
public function applyAction(Request $request) {
$jobApplication = new JobApplications(); // This is the entity I use to store everything.
$form = $this->createFormBuilder($jobApplication)
->add('jobId')
->add('email')
->add('coverLetter')
->add('file')
->getForm();
$form->handleRequest($request);
$response = array (
'isValid' => $form->isValid(), // false
'isSubmitted' => $form->isSubmitted(), // false < that's why the form is invalid
'isErrors' => $form->getErrorsAsString() // empty array
);
if ($form->isValid()) { // is not valid so the following section is not complete
$em = $this->getDoctrine()->getManager();
$jobApplication->upload();
$em->persist($jobApplication);
$em->flush();
//return $this->redirect($this->generateUrl('idea_presentation_careers'));
}
return new JsonResponse($response);
}

My .ajax call isn't working

I have a form on my website that pushes to my e-mail address. Previously before I wrote an ajax function the form would successfully push to my e-mail address. Only problem is when the user fills out the form it takes them to another page upon submitting the form. The HTML for my form is below.
<form id="contact" method="post" action="E-mail-form.php" name="EmailFromMyWebsite">
<label for="name">Name</label> <br>
<input type="text" name="name" class="required" placeholder="Your Name" title=" (Your name is required)"> <br />
<label for="email">E-mail</label> <br>
<input type="email" name="email" class="required email" placeholder="Name#email.com" title=" (Your email is required)"> <br />
<label for="message">Message/Comment</label> <br>
<textarea name="message" class="required" placeholder="Leave a brief message" title=" (Please leave me a brief message)"></textarea> <br />
<input type="submit" name="submit" id="submit" value="Send Message" />
</form>
</div><!-- /end #contact-form -->
The ajax call I wrote is...
$("#submit").on('click', function(){
var formData = $('#contact').serialize();
$.ajax({
type:"POST",
data:"formData",
url:"Email-form.php",
success: function(data){
$('#contact').html('<p>Your message has been sent</p>');
}
});
});
My javaScript console shows no errors so I think the problem is with my jQuery logic. On Chrome when I click submit I am redirected to my homepage. On Firefox the form submits but I am redirected to another page, therefore it is completely ignoring my AJAX call. Can someone with AJAX experience tell me what I'm doing wrong? Also I would love to attach a message if the call fails. Can I add 'failure:' and for the value put a function just like I did for success?
You have two issues
your form is submitting normally
you're attempting to post the wrong data
To prevent the form from submitting you can return false from the jQuery click handler or call preventDefault from the event object.
You are sending a string "formData" as the form data instead of the string in the formData variable
$("#submit").on('click', function(event){
var formData = $('#contact').serialize();
$.ajax({
type:"POST",
data:formData,
url:"Email-form.php",
success: function(data){
$('#contact').html('<p>Your message has been sent</p>');
}
});
event.preventDefault();
// or
return false;
});
There's a good chance that the normal form submit action is still taking place, even though you have an AJAX call as well. An easy fix for this may be to simply change the button type from submit to button. That way your click handler will still work, but it won't perform the default action of submitting the form on its own.
<input type="button" name="submit" id="submit" value="Send Message" />

Portlet:actionurl is not going to #Actionmapping in controller

I am using Liferay portal-6 with Spring-3.
In my portlet, first it goes to the #Rendermapping without params and displays the default jsp,
when I click on a button I am passing the actionurl, but its not going to the corresponding #Actionmapping.
My searchForm.jsp looks like this:
<portlet:actionURL var="showSearchResultsUrl">
<portlet:param name="myaction" value="searchResults" />
</portlet:actionURL>
<form:form id="searchForm" name="searchForm" commandName="patientStoryForm" method="POST" action="${showSearchResultsUrl}" enctype="multipart/form-data" >
<input type="button" name="search_btn" value="Search"/></td>
</form:form>
My controller is like this:
#Controller(value = "searchPatientStoryController")
#RequestMapping(value = "VIEW")
#SessionAttributes(types = PatientStoryForm.class)
public class SearchPatientStoryController {
#RenderMapping
public String showSearchForm(RenderResponse response, Model model) {
return "searchForm";
}
#RenderMapping(params = "myaction=searchResultsForm")
public String showSearchResultsForm(RenderResponse response, Model model) {
return "searchResultsForm";
}
#ActionMapping(params = "myaction=searchResults")
public void searchResults(
#ModelAttribute(value = "patientStoryForm") PatientStoryForm patientStoryForm,
BindingResult bindingResult, ActionResponse response, ActionRequest request,
SessionStatus sessionStatus, Model model) {
response.setRenderParameter("myaction", "searchResultsForm");
}
}
When I click on button in searchForm.jsp it is supposed to go to #Actionmapping but it is not going.
Please help me.
Thanks in advance.
You would need to submit the form in order to invoke the associated action method.
However, in the jsp code, I see the below code which indicates that it is just a button.
<input type="button" name="search_btn" value="Search"/>
In order to submit the form, you can do either of the following ways:
Use a submit button instead of a normal form button like this:
<input type="submit" name="search_btn" value="Search"/>
Invoke a javascript function on click event of the button and submit the form:
<script type="text/javascript">
function submitForm(form){
var actionUrl = '<portlet:actionURL var="showSearchResultsUrl"><portlet:param name="myaction" value="searchResults"/></portlet:actionURL>';
form.action = actionUrl;
form.submit();
}
</script>
and
<input type="button" name="search_btn" value="Search" onclick="javascript:submitForm(this.form)"/>
Can you try replacing your actionURL like the following
<portlet:actionURL name="searchResults" var="showSearchResultsUrl">
<portlet:param name="myaction" value="searchResults" />
</portlet:actionURL>
You need to set the action of your form, something like this:
<form:form action="<%=showSearchResultsUrl%>" id="searchForm" name="searchForm" commandName="patientStoryForm" method="POST" action="${showSearchResultsUrl}" enctype="multipart/form-data" >
<input type="button" name="search_btn" value="Search"/></td>
This way your action name will be "myaction" with the value "searchResults" as your mapping on the controller side.

Resources