How to check if a particular row has validation error for a multi row validation in Spring using BindingResult - spring

The current logic will check if the BindingResult has errors and the display the data and errors in a jsp.
The logic needed is to check Errors for each row and display only those rows containing validation errors and update the rows which don't have validation errors.
#Autowired
private IncidentExtractStgService incidentExtractStgService;
#RequestMapping(value = "/validatingIncidentList", method = RequestMethod.POST)
public String ValidateIncidentList( #Valid #ModelAttribute("incidentsForm") IncidentsForm incidentsForm,
BindingResult bindingResult,RedirectAttributes redirectAttributes) {
if (bindingResult.hasErrors()) {
for(ObjectError error: bindingResult.getAllErrors()){
System.out.println(error);
}
redirectAttributes.addFlashAttribute("org.springframework.validation.BindingResult.incidentsForm", bindingResult);
redirectAttributes.addFlashAttribute("incidentsForm", incidentsForm);
return "redirect:/validateIncidentList";
}
else
{
for(IncidentExtractStg ie : incidentsForm.getIncidents()) {
ie.setValidated(1);
incidentExtractStgService.update(ie);
System.out.println(ie.getNumber()+" "+ie.getWaitTime());
}
return "redirect:/validateIncidentList";
}
The below code snippet will check if the model contains attribute "incidetsForm",if so will send the same to example.jsp which in-turn will display the data and validation errors.
#RequestMapping(value = "/validateIncidentList", method = RequestMethod.GET)
public String incidentList(Model model) {
if (!model.containsAttribute("incidentsForm")) {
List<IncidentExtractStg> incidents = incidentExtractStgDao.validateList();
incidentsForm.setIncidents(incidents);
model.addAttribute("incidentsForm", incidentsForm);
return "example";
}
model.addAttribute("errormessage","Please Check the Validation Errors column for Errors");
return "example";
}
Example.jsp code snippet
<c:forEach var="ie" items="${incidentsForm.incidents}" varStatus="status">
<tr>
<td><form:input path="incidents[${status.index}].id" value="${ie.id}" readonly ="true"/></td>
<td><form:errors path="incidents[${status.index}].id" cssClass="error" /></td>
<td><form:input path="incidents[${status.index}].number" value="${ie.number}"/></td>
<td><form:errors path="incidents[${status.index}].number" cssClass="error" /></td>
</tr>
IncidentsForm.java:
import java.util.List;
import javax.validation.Valid;
import com.infosys.sla.model.IncidentExtractStg;
public class IncidentsForm {
#Valid
private List<IncidentExtractStg> incidents;
public List<IncidentExtractStg> getIncidents() {
return incidents;
}
public void setIncidents(List<IncidentExtractStg> incidents) {
this.incidents = incidents;
}
}
IncidentExtractStg.java snippet
#Entity
#Table(name="incident_extract_stg")
public class IncidentExtractStg {
#Id
#Column(name="ies_id")
private int id;
#NotBlank(message="number cannot be empty")
#Pattern(regexp="[A-Za-z0-9]*",message="number can contain only alphabets and numbers")
#Column(name="ies_number")
private String number;

First of all, if I were you I will extract all the logic inside a Service Layer. To proceed, you could create an interface IncidentService and its own concrete implementation IncidentServiceImpl in which you can safely will handle your needs. Controllers are definitely not done to do everything.
Then, what are your needs?
"check Errors for each row and display only those rows containing validation errors and update the rows which don't have validation errors"
A method inside your Service Layer could be like that:
public void handleErrors(IncidentsForm incidentsForm, BindingResult bindingResult){
List<String> fieldsInErrorState = new ArrayList<String>(10);
if (bindingResult.hasErrors()) { //
Map<String, Object> bindingModel = bindingResult.getModel();
for (Map.Entry<String, Object> entry : bindingModel.entrySet()) {
String key = entry.getKey();
//Object value = entry.getValue(); you don't need to parse that unless you want specific domain model handlers to run
//you need to store the key as a form field which is in error state
fieldsInErrorState.add(key);
//you already have all the stuff to parse and display errors in your JSP
//thanksfully to bindingResult and JSTL tags.
}
ContactMessageForm cmForm2 = new ContactMessageForm();
// get the list of the fields inside your form
Field[] declaredFields = ContactMessageForm.class.getDeclaredFields();
for (Field field : declaredFields) {
if (!fieldsInErrorState.contains(field.getName())) {
if (field.getName().equalsIgnoreCase("firstname")) {
cmForm2.setFirstname(contactMessageForm.getFirstname());
}
if (field.getName().equalsIgnoreCase("lastname")) {
cmForm2.setLastname(contactMessageForm.getLastname());
}
//etc for each properties of your form object.
}
// then store your dbmodel object
// BUT i think you must be carefull to your data integrity... It is maybe not safe to save an object like that with bypassing some stuff...
// Your form was built like that maybe for a good reason looking at your objects graph.
// If your form is too big, then split it in small parts, it will be much easy to handle, to update, and to work with daily.
}
}
}
Of course you need to customize that code, don't forget to add the throws IntrospectionException to your service method, and you are on the good way.
Cheers!

As the logic is to display only those rows containing validation errors, a new List is created to store the rows which are having at least one validation error.
A new BindingResult is created to store the Errors against the index of the new List.(If this is not done then the error message wont be displayed against the displayed row).
The below logic is to check for field error against each field of the row and from the jsp you can see the filed name has "incidents[${status.index}].id".
- Counter i to get the row count
- Counter j is to set the index for BindingResult.
BeanPropertyBindingResult result2 = new BeanPropertyBindingResult(incidentsForm, bindingResult.getObjectName();
List<IncidentExtractStg> incidents= new ArrayList<IncidentExtractStg>();
int i=0;// to get the row count
int j=0;// to set the index
for(IncidentExtractStg ies : incidentsForm.getIncidents())
{
int count=0;
Field[] declaredFields = IncidentExtractStg.class.getDeclaredFields();
for (Field field : declaredFields)
{
if (bindingResult.hasFieldErrors("incidents["+i+"]."+field.getName()))
{
for (FieldError error: bindingResult.getFieldErrors("incidents["+i+"]."+field.getName()))
{
result2.addError(new FieldError(error.getObjectName(), "incidents["+j+"]."+field.getName(), error.getRejectedValue(), error.isBindingFailure(), error.getCodes(), error.getArguments(), error.getDefaultMessage()));
}
count++;
}
}
if(count>0)
{
j++;
incidents.add(ies);
}
else
{
ies.setValidated(1);
incidentExtractStgService.update(ies);
}
i++;
}
i=0;
j=0;
if (bindingResult.hasErrors()) {
incidentsForm.setIncidents(incidents);
System.out.println("error block");
for (FieldError error: result2.getFieldErrors()) {
System.out.println("field errors are "+error.getField());
System.out.println("field errors are "+error);
}
redirectAttributes.addFlashAttribute("org.springframework.validation.BindingResult.incidentsForm", result2);
redirectAttributes.addFlashAttribute("incidentsForm", incidentsForm);
return "redirect:/validateIncidentList";
}
E.g: If in row number 30 ,incidents[30].number field has validation error. Then i=30 and count>0 and j=0.
Hence the entire row incidents[30] will be saved at index 0 in the newly initialized list and the bindingresult will also be added at index 0. If the J is not being set to the result2 and bindingresult is used then it will still point to index 30 and error message will not be displayed against the field which is now stored at index 0.
Now this updated IncidentsForm and result2 will be send to jsp page which will display only those rows having validations errors and there respective error messages.

Related

How to get two attributes of a model in the same HTML file

Code
#RequestMapping(value = "/cars", params = "request")
public String showSomeAmountCars(HttpServletRequest request, Model model) {
String count = request.getParameter("count");
int parsedCount = Integer.parseInt(count);
model.addAttribute("someCars", carService.getCars(parsedCount));
return "cars";
}
In the method showSomeAmountCars I get some number of cars using parameter in URL link.
In the method showAllCars I get the whole list of cars.
Expected behavior
if I request /cars, then I get the list of cars from the HTML file
if I request /cars?count = 2, then I get the list of two cars from the same HTML file
Question
How can I get the two added attributes in the HTML file and make sure they don't conflict?
You can have an optional request parameter like this:
#GetMapping("/cars")
public String showSomeAmountCars(#RequestParam(name = "cars", required = false) Integer count, Model model) {
if( count == null ) {
model.addAttribute("cars", carService.getCars());
} else {
model.addAttribute("cars", carService.getCars(count));
}
return "cars";
}

Vaadin Select - fields changed inside binder's apply do not write changes to bean from item

Using Vaadin 14.7.0.
Inside a CRUD editor (Enhanced CRUD Editor) I'm building various fields, amongst which I have a Select.
The Select is initialized with a list of options but I'm also trying to change the items from CRUD form edit to CRUD form edit depending on changes from my underlying database so that the user can select new values.
BindingBuilder<Item, SelectOption> bindingBuilder = binder.forField(s);
if (prop.isMandatory()) {
bindingBuilder.asRequired(requiredI18n);
}
bindingBuilder.bind(new ValueProvider<Item, SelectOption>() {
private static final long serialVersionUID = 1L;
#Override
public SelectOption apply(final Item item) {
ListPropertyDefinition lp = ((ListPropertyDefinition)prop);
Serializable currentValue = item.get(lp.getName());
Collection<SelectOption> sOptions = null;
if (lp.getSelectOptions() != null) {
ListDataProvider<SelectOption> ldp = (ListDataProvider)s.getDataProvider();
sOptions = ldp.getItems();
} else {
sOptions = getNewOptions(item, prop.getName());
s.setItems(sOptions);
}
return new SelectOption("N/A", currentValue);
}
}, new Setter<Item, SelectOption>() {
private static final long serialVersionUID = 1L;
#Override
public void accept(final Item bean, final SelectOption fieldvalue) {
bean.set(prop.getName(), fieldvalue != null ? fieldvalue.getValue() : null);
}
});
Now, if the s.setItems(sOptions) branch is being called then the Select field gets populated with the new values sent by the backend but when I'm saving the item the value that I get is null, regardless of what I select in the select field.
This does not happen when I do not change the items in the select field (i.e. if branch).
I did some debugging for comparing 2 select fields - one that changes its values on the fly and one that has values that don't change... from what I could see the field that has values changing on the fly has a null buffered value as seen in the attached image:
vs the field that does not have its values modified in the binder's apply method:
Not sure if what I'm doing is the right way of "refreshing" a select field's values and / or what should I do so that I get the selected value back in the bean on CRUD form save.
I think you are doing things in overly complicated manner. Based on your code I think your principal challenge is how to set empty selection to be "N/A"
For that you simply need to enable empty selection to be allowed. You need to have one placeholder item for empty selection, for which you generate "N/A" as caption. Then you can just do:
Binder<Item> binder = new Binder<>();
Select<SelectOption> select = new Select<>();
...
select.setEmptySelectionAllowed(true);
select.setEmptySelectionCaption("N/A");
binder.forField(s).bind(Item::getProperty,Item::setProperty);
public class SelectOption {
...
}
// Make the item bean also to follow POJO convention
public class Item {
private SelectOption property;
public SelectOption getProperty() {
return property;
}
public void setProperty(SelectOption property) {
this.property = property;
}
}

MudBlazor dropdown not defaulting to value from database

I'm using Blazor with MudBlazor and I have the following form on an Edit page:
<EditForm Model="BookRequestVM" OnInvalidSubmit="InvalidBookRequest" OnValidSubmit="#ValidBookRequest">
...
<MudItem xs="12" sm="4">
<MudSelect T="BookType" Label="Book Type" #bind-Value="#BookRequestVM.BookType" #bind-SelectedValues="hashBookTypes" Required="true">
#foreach (var selectItem in BookTypes)
{
<MudSelectItem Value="#selectItem">#selectItem.TypeTitle</MudSelectItem>
}
</MudSelect>
</MudItem>
</EditForm>
...
#code {
public class BookType
{
public int BookTypeId { get; set; }
public string TypeTitle { get; set; }
}
public HashSet<BookType> hashBookTypes = new HashSet<BookType>();
...
protected override async Task OnInitializedAsync()
{
BookRequestVM = await _bookService.GetBookRequest(Id); // Fetch info from database
BookTypes = _bookService.GetBookTypes().ToList(); // Get all valid dropdown values
hashBookTypes = new HashSet<BookType>(BookTypes);
}
}
Because I'm pulling in existing data (this Book Type field is required when creating a book request), there will always be a Book Type associated with this Book Request. I see that the BookTypeVM was able to pull the Book Type in from the database in the service call, and on the valid submit method, it's bound and gets saved properly. It's just when it loads in, it doesn't default to the value that was saved to the database--only the first value from the dropdown list. Any ideas on what's going on here?
Is this a multi-select? If not then why are you setting #bind-SelectedValues="hashBookTypes". hashBookTypes comes from BookTypes which is a list of all the book types. I'm no expert on MudBlazor, but it appears your setting the selected values to the full list of values. Without MultiSelection="true" then I'm guessing its setting the current value to the first value in the list.
Your code has more problems than the one MrC found. You need to be very careful with using a POCO class in a select without overriding Equals() and GetHashCode() because the select uses a HashSet internally to find out which item is selected. Also, if you want it to convert the selected BookType to string it should override ToString().
Your BookType class should look like this:
public class BookType
{
public string Title { get; set; }
public override bool Equals(object other) {
return (other as BookType)?.Title == Title;
}
public override int GetHashCode()
{
return this.Title.GetHashCode();
}
public override string ToString() => Title;
}
And here is the Select to go with it:
<MudSelect T="BookType" Label="Book Type" #bind-Value="#RequestedBookType" Required="true">
#foreach (var selectItem in BookTypes)
{
<MudSelectItem Value="#selectItem">#selectItem.Title</MudSelectItem>
}
</MudSelect>
Here is a fiddle that demonstrates your code with above changes to make it work: https://try.mudblazor.com/snippet/mOwvPvbhHYHFBoiV
#bind-SelectedValues="hashBookTypes" was the culprit. This is used for multiselect. Unfortunately, I don't recall adding this code, but removing this resolved this issue.

ID in Spring-MVC 2.5 edit form using #Controller

I have a problem with the my Controller code. GET works fine (both empty form + form populated from db), POST works fine only for creating new object, but doesn't work for editing. Part of my #Controller class:
#RequestMapping(value = "/vehicle_save.html", method = RequestMethod.GET)
public String setUpForm(#RequestParam(value="id", required = false) Long id, ModelMap model) {
Vehicle v;
if (id == null) {
v = new Vehicle();
} else {
v = vehicleManager.findVehicle(id);
}
model.addAttribute("vehicle", v);
return "vehicle_save";
}
#RequestMapping(value = "/vehicle_save.html", method = RequestMethod.POST)
public String save(#ModelAttribute("vehicle") Vehicle vehicle, BindingResult result, SessionStatus status) {
vehicleValidator.validate(vehicle, result);
if (result.hasErrors()) {
return "vehicle_save";
}
if(vehicle.getId() == null) {
vehicleManager.createVehicle(vehicle);
} else {
vehicleManager.updateVehicle(vehicle);
}
status.setComplete();
return "redirect:vehicle_list.html";
}
The first method creates a vehicle object (including its ID). But the second method gets the same object without the ID field (set to null).
What could I do: manually set vehicle.setID(id from parameters) and then save it to database. This causes JPAOptimisticLockException + I don't like that solution.
Is there a way to pass my Vehicle object with ID to the second method? BTW, I would like to avoid adding hidden ID field to the JSP.
the example you suggested is using session to store the value. the #SessionAttribute is to bind an existing model object to the session. Look at the source code the class is annotated with #SessionAttributes("pet").Which means your model attribute named "pet" is getting stored in session.Also look at the code in processSubmit method of EditPetForm class
#RequestMapping(method = { RequestMethod.PUT, RequestMethod.POST })
public String processSubmit(#ModelAttribute("pet") Pet pet, BindingResult result, SessionStatus status) {
new PetValidator().validate(pet, result);
if (result.hasErrors()) {
return "pets/form";
}
else {
this.clinic.storePet(pet);
status.setComplete(); //look at its documentation
return "redirect:/owners/" + pet.getOwner().getId();
}
}
I havnt used something like this before.But i guess putting ur id in session is the way
BTW, I would like to avoid adding hidden ID field to the JSP.
This is common solution. What's wrong with it ? You should create hidden input with id.
May be you can try using session, cause you cant store info between two request. But that will be uglier i guess.
Btw, Can you please explain a little why you want to avoid adding hidden fields? I'm little curious

Visualforce 'Conversion Error setting value' -- binding problem with selectCheckboxes

I am getting a 'Conversion error setting value' when I try to save a record in my controller extension. The page looks the way I would expect, but when I select one or more checkboxes, it gives my that error. I'm not seeing what the error is here. Thanks for the help.
The page:
<apex:pageBlockSectionItem >
<apex:OutputLabel value="Assigned Areas of Coverage" for="books" />
<apex:selectCheckboxes value="{!selectedBooks}"
layout="pageDirection" id="books">
<apex:selectOptions value="{!options}" />
</apex:selectCheckboxes>
</apex:pageBlockSectionItem>
The controller:
public String[] selectedBooks {get; set;}
public List<SelectOption> options
{
get
{
List<SelectOption> result = new List<SelectOption>();
List<String> optionNames = bookNames(books);
optionNames.sort();
for(String n : optionNames){
if(!blacklist.contains(n)){
result.add(new SelectOption(n, n));
}
}
return result;
}
}
private List<Book__c> books
{
get
{ if (books == null){
books = [select Id, Name from Book__c];
}
return books;
}
set;
}
private List<String> bookNames(List<Coverage__c> coverage)
{
List<String> result = new List<String>();
for(Coverage__c c : coverage){
result.add(c.Book__r.Name);
}
return result;
}
private List<String> bookNames(List<Book__c> books)
{
List<String> result = new List<String>();
for(Book__c b : books){
result.add(b.Name);
}
return result;
}
private List<Id> bookIDs(List<String> bookNames)
{
List<Id> result = new List<Id>();
Set<String> bookNamesSet = new Set<String>(bookNames);
for(Book__c b : books){
if(bookNamesSet.contains(b.Name)){
result.add(b.Id);
}
}
return result;
}
I think you have to post more code and it's not clear what exactly you wish to achieve:
What's your actual "command" function, I don't see any <apex:commandButton action="{!fun}" ... > and not a single function from the controller looks like "public void fun()" or "public PageReference fun()" - I especially mean the part about taking no input arguments.
Your code does not compile, I've replaced Book__c with Account and Coverage with Contact, but for example the "blacklist" variable in
if(!blacklist.contains(n)){
is undefined. I can assume that this is same as "result", but you know... garbage in, garbage out. We might need just the part you've omitted ;) Also - can you replicate the problem using standard Salesforce objects so it's easier to test for us?
"selectedBooks" is read in the page but you never set value? Not even to the simplest selectedBooks = new String[]... The uninitialized variable a is solution to similar problem discussed on Salesforce message board.
Just a random thought - for bookIDs() function you might be better using Map<Id, Book__c> or Map<Id, String>. The first one can even be instantiated directly from [SELECT Id, Name FROM Book] if you need it. You want set of IDs - use the keySet() method afterwards. You want a list of books - use the values().

Resources