Create a form with multiple checkboxes in spring - java

I have a game and a developer. The game has a set of developers. I want to create a form where I can add a game to the database. In the form I enter the name, price and check the developers. So i create a checkbox for every developer. However when I check those the page just seems to refresh. When I debug it seems that my controller never gets to the doSubmitAction function. When I leave out the checkboxes everything works as it is supposed to.
Is spring unable to create the collection? I don't understand fully what is happening behind the scenes of Spring. This is my first project I'm creating using spring.
Here is my form:
<form:form method="POST" commandName="game" >
<table>
<tr>
<td>
Name
</td>
<td>
<form:input path="gameNaam" size="20" />
</td>
</tr>
<tr>
<td>Choose Developers</td>
<td>
<form:checkboxes id="selectdeveloper" items="${developers}" path="developers" itemLabel="naam" />
</td>
</tr>
<tr>
<td>
Price
</td>
<td>
<form:input path="prijs" size="10" />
</td>
</tr>
<tr>
<td>
<input type="submit" value="Add" />
</td>
<td></td>
</tr>
</table>
</form:form>
And the formController:
public class GameFormController extends SimpleFormController {
private GameOrganizer gameOrganizer;
public GameFormController() {
setCommandClass(Game.class);
setCommandName("game");
setFormView("AddGame");
setSuccessView("forward:/Gamedatabase.htm");
}
public void setGameOrganizer(GameOrganizer gameOrganizer){
this.gameOrganizer=gameOrganizer;
}
#Override
protected Object formBackingObject(HttpServletRequest request) throws Exception {
Game game = null;
long id = ServletRequestUtils.getLongParameter(request, "id");
if(id<=0){
game = new Game();
}else{
game = gameOrganizer.getGame(id);
}
return game;
}
#Override
protected void doSubmitAction(Object command) throws Exception {
Game game = (Game) command;
if(game.getId()<=0){
gameOrganizer.addGame(game);
}else{
gameOrganizer.update(game);
}
}
#Override
protected Map referenceData(HttpServletRequest request) throws Exception {
Set<Developer> developers = gameOrganizer.getAllDevelopers();
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("developers", developers);
return map;
}
}

Ok so apparently I had to make a propertyEditor for Developer.
There is a good explanation on this site:
http://static.springsource.org/spring/docs/2.0.x/reference/validation.html
Edit extra information:
So apparently when you check a checkbox it will give you the value as a string.
Ofcourse the Collection had to be made with developer objects.
So I created a developerEditor:
package domainmodel;
import java.beans.PropertyEditorSupport;
public class DeveloperEditor extends PropertyEditorSupport {
private GameOrganizer gameOrganizer;
public void setGameOrganizer(GameOrganizer gameOrganizer) {
this.gameOrganizer = gameOrganizer;
}
#Override
public void setAsText(String id) {
long id2 = Long.parseLong(id);
Developer type = gameOrganizer.getDeveloper(id2);
setValue(type);
}
}
And with the checkboxes I gave as itemvalue the id of the object
<form:checkboxes id="selectdeveloper" items="${allDevelopers}" itemValue="id" path="developers" itemLabel="name" />
Then in the formcontroller I override the initBinder method.
So that when I Spring has to fill in a developer object it will first convert it from string to a Developer Object using my editor.
private DeveloperEditor developerEditor;
public void setDeveloperEditor(DeveloperEditor developerEditor){
this.developerEditor = developerEditor;
developerEditor.setGameOrganizer(gameOrganizer);
}
#Override
protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) {
binder.registerCustomEditor(Developer.class, developerEditor);
}
That's it folks.
If anyone has any questions I will be glad to answer them.

Related

Thymeleaf dynamic datatype for form

I have a HashMap, providing the data for certain documents:
Key | Value
----------------
Name | test1
Type | type1
...
Number of rows is not specified and both, Keys and Values, are Strings at first.
Now i want to edit this dynamic data. Therfor i created this template:
<form action="#" th:action="#{/documents/{id}(id=${doc_id})}" th:object="${properties}" method="post">
<h1 th:text="${document_h1_text}">Documents</h1>
<table class="table table-striped">
<tr>
<th>Property</th>
<th>Value</th>
</tr>
<tr th:each="property : ${document_properties}">
<td th:text="${property.key}">Property</td>
<td>
<input name="${property.key}" th:value="${property.value}" />
</td>
</tr>
</table>
<button type="submit" class="btn btn-default">Submit</button>
</form>
The document_properties is related to a HashMap<String,String>
This works fine when i GET this web service with
#RequestMapping(path = "documents/{doc_id}", method = RequestMethod.GET)
public String document(#PathVariable long doc_id, Model model) { ... }
Now i want to edit this output in the front-end and submit the changes:
#PostMapping("documents/{doc_id}")
public String editDocument(#PathVariable long doc_id, #ModelAttribute HashMap<String, String> properties,Model model) { ... }
Unfortunately the HashMap properties is empty when i call it thorugh clicking on the "Submit" button. Does anybody of you know how to solve this problem? FYI: I deliberately chose to not using conventional class binding here. The reason is that i want get a dynamic solution, working with arbitrary classes.
You're going to need a wrapper around the Map at the very least (I don't think you can bind straight to a Map in the way you want it to work).
You should always be using th:field instead of th:name and th:value when possible.
Here's a quick example that should work the way you want it to.
Wrapper
public class MapWrapper {
private Map<String, String> map = new HashMap<>();
public Map<String, String> getMap() {
return map;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
}
Controller
#Controller
public class MapController {
#GetMapping("map.html")
public String get(Map<String, Object> model) throws Exception {
MapWrapper wrapper = new MapWrapper();
wrapper.getMap().put("1", "One");
wrapper.getMap().put("2", "Two");
wrapper.getMap().put("3", "Three");
model.put("wrapper", wrapper);
return "map";
}
#PostMapping("map.html")
public String post(Map<String, Object> model, #ModelAttribute("wrapper") MapWrapper wrapper) throws Exception {
return "map";
}
}
Form
<form action="map.html" th:object="${wrapper}" method="post">
<table class="table table-striped">
<tr>
<th>Property</th>
<th>Value</th>
</tr>
<tr th:each="key : ${wrapper.map.keySet()}">
<td th:text="${key}" />
<td>
<input th:field="*{map[__${key}__]}" />
</td>
</tr>
</table>
<button type="submit" class="btn btn-default">Submit</button>
</form>
It has overbidding backing object and don't seems a good way , I think we need some generic pattern such :
public class GenericEntity {
protected Map<String, Object> properties = new LinkedHashMap<String, Object>();
//setter getter
}
where entity extends GenericEntity
and in controller
<tr th:each=" key : ${wrapper.map.keySet()}">
<td th:text="${key}" />
<td>
<input th:field="*{properties[__${key}__]}" />
</td>
</tr>
but you should find a way to set properties map in super class and it is so hardcoded , the other way may write down custom dialect for thymleafe v3 .

How to bind an object list with thymeleaf?

I am having a lot of difficulty with POSTing back a form to the controller, which should contain simply an arraylist of objects that the user may edit.
The form loads up correctly, but when it's posted, it never seems to actually post anything.
Here is my form:
<form action="#" th:action="#{/query/submitQuery}" th:object="${clientList}" method="post">
<table class="table table-bordered table-hover table-striped">
<thead>
<tr>
<th>Select</th>
<th>Client ID</th>
<th>IP Addresss</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr th:each="currentClient, stat : ${clientList}">
<td><input type="checkbox" th:checked="${currentClient.selected}" /></td>
<td th:text="${currentClient.getClientID()}" ></td>
<td th:text="${currentClient.getIpAddress()}"></td>
<td th:text="${currentClient.getDescription()}" ></td>
</tr>
</tbody>
</table>
<button type="submit" value="submit" class="btn btn-success">Submit</button>
</form>
Above works fine, it loads up the list correctly. However, when I POST, it returns a empty object (of size 0). I believe this is due to the lack of th:field, but anyway here is controller POST method:
...
private List<ClientWithSelection> allClientsWithSelection = new ArrayList<ClientWithSelection>();
//GET method
...
model.addAttribute("clientList", allClientsWithSelection)
....
//POST method
#RequestMapping(value="/submitQuery", method = RequestMethod.POST)
public String processQuery(#ModelAttribute(value="clientList") ArrayList clientList, Model model){
//clientList== 0 in size
...
}
I have tried adding a th:field but regardless of what I do, it causes an exception.
I've tried:
...
<tr th:each="currentClient, stat : ${clientList}">
<td><input type="checkbox" th:checked="${currentClient.selected}" th:field="*{}" /></td>
<td th th:field="*{currentClient.selected}" ></td>
...
I cannot access currentClient (compile error), I can't even select clientList, it gives me options like get(), add(), clearAll() etc, so it things it should have an array, however, I cannot pass in an array.
I've also tried using something like th:field=${}, this causes runtime exception
I've tried
th:field = "*{clientList[__currentClient.clientID__]}"
but also compile error.
Any ideas?
UPDATE 1:
Tobias suggested that I need to wrap my list in a wraapper. So that's what I did:
ClientWithSelectionWrapper:
public class ClientWithSelectionListWrapper {
private ArrayList<ClientWithSelection> clientList;
public List<ClientWithSelection> getClientList(){
return clientList;
}
public void setClientList(ArrayList<ClientWithSelection> clients){
this.clientList = clients;
}
}
My page:
<form action="#" th:action="#{/query/submitQuery}" th:object="${wrapper}" method="post">
....
<tr th:each="currentClient, stat : ${wrapper.clientList}">
<td th:text="${stat}"></td>
<td>
<input type="checkbox"
th:name="|clientList[${stat.index}]|"
th:value="${currentClient.getClientID()}"
th:checked="${currentClient.selected}" />
</td>
<td th:text="${currentClient.getClientID()}" ></td>
<td th:text="${currentClient.getIpAddress()}"></td>
<td th:text="${currentClient.getDescription()}" ></td>
</tr>
Above loads fine:
Then my controller:
#RequestMapping(value="/submitQuery", method = RequestMethod.POST)
public String processQuery(#ModelAttribute ClientWithSelectionListWrapper wrapper, Model model){
...
}
The page loads correctly, the data is displayed as expected. If I post the form without any selection I get this:
org.springframework.expression.spel.SpelEvaluationException: EL1007E:(pos 0): Property or field 'clientList' cannot be found on null
Not sure why it's complaining
(In the GET Method it has: model.addAttribute("wrapper", wrapper);)
If I then make a selection, i.e. tick the first entry:
There was an unexpected error (type=Bad Request, status=400).
Validation failed for object='clientWithSelectionListWrapper'. Error count: 1
I'm guessing my POST controller is not getting the clientWithSelectionListWrapper. Not sure why, since I have set the wrapper object to be posted back via the th:object="wrapper" in the FORM header.
UPDATE 2:
I've made some progress! Finally the submitted form is being picked up by the POST method in controller. However, all the properties appear to be null, except for whether the item has been ticked or not. I've made various changes, this is how it is looking:
<form action="#" th:action="#{/query/submitQuery}" th:object="${wrapper}" method="post">
....
<tr th:each="currentClient, stat : ${clientList}">
<td th:text="${stat}"></td>
<td>
<input type="checkbox"
th:name="|clientList[${stat.index}]|"
th:value="${currentClient.getClientID()}"
th:checked="${currentClient.selected}"
th:field="*{clientList[__${stat.index}__].selected}">
</td>
<td th:text="${currentClient.getClientID()}"
th:field="*{clientList[__${stat.index}__].clientID}"
th:value="${currentClient.getClientID()}"
></td>
<td th:text="${currentClient.getIpAddress()}"
th:field="*{clientList[__${stat.index}__].ipAddress}"
th:value="${currentClient.getIpAddress()}"
></td>
<td th:text="${currentClient.getDescription()}"
th:field="*{clientList[__${stat.index}__].description}"
th:value="${currentClient.getDescription()}"
></td>
</tr>
I also added a default param-less constructor to my wrapper class and added a bindingResult param to POST method (not sure if needed).
public String processQuery(#ModelAttribute ClientWithSelectionListWrapper wrapper, BindingResult bindingResult, Model model)
So when an object is being posted, this is how it is looking:
Of course, the systemInfo is supposed to be null (at this stage), but the clientID is always 0, and ipAddress/Description always null. The selected boolean is correct though for all properties. I'm sure I've made a mistake on one of the properties somewhere. Back to investigation.
UPDATE 3:
Ok I've managed to fill up all the values correctly! But I had to change my td to include an <input /> which is not what I wanted... Nonetheless, the values are populating correctly, suggesting spring looks for an input tag perhaps for data mapping?
Here is an example of how I changed the clientID table data:
<td>
<input type="text" readonly="readonly"
th:name="|clientList[${stat.index}]|"
th:value="${currentClient.getClientID()}"
th:field="*{clientList[__${stat.index}__].clientID}"
/>
</td>
Now I need to figure out how to display it as plain data, ideally without any presence of an input box...
You need a wrapper object to hold the submited data, like this one:
public class ClientForm {
private ArrayList<String> clientList;
public ArrayList<String> getClientList() {
return clientList;
}
public void setClientList(ArrayList<String> clientList) {
this.clientList = clientList;
}
}
and use it as the #ModelAttribute in your processQuery method:
#RequestMapping(value="/submitQuery", method = RequestMethod.POST)
public String processQuery(#ModelAttribute ClientForm form, Model model){
System.out.println(form.getClientList());
}
Moreover, the input element needs a name and a value. If you directly build the html, then take into account that the name must be clientList[i], where i is the position of the item in the list:
<tr th:each="currentClient, stat : ${clientList}">
<td><input type="checkbox"
th:name="|clientList[${stat.index}]|"
th:value="${currentClient.getClientID()}"
th:checked="${currentClient.selected}" />
</td>
<td th:text="${currentClient.getClientID()}" ></td>
<td th:text="${currentClient.getIpAddress()}"></td>
<td th:text="${currentClient.getDescription()}" ></td>
</tr>
Note that clientList can contain null at
intermediate positions. Per example, if posted data is:
clientList[1] = 'B'
clientList[3] = 'D'
the resulting ArrayList will be: [null, B, null, D]
UPDATE 1:
In my exmple above, ClientForm is a wrapper for List<String>. But in your case ClientWithSelectionListWrapper contains ArrayList<ClientWithSelection>. Therefor clientList[1] should be clientList[1].clientID and so on with the other properties you want to sent back:
<tr th:each="currentClient, stat : ${wrapper.clientList}">
<td><input type="checkbox" th:name="|clientList[${stat.index}].clientID|"
th:value="${currentClient.getClientID()}" th:checked="${currentClient.selected}" /></td>
<td th:text="${currentClient.getClientID()}"></td>
<td th:text="${currentClient.getIpAddress()}"></td>
<td th:text="${currentClient.getDescription()}"></td>
</tr>
I've built a little demo, so you can test it:
Application.java
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
ClientWithSelection.java
public class ClientWithSelection {
private Boolean selected;
private String clientID;
private String ipAddress;
private String description;
public ClientWithSelection() {
}
public ClientWithSelection(Boolean selected, String clientID, String ipAddress, String description) {
super();
this.selected = selected;
this.clientID = clientID;
this.ipAddress = ipAddress;
this.description = description;
}
/* Getters and setters ... */
}
ClientWithSelectionListWrapper.java
public class ClientWithSelectionListWrapper {
private ArrayList<ClientWithSelection> clientList;
public ArrayList<ClientWithSelection> getClientList() {
return clientList;
}
public void setClientList(ArrayList<ClientWithSelection> clients) {
this.clientList = clients;
}
}
TestController.java
#Controller
class TestController {
private ArrayList<ClientWithSelection> allClientsWithSelection = new ArrayList<ClientWithSelection>();
public TestController() {
/* Dummy data */
allClientsWithSelection.add(new ClientWithSelection(false, "1", "192.168.0.10", "Client A"));
allClientsWithSelection.add(new ClientWithSelection(false, "2", "192.168.0.11", "Client B"));
allClientsWithSelection.add(new ClientWithSelection(false, "3", "192.168.0.12", "Client C"));
allClientsWithSelection.add(new ClientWithSelection(false, "4", "192.168.0.13", "Client D"));
}
#RequestMapping("/")
String index(Model model) {
ClientWithSelectionListWrapper wrapper = new ClientWithSelectionListWrapper();
wrapper.setClientList(allClientsWithSelection);
model.addAttribute("wrapper", wrapper);
return "test";
}
#RequestMapping(value = "/query/submitQuery", method = RequestMethod.POST)
public String processQuery(#ModelAttribute ClientWithSelectionListWrapper wrapper, Model model) {
System.out.println(wrapper.getClientList() != null ? wrapper.getClientList().size() : "null list");
System.out.println("--");
model.addAttribute("wrapper", wrapper);
return "test";
}
}
test.html
<!DOCTYPE html>
<html>
<head></head>
<body>
<form action="#" th:action="#{/query/submitQuery}" th:object="${wrapper}" method="post">
<table class="table table-bordered table-hover table-striped">
<thead>
<tr>
<th>Select</th>
<th>Client ID</th>
<th>IP Addresss</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr th:each="currentClient, stat : ${wrapper.clientList}">
<td><input type="checkbox" th:name="|clientList[${stat.index}].clientID|"
th:value="${currentClient.getClientID()}" th:checked="${currentClient.selected}" /></td>
<td th:text="${currentClient.getClientID()}"></td>
<td th:text="${currentClient.getIpAddress()}"></td>
<td th:text="${currentClient.getDescription()}"></td>
</tr>
</tbody>
</table>
<button type="submit" value="submit" class="btn btn-success">Submit</button>
</form>
</body>
</html>
UPDATE 1.B:
Below is the same example using th:field and sending back all other attributes as hidden values.
<tbody>
<tr th:each="currentClient, stat : *{clientList}">
<td>
<input type="checkbox" th:field="*{clientList[__${stat.index}__].selected}" />
<input type="hidden" th:field="*{clientList[__${stat.index}__].clientID}" />
<input type="hidden" th:field="*{clientList[__${stat.index}__].ipAddress}" />
<input type="hidden" th:field="*{clientList[__${stat.index}__].description}" />
</td>
<td th:text="${currentClient.getClientID()}"></td>
<td th:text="${currentClient.getIpAddress()}"></td>
<td th:text="${currentClient.getDescription()}"></td>
</tr>
</tbody>
When you want to select objects in thymeleaf, you dont actually need to create a wrapper for the purpose of storing a boolean select field. Using dynamic fields as per the thymeleaf guide with syntax th:field="*{rows[__${rowStat.index}__].variety}" is good for when you want to access an already existing set of objects in a collection. Its not really designed for doing selections by using wrapper objects IMO as it creates unnecessary boilerplate code and is sort of a hack.
Consider this simple example, a Person can select Drinks they like. Note: Constructors, Getters and setters are omitted for clarity. Also, these objects are normally stored in a database but I am using in memory arrays to explain the concept.
public class Person {
private Long id;
private List<Drink> drinks;
}
public class Drink {
private Long id;
private String name;
}
Spring controllers
The main thing here is that we are storing the Person in the Model so we can bind it to the form within th:object.
Secondly, the selectableDrinks are the drinks a person can select on the UI.
#GetMapping("/drinks")
public String getDrinks(Model model) {
Person person = new Person(30L);
// ud normally get these from the database.
List<Drink> selectableDrinks = Arrays.asList(
new Drink(1L, "coke"),
new Drink(2L, "fanta"),
new Drink(3L, "sprite")
);
model.addAttribute("person", person);
model.addAttribute("selectableDrinks", selectableDrinks);
return "templates/drinks";
}
#PostMapping("/drinks")
public String postDrinks(#ModelAttribute("person") Person person) {
// person.drinks will contain only the selected drinks
System.out.println(person);
return "templates/drinks";
}
Template code
Pay close attention to the li loop and how selectableDrinks is used to get all possible drinks that can be selected.
The checkbox th:field really expands to person.drinks since th:object is bound to Person and *{drinks} simply is the shortcut to referring to a property on the Person object. You can think of this as just telling spring/thymeleaf that any selected Drinks are going to be put into the ArrayList at location person.drinks.
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" >
<body>
<div class="ui top attached segment">
<div class="ui top attached label">Drink demo</div>
<form class="ui form" th:action="#{/drinks}" method="post" th:object="${person}">
<ul>
<li th:each="drink : ${selectableDrinks}">
<div class="ui checkbox">
<input type="checkbox" th:field="*{drinks}" th:value="${drink.id}">
<label th:text="${drink.name}"></label>
</div>
</li>
</ul>
<div class="field">
<button class="ui button" type="submit">Submit</button>
</div>
</form>
</div>
</body>
</html>
Any way...the secret sauce is using th:value=${drinks.id}. This relies on spring converters. When the form is posted, spring will try recreate a Person and to do this it needs to know how to convert any selected drink.id strings into the actual Drink type. Note: If you did th:value${drinks} the value key in the checkbox html would be the toString() representation of a Drink which is not what you want, hence need to use the id!. If you are following along, all you need to do is create your own converter if one isn't already created.
Without a converter you will receive an error like
Failed to convert property value of type 'java.lang.String' to required type 'java.util.List' for property 'drinks'
You can turn on logging in application.properties to see the errors in detail.
logging.level.org.springframework.web=TRACE
This just means spring doesn't know how to convert a string id representing a drink.id into a Drink. The below is an example of a Converter that fixes this issue. Normally you would inject a repository in get access the database.
#Component
public class DrinkConverter implements Converter<String, Drink> {
#Override
public Drink convert(String id) {
System.out.println("Trying to convert id=" + id + " into a drink");
int parsedId = Integer.parseInt(id);
List<Drink> selectableDrinks = Arrays.asList(
new Drink(1L, "coke"),
new Drink(2L, "fanta"),
new Drink(3L, "sprite")
);
int index = parsedId - 1;
return selectableDrinks.get(index);
}
}
If an entity has a corresponding spring data repository, spring automatically creates the converters and will handle fetching the entity when an id is provided (string id seems to be fine too so spring does some additional conversions there by the looks). This is really cool but can be confusing to understand at first.

How to map a complex structure in jsp view to a model object in spring MVC

I'm using spring mvc for the first time and I'm trying to display and edit a structure in jsp.
I have a class Snippet, which holds a list of objects of type Sentence:
public class Snippet {
private int id;
private List<Sentence> sentences;
// getters, setters, default constructor
}
public class Sentence {
private int id;
private int scale;
private String text;
// getters, setters, default constructor
}
In my controller I give a new snippet for editing and when user clicks "save" store it to my db, then return another one. Currently the sentences list of the snippet is null:
#RequestMapping("/snippet")
public ModelAndView getSnippet() {
return new ModelAndView("snippet", "snippet", snippetService.getSnippet());
}
#RequestMapping("/save")
public ModelAndView saveSnippet(#ModelAttribute Snippet snippet) {
if(snippet != null && snippet.getSentences() != null && !snippet.getSentences().isEmpty()) {
snippetService.updateSnippet(snippet);
}
return new ModelAndView("snippet", "snippet", snippetService.getSnippet());
}
In my snippet.jsp I'd like to display the snippet sentences with their scale, and on save, pass snippet with sentences and scales to the controller for storage:
<form:form method="post" action="save" modelAttribute="snippet">
...
<c:forEach var="sentence" items="${snippet.sentences}">
<tr>
<td>${sentence.id}</td>
<td>${sentence.text}</td>
<td><input type="range" name="sentence.scale" value="${sentence.scale}"
path="sentence.scale" min="0" max="5" /></td>
</tr>
</c:forEach>
<tr>
<td colspan="4"><input type="submit" value="Save" /></td>
</tr>
I think I have to find the right way to use the path attribute but I can't figure it out.
The JSTL c:forEach tag provides the attribute varStatus, which will expose the loop status to the specified variable. Reference the index of varStatus to obtain the index of the current loop and use that index to specify the index of the collection item you want to bind or display.
<c:forEach var="sentence" items="${snippet.sentences}" varStatus="i">
<tr>
<td>${sentence.id}</td>
<td>${sentence.text}</td>
<td>
<form:input type="range"
name="snippet.sentences[${i.index}].scale"
path="sentences[${i.index}].scale"
min="0" max="5"
/></td>
</tr>
</c:forEach>

Struts2 internalization

I'm try to internalize my application with the next code:
For the jsp I have this:
<table>
<tr class="trlleno">
<td>
<div id="Panel_cliente">
<s:select label="Selecciona un idioma" name="IdiomaID" id="IdiomaID"
headerValue="--Selecciona un idioma--" headerKey="-1"
list="#{'1':'Español','2':'English','3':'Deutch','4':'Português','5':'русский','6':'Français'}" value="2"/>
</div>
</td>
</tr>
<td class="trboton" colspan="2" align="center">
<input type="submit" name="submit" id="submit" value="CAMBIAR IDIOMA" class="divboton"/>
</td>
</tr>
</table>
</form>
In the action I have this:
public class CambiarIdiomaAction extends ActionSupport implements ServletRequestAware{
private HttpServletRequest servletRequest;
Map session;
#Override
public String execute() throws Exception {
session = ActionContext.getContext().getSession();
int idm=Integer.valueOf(servletRequest.getParameter("IdiomaID"));
System.out.println(idm);
//Trying with English
Locale locale=new Locale("en","EN");
return "SUCCESS";
}
#Override
public void setServletRequest(HttpServletRequest request) {
this.servletRequest = request;
}
public HttpServletRequest getServletRequest() {
return servletRequest;
}
}
When I see if there is a change with the language, I see nothing, no changes. Why?. Thanks so much
You have to set locale in session if you want to change locale.
If you put this code in an Action:
ActionContext context = ActionContext.getContext();
context.setLocale(Locale.ENGLISH);
You will change the locale of your app to English.
In your code you are doing nothing, you only are setting a variable locale put you don't do anything with it.
If you really want to change locale in your action then use
ActionContext.getContext().setLocale(locale)
and to put it in HTTP session
session.put(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE, locale)
But Struts2 has out of the box localization support. I suggest you to read about localization in Struts2 http://struts.apache.org/2.x/docs/localization.html.

Binding problems using Spring MVC

I am using Spring MVC 3.1 to develop a Java web app. I have a JSP that has two paired radio buttons, an entry field, and a dropdown select box. I need these values to be available to my mapped controller, via a model class' fields.
The security and URL mapping works fine, as I've seen in debugger before. The issue is that when I tried to get the JSP data values populating my model, I get an error:
java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'cccForm' available as request attribute
Here is part of my JSP:
<c:url var="cccUrl" value="/registers/default/ccPreauth/authorize" />
<div class="mainWrapper">
<form:form id="cccForm" action="${cccUrl}" method="post" modelAttribute="cccForm">
...
<table>
<tbody>
<tr>
<th>Select an option.</th>
</tr>
<tr>
<td>
<div class="field-input">
<form:radiobutton id="paymentOption" path="paymentOption" value="authorizeCC" />
Collect Credit Card Information
</div>
<div class="field-input">
Authorization Amount $
<form:input path="authAmount" maxlength="10" size="10" class="extendWidth"/>
<span class="instructions">
<spring:message code="label.authorization.note" />
</span>
</div>
<div class="field-input">
<form:radiobutton id="paymentOption" path="paymentOption" value="cancelAuth" />
Choose a Reason and Cancel Credit Card Collection
</div>
<div class="field-input right">
<form:select id="selectedReason" path="selectedReason" >
<c:forEach items="${reasonList}" var="reason">
<option value=${reason.reasonText}>${reason.reasonText}</option>
<br />
</c:forEach>
</form:select>
</div></td>
</tr>
</tbody>
</table>
</div>
<div class="right">
<button class="btnBlue" id="submitButton" type="submit">
Here is part of my controller:
#Controller
#RequestMapping(value = "/registers/default/ccPreauth")
#SessionAttributes(ControllerConstants.DEFAULT_REGISTER_ATTR_NM)
public class CCCaptureController {
...
#RequestMapping(value="/authorize" )
public ModelAndView authorize(
final Authentication auth,
final #ModelAttribute("ccCapturePaymentRequest") CCCapturePaymentForm ccCapturePaymentRequest,
final BindingResult result,
final HttpServletResponse response) {
final ModelAndView mav = new ModelAndView(CC_PREAUTH_PAYMENT_VIEW);
return mav;
}
and finally, here is my model class:
public class CCCapturePaymentForm implements Serializable {
private static final long serialVersionUID = 6839171190322687142L;
#NumberFormat(style = Style.CURRENCY)
private BigDecimal authAmount;
private String selectedReason;
private String paymentOption;
public BigDecimal getAuthAmount() {
return authAmount;
}
public void setAuthAmount(BigDecimal authAmount) {
this.authAmount = authAmount;
}
public String getSelectedReason() {
return selectedReason;
}
public void setSelectedReason(String selectedReason) {
this.selectedReason = selectedReason;
}
public String getPaymentOption() {
return paymentOption;
}
public void setPaymentOption(String paymentOption) {
this.paymentOption = paymentOption;
}
}
Can anyone tell me what I need to get this to work correctly? Please don't stop at just the reason for the exception above - please verify and correct my code as I am on a tight schedule as usual and have little experience with Spring MVC. Thanks!
You have this in your form:
modelAttribute="cccForm"
So you should have this in your controller:
#ModelAttribute("cccForm") CCCapturePaymentForm ccCapturePaymentRequest
That's how you bind the form backing object with the model attribute.
I found the answer for those Spring MVC newbies...
I had to set the "cccForm" to a new instance of my next page's form inside the controller search method (that then tries to being up the page that was getting the error).
In a nutshell: I had to set the empty backing bean value in the preceding controller method so that the follow-on method and JSP page have it to work with.
Hope this helps someone else avoid my mistake.
Mark

Categories

Resources