I just started with Spring MVC so it's probably a rookie mistake.
The ModelAttribute is reused every request. How can I make sure every POST starts with a clean object?
My controller (MyController.java):
#Controller
public class MyController {
#RequestMapping(method = RequestMethod.POST)
public String processChoice(#ModelAttribute("myData") MyData myData, BindingResult bindingResult) {
System.out.println("POST: myData = " + myData);
return "redirect:/myview?choice=" + myData.getChoice();
}
#RequestMapping(method = RequestMethod.GET)
public String displayChoice(#RequestParam(required = false) String choice, Model model) {
System.out.println("GET: Choice = " + choice);
model.addAttribute("myData", new MyData(choice));
return "myview";
}
}
My view (myview.jsp):
<%# page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%# taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%# taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Spring Tests</title>
</head>
<body>
<form:form modelAttribute="myData" method="POST">
<form:select path="choice">
<option></option>
<option value="one">One</option>
<option value="two">Two</option>
</form:select>
<input type="submit" value="Choose"/>
</form:form>
<c:if test="${not empty myData.choice}">Choice = ${myData.choice}</c:if>
<c:if test="${empty myData.choice}">No choice</c:if>
</body>
</html>
Successive clicks on the "Choose" button appends the chosen values instead of just POST-ing the current one:
GET: Choice = null
POST: myData = MyData [choice=two]
GET: Choice = two
POST: myData = MyData [choice=two,one]
GET: Choice = two,one
POST: myData = MyData [choice=two,one,]
GET: Choice = two,one,
POST: myData = MyData [choice=two,one,,one]
GET: Choice = two,one,,one
Add this into your controller:
#ModelAttribute("myData")
public MyData getMyData() {
return new MyData();
}
Related
I have Spring controller and I am setting a model value and want to display in my JSP:
public class TestController {
#RequestMapping(value = "/hello", method = RequestMethod.GET)
public String hello(Model model){
model.addAttribute("message", "Programmer Gate");
return "/test";
}
}
My test.jsp code:
<%# page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<%#taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Welcome</title>
</head>
<body>
<%
double num = Math.random();
if (num > 0.95) {
%>
<h2>You'll have a luck day!</h2><p>(<%= num %>)</p>
<%
} else {
%>
<h2>Well, life goes on ... </h2><p>(<%= num %>)</p>
<%
}
%>
<p>${message}</p>
</body>
</html>
I am not seeing the message value as the JSP rendering as follows:
Well, life goes on ...
(0.3343913194169942)
${message}
How to access the model message variable value in the JSP?
I'm extremely new to JSP. Anyway, I'm trying to print every object in a list using c:foreach but it does not work and I can't figure out why. I've already checked out similar issues but nothing has solved my problem.
<h2>
Your account information:
<%
LinkedList<BankAccount> accounts = null;
accounts = account1.getAccountList();
request.setAttribute("accounts", account1.getAccountList());
%>
</h2>
<c:foreach items="${accounts}" var="acct">
<p>${acct.accountName}</p><br/>
<p>$${acct.AccountBalance}</p><br/>
</c:foreach>
<TD valign="top"><B><%=accounts.get(0).accountName%></b><br>
<TD valign="top"><b>$<%=accounts.get(0).AccountBalance%></b></br>
</br></br>
<TD valign="top"><b><%=accounts.get(1).accountName%></b><br>
<TD valign="top"><b>$<%=accounts.get(1).AccountBalance%></b></br>
The bottom code works --- accounts.get(0), etc. But I can't use this because if I add data to the database then I have to add more code every time.
Thanks for the help.
You didn't provide info about your BankAccount class, therefore i would make an assumption:
package testingThings.EL.linkedlist;
public class BankAccount {
protected String accountName;
protected double accountBalance;
public BankAccount(String accountName, double accountBalance) {
this.accountName = accountName;
this.accountBalance = accountBalance;
}
public String getAccountName() {
return accountName;
}
public void setAccountName(String accountName) {
this.accountName = accountName;
}
public double getAccountBalance() {
return accountBalance;
}
public void setAccountBalance(double accountBalance) {
this.accountBalance = accountBalance;
}
}
I changed AccountBalance accountBalance to stick to the conventions.
In your JSP you need the line that LeHill mentioned.
<%# taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
Don't forget the protocol: http://
The JSP:
<%# page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%# page import="testingThings.EL.linkedlist.BankAccount"%>
<%# page import="java.util.LinkedList"%>
<%# taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<%
LinkedList<BankAccount> accounts = new LinkedList<BankAccount>();
accounts.add(new BankAccount("acc1", 1000.00));
accounts.add(new BankAccount("acc2", 2000.00));
pageContext.setAttribute("accounts", accounts);
%>
<c:forEach items="${accounts}" var="acct">
<p>${acct.accountName}</p>
<br />
<p>${acct.accountBalance}</p>
<br />
</c:forEach>
</body>
</html>
The Output in Browser:
acc1
1000.0
acc2
2000.0
It looks like you don't have getters and setters.
Your scriptlet has the attribute name as "accountName".
JSTL expects "get" or "is" as the beggining of the beans method name.
you can't call the attribute directly. You have to use getter methods.
if you created a method called "getAccountName" it should work.
I want to add another user in registration JSP file to my HashMap which is created in my homepage, but it seems the another HashMap is being created when I'm trying to register user.
How to access the HashMap from homepage JSP file in another?
This is my base class:
package com.jakub.spring;
import java.util.HashMap;
public class registeredUsers {
public HashMap<String, String> userSource;
public registeredUsers() {
userSource=new HashMap<String, String>();
}
public void register(String name, String password) {
userSource.put(name, password);
}
public String userExists(String user) {
String passwordFromSource = userSource.get(user);
if(passwordFromSource != null) {
return passwordFromSource;
}else
return "";
}
}
This is my homepage:
<%# taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%# page session="true" %>
<html>
<head>
<title>Home</title>
</head>
<body bgcolor="#CCCFFF">
<form method="post" action="validate.jsp">
<p align="left">Podaj login:</p>
<input type="text" name="name" />
<p align="left">Podaj haslo:</p>
<input type="text" name="password" />
<input type="submit" value="Zaloguj" />
</form>
Rejestracja
<jsp:useBean id="registeredUsers"
class="com.jakub.spring.registeredUsers" scope="application"></jsp:useBean>
<%
out.println("Dostepni uzytkownicy w systemie: \n");
out.print(registeredUsers.userSource.keySet());
%>
</body>
</html>
This is my registration page:
<%# page language="java" contentType="text/html; charset=ISO-8859-2"
pageEncoding="ISO-8859-2"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-2">
<title>Konto utworzone</title>
</head>
<body>
<jsp:useBean id="registeredUsers"
class="com.jakub.spring.registeredUsers" scope="application"></jsp:useBean>
<%
registeredUsers.register(request.getParameter("name"),request.getParameter("password"));
out.print("Konto zostało utworzone");
out.print(registeredUsers.userSource.keySet());
%>
Powrót do strony glownej
</body>
</html>
Use ConcurrentHashMap instead of HashMap because it's in Application scope and that can be accessed simultaneously by multiple threads at a time and it results in ConcurrentModificationException.
Please have a look at Thread Safe JSP-Servlet Q&A
I use Spring MVC 3 in my project.
This is my AddressController :
#Controller
public class AddressController {
private static Logger logger = Logger.getLogger(AddressController.class);
#RequestMapping(value="/address",method=RequestMethod.GET)
public ModelAndView init(
#RequestParam(value="language",required=false,defaultValue="fr") String language){
Locale locale = new Locale(language);
logger.info("here");
String[] isoCountries = locale.getISOCountries();
Map<String,String> treeMap = new TreeMap<String,String>();
for(String isoCountry : isoCountries){
Locale countryLoc = new Locale(language, isoCountry);
String name = countryLoc.getDisplayCountry(locale);
if(!"".equals(name)){
treeMap.put(name,name);
}
}
Map<String,String> tree = new TreeMap<String,String>(treeMap);
ModelAndView modelAndView = new ModelAndView("address");
modelAndView.addObject("address",new Address());
modelAndView.addObject("countriesList", tree);
return modelAndView;
}
}
The first time, when I executed /address, it's going well to my controller and it returns my address.jsp by executing the javascript in this last one. But when I execute /address?language=fr or /address?language=en, the javascript code of my address.jsp is not executed.
This is a part of my address.jsp :
<%#page import="org.springframework.context.i18n.LocaleContextHolder"%>
<%# taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%# taglib prefix="forms" uri="http://www.springframework.org/tags/form" %>
<%#page import="com.application.myGoogleAppEngine.Internationale"%>
<%# page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%# page import="java.util.Locale" %>
<%# page import="java.util.List" %>
<%# page import="java.util.ArrayList" %>
<%# page import="java.util.Collections" %>
<html>
<head>
<jsp:include page="ressources.jsp"></jsp:include>
<link rel="stylesheet" type="text/css" href="stylesheets/320x480/portrait/address.css" />
<%! Internationale internationale = Internationale.getInstance(); %>
<script>
$(document).ready(function(){
//checkParams();
alert("here");
var unit = "em";
alert("here2");
$("#backButton").attr("href","/index");
$('#validationBtn').click(function(){
var streetName = $('#streetName').val();
var streetNumber = $('#streetNumber').val();
var zipCode = $('#zipCode').val();
var city = $('#city').val();
var country = $('#country').val();
var ref = "MyServlet?streetName="+streetName+"&streetNumber="+streetNumber+"&zipCode="+zipCode+"&city="+city+"&country="+country;
$(this).attr("href",ref);
});
});
//rest of the script
</script>
<body>
<a id="backButton" data-role="button" data-icon="arrow-l"
data-ajax="false">
<spring:message code="backButton"/>
</a></div>
//rest of the code
</body>
In web MVC spring serves as server side technology. Javascript is client (browser) side technology, thus Spring "cannot" execute javascript.
I have a Person model attribute that contains a list of emails.
I've created some JavaScript code that deletes elements from an HTML list of emails. This is pure JavaScript client side code, no AJAX call.
After submitting, I don't understand why I get all the emails in the corresponding #Controller method, even the ones that were deleted in the HTML.
Can anyone please explain?
JSP
<%# taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%# taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%# taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%# taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"></meta>
<link rel="stylesheet" href="<c:url value="/styles/resume.css"/>" type="text/css"></link>
<link href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/themes/base/jquery-ui.css" rel="stylesheet" type="text/css"></link>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8/jquery.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/jquery-ui.min.js"></script>
<script src="/resume/js/jquery.editable-1.0.1.js"></script>
<title>Resumes manager</title>
<script>
$(document).ready(function() {
$('.trash').click(function() {
$(this.parentNode).remove();
});
});
</script>
</head>
<body>
<h1>Personal data</h1>
<form:form modelAttribute="person" action="/resume/person/edit/save" id="personForm" method="post" >
<table>
<tr>
<td>Email addresses:</td>
<td colspan="4">
<ol id="emails">
<c:forEach items="${person.emails}" varStatus="status">
<li><form:hidden path="emails[${status.index}].order" class="emailsDisplayOrder"></form:hidden><form:input path="emails[${status.index}].label"></form:input><form:input type="email" path="emails[${status.index}].value"></form:input><input type="image" src="/resume/images/trash.png" class="trash" value="${status.index}"></input></li>
</c:forEach>
</ol>
</td>
</tr>
</table>
</form:form>
</body>
</html>
Controller
#Controller
#SessionAttributes(types={Person.class}, value={"person"})
public class PersonController {
private final static String PERSON_VIEW_NAME = "person-form";
private ResumeManager resumeManager;
#Autowired()
public PersonController(ResumeManager resume) {
this.resumeManager = resume;
}
#InitBinder
public void initBinder(WebDataBinder dataBinder) {
dataBinder.setDisallowedFields("id");
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
dateFormat.setLenient(false);
dataBinder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
}
#RequestMapping(value="/person/edit/save")
public String save(#ModelAttribute(value="person") Person p, BindingResult result, SessionStatus status) {
new PersonValidator().validate(p, result);
Collections.sort(p.getEmails()); //this collection still contains client-side dropped objects
this.resumeManager.savePerson(p);
return PERSON_VIEW_NAME;
}
}
Explanation
When you load a page with <form:form modelAttribute="person" ...>, there are two cases :
case 1 : if person doesn't exist, it creates an empty Person
case 2 : if person already exists, it uses it
In all cases, when a page is loaded, there is an existing person.
When you submit a form, Spring MVC updates this existing person only with the submitted information.
So in case 1, if you submit email 1, 2, 3 and 4, Spring MVC will add 4 emails to the empty person. No problem for you in this case.
But in case 2 (for example when you edit an existing person in session), if you submit email 1 and 2, but person has already 4 emails, then Spring MVC will just replace email 1 and 2. Email 3 and 4 still exist.
A possible solution
Probably not the best one, but it should work.
Add a remove boolean to the Email class :
...
public class Email {
...
private boolean remove; // Set this flag to true to indicate that
// you want to remove the person.
...
}
In the save method of your controller, remove the emails that have remove set to true.
Finally, in your JSP, add this hidden field :
<form:hidden path="emails[${status.index}].remove" />
And tell your Javascript to set the input value to true when the user clicks to delete the email.
Alternate solution to Jerome Dalbert one
Jerome's solution should work (thanks again for clear answer and solution), but I didn't want to modify business model.
So here is the way I found out: mark HTML elements to remove using java-script and actually remove it using ajax calls at form submit (I initially avoided ajax to keep model unchanged until user submits, but this solutions preserves that requirement).
JSP:
<%# taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%# taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%# taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%# taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"></meta>
<link rel="stylesheet" href="<c:url value="/styles/resume.css"/>" type="text/css"></link>
<link href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/themes/base/jquery-ui.css" rel="stylesheet" type="text/css"></link>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8/jquery.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/jquery-ui.min.js"></script>
<script src="/resume/js/jquery.editable-1.0.1.js"></script>
<title>Resumes manager</title>
<script>
$('.sortable').sortable({
update: function(event,ui) {
var liElements = this.getElementsByTagName('li');
$(liElements).each(function(i, liElement) {
var orderElements = liElement.getElementsByClassName('order');
$(orderElements).each(function (j, orderElement) {
orderElement.value = i;
});
});
}
});
$('.trashable').click(function() {
$(this.parentNode.childNodes).each(function(index, element) {
if(element.src.match(/trash.png/) != null) {
element.src = '/resume/images/back.png';
this.parentNode.className = 'trashed';
} else if(element.src.match(/back.png/) != null) {
element.src = '/resume/images/trash.png';
this.parentNode.className = '';
} else {
element.disabled = !element.disabled;
}
});
});
function trash(element) {
var sfx = element.alt;
var lnk = ('/resume/person/edit/').concat(sfx);
$.ajax({
url: lnk
});
}
$('#personForm').submit(function() {
var trashed = $(this).find('.trashed');
$(trashed).each(function(index, element) {
var img = $(element).find('.trashable');
var tmp = $(img)[0];
trash(tmp);
});
});
});
</script>
</head>
<body>
<h1>Personal data</h1>
<form:form modelAttribute="person" action="/resume/person/edit/save" id="personForm" method="post" >
<table>
<tr>
<td>Email addresses:</td>
<td colspan="4">
<ol class="sortable">
<c:forEach items="${person.emails}" varStatus="status">
<li><form:hidden path="emails[${status.index}].order" class="order"></form:hidden><form:input path="emails[${status.index}].label"></form:input><form:input type="email" path="emails[${status.index}].value"></form:input><img src="/resume/images/trash.png" class="trashable" alt="dropEmail/${person.emails[status.index].id}"></img></li>
</c:forEach>
</ol>
</td>
</tr>
<tr><td colspan="5"><form:button name="save" value="${person.id}">${person.id == 0 ? 'save' : 'update'}</form:button></td></tr>
</table>
</form:form>
</body>
</html>
Controller:
#Controller
#SessionAttributes(types={Person.class}, value={"person"})
public class PersonController {
private final static String PERSON_VIEW_NAME = "person-form";
private ResumeManager resumeManager;
#Autowired()
public PersonController(ResumeManager resume) {
this.resumeManager = resume;
}
#InitBinder
public void initBinder(WebDataBinder dataBinder) {
dataBinder.setDisallowedFields("id");
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
dateFormat.setLenient(false);
dataBinder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
}
#RequestMapping(value="/person/edit/{id}")
public String edit(#PathVariable("id") long personId, Model model) {
Person p = this.resumeManager.getPersonById(personId);
if(p != null) {
model.addAttribute("person", p);
return PERSON_VIEW_NAME;
} else {
return "redirect:/";
}
}
#RequestMapping(value="/person/edit/save")
public String save(#ModelAttribute(value="person") Person p, BindingResult result, SessionStatus status) {
new PersonValidator().validate(p, result);
Collections.sort(p.getEmails());
this.resumeManager.savePerson(p);
return PERSON_VIEW_NAME;
}
#RequestMapping(value="/person/edit/dropEmail/{id}")
#ResponseBody
public void dropEmail(#ModelAttribute(value="person") Person p, #PathVariable("id") long id) {
int i = 0;
for(Email e : p.getEmails()) {
if(e.getId() == id) {
p.getEmails().remove(i);
break;
}
i++;
}
}
}