No values in dropdown lists with Thymeleaf and SpringBoot - spring-boot

I have a similar issue on my drop down list not showing any values. Like
the solution: dropdown lists with Thymeleaf and SpringBoot No data showing
in dropdown list. If I look in the IE debugger I can see my list for the
dropdown, but the dropdown won't display my list to pick from.
The HTML snippet is here:
<div class="col-lg-3 selectContainer">
<div class="input-group">
<span class="input-group-addon"><i
class="glyphicon glyphicon-list"></i></span>
<select th:field="*{savings_type}" name="savings_type_id"
id="savings_type_id" class="form-control selectpicker">
<option value="">Select Savings Type</option>
<option th:each="dropDownItem : ${leanTypesList}"
th:value="${dropDownItem.lt_id}"
th:text="${dropDownItem.lt_name}">
</option>
</select>
</div>
</div>
Code here:
My Model leanDeatil.java - data populating Savings_Type field
This is value is going too.
#Entity
#Table(name="lean_detail")
public class LeanDetail implements Serializable {
private static final long serialVersionUID = 1L;
#Id
//#Column (name="LEANDETAIL_ID")
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
#Column (name="SAVINGS_TYPE")
private int savings_type;
#Column (name="SAVING_AMOUNT")
private BigDecimal saving_amount;
#Column (name="TITLE")
private String title;
#Column (nullable=true, name="IMPROVEMENT_DESC")
private String improvement_desc;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "SAVINGS_TYPE", referencedColumnName = "lt_id",
insertable = false, updatable = false)
private LeanTypes leanTypes;
public LeanDetail(){}
public LeanDetail(String title,int savings_type, BigDecimal saving_amount,
String improvement_desc {
super();
this.title = title;
this.savings_type = savings_type;
this.saving_amount = saving_amount;
this.improvement_desc = improvement_desc;
}
getters and setters....
My Model leanTypes.java data coming from
#Entity
#Table(name="lean_types")
public class LeanTypes {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column (name="LT_ID")
private int lt_id;
#Column (name="LT_NAME")
private String lt_name;
#Column (name="LT_DESC")
private String lt_desc;
#Column(nullable=false, name="LT_ACTV")
private boolean lt_actv;
#OneToMany(mappedBy="leanTypes", fetch=FetchType.EAGER)
#Fetch(value = FetchMode.SUBSELECT)
private List<LeanDetail> leanDetail = new ArrayList<LeanDetail>();
public LeanTypes(){}
public LeanTypes(int lt_id, String lt_name, String lt_desc, Boolean lt_actv) {
super();
this.lt_id = lt_id;
this.lt_name = lt_name;
this.lt_desc = lt_desc;
this.lt_actv = lt_actv;
}
getters and setters....
}
From my Controller LeanDetail.java
#GetMapping("/update-leanDetail")
public String updateLeanDetail(#RequestParam int id, HttpServletRequest
request){
request.setAttribute("leanTypesList", leanTypesService.findAll());
request.setAttribute("leanDetail", leanDetailService.findLeanDetail
(id));
request.setAttribute("mode", "MODE_UPDATE");
return "leanDetail";
}

Related

How to prevent user from injecting field into form backing bean?

An user upload his comment via this form.
Thymeleaf
<form th:action="#{/comment}" th:id="form" method="post">
<input type="hidden" th:name="productId.id" th:value="${product.id}">
<textarea th:field="${comment.message}" class="comment"
placeholder="Write comment here"></textarea>
<input type="submit" id="submit" value="comment">
</form>
Actual HTML
<form action="/comment" id="form" method="post" class="">
<input type="hidden" name="_csrf" value="f6b3f296-3284-4d2d-a2b2-0a9975f5e071">
<input type="hidden" name="productId.id" value="38">
<textarea class="comment" placeholder="Write comment here" id="message" name="message"></textarea>
<input type="submit" id="submit" value="comment">
</form>
However if user overwrites the actual HTML like this, the product's name will be changed to "ABCD"
<form action="/comment" id="form" method="post" class=""><input type="hidden" name="_csrf" value="f6b3f296-3284-4d2d-a2b2-0a9975f5e071">
<input type="hidden" name="productId" value="38">
<input type="hidden" name="productId.name" value="ABCD">
<textarea class="comment" placeholder="Write comment here" id="message" name="message"></textarea>
<input type="submit" id="submit" value="comment">
</form>
I think what happened here is Spring queried the productId and it became managed Entity, and when the user set the name to be "ABCD", it would be saved.
Here is my solution:
Basically just use #Validated with a bunch of groups and put constraint with appropriate groups (UploadCommentValidation in this case) on every single field, which works but seems really messy especially when it gets big.
Example with upload comment above:
Comment Entity: productId and message must be #Not Null, productId must be #Valid,other fields must be #Null
Product Entity: Id must be #NotNull, other fields must be #Null
Comment entity
public class Comment implements Comparable<Comment> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Null(groups = {UploadCommentValidation.class})
#NotNull(groups = {DeleteCommentValidation.class, UpdateCommentValidation.class})
private Integer id;
#ManyToOne
#JoinColumn(name = "product_id", referencedColumnName = "id")
#JsonBackReference
#Valid
#NotNull(groups = {UploadCommentValidation.class})
#Null(groups = {DeleteCommentValidation.class, UpdateCommentValidation.class})
private Product productId;
#ManyToOne
#JoinColumn(name = "user_id", referencedColumnName = "id")
#JsonBackReference
#Null(groups = {UploadCommentValidation.class, DeleteCommentValidation.class, UpdateCommentValidation.class})
private User userId;
#Column(name = "message")
#NotBlank(message = "please write a comment", groups = {UploadCommentValidation.class, UpdateCommentValidation.class})
#Null(groups = {DeleteCommentValidation.class})
private String message;
#Column(name = "created_at", insertable = false, columnDefinition = "timestamp with time zone not null")
#Temporal(TemporalType.TIMESTAMP)
#Null(groups = {UploadCommentValidation.class, DeleteCommentValidation.class, UpdateCommentValidation.class})
private Calendar createdAt;
#Column(name = "updated_at", columnDefinition = "timestamp with time zone not null")
#Temporal(TemporalType.TIMESTAMP)
#Null(groups = {UploadCommentValidation.class, DeleteCommentValidation.class, UpdateCommentValidation.class})
private Calendar updatedAt;
#Override
public int compareTo(Comment o) {
return this.getId().compareTo(o.getId());
}
}
Product entity
public class Product implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#NotNull(message = "product id null", groups = {AddOrderValidation.class, UploadCommentValidation.class})
#Null(message = "bad request", groups = {ProductRegisterValidation.class})
private Integer id;
#NotBlank(message = "please fill in product name", groups = {ProductRegisterValidation.class})
#Length(max = 255, message = "too long", groups = {ProductRegisterValidation.class})
#Null(groups = {AddOrderValidation.class, UploadCommentValidation.class})
#Column(name = "name")
private String name;
#Column(name = "price")
#Positive(message = "the price must be non-negative", groups = {ProductRegisterValidation.class})
#NotNull(message = "please fill in price", groups = {ProductRegisterValidation.class})
#Null(groups = {AddOrderValidation.class, UploadCommentValidation.class})
private Integer price;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "category_id", referencedColumnName = "id")
#Valid
#NotNull(message = "please select category name", groups = {ProductRegisterValidation.class})
#Null(groups = {AddOrderValidation.class, UploadCommentValidation.class})
private Category categoryId;
#NotBlank(message = "please fill in description", groups = {ProductRegisterValidation.class})
#Length(max = 10000, message = "too long", groups = {ProductRegisterValidation.class})
#Null(groups = {AddOrderValidation.class, UploadCommentValidation.class})
#Column(name = "description")
private String description;
#OneToMany(mappedBy = "product", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#Null(groups = {ProductRegisterValidation.class, AddOrderValidation.class, UploadCommentValidation.class})
private List<ProductImage> productImages;
#OneToOne(mappedBy = "product", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#Null(groups = {ProductRegisterValidation.class, AddOrderValidation.class, UploadCommentValidation.class})
private Thumbnail thumbnail;
#OneToMany(mappedBy = "productId", fetch = FetchType.LAZY)
#JsonManagedReference
#Null(groups = {ProductRegisterValidation.class, AddOrderValidation.class, UploadCommentValidation.class})
private List<Comment> comments;
#OneToMany(mappedBy = "product", fetch = FetchType.LAZY)
#Null(groups = {ProductRegisterValidation.class, AddOrderValidation.class, UploadCommentValidation.class})
private List<Order> orders;
}
Any ideas how to do it the right way? This seems super messy!
UPDATE 1: This is my rest controller
#PostMapping("/comment")
public ResponseEntity<Map<String, String>> commentResponseEntity(#Validated({UploadCommentValidation.class}) Comment comment, BindingResult result) {
if (result.hasErrors()) {
result.getAllErrors().forEach(System.out::println);
return ResponseEntity.noContent().build();
}
User user = getUser();
comment.setUserId(user);
commentRepository.saveAndFlush(comment);
Map<String, String> response = new HashMap<>();
response.put("comment", comment.getMessage());
response.put("user", user.getName());
response.put("commentId", comment.getId().toString());
return ResponseEntity.ok().body(response);
}
You can do this by registering an #InitBinder method
You can do this at the individual controller level or by registering a #ControllerAdvice to be applied to all, or a subset of all, controllers.
#InitBinder()
public void initBinder(WebDataBinder binder) {
binder.setDisallowedFields(new String[] { "id", "version" });
}

EL1007E: Property or field 'name' cannot be found on null

I am using a Spring framework with thymeleaf and mysql.
I am getting the error
org.springframework.expression.spel.SpelEvaluationException: EL1007E: Property or field 'name' cannot be found on null
This error is being caused by the ${selectProject.client.name} in the html code.
I have written a form to allow a user to add information about a project. As part of the form, there is a dropdown list of the different project names. However, in the dropdown list I also want it to give the name of the client alongside the name of the project.
My html code:
<form action="#" id="informationForm" method="post" th:action="#{/add-information}" th:object="${information}">
<div class="row">
<div class="form-group col-12">
<label class="col-form-label" for="project">Project</label>
<select class="form-control" id="project" th:field="*{project}">
<option value="0">Please select a project</option>
<option th:each="selectProject : ${projectList}"
th:text="${selectProject.client.name} + ' - ' +${selectProject.name}"
th:value="${selectProject.id}"></option>
</select>
Here is the information controller:
#Controller
#Slf4j
#SessionAttributes({"project", "information"})
public class InformationController {
private final InformationService informationService;
private final ProjectsService projectsService;
#Autowired
public InformationController(final ProjectsService projectsService,
final InformationService informationService) {
this.projectsService = projectsService;
this.informationService = informationService;
}
#ModelAttribute("information")
public Information getInformation() {
return informationService.createEmptyInformation();
}
#ModelAttribute("projectList")
public List<Project> getProjects() {
return projectsService.getProjects();
}
#GetMapping("add-information")
public String showAddInformationForm(Model model, #ModelAttribute("information") Information information) {
information = informationService.createEmptyInformation();
model.addAttribute(information);
return "add-information";
}
#PostMapping("add-information")
public String addInformationForm(#Valid Information information, BindingResult result, Model model) {
if (result.hasErrors()) {
return "add-information";
}
Long id = informationService.createInformationFromInput(information);
return "redirect:/homepage";
}
The relevant methods in the information service are:
public Information createEmptyInformation() {
return new Information();
}
public Long createInformationFromInput(Information information) {
try {
informationDao.save(information);
} catch (Exception ex) {
log.error("Failed to save information for {} because of {}", information.getId(), ex.getLocalizedMessage());
}
return information.getId();
}
and in the projects services:
public List<Project> getProjects() {
return projectDao.findAll();
}
Clients have a many to one relationship with projects
Project domain:
#Getter
#Setter
#Entity
#Table(name = "projects")
public class Project {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "client_id")
private Client client;
#Column(name = "name")
private String name;
Client domain:
#Getter
#Setter
#Entity
#Table(name = "clients")
public class Client {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank(message = "Name is required")
#Size(min = 5, max = 80, message = "Name must be between 5 and 80 characters")
#Column(name = "name", nullable = false)
private String name;
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "client_id")
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
private List<Project> projects;
Information domain:
#Getter
#Setter
#Entity
#Table(name = "information")
public class Information {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne (fetch = FetchType.LAZY)
#JoinColumn(name = "project_id")
private Project project;
#Column(name = "date")
private Date date;

Changes not persisted in database in a Spring Boot application using Spring JPA

I have a Spring Boot application that needs adds a post to a feed list. A post is written by a user and consists of a content and several attachments. The database has 3 tables: post, attachment and user.
The main class of the application is:
#SpringBootApplication
public class SocialMediaApplication {
public static void main(String[] args) {
SpringApplication.run(SocialMediaApplication.class, args);
}
}
The entities are the following:
Post.java
#Entity
public class Post implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(nullable = false)
private String content;
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
#Column(nullable = false)
private Timestamp createdAt;
#Column
private String location;
#OneToMany(mappedBy = "post", cascade = CascadeType.ALL)
private List<Attachment> attachmentList;
#OneToMany(mappedBy = "post", cascade = CascadeType.ALL)
private List<Rating> ratingList;
public Post() {
}
public Post(String content, User user, Timestamp createdAt, String location, List<Attachment> attachmentList, List<Rating> ratingList) {
super();
this.content = content;
this.user = user;
this.createdAt = createdAt;
this.location = location;
this.attachmentList = attachmentList;
this.ratingList = ratingList;
}
// ...
}
Attachment.java
#Entity
public class Attachment implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Lob
#Column(length = 100_000, nullable = false)
private byte[] content;
#ManyToOne
#JoinColumn(name = "post_id")
private Post post;
public Attachment() {
}
public Attachment(byte[] content, Post post) {
super();
this.content = content;
this.post = post;
}
// ...
}
User.java
#Entity
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
private Long id;
#Column(nullable = false)
private String firstName;
#Column(nullable = false)
private String lastName;
#Column(nullable = false)
private Date dateOfBirth;
#Column(nullable = false)
private String credential;
#Column(nullable = false)
private String password;
#Column
private String location;
#Lob
#Column(length = 100_000)
private byte[] photo;
#Column
private String motto;
public User() {
}
public User(String firstName, String lastName, Date dateOfBirth, String credential, String password,
String location, byte[] photo, String motto) {
super();
this.firstName = firstName;
this.lastName = lastName;
this.dateOfBirth = dateOfBirth;
this.credential = credential;
this.password = password;
this.location = location;
this.photo = photo;
this.motto = motto;
}
// ...
}
All repositories extend CrudRepository and are annotated with #Transactional:
PostRepository.java
#Transactional
public interface PostRepository extends CrudRepository<Post, Long> {
}
AttachmentRepository.java
#Transactional
public interface AttachmentRepository extends CrudRepository<Attachment, Long> {
}
UserRepository.java
#Transactional
public interface UserRepository extends CrudRepository<User, Long> {
}
The controller that should add a post to the feed is the following:
#Controller
#RequestMapping("/post")
public class PostController {
#Autowired
PostRepository postRepository;
#GetMapping("/add")
public String greetingForm(Model model) {
model.addAttribute("post", new Post());
return "addPost";
}
#PostMapping("/add")
public String addPost(#ModelAttribute Post post, #RequestParam("attachment") MultipartFile uploadingFile) throws IOException {
User user = new User();
user.setId(1L);
post.setUser(user);
post.setCreatedAt(Timestamp.valueOf(LocalDateTime.now()));
List<Attachment> attachmentList = new ArrayList<>();
Attachment attachment = new Attachment();
attachment.setContent(uploadingFile.getBytes());
attachment.setPost(post);
attachmentList.add(attachment);
post.setAttachmentList(attachmentList);
List<Rating> ratingList = new ArrayList<>();
post.setRatingList(ratingList);
postRepository.save(post);
return "allPosts";
}
}
The addPost.html page has the following content:
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Add Post</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<h1>Add Post</h1>
<form action="#" th:action="#{/post/add}" th:object="${post}" method="post" enctype="multipart/form-data">
<table border="0">
<tr>
<td>Content</td>
<td><textarea id="content" th:field="*{content}" rows="5" cols="50"></textarea></td>
</tr>
<tr>
<td>Location</td>
<td><input type="text" id="location" th:field="*{location}"/></td>
</tr>
<tr>
<td>Attachment</td>
<td><input type="file" id="attachment" name="attachment"/></td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="Submit" />
<input type="reset" value="Reset" />
</td>
</tr>
</table>
</form>
</body>
</html>
The application.properties file has the following content:
spring.datasource.url=jdbc:mysql://localhost:3306/****?useSSL=false
spring.datasource.username=root
spring.datasource.password=****
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.properties.hibernate.id.new_generator_mappings=false
spring.jpa.show-sql=true
spring.thymeleaf.cache=false
spring.thymeleaf.enabled=true
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
However, when I press the submit button, nothing is persisted into the database, although the queries are being displayed in the console:
Hibernate: insert into post (content, created_at, location, user_id) values (?, ?, ?, ?)
Hibernate: insert into attachment (content, post_id) values (?, ?)
What could be the cause?

Spring MVC Error: Failed to convert property value of type java.lang.String to required type

I can't let this exception go:
Failed to convert property value of type java.lang.String to required type com.company.springdemo.entity.Product for property productId; nested exception is java.lang.IllegalStateException: Cannot convert value of type java.lang.String to required type com.company.springdemo.entity.Product for property productId: no matching editors or conversion strategy found
Order Model
#Entity
#Table(name = "orders") // naming the table only order, will throw exception
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "order_id")
private Integer orderId;
#OneToOne(cascade = {CascadeType.DETACH,CascadeType.MERGE,CascadeType.PERSIST,CascadeType.REFRESH})
#JoinColumn(name = "product_id")
private Product productId;
#ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.DETACH,CascadeType.MERGE,CascadeType.PERSIST,CascadeType.REFRESH})
#JoinColumn(name = "client_id")
private Client client;
....
Product Model
#Entity
#Table(name = "product")
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "product_id")
private Integer id;
#Column(name = "product_name")
private String productName;
#Column(name = "product_serial")
private String productSerial;
...
Client Model
#Entity
#Table(name = "clients")
public class Client {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#NotEmpty
#Column(name = "first_name")
private String firstName;
#NotEmpty
#Column(name = "last_name")
private String lastName;
#NotEmpty
#Email
#Column(name = "email")
private String email;
#NotEmpty
#Column(name = "location")
private String location;
#OneToMany(mappedBy = "client",cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<Order> orders;
Controller, where I save the order with related client and product
#PostMapping("add")
public ModelAndView addOrder( #Validated #ModelAttribute("ords") Order order, BindingResult bindingResult ){
if (bindingResult.hasErrors()) {
System.out.println("Having errors: " + bindingResult.getAllErrors());
Iterable<Product> products = productService.listProducts();
Iterable<Client> clients = clientService.listClients();
System.out.println("Error "+ bindingResult.getAllErrors());
ModelAndView mv = new ModelAndView("orders/add-order");
mv.addObject("products",products);
mv.addObject("clients",clients);
return mv;
}
try {
orderService.saveOrder(order);
} catch (Exception e) {
e.printStackTrace();
}
ModelAndView mv = new ModelAndView("redirect:list");
return mv;
}
Finally, my JSP form View page
<form:form action="add" method="post" modelAttribute="ords">
<label for="productId" >Product Id</label>
<form:select path="productId" >
<c:forEach var="product" items="${products}">
<form:option value="${product.id}">${product.productName}</form:option>
</c:forEach>
</form:select>
<form:errors path="productId"/>
<br>
<label for="client" >Client Id</label>
<form:select path="client" >
<c:forEach var="client" items="${clients}">
<form:option value="${client.id}">${client.id} - ${client.lastName}</form:option>
</c:forEach>
</form:select>
<form:errors path="client"/>
<br>
<input type="submit" value="Place Order">
</form:form>
What am I doing wrong?
You most likely need to build a converter class such as this one :
#Component("facilityConverter")
public class FacilityConverter implements Converter<String, Facility>
{
#Autowired
FacilityService facilityService;
#Override
public Facility convert(String id)
{
return facilityService.findById(Integer.parseInt(id));
}
}
Then, you need to register it by implementing the addFormatters method inside of a configuration class implementing WebMvcConfigurer like so :
#Override
public void addFormatters (FormatterRegistry registry)
{
registry.addConverter((FacilityConverter)ctx.getBean("facilityConverter"));
}
Your entities will then correctly be mapped from a dropdown selection. Also, this might not be part of your issue but you can just build your dropdowns like this :
<form:select name="linkedInterface" path="linkedInterface" id="linkedInterface">
<form:options items="${interfaces}" itemLabel="name" itemValue="id"/>
</form:select>
The productId field is actually a Product object, not an ID (String/int). You need your JSP to use path="productId.id" rather than path="productId".
(Although I'd also suggest you also rename the field product rather than productId.)
<form:select path="product.id">
I think you'll hit the same issue on your <form:select path="client"> too.

Spring 4 MVC Form: Create Object with list of sub objects

I looking for help in understanding how to create a new object through a form which has the user select multiple sub-objects (which will come pre-populated) and available to select with a checkbox.
OrderController.java
#RequestMapping(value = { "/order" }, method = RequestMethod.GET)
public String order(ModelMap model) {
List<Exam> exams = examService.findAllExams();
List<Document> documents = documentService.findAllDocuments();
model.addAttribute("exams", exams);
model.addAttribute("documents", documents);
return "order"; // jsp page reference
}
Order.java
#Entity
#Table(name="\"order\"")
public class Order implements Serializable
{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name = "order_id", unique = true, nullable = false)
private Integer id;
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
#Column(name = "uuid", unique = true, nullable = false)
private String uuid;
#Temporal(TemporalType.DATE)
#Column(name = "order_date", unique = true, nullable = false)
private Date orderDate;
#Column(name="order_status", nullable=false)
private String orderStatus;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id", nullable = false)
private User user;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "order")
private Set<OrderExam> orderExams = new HashSet<OrderExam>(0);
#OneToMany(fetch = FetchType.LAZY, mappedBy = "order")
private Set<OrderDocument> orderDocuments = new HashSet<OrderDocument(0);
//getters & setters
}
OrderExam.java
#Entity
#Table(name="order_exam")
public class OrderExam implements Serializable
{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name = "order_exam_id", unique = true, nullable = false)
private Integer id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "order_id", nullable = false)
private Order order;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "exam_id", nullable = false)
private Exam exam;
#Column(name="exam_amount", nullable=true)
private Integer examAmount;
#Column(name="answer_sheet_amount", nullable=true)
private String answerSheetName;
#Column(name="students_per_csv", nullable=true)
private String studentsPerCSV;
#Column(name="pas", nullable=true)
private Boolean pearsonAnswerSheet;
//getters & setters
}
Exam.java
#Entity
#Table(name="exam")
public class Exam implements Serializable
{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name = "exam_id", unique = true, nullable = false)
private Integer id;
#NotEmpty
#Column(name="name", unique=true, nullable=false)
private String name;
#NotEmpty
#Column(name="code", unique=true, nullable=false)
private String code;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "exam")
private Set<OrderExam> exams = new HashSet<OrderExam>(0);
//getters & setters
}
As you can see I am passing in a list of exams and documents which will populate a form with available options (can be seen in the image below (exams anyway)). The user needs to be able to select multiple rows, so that a single order has multiple exams and documents associated to it.
My order.jsp is a little much to post the entire thing here but here is the part I have which is displayed in the image above.
Order.jsp
<form:form method="POST" modelAttribute="order" class="form-horizontal form-label-left">
<c:forEach items="${exams}" var="exam">
<tr>
<th scope="row"><input type="checkbox" class="flat"></th>
<td><input id="middle-name" type="text" name="middle-name" readonly="readonly" value="${exam.name} - ${exam.code}" class="form-control col-md-7 col-xs-12"></td>
<td><input id="middle-name" type="text" name="middle-name" value="0" class="form-control col-md-3 col-xs-12"></td>
<td><input id="middle-name" type="text" name="middle-name" value="0" class="form-control col-md-3 col-xs-12"></td>
<td><input id="middle-name" type="text" name="middle-name" value="0" class="form-control col-md-3 col-xs-12"></td>
<c:choose>
<c:when test="${exam.name == 'Algebra 2 (Common Core)'}">
<th scope="row"><input type="checkbox" class="flat"></th>
</c:when>
<c:otherwise>
<th scope="row"></th>
</c:otherwise>
</c:choose>
</tr>
</c:forEach>
<!-- Other Stuff Goes Here -->
</form:form>
So in short, would someone be willing to show me how to set up the form in the way I described above? Thanks in advance.
Your question is a bit broad however you could try this as below. I have only covered exams. Principal for documents will be the same.
You will need a couple of new classes to capture the submitted form inputs:
Order Form to Capture Selections
public class OrderForm{
private List<ExamWrapper> allAvailableExams = new ArrayList<>();
private XOptionPrintWrapper selectedWrapper;
public OrderForm(){
}
//getters and setters
}
Exam Wrapper : Decorates an Exam with a 'selected' property
public class ExamWrapper{
private boolean selected;
private Exam exam;
public ExamWrapper(Exam exam){
this.exam = exams;
}
//getters and setters
}
Change Contoller to
public class OrderController{
//Exams model populated by the method below
//moved as we also need it populated on POST
#RequestMapping(value = { "/order" }, method = RequestMethod.GET)
public String order(ModelMap modelMap) {
//only needed on GET so put in model here
List<XOptionPrintWrapper> availableWrappers = //someList;
modelMap.put("availableWrappers", availableWrappers);
return "order";
}
//handles for submit
//model atribute is automatically populated by the framework
#RequestMapping(value = { "/order" }, method = RequestMethod.POST)
public String order(#ModelAttribute("orderForm") OrderForm orderForm) {
//process selected exams
return "nextView";
}
//on get populates the initial model for display
//on post create an instance which the form params will be bound to
#ModelAttribute("orderForm")
public OrderForm getOrderForm(){
OrderForm orderForm = new OrderForm();
List<Exam> exams = examService.findAllExams();
for(Exam exam : exams){
orderForm.getAllAvailableExams.add(new ExamWrapper(exam));
}
return orderForm;
}
}
In JSP use Sping support for binding to indexed properties:
<form:form method="POST" modelAttribute="orderForm" class="form-horizontal form-label-left">
<c:forEach items="${orderForm.allAvailableExams}" var="exam" varStatus="status">
<tr>
<th scope="row"><input name="allAvailableExams[${status.index}].selected"
type="checkbox" class="flat"></th>
</tr>
</c:forEach>
<form:select path="selectedWrapper">
<form:options items="${availableWrappers}"
itemValue="somePropertyOfXOptionPrintWrapper "
itemLabel="somePropertyOfXOptionPrintWrapper " />
</form:select>
</form>
I obviously haven't been able to try all of this but think it should all be okay.

Resources