I have a .JSP file that has three forms on it whose fields are dynamically shown/hidden based on user interaction with Javascript/Jquery elements.The forms are spring forms sending their action to a URL that matches a controller.
The issue is that when I submit a form, and it does not validate, the URL the form submitted to stays in the URL. Then any action I take that is URL dependent is basically corrupted because the URL has the form action appended to it.
For example, if my .JSP's normal URL is /admin/, and my spring form is:
<form:form id="form" method="POST" action="${pageContext.request.contextPath}/admin/createUser" modelAttribute="User">
If validation fails my URL will now be /admin/createUser. If I am then taking some action using Javascript/Jquery the URL is no longer a valid way to navigate. I could just work around the URL but it just seems...un-ideal.
I have tried using redirects like: "redirect:/admin/", but spring validation will not work with this because you are basically just reloading the page.
Are there any best-practice or "elegant" solutions to this, or something really simple that I'm overlooking?
The redirection should probably be done on the client side anyways with a param of returnToURL, and as for error handling in Spring, you can register method as error handler to return proper error responses:
class YourController {
/**
* see #link{org.springframework.web.bind.annotation.ExceptionHandler}
*/
#ExceptionHandler(Exception.class)
public #ResponseBody String handleException(Exception e, HttpServletResponse rsp) {
// set the response status
return "{\"error\" : ...}"'
}
}
If you get error you can return error message to a custom error page to display the error message. or in your case you can return the admin page again..
here is some example code .
#RequestMapping(value = "/createUser" ,method = RequestMethod.POST)
public ModelAndView create(#ModelAttribute User user) {
if(user == null){
Map<String, String> model = new HashMap<String, String>();
model.put("errorCode", "00");
model.put("errorText", "Error Message Here");
return new ModelAndView("error_page", model);
}
return new ModelAndView("Welcome_page");
}
Related
I know PRG pattern can prevent duplicate submission(POST) for a page. But I have a JSP page with window.history.back() link. this link take back the user to the previous page that was POST, but all browsers show resubmission warning and suggest to press reload button. I want to allow user go back to that page wihout any confirmation. The POST controller does a search. Inthe JSP page I am using:
...
and the end point is:
#RequestMapping(value="search.htm", method = RequestMethod.POST)
public String searchResults(Model model, #ModelAtrribute(value="search") Serach search, HttpServletRequest request){
......
}
**** I don't want to prevent re-submission.
I can't try it now, so take following example like a "guideline"... anyway I think you should use SessionAttributes within the ModelAttribute in order to keep your model still using window.history.back() link.
So, you could modify your Controller like:
#Controller
#SessionAttributes("searchObj")
public class YourController {
#RequestMapping(value="search.htm")
public ModelAndView searchResults(#ModelAtrribute(value="search") Serach search){
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("search-result-page");
modelAndView.addObject("search", search);
modelAndView.addObject("searchObj", search);
return modelAndView;
}
}
I am writing a single page Spring MVC application.
Requirements:
I want it to change the state of the page according to the URL that is entered.
I want it to change the state of the page on a click of a button.
Example use cases:
If I enter a URL "my.site.com/", I want only my site skeleton to be loaded, with no data for the User. (User is an object in my model).
If I enter a URL "my.site.com/users/John", I want the data for "John" to be displayed (the page can be reloaded).
If I enter string "John" in a textbox, and hit button Go!, I want only the part of the page displaying user data to be refreshed and not the whole page reloaded.
Design Question:
I understand that for 1) and 2) I would need to return a new ModelAndView object, and for 3) I could use AJAX. This probably implies I would need three controller methods.
What I don't know is how to avoid conflicting URLs between the MVC and AJAX controller methods, and how to actually then call my AJAX controller method from Javascript, and not the ModelAndView controller method.
Code Example:
What I would need is something like this, except that this, of course, causes conflicting URLs.
/*
* Default view.
*/
#RequestMapping(value = "/users")
public ModelAndView displayDefault() {
return new ModelAndView("userdisplay_default.jsp");
}
/*
* View for a specific user.
*/
#RequestMapping(value = "/users/{username}")
public ModelAndView displaySpecific(#PathVariable(value = "username") String username) {
User user = new User(username);
return new ModelAndView("userdisplay_specific.jsp", "Specific User", user);
}
/*
* AJAX controller method.
*/
#RequestMapping(value = "/users/{username}", produces = "application/json", method = RequestMethod.GET)
public #ResponseBody User getTime(#PathVariable(value = "username") String username) {
return new User(username);
}
In Javascript I would then fetch the POJO like this:
// obtain user
var user = $('#user_input').val(); // this is a text input
$.getJSON("/users/"+user, function() {
//...
});
NOTE: My way of trying to achieve that could be wrong // insufficient // not optimal, so please feel free to also suggest some other way on how to do that.
Could you please give me an explanation along with a code example how what I need should be accomplished?
You can make different methods for your controllers.
For example:
#RequestMapping(value = "/users") and #RequestMapping(value = "/users/{username}") - there are GET methods. But for AJAX make controller with POST:
#RequestMapping(value = "/users/{username}", produces = "application/json", method = RequestMethod.POST)
And JS will be smth like this:
// Send the request
$.post("/users/"+user, data, function(response) {
// Do something with the request
});
Another advice (if it's possible in your situation) - rename url for your rest. E.g. add word api into the url.
As a followup to my earlier question about using Thymeleaf and preventing page refresh:
http://forum.thymeleaf.org/Preventing-page-refresh-Thymeleaf-amp-Spring-MVC-td4029155.html
Basically I had a working Spring MVC app that uses Thymeleaf to save form data. When the user saves the data the page would refresh (since I wanted to leave them on the page for more edits) and I wanted to eliminate the page refresh.
I have coded up some Javascript to use JQuery Ajax to post the data to my Spring MVC Controller. The trick seemed to be to not use a submit button, just a regular button and bind a JS function to it for sending the data to the server.
It all seems to work perfectly, but I want to make sure I understand what is happening. In particular I'm wondering if Thymeleaf is now redundant. I don't think it is because when I initially load the page Thymeleaf is still bound to the data bean. From using the debugger on the server side in the controller it looks like the post request calls the mapped method and passes in the data to the model.
I would appreciate your comments on whether or not this is the correct way to accomplish this.
Finally, how do I handle an error, say for example the repository fails to persist the data for any reason?
Thanks very much.
Here are the important parts of the form:
<FORM id="adminDataForm" action="#" th:action="#{/admin_ajax}" th:object="${adminFormAjax}" method="post">
<input type="button" value="Save Changes" id="post" onClick="sendData()" />
Here is the Javascript:
function sendData()
{
$.ajax(
{
type: "POST",
data: $("#adminDataForm").serialize(),
cache: false,
url: "/admin_ajax",
success: function(data)
{
alert("Your changes have been saved");
},
error: function()
{
alert("Error - Data not saved");
}
});
}
Here is the controller:
#SessionAttributes("adminFormAjax")
#Controller
public class TestController
{
final static protected long INDEX_RA = 2L;
#Autowired
private AdminDataRepository rep;
#RequestMapping(value="/admin_ajax", method=RequestMethod.GET)
public String adminFormAjax(Model model)
{
AdminData ad = rep.findById(INDEX_RA);
// If there is no configuration record, create one and assign the primary key
if(ad == null)
{
ad = new AdminData();
ad.setId(INDEX_RA);
}
model.addAttribute("adminFormAjax", ad);
return "adminFormAjax";
}
#RequestMapping(value="/admin_ajax", method=RequestMethod.POST)
public #ResponseBody AdminData adminSubmit(#ModelAttribute("adminFormAjax") AdminData ad, Model model)
{
rep.save(ad);
model.addAttribute("adminFormAjax", ad);
return ad;
}
}
So breakdown of answer.
Thymeleaf not redundant, it will still render the HTML page prior to sending to client. Ajax just does the further processing for you on client side.
You can use submit button as well, you just need to ensure your form is properly structured and you have javascript listening for your submit button click e.g.
$("#submitbutton").on('click', function (){//do stuff});
You handle any and all exceptions/issues within your Ajax controller as you would with standard controller. You need to separate issue handling at different levels. e.g. respository level issues should be managed at rep level, controller/pojo should be at controller level (or pojo if you using one for processing). You should also be capturing any exceptions through a global medium (e.g. ControllerAdvice).
Any issues/errors you pick up you should be communicating back via your return call in adminSubmit, and managing the relevant client response in ajax.
I have the following concern (maybe looking for a different way to do this). I have a form that I send to a post controller that returns void. My form uses the following format:
using (Html.BeginForm("Action", "Controller", FormMethod.Post, new { id = "frm", area = "MyArea" }))
I really need those parameters in place for all other things to work, but the problem is that after my form submits, it tries to redirect to that controller/action, and since it's a void action, I get a blank page. I have some javascript I use for the onclick event of the submit button which holds where I want to redirect to:
onclick='var promise = Dialog("Confirm:", "Are you sure you wan to proceed? ","", "frm");
promise.fail(function ()
{successfulSave("Error!", "There was a problem processing your request");});
promise.done(function ()
{ $("#frm").submit(); successfulSave("Success!", "Locale Saved Successfully");
setTimeout(function() {window.location.href="#Url.Action("Action2", "Controller", new {area = "MyArea"})";}, 3000);});'
I want to redirect to Controller2/Action2...not the post action the form holds (Controller/Action) and perform the checks in the onclick event (successfulDialog).
"I want to redirect to Controller2/Action2...not the post action the form holds (Controller/Action)."
If I understand this statement, you want to post to Controller/Action, and then redirect to Controller2/Action2. To do this you return RedirectToAction from the post action.
public ActionResult Action(MyViewModel vm)
{
if(ModelState.IsValid)
{
//do whatever, save to database
return RedirectToAction("Action2", "Controller");
}
//error
return View();
}
I have 2 controller methods:
[HttpGet]
public ActionResult SomeAction(SomeModel model, string someString)
{
//..
return View()
and
[HttpPost]
public ActionResult SomeAction(SomeModel model)
I'm trying to find a way what would allow me to navigate directly to Post method from a controller (some other controller). I've heard the is a way to do it in javascript, but I can't find it anywhere. The idea would be to set a filed in model, and check it at beginning of a view - if it's set, do the javascript thing to redirect immediatly to Post action in controller. If anyone knows how to do it, I would aprreciate it
It sounds to me like you want to check, on page load, if the form has been filled out and if so immediately post it to the appropriate action?
If so
$(function() {
if () { // determine here if the form has been filled out and completed
$('#theform').submit(); // this posts the form, which will hit the post action
}
});