Spring - how do I get the page to refresh after a form is submitted? - spring

Using spring boot, I have a timetable page.
You can click a square, which opens up a small form with which you can add or remove the class objects from the timetable (which are saved in a database).
My issue is, when you click 'add' (or remove), while it successfully adds/removes that object from the timetable, you have to refresh the page in order to see the class be added/removed on the timetable. It appears like nothing happens when add is clicked from the user's perspective (until they manually refresh the page).
The post method redirects back to the timetable page get. I tried having it redirect to another mini get method, which then redirected back to the original timetable page; but from the browser side it still didn't look like anything was happening - just remaining on the same page with the original problem. Would love to hear a potential solution, thanks!
Edit: Here's an example of my get and post methods:
#GetMapping("/timetable/{id}/{semId}")//This method displays timetable.
public String timetable(#PathVariable(value = "id") String id, Model model,
#PathVariable(value = "semId") String semId) {
//code..
model.addAttribute("x", x);
return "timetable";
}
#PostMapping("/timetable/{id}/{semId}")
public String timetablePost(#ModelAttribute TimetableClassDto dto, #PathVariable(value = "id") String id,
Model model, #PathVariable(value = "semId") String semId) {
//code..
return "redirect://timetable/00/" + semId;
}

Are you supposed to have two // in your redirect? I have something similar in my code and it works fine. However, I create a url first then return that. Also, make sure your get mapping is properly filling out the object based on the new parameters gotten from the redirect.
Use the following:
??
String url = "redirect://timetable/00/" + semId;
return url;

Related

How to correctly initialize an object that have to contain the data retrieved by 2 methods of my controller in this Spring MVC application?

I am pretty new in Spring MVC and I have the following doubt about how correctly achieve the following task.
I am working on a web application that implement a user registration process. This registration process is divided into some consecutive steps.
For example in the first step the user have to insert a identification code (it is a code that identify uniquely a user on some statal administration systems) and in the second step it have to compile a form for his personal data (name, surname, birth date, and so on).
So, actually I have the following controller class that handle these steps:
#Controller
public class RegistrazioneController {
#Autowired
private LoadPlacesService loadPlacesService;
#RequestMapping(value = "/iscrizioneStep1")
public String iscrizioneStep1(Model model) {
return "iscrizioneStep1";
}
#RequestMapping(value = "/iscrizioneStep2", method=RequestMethod.POST)
public String iscrizioneStep2(Model model, HttpServletRequest request, #RequestParam("cf") String codicFiscale) {
System.out.println("INTO iscrizioneStep2()");
//String codicFiscale = request.getParameter("cf");
System.out.println("CODICE FISCALE: " + codicFiscale);
model.addAttribute("codicFiscale", codicFiscale);
return "iscrizioneStep2";
}
#RequestMapping(value = "/iscrizioneStep3", method=RequestMethod.POST)
public String iscrizioneStep3(#ModelAttribute("SpringWeb")Step2FormCommand step2Form, ModelMap model, HttpServletRequest request) {
System.out.println("INTO iscrizioneStep3()");
System.out.println("NOME: " + step2FormCommand.getName());
return "iscrizioneStep3";
}
Into the iscrizioneStep2() it is retrieved the first code (#RequestParam("cf") String codicFiscale).
Into the iscrizioneStep3() it is retrieved a command object containing the data inserted into the form of the view in which this form was submitted, this one:
#ModelAttribute("SpringWeb")Step2FormCommand step2FormCommand
It works fine.
Now my problem is that I have another object named Step3View that have to be initialized with the aggregation of the #RequestParam("cf") String codicFiscale object retrieved into the iscrizioneStep2() method and the #ModelAttribute("SpringWeb")Step2FormCommand step2FormCommand retrieved into the iscrizioneStep3() method.
This Step3View class simply contain the String codicFiscale and all the fields of the Step2FormCommand class.
Now my doubts are: what is the best way to handle this situation? Where have I to declare this Step3View object? at controller level? (so I can use it in all my controller methods?). Have I to annotate this class with #Component (or something like this) to inject it in my controller?
What is the best solution for this situation?
I think in order to get an answer you need to understand the question and ask the right question. I think your question is "how do I pass a parameter from one page to another page in SpringMVC?". You specifically want to know how to pass the "cf" param, but readers here will tend to pass over questions that are too specific because it takes too much time to figure out what you want.
In answer to that, see Spring MVC - passing variables from one page to anther as a possible help.
Also, there are many good answers about this question for JSP in general, which can be worked into the SpringMVC architecture. See How to pass value from one jsp to another jsp page? as a possible help.

Using session objects from parent class in component

During my battle with tapestry 5 I created some setup which does not work, and I don't know why. I found few work-arounds, but still I would like to know why initial idea failed.
I have parent abstract page class with application-wide auth procedure:
public abstract class AuthPage {
#SessionState
protected UserAuth user;
public Object onActivate(){
if(user==null)
return LoginForm.class;
else if(user.getLoggedIn().equals(Boolean.FALSE))
return LoginForm.class;
else
return null;
}
}
Then I have index page class, using auth class as aprent:
public class Index extends AuthPage
{
}
This part works smoohtly - when user SSO is initialized then I got Index content, otherwise it goes to LoginForm. Now the problematic part - Index uses a layout component which takes care of showing personalized header and menu. Its logic looks like that:
public class Layout extends AuthPage
{
#Property
private Boolean loggedIn;
#Property
private String userName;
#SetupRender
public boolean checkNames(){
if(user==null){
loggedIn = false;
userName = "unlogged";
}
else if(user.getLoggedIn().equals(Boolean.FALSE)){
loggedIn=false;
userName = "unlogged";
}
else{
loggedIn = true;
userName = this.user.getUsername();
}
return true;
}
}
The idea was simple - session object user from AuthPage should be available in Layout, and used on setup-render stage to get user name and rising the flag for rendering menu etc. From my point of view everything should work, but in practice Layout class didn't get user object from session (despite that it was initialized for sure, because Index renders its content).
So my question is - why Layout class don't see UserAuth object stored in session, but gets it as null instead?
************ little update:
I've refactore layout to that shape:
public class Layout
{
#SessionState
protected UserAuth user;
#Property
private Boolean loggedIn;
#Property
private String userName;
#SetupRender
public boolean checkNames(){
if(user==null){
loggedIn = false;
userName = "unlogged";
}
else if(user.getLoggedIn().equals(Boolean.FALSE)){
loggedIn=false;
userName = "unlogged";
}
else{
loggedIn = true;
userName = this.user.getUsername();
}
return true;
}
}
and it works as I want - Layout (executet from Index page as component) takes user attribute from session, performs checkNames and sets up all properties properly. For me there is no technical difference between initial and second implementation, but somehow when user is defined in parent class is always set to null (no matter what is stored in session). The question is - why it works that way?
Layout is a component, not a page, and onActivate() is page event which won't be fired when the Layout component renders. For a component (Layout) to extend a page (AuthPage) does not make sense. I'm surprised that Tapestry allows it to tell the truth.
On another note, Tapestry has so many features (including filters, class transformations and mixins) that inheritance is almost always not required. Although quite complex, you might find the diagram at the bottom of this page useful. In particular you may be interested in the ComponentRequestFilters and PageRenderRequestFilters.
Here's a couple of options for securing pages:
Howard Lewis Ship's blog - Howard is the creator of Tapestry
Tynamo tapestry security
What version of tapestry are you using? In tapestry 5.3.1 and earlier, instance variables must be private. Only version 5.3.2+ supports protected and package private.
http://tapestry.apache.org/page-and-component-classes-faq.html#PageAndComponentClassesFAQ-Whydomyinstancevariableshavetobeprivate%3F
Ok, I solved the problem! Answer is a little bit unexpected for me, but well - everything seems to work fine now. The trick is the way I was refering to the user attribue ot AuthPage class. I used
this.user
to refere to it from child classes. It turned out that I've got it as null everywhere, even in pages (I had an impression that it worked correctly in Index page, but in fact it was just used in AuthPage class). The solution was to refere by:
super.user
When I switched to such referencing style suddenly every page and component started to work properly and got correct values from session. I'll just take it as it is, but if someone knows why it works that way and no other - I'll appreciate sharing this knowledge with me.

spring 3 not rendering model in jsp

I am a bit disapointed concerning Spring 3 not rendering my model in a jsp using Expression Language and I have to admit that I dont understand why. If anyone could help me understanting why I can't make it work it will be really great.
Here's my context:
My controller have a method (called by ajax from my client) returning a jsp fragment:
#RequestMapping(value = "/datagrid/getGoatCard", method = RequestMethod.POST)
public String getGoatCard(#RequestParam Long id,
#ModelAttribute("goat") Goat goat) {
goat = goatDataService.findGoatById(id);
return "goatCard";
}
I call this method with a requestParam allowing hibernate to retrieve the desired Bean (the model contains all the requiered data, it has been checked).
Then this method retruns a jsp named "goatCard"; here's the jsp code:
<input name="goat.goatName" type="hidden" value="${goat.goatName}"/>
(this isn't the whole page code, cause this won't be easy to read if too many code is presented. My jsp contains JQuery easyui and highcharts javaScript librairies)
I though that the annotation #ModelAttribute("goat") linked the model called "goat" to my jsp allowing to render the model using EL but it doesn't seem so.
Does anybody have any idea, perhaps it just a little thing that I did wrong but I don't see which one!!!!
#ModelAttribute is used for retrieving form model rather than setting to be displayed in JSP.
If you need to display data in JSP, you have to add the data into Model first.
#RequestMapping(value = "/datagrid/getGoatCard", method = RequestMethod.POST)
public ModelAndView getGoatCard(#RequestParam Long id) {
ModelAndView mv = new ModelAndView("goatCard");
Goat goat = goatDataService.findGoatById(id);
mv.addObject("goat",goat);
return mv;
}
And then goat is available in JSP file.
By the way, for retrieving data, better to use RequestMethod.GET.
Thank's a lot for your help. Just an answer to update your code.
As I use Spring 3, it is better to write
#RequestMapping(value = "/datagrid/getGoatCard", method = RequestMethod.POST)
public String getGoatCard(#RequestParam Long id,
Model model) {
model.addAttribute("goat", goatDataService.findGoatById(id));
return "goatCard";
}
It's just to fit more to the preconisation of Spring Foundation (I agree this lead to the same result, but SpringSource recommend the use of String return instead of mav).
Again thanks for your help

Storing model is session. Is there any better way for it?

I have a model class like following
public class ProductModel
{
string ProductName { get; set; }
int Quantity { get; set; }
}
In Controller I have an Action item
public ActionResult ShowProduct()
{
return View();
}
In my view user has two text boxes; where they enter product name and quantity. The first time they come in on this page these fields are empty. Once they enter values in these text boxes they hit a Next button which take them to a next page where they have to enter additional information about order.
On that page I have a back button and they can come back to this first page. Problem is I need to display the information that they entered in first page but on the second page I don’t have that ProductModel anymore. I can store that model in session but not sure if there is any better pattern of doing it in MVC
I would steer clear of Session and TempData. If you're using MVC, and your views are separated by full postbacks, (not Ajax) maybe you could use a view model pattern across different controller actions.
public class OrderController : Controller
{
public ActionResult ShowProduct()
{
return View(new ProductViewModel());
}
[HttpPost]
public ActionResult DoOrderStuff(ProductViewModel vm)
{
if (ModelState.IsValid)
{
// OrderViewModel would contain some product data
// to be used in the DoOrderStuff view
return View(new OrderViewModel(vm));
}
// error, go back to Page 1
return View("ShowProduct", vm);
}
}
This gives you room for validation while still following the wizard style views you described.
Caveat I just realized with this:
If you have a bunch of successive views, your user experience would probably suffer without a lot of hacking together of different view models. E.g. customer is on page 5 of the wizard, and wants to go back to page 2--my answer in its simplest form wouldn't accommodate that. However, with a good abstraction of the values in all your screens, it could be done.
This is pretty much what the Session dictionary was intended to be used for. You may look into using TempData but in essence it is just a lightweight version of Session. I don't see anything wroth with what you are doing.
I don't think you need to store this in the Session/TempData (watch out, how TempData works surprisingly changed quite a bit from MVC 2 to MVC 3). Your next button sounds like a POST, and then you do some sort of Redirect. If instead you made your form POST to the URL you wanted to display next, the ProductModel would be passed right along, and you could then pass it from the Action to the View, through either the Model or ViewData.

Primefaces dataTable issues

What is the method to refresh data on subsequent pages - second page, third page, etc - of a Primefaces dataTable using the LazyDataModel method?
Also, if I select an item in a dataTable to view its detail on another page, then came back to the dataTable using either the browser's Back button or implement JavaScript's history.back() method, it seems that the dataTable always reset its position to the first page instead of going back to the page the user was on. How can I force the dataTable to stay on the last viewed page?
My codes for lazy loading are:
private final class LazyLoader extends LazyDataModel<BookModel>
{
private static final long serialVersionUID = 1L;
public LazyLoader(String sort, String category, String operator, String input) {
setListing(getBookService().getListing(sort, category, operator, input));
}
#Override
public List<BookModel> load(int first, int pageSize, String sortField, boolean sortOrder, Map<String, String> filters) {
return getListing();
}
}
And for the Submit method is:
public String Submit()
{
sort = sortBean.getSort();
category = categoryBean.getCategory();
operator = operatorBean.getOperator();
input = searchBean.getInput();
lazyModel = new LazyLoader(sort, category, operator, input);
lazyModel.setRowCount(listing.size());
return null;
}
I'm using #ViewScoped for listing the book records as well as showing detail of a book record.
Does anyone has similar issues with Primefaces dataTable?
Keep using #ViewScoped. You should not use #SessionScoped unless you have real needs for it.
To remember the last page, you have to set the first attribute of the load method. You can do that with request parameters. Something like: yourview.xhtml?f=3 .
About the refreshing, the thing is that you are using a lazy loader but you're loading everything at once... Your load method is the one that should do the query on demand, that is, page by page.
Does pagination work for you without lazy loading? I would verify that works as expected before you jump into the hardest case.
If you want your dataTable to remember the last pagination after you navigate away from the JSF page then you need to make your managed bean SessionScoped. The lifecycle of the ViewScoped managed bean ends after navigation leaves the view.
In order to keep the selected page you have to do 2 things.
First make the managedBean session scoped.
Second set a binding between the datatable and a UIData object. In your backend bean for example put
private UIData filasUIData = null;
public UIData getFilasUIData() {
return filasUIData;
}
public void setFilasUIData(UIData filasUIData) {
this.filasUIData = filasUIData;
}
Now in your data table
<ice:dataTable
binding="#{yourBean.filasUIData}"
that´s all.

Resources