Just looking at the petclinic sample application, and trying to learn form handling.
It seems the form maps to an entity 1:1 correct? Is there any other configuration that has to be done, or will spring just know that all the form inputs map to the entity because that is what was added to the model in the GET request?
#Controller
#RequestMapping("/owners/*/pets/{petId}/visits/new")
#SessionAttributes("visit")
public class AddVisitForm {
private final Clinic clinic;
#Autowired
public AddVisitForm(Clinic clinic) {
this.clinic = clinic;
}
#InitBinder
public void setAllowedFields(WebDataBinder dataBinder) {
dataBinder.setDisallowedFields("id");
}
#RequestMapping(method = RequestMethod.GET)
public String setupForm(#PathVariable("petId") int petId, Model model) {
Pet pet = this.clinic.loadPet(petId);
Visit visit = new Visit();
pet.addVisit(visit);
model.addAttribute("visit", visit);
return "pets/visitForm";
}
#RequestMapping(method = RequestMethod.POST)
public String processSubmit(#ModelAttribute("visit") Visit visit, BindingResult result, SessionStatus status) {
new VisitValidator().validate(visit, result);
if (result.hasErrors()) {
return "pets/visitForm";
}
else {
this.clinic.storeVisit(visit);
status.setComplete();
return "redirect:/owners/" + visit.getPet().getOwner().getId();
}
}
}
Note the #SessionAttributes annotation on the class:
When the original GET request comes, the newly created Visit is stored in a session.
When the subsequent POST comes, object stored in the session is updated with the input values from the form.
When Visit is finally persisted, status.setComplete() removes the session attribute.
Without #SesssionAttributes, Visit would be recreated using the form input values when POST comes.
On the jsp you have to spring form tags and bind each variable to the modal attribute using the tags and also have to specify the modalattribute name in the form tag. Tag ref
Related
I'm new to Spring MVC Framework. I'm doing some self study to extend my knowledge in Java.
This is how I understand the getProducts() code definition from a tutorial I'm following but please correct me if I'm wrong.
Controller requests something from the Data Access Object >
Data Access Object gets the data from a Database or a Model through the getProductList() method > Stores the information to list > Then binds the list to the model.
So I got two question about this.
Is the inclusion of model as parameter in public String getProducts(Model model) considered the dependency injection
Is products (within quotes) in model.addAttribute("products",products); just a name which I can change to whatever I like or should it match something?
public class HomeController {
private ProductDao productDao = new ProductDao();
#RequestMapping("/")
public String home(){
return "home";
}
#RequestMapping("/productList")
public String getProducts(Model model){
List<Product> products = productDao.getProductList();
model.addAttribute("products",products);
return "productList"; //productList string is the productList.jsp which is a view
}
#RequestMapping("/productList/viewProduct")
public String viewProduct(){
return "viewProduct";
}
}
I'd appreciate any explanation or comment.
Thank you.
Yes,
model is instantiated by spring and injected to your method, means if any of model attribute matches anything in request it will be filled. and it should be the last param in the method
model.addAttribute("products",products);
"products" is just a name which you can use it in your view get the value with ${products}
My code. this is sample.
#Autowired
private ProductService productService;
#RequestMapping(value = "/settings/product")
public ModelAndView showProduct(ModelAndView mav, HttpServletRequest req, Authentication auth) {
CustomUserDetail customUserDetail = (CustomUserDetail) auth.getPrincipal();
int associationIdx = customUserDetail.getAccount().getAssociation().getIdx();
String language = CookieUtil.getCookieValue(req, "lang");
Association association = associationService.findAssociationByIdx(associationIdx);
List<AssociationProductColumnDefine> columns = associationService.findByAssociationAndLanguage(association,
language);
List<AssociationProductColumnDefine> source = associationService.findByAssociationAndLanguage(association,
"ko-kr");
mav.addObject("association", association);
mav.addObject("source", source);
mav.addObject("columns", columns);
mav.setViewName("/association/association_settings_product");
return mav;
}
Yes, you choice model and ModelAndView.
Yes, simple.
I have a requirement where the user selects some data from a form and we need to show that selected data on the next page.
At present we are doing this with a session attribute, but the problem with this is that it overwrites the data if the first page is open in another browser tab, where the data is again selected and submitted. So I just want to get rid of this session attribute while transferring data from one controller to another.
Note: I am using an XML based Spring configuration, so please show a solution using XML, not annotations.
Define RedirectAttributes method parameter in the the handler method that handles form submission from first page:
#RequestMapping("/sendDataToNextPage", method = RequestMethod.POST)
public String submitForm(
#ModelAttribute("formBackingObj") #Valid FormBackingObj formBackingObj,
BindingResult result,
RedirectAttributes redirectAttributes) {
...
DataObject data = new DataObject();
redirectAttributes.addFlashAttribute("dataForNextPage", data);
...
return "redirect:/secondPageURL";
}
The flash attributes are saved temporarily before the redirect (typically in the session) and are available to the request after the redirect and removed immediately.
The above redirect will cause the client (browser) to send a request to /secondPageURL. So you need to have a handler method to handle this request, and there you can get access to the DataObject data set in the submitForm handler method:
#RequestMapping(value = "/secondPageURL", method = RequestMethod.GET)
public String gotoCountrySavePage(
#ModelAttribute("dataForNextPage") DataObject data,
ModelMap model) {
...
//data is the DataObject that was set to redirectAttributes in submitForm method
return "pageToBeShown";
}
Here DataObject data is the object that contains data from the submitForm method.
I worked with this requirement and I used RedirectAttributes, then you can add this redirect attributes to your model. This is an example:
#RequestMapping(value = "/mypath/{myProperty}", method = RequestMethod.POST)
public String submitMyForm(#PathVariable Long myProperty, RedirectAttributes redirectAttributes) {
redirectAttributes.addFlashAttribute("message", "My property is: " + myProperty);
return "redirect:/anotherPage";
}
#RequestMapping(method = RequestMethod.GET)
public String defaultPage(Model model, #RequestParam(required = false) String message) {
if(StringUtils.isNotBlank(message)) {
model.addAttribute("message", message);
}
return "myPage";
}
Hope it helps.
You can use RedirectAttributes ; A specialization of the Model interface that controllers can use to select attributes for a redirect scenario.
public interface RedirectAttributes extends org.springframework.ui.Model
Plus this interface also provide a way to store "Flash Attribute" . Flash Attribute is in FlashMap.
FlashMap : A FlashMap provides a way for one request to store attributes intended for use in another. This is most commonly needed when redirecting from one URL to another.
Quick Example is
#RequestMapping(value = "/accounts", method = RequestMethod.POST)
public String handle(RedirectAttributes redirectAttrs) {
// Save account ...
redirectAttrs.addFlashAttribute("message", "Hello World");
return "redirect:/testUrl/{id}";
}
Reference and detail information are here
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}" />
My controller has a method to return a form backing object:
#ModelAttribute(“userData”)
public UserData formBackingObject() {
return new UserData();
}
When the form submission fails its validation checks, it is redisplayed but when it is re-rendered, the userData object does not contain the user-submitted values - only the values present upon initialiation above.
#RequestMapping(method = RequestMethod.POST)
public void userData(HttpServletRequest request, #ModelAttribute(“userData”) UserData userData, BindingResult bindResult, ModelMap model) {
// do validation checks
if (bindResult.hasErrors()) {
// perform redirect back to same page
}
return "userData";
}
You need to do a model.addAttribute("key", value) . This will help bind the values to the model object check http://static.springsource.org/spring/docs/current/spring-framework-reference/html/mvc.html for sample.
#ModelAttribute at method level is generally given to add reference data kind of data to the model. And these annotations are executed before #RequestMapping; hence your attribute "userData" is refreshed with new object before control reaches your public void userData(). method.
The solution is to add userData to the model within the method which returns your userdata form jsp to browser.
I have a session attribute : user, and I have a url that I want to be viewed by both logged in users and publically by people not logged in as a user.
So what I want to do is this :
#Controller("myController")
#SessionAttributes({"user"})
public class MyController {
#RequestMapping(value = "/MyPage/{id}", method = RequestMethod.GET)
public ModelAndView getPage(#PathVariable Integer id) {
return modelandview1;
}
#RequestMapping(value = "/MyPage/{id}", method = RequestMethod.GET)
public ModelAndView getPage(#PathVariable Integer id, #ModelAttribute User user){
return modelandview2;
}
However, I have a feeling its not going to work ... suggestions very welcome.
You only need the second method, the one that takes the User agument as well. When it's called without request attributes available to populate the User model, you'll just get a User instance with all null (or all default) field values, then in the body of the method you treat each situation accordingly
I don't think it's a right case for #SessionAttributes. This annotation is usually used to keep original instance of a form-backing object, to avoid passing irrelevant parts of its state via hidden form fields.
Your sceanrio is completely different, thus it would be better to use HttpSession explicitly:
#RequestMapping(value = "/MyPage/{id}", method = RequestMethod.GET)
public ModelAndView getPage(#PathVariable Integer id, HttpSession session) {
User user = (User) session.getAttribute(...);
if (user != null) {
...
} else {
...
}
}
Also note that #ModelAttribute is a subject to data binding - user can change its fields by passing request parameters. You definitely don't want it in this case.