I'm trying to use Thymeleaf fragments with Ajax on my project and I have this method on my controller:
public def displayInfo(Model model) {
log.info("Guests method!")
model.addAttribute("content", "testing ajax");
"fragments/content::form-basic";
}
And I get this message before running the application:
WARNING: The [displayInfo] action in [ca.capilanou.hrpay.workload.NonInstructionalWorkloadController] accepts a parameter of type [org.springframework.ui.Model]. Interface types and abstract class types are not supported as command objects. This parameter will be ignored.
public def displayInfo(Model model) {
^
On the html where I want to add the fragment by ajax I have this just for testing:
<button id="bt1" onclick="$('#content').load('/nonInstructionalWorkload/displayInfo');">Load Frag 1</button>
<div id="content"></div>
What is happening is that I get the "Guests method!" message on the console, which means that it's reaching the controller, but when it tries to do:
model.addAttribute("content", "testing ajax");
I get a nullPointerException because the model parameter is coming null.
So I tried to comment this line and just return the fragment I want to display.
This is the fragment I have:
<th:block th:fragment="content" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<div th:text="${content}"></div>
</th:block>
I tried to put the ${content} text hard coded when commenting the model.addAttribute line, but I'm not getting anything back on my screen.
What do I need to do to fix the "WARNING" I'm getting and also the to be able to see the fragment being shown on the right place?
I was able to solve it this way:
Instead of using this method:
public def displayInfo(Model model) {
log.info("Guests method!")
model.addAttribute("content", "testing ajax");
"fragments/content::form-basic";
}
I'm now using this:
ModelAndView addWorkItem() {
log.info("Display Fragment using Ajax")
ModelAndView modelAndView = new ModelAndView("/fragments/content :: content")
modelAndView.addObject("content", "Hello World!")
return modelAndView
}
We need to return the ModelAndView. If there anything you need to use on the screen you can addObject adding its attributes.
By doing this I avoid the Model, so I get rid of the Warning issue.
That's it. I wasn't able to find this solution anywhere.. I found pieces in different articles.
https://github.com/dandelion/dandelion/issues/28
http://www.marcelustrojahn.com/2016/08/spring-boot-thymeleaf-fragments-via-ajax/
http://www.xpadro.com/2014/02/thymeleaf-integration-with-spring-part-2.html
Related
I have a problem with error displaying in my spring mvc app. So let me describe more detail about problem.
I already know what to do if i want to show custom error page instead of default whitelabel error page. I read about this here and watched some tutorials on youtube. It is simple, but the problem is that user will always redirected to new page, containing error description.
I want to display all my backend exceptions in specific format as a modal window, or alert message or toast as shown here but for thymeleaf. Best solution is modal pages, cause this give me more flexibility of content displaying. But modal can be invoked used JavaScript on frontend side, how to use controller exceptions as trigger for opening modal page (without using AJAX if possible)?
Here is what i want actually, when some method in controller, that can throw an exception is called and exception thrown
#GetMapping
public ModelAndView throwException() throws Exception {
throw new Exception("Oops :( something went wrong");
}
something like this shall be displayed
error window
Please help me to find a solution. Thanks a lot for your attention.
P.S. if this is complicated or impossible i want to know atleast how to show error message from controller + some buttons or input fields related to error on current(same) page, without redirecting to new one?
TL;DR
No easy way out. If I were you I'd stick with generic error pages. You can customize them for diffent kind of errors.
Tooling inadequate to your problem.
IMO The problem you've described would be better suited for SPA (single page application) written
in any modern frontend framework with java backend exposing REST API. Normally enpoint would return
json containing actual, requested data. But in case of any error you could
simply return 4xx code with description of an error as a payload. Then
presentation logic in javascript could display it whatever you want without page reload.
Since you've decided to use thymeleaf which is templating engine you should accept its limitations. Most of calls to a controller
is a request for a view. Spring and Thymeleaf takes a template file, populates it with model's data and
send back the whole page to your browser. If this process fails while collecting data the page couldn't be correctly rendered. Therefore the most reasonable
thing to do is to redirect to the error page instead.
Reasonable way out
If generic error pages scares you off, some extra effort can be made. Instead of generic error handling, you
could handle exceptions in each Controller's method.
On error you may provide different "errored" model that will be used to populate the template. It gives you
flexibility on how the error is displayed. It would depend on how you had prepared a template.
E.g.
view.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
</head>
<body>
<h1>Data:</h1>
<div th:if="${actualData}">
<p th:text="${actualData}"></p>
</div>
<!-- Instead of div below, you could supply a modal and pice of javascript opening it -->
<div th:if="${errorData}">
<h3>Error occurred:</h3>
<p th:text="${errorData}"></p>
</div>
</body>
</html>
MyController.java
#Controller
class MyController {
#GetMapping("/view")
public String view(Model model) {
try {
model.addAttribute("actualData", mayThrowExceptionDataFetch());
} catch(Exception e) {
model.addAttribute("errorData", e.getMessage());
}
return "view";
}
private String mayThrowExceptionDataFetch() {
// Either return a value or throw an exception
}
}
That way a view will always be returned. Even when error happens it is handled and displayed.
Unreasonable way out
Important note: this is demo only. For sure I wouldn't try it in production.
I found your requriements so unusual (controller exceptions as trigger for opening modal page) that I wanted to check
if something alike is even possible. I've imagined a solution may be something like:
Frontend: subscribe to server send events endpoint (source of errors).
Backend: while catching an exception, abort the request so that server doesn't return anything.
Backend: send a message containing error description to appriopriate client.
Frontend: receive event, alert it somehow.
Then I come up with this working demo, that indeed doesn't cause page reload yet displays an error:
index.html
<!DOCTYPE HTML>
<html xmlns:th="https://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
</head>
<body>
<a th:href="#{/exception}">Click for exception</a>
<script>
var sse = new EventSource('http://localhost:8080/sse');
sse.onmessage = function (evt) {
alert(evt.data);
};
</script>
</body>
</html>
MyService.java
#Service
public class MyService {
private final SseEmitter emitter = new SseEmitter();
public SseEmitter getEmitter() {
return emitter;
}
}
MyExceptionHandler.java
#ControllerAdvice
public class MyExceptionHandler {
private final MyService service;
#Autowired
public MyExceptionHandler(MyService service) {
this.service = service;
}
#ExceptionHandler(Exception.class)
#ResponseStatus(HttpStatus.CONTINUE) // I don't figure out how to abort request
public ResponseEntity<Void> handle(Exception ex) {
try {
service.getEmitter().send(SseEmitter.event().data(ex.getMessage()));
} catch (IOException e) {
// TODO
}
}
}
MyController.java
#Controller
public class MyController {
private final MyService service;
#Autowired
public MyCtrl(MyService service) {
this.service = service;
}
#GetMapping("/sse")
public SseEmitter sse() {
return service.getEmitter();
}
#GetMapping("/index")
public String index() {
return "index";
}
#GetMapping("/exception")
public String exception() {
throw new RuntimeException("Oops :o");
}
}
Everyting above is spring-boot project with spring-boot-starter-thymeleaf and spring-boot-starter-web dependencies.
Run application, go to localhost:8080/index then click a link. An alert should appear without page reload.
I am trying to get the parameter inside a post request in Spring. I have used both the annotation (#RequestParam) and also the getParam method, but none of them work for me. Same code is able to get the parameters when I send them by URL (?input=input). Can anyone help me with this? Here is my code that handles the request:
#RequestMapping(value ="/this",method = RequestMethod.POST)
public String receiver(HttpServletRequest request,#RequestParam(value="input") String input, Model model){
String input2 = request.getParameter("input");
model.addAttribute("input",input);
return "test";
}
Right now the code uses the annotation to take the input. If I change the model.attribute("input",input) to model.attribute("input",input2) then I would be using the get attribute method. Both work fine when I pass the parameter with the URL but when I pass the parameter using POST form none of them work.
And here is the view. it just prints the input parameter from the model:
<!DOCTYPE http>
<html>
<body>
test
${input}
</body>
</html>
I finally found my answer. I did not know about different types of POST requests. All I needed to do was to enable multipartResolver to be able to get "multipart/form-data". I was not aware that the Request mapping by default does not take POST of type "multipart/form-data". If you want to use multipartResolver here is the documentation.
I have the following code:
<c:forEach var="listValue" items="${upcomingMovieslists}">
div style="border:thin inset #6E6E6E; text-align: justify;> <p margin-left: 1em;"> <c:set var="movieName" scope="application" value="${listValue.key}"/><a href="/myapp/movie/SubmitMovie/" >${listValue.key}</a></p></div>
</c:forEach>
and movieName is going to be in #RequestParam String movieName in the page that it is going next.
So, When I run this code I am getting an error telling:
error:
message Required String parameter 'movieName' is not present
description The request sent by the client was syntactically incorrect.
Controller method:
My controller class to where the call is going:
#RequestMapping(value="/SubmitMovie", method = RequestMethod.GET)
public ModelAndView getSearchedMovie(#RequestParam String movieName, ModelMap model)
The URL currently is: /myapp/movie/SubmitMovie/
It should be /myapp/movie/SubmitMovie/?movieName=deadpool
in order to work
I should have /?movieName= to show the results in the next page but where as with the above jsp code I will not get the movieName in String format instead it comes in the form ${movieName} which cannot be accepted to a String present in the RequestParam and hence it throws an error.
I want to know how I can fix it to get the moviename in Stringformat in the URL so that I can populate the results
Thanks
There's not a whole lot of code there, so I don't know exactly what you're going for, but you can always add a required=false condition to a request parameter, like so:
#RequestParam(value = "movieName", required = false) String movieName
That should at least clear that error. If the logic in your model does require movieName, though, then you're going to need to refactor around that -- i.e., your link would need to look like href="/myapp/movie/SubmitMovie?movieName='${listValue.key}'" .
(Note: I'm inferring from your code that ${listValue.key} is the name of the movie. Whatever variable you want the controller to receive as the #RequestParam String movieName, place it after ?movieName= in the href string, after escaping it with single quotes (see how I did so above.)
If you're still stuck, maybe try showing the controller for the page with that parameter?
I have my form below:
<form id="form1" name="form1">
<fieldset>
<ol>
<li>
<label>Project:</label>
<select id="project" name="project" required></select>
</li>
</ol>
<input type="button" onclick="request();">
</fieldset>
</form>
I created a request() function on the onclick event of the input button. I changed the input type to button because I didn't want the page to reload.
The request function uses the RestEasy REST.Request class to create a custom rest request. I used setEntity on the REST.Request object but I don't know how to access that information on the server side.
Below is the request function:
request: function() {
var myRequest = new REST.Request();
var form1 = document.forms["form1"];
var projectTxt = form1.elements["project"].value;
myRequest.setURI(REST.apiURL + "/request/item");
myRequest.setMethod("GET");
myRequest.setEntity({project:projectTxt});
myRequest.execute(function(status, request, entity) {
if (status === 200 && request.readyState === 4) {
// entity is always null
sessionStorage.setItem("project", entity);
console.log("entity=" + entity);
}
});
},
In the above code, entity in the function passed to myRequest.execute() is always null.
Here is the Java code:
#Path("item")
#GET
public String itemFormatRequest(#FormParam("project") String project)
{
// project is always null
return "blarg!!! project is " + project;
}
In the above code, project is null. I've tried using #QueryParam and that doesn't work either. Am I just using this incorrectly or is there something else I'm missing? I've done much trial and error by changing #GET to #POST in both the javascript and java codes. Have also tried adding #Consumes("application/x-www-form-urlencoded") in the java code and that didn't work.
The only thing I did get to work is adding query parameters to the REST.request object like this:
myRequest.addQueryParameter("project", projectTxt);
And then I'm able to retrieve this using (#QueryParam("project") String project).
Thanks!
You are using GET as request method. A GET is intended to retrieve data and so you don't pass an entity. If you just want to read data of a selected project you can use a GET in conjunction with a #QueryParam. But then you don't pass an entity. Just add the queryParameter:
myRequest.addQueryParameter('project', projectTxt);
If you want to pass data to the server you should change the method of the request to POST (in the client and the server code). Also your entity is JSON but your server code expects a #FormParam. The REST.Request object has a addForm and add addFormParameter method. So one of the following should also work (untested):
myRequest.addForm('form1', form1);
or
myRequest.addFormParameter('project', projectTxt);
I am using The play framework 2.1.3 and have a question concerning form validation using jquery and ajax. I have followed the advice in this post (http://franzgranlund.wordpress.com/2012/03/29/play-framework-2-0-javascriptrouter-in-java/) for setting up JavaScript routing and that part is working great! I need help with the second part, returning a rendered form containing error messages and displaying it on my page. I have created the following example to illustrate my problem.
The routes file looks as follows:
# AJAX calls
POST /validateForm controllers.Application.validateForm()
# JavaScript Routing
GET /assets/javascripts/routes controllers.Application.javascriptRoutes()
The Application Controller looks as follows:
private static Form<User> form = Form.form(User.class);
public static Result validateForm() {
Form<User> boundForm = form.bindFromRequest();
if (boundForm.hasErrors()) {
return badRequest(user_form.render(boundForm));
} else {
return ok();
}
}
public static Result javascriptRoutes() {
response().setContentType("text/javascript");
return ok(Routes.javascriptRouter("jsRoutes", controllers.routes.javascript.Application.validateForm()
));
}
Without getting too technical, I am using Twitter Bootstrap to render my forms so I have created the following view element (see this post for more information Form validation and form helper) which is rendered by the validateForm action and returns it to the calling jquery.ajax() .
#(userForm: Form[User])
#import helper._
#implicitFieldConstructor = #{ FieldConstructor(twitterBootstrapInput.f) }
#form(routes.Application.createUser(), 'id -> "createUserForm") {
<fieldset>
<legend>New User</legend>
#inputText(userForm("name"), 'class -> "form-control", 'placeholder -> "First Name")
#inputText(userForm("surname"), 'class -> "form-control", 'placeholder -> "Last Name")
<button id="createUserBtn" type="button" class="btn">Create User</button>
</fieldset>
}
I am using jquery and my javascript file looks as follows. For the purposes of this question, assume validation fails and a badRequest is returned.
$("#createUserBtn").click(function() {
jsRoutes.controllers.Application.validateForm().ajax({
type: "POST",
data: $("#createUserForm").serialize(),
success: function(response) {
// something here
},
error: function(response) {
$("#createUserForm").html(response);
}
});
});
Without going into too much detail on the view, I correctly include the JavaScript file in the view and there is an element called #createUserForm.
The communication between client-side and server-side seems to be working great, however, I am unable to load the rendered view element contained in the response onto the page through .html(response). So, how does one accomplish this? Where have I gone wrong? I am fairly new to many of these concepts and would appreciate any helpful input. Thanks.
UPDATE:
Two changes changes will allow this code to work beautifully. (1) Since we are replacing the entire form, including the button, we need to make use of the jQuerys .on() to handle the button click event. (2) Calling responseText property will correctly return the html contained within the result.
$(document).on("click", "#createUserBtn", function() {
jsRoutes.controllers.Application.validateForm().ajax({
type: "POST",
data: $("#createUserForm").serialize(),
success: function(response) {
// something here
},
error: function(response) {
$("#createUserForm").html(response.responseText);
}
});
});
Any comments? How would you do this better?