I'm trying to achieve a loop of the following steps before save is called.
Show list
add to list
It works adding one item. On the second item added spring has lost the reference to the ModelAttribute which contained an item already and tries to reconstruct it from the form data, which it must not because it contains polymorphic types.
How do I preserve the updated model throughout every add?
Sample
#RequestMapping(value = "/foo", method = RequestMethod.GET)
public String show(#ModelAttribute("foos") ArrayList<Foo> foo, Map model) {
model.put("foo", foo);
return "foo.jsp";
}
#RequestMapping(value = "/addFoo", method = RequestMethod.POST)
public String add(#ModelAttribute("foos") ArrayList<Foo> foo,
RedirectAttributes redirectAttributes) {
foo.add(new FooImpl());
redirectAttributes.addFlashAttribute("foos", foo);
return "redirect:foo";
}
How can I do this without using SessionAttributes?
Related
I am new to Spring MVC. I have problem regarding passing objects between views. I can pass object Flight from view "flight" to view "searchFlight" without problem. There should be no passing of any object from "searchFlight" to "bookFlight" as at this point I store needed info in static object outside of controller.
In "bookFlight" I load all the data from static object (not editable in view) and then I want to gather data in "bookFlight" and pass it to "performBook" view. Gathered data is also stored in other static object outside of controller. At this point in "performBook" I can refer to static object and display data stored in - but I can not save anything from "bookFlight" into my static object.
If I change static object to non-static (simply copy-paste flighSearch method with different metohd body) then I also can not collect data from form:form and save it in object or pass it to next view.
This is my controller. Everything in GlobalCache is public static. Every model object has setters and getters.
#RequestMapping(value = "/flight", method = RequestMethod.GET)
public ModelAndView flightSearch(ModelMap model) {
GlobalCache.flight=new Flight();
GlobalCache.flightTime=GlobalCache.generateFlightTIme()+1;
GlobalCache.flight.setTravelLength(GlobalCache.flightTime);
return new ModelAndView("flightSearch","command",new Flight());
}
#RequestMapping(value="searchFlight", method = RequestMethod.POST)
public String search(#ModelAttribute("user") Flight flight, ModelMap model) {
model.addAttribute("departureDate",flight.getDepartureDate());
model.addAttribute("departureAirport",flight.getArrivalAirport());
model.addAttribute("arrivalDate", flight.getArrivalDate());
model.addAttribute("arrivalAirport", flight.getArrivalAirport());
model.addAttribute("ticketNo", flight.getTicketNo());
model.addAttribute("travelType", flight.getTravelType());
model.addAttribute("flight", flight);
GlobalCache.flight=flight;
GlobalCache.flightTime=GlobalCache.generateFlightTIme()+1;
GlobalCache.flight.setTravelLength(GlobalCache.flightTime);
return "results";
}
#RequestMapping(value="/bookFlight", method = RequestMethod.POST)
public ModelAndView bookFLight(#ModelAttribute("user") Ticket ticket, ModelMap model) {
ticket=new Ticket();
ticket.setClassesList(classes);
ticket.setPaymentTypeList(paymentType);
ticket.setSeatPositionList(seatPosition);
ticket.setFlight(GlobalCache.flight);
ticket.setPayment(new Payment());
ticket.setUtil(new Util());
ticket.getPayment().setAmountToPay(GlobalCache.generateTicketCost());
Traveler traveler = new Traveler();
ticket.setTraveler(traveler);
model.addAttribute("ticket", ticket);
model.addAttribute("flight", GlobalCache.flight);
model.addAttribute("traveler", ticket.getTraveler());
model.addAttribute("payment", ticket.getPayment());
GlobalCache.ticket=ticket;
return new ModelAndView("book","command",GlobalCache.ticket);
}
#RequestMapping(value="performBook", method = RequestMethod.POST)
public ModelAndView booked(#ModelAttribute("user") Ticket ticket, ModelMap model) {
model.addAttribute("ticket", GlobalCache.ticket);
return new ModelAndView("booked","command",GlobalCache.ticket);
}
Example of handling form in "bookFlight" view.
<form:input path="traveler.name" value="${traveler.name}" type="text" id="5" class="form-control" aria-describedby="basic-addon1"/>
Class structure:
class Ticket{
private Flight flight;
private Traveler traveler;
private Util util;
//getters and setters and other attributes
}
At this time I do not have time to create separate controllers, hope it can be solved with just one.
I have a method which is taking #modelattribute as the parameter and is returning model and view object as shown below
#RequestMapping(value = "/pathtorequest", method = RequestMethod.POST)
public ModelAndView redirectdemo( HttpServletRequest req,#ModelAttribute(value="demo") Employee e) {
ModelAndView m=new ModelAndView("result");
Map<String,Object> map=m.getModel();
for(String s:map.keySet()){
System.out.println("key::"+s+" value::"+map.get(s));
}
return m;
}
foreach loop is not printing anything whereas an object is added to model with name=demo.
in the view page which is result i am getting the value of modelattribute in requestScope.
Why the object demo is not added to model map? isnt the demo a model object?
Because, although the Employee object is added by the #ModelAttribute annotated parameter, you then create a brand new ModelAndView with the line
ModelAndView m=new ModelAndView("result");
Then you iterate over m which contains only a view name (i.e. "result") but no model.
When you return a modelAndView, Spring will add to it all the other model attributes created by #ModelAttribute annotations.
If you want to manilpulate the model in your method, add it as a parameter:
#RequestMapping(value = "/pathtorequest", method = RequestMethod.POST)
public ModelAndView redirectdemo( HttpServletRequest req,#ModelAttribute(value="demo") Employee e, ModelMap modelMap) {
for(String s : modelMap.keySet()){
System.out.println("key::"+s+" value::"+modelMap.get(s));
}
}
The post request to my controller action is coming from ajax like this:
$.post("myurl.htm", { view: $("#selView").val(),
val1: "value1", val2: "value2"
});
I have two Models Foo and Bar
public class Foo {
public String val1;
//getter/setters
}
public class Bar {
public String val2;
//getters/setters
}
Now, my controller action looks like this:
#RequestMapping(value="/myurl.htm", method=RequestMethod.POST)
public ModelAndView doSomething (
#RequestParam(value="view", required=true) String view,
#RequestParam(value="val1", required=false) String val1,
#RequestParam(value="val2", required=false) String val2) {
Foo foo = new Foo();
Bar bar = new Bar();
if (view.equalsIgnoreCase("something"))
foo.setVal1(val1);
else if (view.equalsIgnoreCase("somethingelse"))
foo.setVal2(val2);
fooService.doSomeStuffWithDb(foo);
barService.doSomeStuffWithDb(bar);
}
Questions
Even though, everything above works I think there should be a better way to do this...? What if I had 10 parameteres posting in my post request, would I have 10 parameters in my controller action?? This would not scale well.
So, is there a way that spring can automagically bind the parameters to there relevant getters/setters on the model?
spring can inject request parameters as a Map:
#RequestMapping(value="/foo", method=RequestMethod.POST)
public ModelAndView doSomething (#RequestParam Model<String, String> params) {
params.get("val1");
params.get("val2");
// ...
}
use your javascript unmodified. this is not as elegant as the #ModelAttribute version, but scales prety well with a lot of parameters.
If you are passing many variables to the controller, its best to start to use an object to hold these params. This object is sometimes known as a data transfer object (DTO).
Here is an example:
#RequestMapping(method = RequestMethod.POST, produces = "text/html")
public String create(#Valid Market market, BindingResult bindingResult, Model uiModel, HttpServletRequest httpServletRequest) {
if (bindingResult.hasErrors()) {
return "markets/create";
}
// store market
return "redirect:/markets/" + encodeUrlPathSegment(market.getId().toString(), httpServletRequest);
}
I have a controller that gets an ID from a form in search.jsp. I want it to redirect to entitydemo.jsp, which should be able to access EntityDemo and output its attributes. How do I do that? Do I need to use redirect and put EntityDemo as a session attribute somehow?
#Controller
public class SearchEntityController {
#RequestMapping(value = "/search", method = RequestMethod.GET)
public EntityDemo getEntityDemoByID(#ModelAttribute("search") Search search, BindingResult result) {
EntityDemo entityDemo = null;
if (search.getId() != null) {
int id = Integer.parseInt(search.getId());
entityDemo = DBHelper.getEntityDemo(id);
}
return entityDemo;
}
}
Assuming that you have some class named EntityDemo which has Getters and Setters for all the fields, I think you should do something like so:
#Controller
public class SearchEntityController {
#RequestMapping(value = "/search", method = RequestMethod.GET)
public ModelAndView getEntityDemoByID(#ModelAttribute("search") Search search, BindingResult result) {
EntityDemo entityDemo = null;
Map<String, Object> model = new HashMap<String, Object>();
if (search.getId() != null) {
int id = Integer.parseInt(search.getId());
entityDemo = DBHelper.getEntityDemo(id);
model.put("entityDemo", entityDemo);
}
return new ModelAndView(new RedirectView(pageIWantToRedirectTo), model);
}
}
Then, in your JSP, you can use JSTL and do something like this: ${entityDemo.name}, where name is a field I am assuming that the EntityDemo class has together with an appropriate Getter, this being public String getName(){return this.name;}.
To my knowledge, Controller methods do not return entire objects, they either return String values which denote the name of the view, such as \foo\bar\myPage.jsp or else, entire ModelAndView objects (there are 2 types of objects, one of them has portlet in its full name and the other has servlet. In this case you must use the one that has servlet in its full name. Just for clarity, when I say full name, I mean the name which includes the package within which it resides. If memory serves me well the one you are looking for is in springframework...servlet.ModelAndView or something like that.
EDIT: If you want to redirect upon submit, then, you will need to make 2 controllers, one which will render the form and the other which will redirect once the form is submitted.
Regarding your JSP Page, you should have an xml file name dispatcher-servlet.xml. The name could be different, depending on your configurations in web.xml, but they all have the structure of <servletname>-servlet.xml. There should be a property named viewResolver (Although this should be the case, certain IDE's do not populate much for you. On the other hand, IDE's such as Netbeans set up most of the initial configuration for you). This is another controller which acts upon your views. What it does is that it automatically appends items before and after your view name which you specify in your controller. Usually it appends a prefix of pages/jsp/ and a suffix of .jsp. So, if you have a page with the following path pages/jsp/myPage.jsp, all you need to pass in your controller would be myPage. The full path to the page will be constructed by the view resolver. If you pass in the whole URL, it will still keep on adding stuff so the page still won't be found even though you specified a correct path.
I got it to work using 2 methods in my controller - one to display the form and another for the search results
Controller:
#Controller
public class SearchEntityController {
#RequestMapping(value = "/search", method = RequestMethod.GET)
public void searchForm(Model model) {
model.addAttribute(new Search());
}
#RequestMapping(value = "/entitydemo", method = RequestMethod.POST)
public void showSearchResult(#ModelAttribute Search search, Model model) {
model.addAttribute("entityDemo", getEntityDemo(search));
}
// code to load entity here
}
(Search is a class with an int id and accessors)
Form in search.jsp:
<form:form action="entitydemo" commandName="search">
ID: <form:input path="id" />
</form:form>
Showing results in entitydemo.jsp:
<core:out value="${entityDemo.foo}" /> <br/>
<core:out value="${entityDemo.bar}" />
I'm using the following action on a SpringMvc application:
#RequestMapping(value = "/test", method = RequestMethod.GET)
public ModelAndView test(
#ModelAttribute List<Group> groups
) {
//return whatever
}
My Group class has an 'id' and a 'name' property. Default getter/setter.
How should i call this action in order to have this list correctly instanciated?
I tried something like:
/test?groups.id=2&groups.name=stackrocks&groups.id=3&groups.name=stackrules
Didn't work.
Also tried:
/test?groups[].id=2&groups[].name=stackrocks&groups[].id=3&groups[].name=stackrules
No success.
So, how to bind a list when using SpringMvc?
You can't bind parameters of method with the exactly that signature. #ModelAttribute binds attributes to the fields of the corresponding model object, so you can encapsulate your List into object:
public class Groups {
private List<Group> list = new AutoPopulatingList<Group>(Group.class);
...
}
#RequestMapping(value = "/test", method = RequestMethod.GET)
public ModelAndView test(
#ModelAttribute Groups groups
) {
//return whatever
}
and then call it as follows:
/test?list[0].id=2&list[0].name=stackrocks&list[1].id=3&list[1].name=stackrules