Problem: Checkboxes values are not being bound to my form, properly. The result is that all my boolean values are null (at least the ones not being bound), and the existing ones are not being updated with values changed by the user.
Details: I'm aware that checkboxes are not submitted to the server if they are not selected. However, I do see the values in the request when hooking up an Eclipse debugger. The data is populated using jQuery/Datatable, but the data is posted back to the server using a form submit.
Spring MVC Version: 3.2.8
I'm assuming its configuration, but I'm not seeing where I am wrong. Here is a small code snippet of what I'm doing in my controller.
#Controller
public class CheckboxController {
...
#RequestMapping(value = "saveCheckboxes*", method = RequestMethod.POST)
public String saveCheckboxes(#ModelAttribute(SESSION_FORM_KEY) CheckboxForm form, BindingResult result, ModelMap model) {
// VALIDATE HERE...
if ( !result.hasErrors() ) {
// SAVE
}
else {
// DON'T SAVE (alert user)
}
}
}
So Spring MVC is used to binding request inputs to my form. The form is defined below, which has a list of summary objects with a boolean property.
public class CheckboxForm {
private List<Summary> summaries;
...
}
public class Summary {
private boolean selected;
...
}
I use jQuery/Datatables to populate my online grid of data. The inputs are created dynamically using a callback within datatables.
var tableWidget = (function($) {
init = function() {
...
"aoColumnDefs": [
{ "aTargets": [0], "sName": "", "mData": "selected" "stype": "html", "sClass": "center", "mRender": renderCheckbox, "bSortable":false, "sWidth": "50px" }
...
};
renderCheckbox = function(source, type, row) {
var $name = 'checkboxForm.summaries['+row.index+'].selected';
return createCheckbox($name, source);
};
createCheckbox = function(name, checked) {
var $checked = (checked === true) ? ' checked="checked"' : '';
return '<input type="checkbox" name="'+name+'" value="true"'+$checked+'/><input type="hidden" name="_'+name+'" value="on"/>';
}
...
))(jQuery);
After all of this, I hooked up the debugger and traced it into the WebDataBinder. I found that it seems to throw and exception in the method:
public boolean isWritableProperty(String propertyName)
saying the property cannot be evaluated. This happens for each property returned. However, I can confirm that what is in the request is the very inputs that I am expecting.
First, unless you know why avoid relative URL in #RequestMapping methods. It is a common cause of errors.
Next, as you directly generate your checkboxes without all the bells and whistles that adds spring:checkbox, you wont't get automatic error messages, and could experience problems in getting last checkboxes values if they are unchecked, as they will not be transmitted by browser and Spring will never see them giving a shorter list (or even an empty list if all are unchecked).
That being said, your problem is that you use checkboxForm.summaries[index].selected where Spring would expect only summaries[index].selected. Remove checkboxFormand your controller should affect values to the #ModelAttribute CheckboxForm form.
Related
I am trying to call a GetParam method from another controller within the same Controller class to refresh the page after some set of operations. This is what I have tried.
#GetMapping("/inventories")
public String showInventories(Model model) {
model.addAttribute("inventories", inventoriesService.getUserInventory());
return "user_inventories";
}
#PostMapping(value = "create_inventory_record", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> createInventoryRecord(#RequestBody MultiValueMap<String, String> inventoryRecord)
throws Exception {
InventoryRecord newInventory = new InventoryRecord(
0,
inventoryRecord.getFirst("addProductType"),
inventoryRecord.getFirst("addProductDescription"),
inventoryRecord.getFirst("addProductCategory")
);
// Response HashMap
HashMap<String, String> inventoryCreationResponse = inventoryService
.createInventoryRecord(newInventory);
boolean inventoryCreated = Boolean.parseBoolean(limitCreationResponse.get("ItemCreated"));
boolean inventoryItemExists = Boolean.parseBoolean(limitCreationResponse.get("ItemAlreadyExists"));
// Check if item created/stored
if (!inventoryCreated) {
// Check if item already exists
if (!inventoryItemExists) {
// Throw exception if Inventory didn't exist but still record wasn't created
return ResponseEntity.unprocessableEntity().body("Inventory item not created!!");
} else {
// Inventory already exists, advice user on front-end
return ResponseEntity.ok(inventoryCreationResponse);
}
} else {
// Refresh inventories page
return ResponseEntity.status(HttpStatus.FOUND).location(URI.create("inventories")).build();
}
}
The showInventories(Model model) method shows/loads the inventory when the page is loaded. I have a button within the page that shows a form to get inputs for the inventory details. I then send a POST request to the springboot controller. The data is received and the whole process succeeds i.e., the data is stored in the database, I get a response on whether the record was created, if not it will notify for existing similar record, if not throw an exception.
Problem
The controller that receives the data before processing is of type ResponseEntity<?>, the one named createInventoryRecord. After checking for record creation, and if record exists, in case the record was stored successfully, I want to call the showInventories method with the #GetMapping("inventories").
I have tried using ResponseEntity as below to try calling that path but it does not work.
My problem is.
return ResponseEntity.status(HttpStatus.FOUND).location(URI.create("inventories")).build();
There is something I am missing, where am I going wrong, what should I do, or do better?
try returning with redirect:/, like this:
return "redirect:/inventories";
I am attempting to load a custom component(descending from UIInput). I then encode an html input back to the client. My component loader is thus:
#FacesComponent("TomsWidgetComponent")
#SessionScoped
public class TomsWidgetComponent {
public TomsInput getNewInput(UIComponent parent)
{
ExpressionFactory factory = getFacesContext().getApplication().getExpressionFactory();
TomsInput newComponent = (TomsInput) getFacesContext().getApplication().createComponent(getFacesContext(), "org.tom.example.toms.TomsInput", "org.tom.example.toms.TomsInput");
String newId = FacesContext.getCurrentInstance().getViewRoot().createUniqueId();
newComponent.setId(newId);
elements.put(newId, newComponent);
newComponent.setInputData(new InputData());
ValueExpression valueExpression = factory.createValueExpression(getFacesContext().getELContext(),"#{tomsInput.string}",String.class);
newComponent.setValueExpression("value", valueExpression);
getChildren().add(newComponent);
pushComponentToEL(getFacesContext(), newComponent);
return newComponent;
}
html:
<"input type="tel" id="j_id2" oninput="mojarra.ab(this,event,0,'execute',0)" /input >"
The input shows up just fine, but ajax event never seems show up in my component. I've tried several permutations, with the key on setValueExpression, and adding behavior listeners.
...
Ive gotten passed the exceptions but the Ajax now coming back from the form is causing my component to reload. It's content is:
tomswidgetform=tomswidgetform&j_id2=fffdsdfgbg&javax.faces.ViewState=-6270730402975544133%3A7227399941332846704&javax.faces.source=j_id2&javax.faces.partial.event=input&javax.faces.partial.execute=j_id2%20j_id2&javax.faces.behavior.event=change&AJAX%3AEVENTS_COUNT=1&javax.faces.partial.ajax=true
Any idea what might be wrong? Thanks.
In a Play 2.2! web project I'm pursuing, I'd like to return views by name in Application.java
I've added the following in the routes config :
GET /:page controllers.Application.show(page: String)
And i'd like Application.java to return the correct view only using it's name (the String page).
At the moment I have :
public static Result show(String page) {
switch(page){
case "home":
return ok(home.render());
case "register":
return ok(register.render());
}
return ok(home.render());
}
I'd like to avoid the switch statement here, and have show(String page) programatically find the view that matches the String page given in argument and return it (or home page if no match has been found).
EDIT : I've read some stuff about reflexion but I don't really know what that is or how to use it :/
Thanks for your insight :)
Remember that Play's view is a Scala function - which takes parameters, for ensuring typesafety, dynamic content etc, etc. In this scenario you shouldn't use :path argument but make usage of different routes to different actions like:
GET /home controllers.Application.home
GET /register controllers.Application.register
Actions:
public static Result home() {
return ok(home.render());
}
public static Result register() {
return ok(register.render());
}
Crypto-advertisment: Use Intellij - create templates for actions and routes and you will be doing it within milliseconds ;)
On the other hand if you have a large set of HTML files you can render them as... files instead of Play views like (pseudo code, debug it yourself!)
public static Result show(String page) {
File htmlFile = new File(page+".html");
if (!htmlFile.exists()) htmlFile = new File("home.html");
return ok(htmlFile).as("text/html");
}
I will only add that is absolutely NOT Play's way for working with templates ;)
For example I have an Action Class called UsersAction where I have some methods like: login, logout, register, and so on.
And I have written validate() method as follows:
#Override
public void validate() {
if ("".equals(username)) {
addFieldError("username", getText("username.required"));
}
if ("".equals(email)) {
addFieldError("email", getText("email.required"));
} else if (!Utils.isValidEmail(email)) {
addFieldError("email", getText("invalid.email.address"));
}
if ("".equals(phone)) {
addFieldError("phone", getText("phone.required"));
}
if ("".equals(password)) {
addFieldError("password", getText("password.required"));
}
}
The problem is that this solution works only when register action is called, anyway it wont work on login or logout cause it will check if the fields aren't null or email is correct and always will give an error. Okay, the solution for logout was to add #SkipValidation annotation above it, but I don't know how to tell to it that login have only 2 fields username and password and that it's not necessary to check email and phone too. I don't want to write an Action Class for each action in part, cause the purpose of Struts 2 is not this.
Create validateMethodName methods, where methodName is the name of the method, e.g.,
validateLogin() { ... }
Otherwise provide some form of contextual information to your validate method.
Using annotations, annotate your action method login with
#Action(value="login", results = {
#Result(name="input", location = "/login.jsp")
},interceptorRefs = #InterceptorRef(value="defaultStack", params = {"validation.validateAnnotatedMethodOnly", "true"}))
#Validations(requiredFields = {
#RequiredFieldValidator(type = ValidatorType.FIELD, fieldName = "username", message = "${getText("username.required")}"),
#RequiredFieldValidator(type = ValidatorType.FIELD, fieldName = "password", message = "${getText("password.required")}")
})
it will only validate username and passsword fields. Similar do the other action methods.
References:
Convention plugin
Validation
Validatin annotation
I am binding a multi select list in spring the item does not get its data from the DAO the data is added from another select option list. The user clicks a button and the data is sent to the multi select option list using jquery.
When the form is posted databinding does not happen for the item since its a complex data type so i registered a CustomEditor and attached it to the #initbinder.
EDITED
I have updated the code the CollectionEditor is now returning a list of citizens back to the view however i am unable to get the data in the list to fill the select option. I am trying to add elements to the list however the jsp still selects remain null when return form the server.
Under is the code:
CustomCollectionEditor
#InitBinder("crime")
protected void initBinder(WebDataBinder binder, HttpServletRequest request, ServletRequestDataBinder victimbinder){
victimbinder.registerCustomEditor(List.class, "victims", new CustomCollectionEditor(List.class){
protected Object convertElement(Object element){
Citizens victims = new Citizens();
String ssNumber = "";
if (element instanceof String){
ssNumber = (String) element;
}
logger.debug("element is ;" +element);
try {
int socialSecurityNumber = Integer.parseInt(ssNumber);
victims = citizenManager.getCitizen(socialSecurityNumber);
} catch (NumberFormatException e) {
logger.error(e.getMessage());
} catch (Exception e) {
logger.error(e.getMessage());
}
return victims;
}
});
Jsp that is filled from DAO in controller
This contains data filled form DAO class when the button is clicked it takes the data from the list on appends it to the other list under which is bind to the POJO
<label>Victims List</label><buttonid="addVictimBtn">/button>
<form:select path="" id="dbvictims" title="Victims Of Crime" class="victimLst">
<form:options items="${dbvictims.dbvictimList}" itemValue="socialSecurityNumber" itemLabel="name"/>
</form:select>
Jsp select item that is bind to POJO
<label>Victims In Crime</label><button id="removeVictimBtn">-</button>
<form:select path="victims" id="victims" title="Victims Of Crime" multiple="multiple" class="victimLst">
<form:options items="${victimList}" itemValue="socialSecurityNumber" itemLabel="name"/>
</form:select><form:errors path="victims" class="errors" />
The Solution to this issue was very simple all of the work was already done in the CustomCollectionEditor. This is important when binding complex data types such as above. There may be other approaches to doing this however i find this to be a very clean and simple approach.
The return statement is very important since it binds to the item attribute of the element in the view. CustomCollectionEditor return a list of objects (victims) The use of the DAO gets the object from the database. This is important since the post only sends the select value not the label, hence we reconstruct the list and resend to the view.
The part of this that i omitted was passing the List Object from the controller back to the view.
Controller
#RequestMapping(value="save.htm", method = RequestMethod.POST)
public ModelAndView handleSave(#Valid #ModelAttribute Crime crime,
BindingResult result,
ModelMap m,
Model model) throws Exception {
if(result.hasErrors()){
model.addAttribute("victimList",crime.getVictims());
return new ModelAndView("*Your View*");
...............