Currently I am struggling with the problem as below:
I have a program that will hold information on the electricity meters and their readings.
I wanted to create a form in which I can insert all the readings for all registered meters at once.
I am currently stuck at this point:
Here is my controller:
#RequestMapping("/registerEnergyReading")
public ModelAndView registerEnergyReading(#ModelAttribute("reading") Reading reading, BindingResult result) {
List<ElectricityMeter> meters= meterService.getMeterList();
Map<String, Object> model = new HashMap<String, Object>();
model.put("meters", meters);
System.out.println("Register Form");
return new ModelAndView("RegisterEnergyReading", "model", model);}
Now the jsp file would create a table dynamically based on how many meters are there.
That would be done by < c:forEach items="${meters}" var="meters" >
But the tricky part for me is how to pass these multiple entries for the next controller to be saved? What form should be used here?
#RequestMapping("/saveReadings")
public ModelAndView saveReadings(
#ModelAttribute("reading") List<Reading> reading, BindingResult result) {
for(Reading i:reading) {
readingService.saveReading(i);
}
return new ModelAndView("redirect:/najemcaLista.html");
}
Am I going the right direction here?
Appreciate your help,
Maciej
I got a bit further but got stuck.
My controller looks now:
#RequestMapping("/registerEnergyReading")
public ModelAndView registerEnergyReading(
#ModelAttribute("listReadingModel") ListReadingModel listReadingModel, BindingResult result) {
List<EnergyMeter> energyMetersList= meterService.getMeters();
List<EnergyReading> readings= new ArrayList<EnergyReading>();
for (int i = 0; i < energyList.size(); i++) {
EnergyReading reading= new EnergyReading ();
reading.setMeter(energyList.get(i));
readings.add(reading);
}
listReadingModel.setEnergyReadings(readings);
Map<String, Object> model = new HashMap<String, Object>();
model.put("energyMetersList", energyMetersList);
model.put("readings", readings);
return new ModelAndView("registerEnergyReading", "model", model);
}
#RequestMapping("/saveEnergyReading")
public ModelAndView saveEnergyReading(
#ModelAttribute("listReadingModel") ListReadingModel listReadingModel, BindingResult result) {
List<EnergyMeter> energyMetersList=listReadingModel.getEnergyReadings()
for (EnergyReading i : reading) {
readingService.saveReading(i);
}
return new ModelAndView("redirect:/energyReadingsList.html");
}
here is my view file:
<c:url var="listReadingModel" value="listReadingModel.html" />
<form:form id="listReadingModel" modelAttribute="listReadingModel" method="post" action="${listReadingModel}">
<table width="400px" height="150px">
<c:forEach items="${model.readings}" var="readings">
<tr>
<td>${readings.meter.description}</td>
<td><form:label path="${readingValue}">Reading Value</form:label></td>
<td><form:input path="${readingValue}" /></td>
<td><form:label path="${readingDate}">date</form:label></td>
<td><form:input type="date" path="${readingDate}" /></td>
</tr>
</c:forEach>
<tr>
<td colspan="6"><input type="submit" value="Save" /></td>
</table>
</form:form>
'
When I open the page in browser it all looks ok (there are as many fields as there should be) but when I press "save" I get java.lang.NullPointerException. It seems the form doesn't pass listReadingModel to controller. Any ideas how to solve it? Appologies for any typos. The code is in different language so I translated it on the go.
Regards, Maciej
If I understand you right, I think what you need is to create a wrapper class as
public class ListReadingModel {
private List<Reading> readings;
// Getter, setter
}
And then under your registerEnergyReading method, add this ListReadingModel to your Model.
ListReadingModel listReadingModel = new ListReadingModel ();
model.put("listReadingModel", listReadingModel);
You can bind your ListReadingModel to your form element in the jsp file.
<form:form method="POST" commandName="listReadingModel">
// add meters and readings here
When submitted your jsp you can call your controller like that:
#RequestMapping("/saveReadings")
public ModelAndView saveReadings(
#ModelAttribute("listReadingModel") ListReadingModel listReadingModel , BindingResult result) {
List<Reading> reading = listReadingModel.getReadings();
for(Reading i:reading) {
readingService.saveReading(i);
}
return new ModelAndView("redirect:/najemcaLista.html");
}
This can work but I did not try it. You can search named wrapper class on google to find more information.
Related
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 .
I am using Spring MVC and I am trying to pass both HashMap and ArrayList values to my view file. But I can't find a way to achive this.
Could you please help me?
My Controller Mehtod
#RequestMapping(value="/do_register", method= RequestMethod.GET)
public ModelAndView RegistrationForm(#ModelAttribute Subscriber subscriber, BindingResult result)
{
HashMap<Integer, String> interest = new HashMap<Integer, String>();
interest.put(1,"Java");
interest.put(2,"PHP");
interest.put(3, "Both");
List<City> myCities = subService.getCity();
// I want to pass both "myCities" and "interest" to my view File
return new ModelAndView("regForm", "records", " ");
}
Form
<c:url var="action" value="/register" ></c:url>
<form:form action="${action}" modelAttribute="subscriber" method="POST" >
<div>
<label>City</label>
<form:select path="city">
<c:forEach items="${records}" var="city">
<option value="${city.cityId}">${city.cityName}</option>
</c:forEach>
</form:select>
</div>
<div>
<label>Interests</label>
<form:checkboxes path="interest" items="${records.interests}"/>
</div>
<input type="submit" value="Submit">
</form:form>
A ModelAndView contains a Model which is a kind of Map. It can contain as many objects as you need. Simply modify your code like :
#RequestMapping(value="/do_register", method= RequestMethod.GET)
public ModelAndView RegistrationForm(#ModelAttribute Subscriber subscriber, BindingResult result)
{
...
// I want to pass both "myCities" and "interest" to my view File
ModelAndView mav = ModelAndView("regForm");
mav.addAttribute("interests", interest);
mav.addAttribute("records", myCities);
return mav;
}
And you find your objects in the JSP with ${records} and ${interests}
Not sure about surrounding logic in your code base but one way to do that is create Envelop object contain both items or use subscriber object to handle both item and iterate on view.
For more reference here its similar type of question link
How to pass multiple model objects from controller and how to pass all as command objects into the form:form in spring mvc?
Hope this helps
I am teaching myself Spring Form tags, and have run into what is probably a simple error that I haven't been able to solve. I get the following error when I launch this app in a browser:
java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'index' available as request attribute
I've tried most of the fixes that came up in a Google search to no avail. Can someone spot where I went wrong? Below are the relevant components. Thanks very much.
Controller:
#Controller
#RequestMapping("/registration")
public class LoginController {
#RequestMapping(method = RequestMethod.GET)
public String setupForm(ModelMap model) {
Registration registration = new Registration();
model.addAttribute("registration", registration);
return "index";
}
#RequestMapping(method = RequestMethod.POST)
public String onSubmit(#ModelAttribute("registration") Registration registration, Map model) {
return "success";
}
}
JSP (/index.jsp):
<form:form commandName="index">
<table border="0" cellspacing="12">
<tr>
<td>
<form:input path="email"/>
</td>
</tr>
<tr>
<td>
<form:password path="password"/>
</td>
</tr>
<tr>
<td>
<input type="submit" value="Submit"/>
</td>
</tr>
</table>
</form:form>
Command Object (Registration.java) :
public class Registration {
private String password;
private String email;
// getters,setters
Facing same issue few days back, What i understood from hit and trail is, Index page is a static page and no processing happens before the same is rendered. In case i want to use a form model binding in index page i should have a controller's handler method which will create a Registration object and place the same in ModelAndView before index.jsp is rendered
add a method in your controller like the below and try
#RequestMapping(method = RequestMethod.GET, value="/")
public ModelAndView initiate(){
ModelAndView objModView = new ModelAndView("/index.jsp");
objModView.addObject("registration",new Registration());
return objModView;
}
In your index page correct the following and try
<form:form commandName="index"> to <form:form commandName="registration">
You can also do it like this if the above does not work
<form:form modelAttribute="registration" commandName="registration">
Thanks
The error which is seen is because when you submit the form you have to have a Binding Result associated with the #ModelAttribute annotation.
Try changing you code to this :
#RequestMapping(method = RequestMethod.POST)
public String onSubmit(#ModelAttribute("registration") Registration registration, BindingResult result, Map model){
return "success";
}
Also note that the Binding Result object should be followed immediately after the Model Attribute.
And if you are using two #ModelAttributes then each one should have its own binding result object which follows it.
Please refer the spring source guide for all the related documentation
http://static.springsource.org/spring/docs/3.1.x/spring-framework-reference/html/mvc.html
I am puzzled why autocomplete does not work. Here is the form in the .jsp code below:
<form:form method="post" action="save.html" modelAttribute="word">
<table>
<tr>
<th>German word</th>
<td><form:input path="german" id="german" /></td>
</tr>
<tr>
<td colspan="2"><input type="submit" value="Save" /></td>
</tr>
</table>
<br />
</form:form>
here are the javascript functions (in the same .jsp file)
$(document).ready(function() {
$( "#german" ).autocomplete({
source: '${pageContext. request. contextPath}/get_word_list.html'
});
});
and here is the relevant part of the controller:
#Autowired
private WordService wordService;
#RequestMapping(value = "/goToDictionary", method = RequestMethod.GET)
public ModelAndView index() {
Word word = new Word();
return new ModelAndView("dictionary", "word", word);
}
#RequestMapping(value = "/get_word_list", method = RequestMethod.GET, headers = "Accept=*/*")
public #ResponseBody
List<String> getCountryList(#RequestParam("term") String query) {
System.out.println(query);
return getMatch(query);
}
public List<String> getMatch(String query) {
query = query.toLowerCase();
List<String> matched = new ArrayList<String>();
for (Word v : wordService.getAllWord()) {
if (v.getGerman().toLowerCase().startsWith(query)) {
matched.add(v.getGerman());
}
}
return matched;
}
I know for sure that the getMatch(String query) gets called and works properly.
So I guess the problem is on the jsp. file
any help is greatly appreciated.
[For JSON to fill the list]
Maybe you should look at the produces property of the #RequestMapping annotation. It takes a String[] as value. Available since Spring 3.1.x version. I currently use the 3.1.2 and I can get some application/json without any problem.
And of course you should add the produces in the data provider of your country list.
[For JavaScript to fill the list]
Maybe the part ${pageContext. request. contextPath} is not evaluated correctly. Did you check inside the produced code of your JSP e.g. with Firebug.
OK, I have found the solution. it is in the spring-servlet.xml file. It did not work because i failed to add this.
xsi:schemaLocation="ttp://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
I added, and now everything is OK
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.