I defined the following drop down box in my page:
...
<td>
<h:selectOneMenu id="bootenvironment1" value="#{detailModel.selectedBootenvironment1}"
disabled="#{detailModel.mode == detailModel.viewMode}">
<f:selectItems value="#{detailModel.availableBootenvironments}"/>
</h:selectOneMenu>
</td>
In my model I have:
...
private Map<String, Bootenvironment> availableBootenvironments;
public DefinitionDetailModel()
{
super();
}
public String getSelectedBootenvironment1()
{
if (((Definition) getAfterObject()).getBootenvironment1() != null)
{
return ((Definition) getAfterObject()).getBootenvironment1().getEnvironmentName();
}
return "--Please select one--";
}
public void setSelectedBootenvironment1( String selectedBootenvironment )
{
((Definition) getAfterObject()).setBootenvironment1(availableBootenvironments.get(selectedBootenvironment));
}
...
And in the controller I set the availableBootenvironments map:
private void fetchBootenvironments()
{
...
#SuppressWarnings( "unchecked" )
List<Bootenvironment> bootenvironments = (List<Bootenvironment>) ...
Map<String, Bootenvironment> availableBootenvironments = new HashMap<String, Bootenvironment>();
availableBootenvironments.put("--Please select one--", null);
for(Bootenvironment bootenvironment : bootenvironments)
{
availableBootenvironments.put(bootenvironment.getEnvironmentName(), bootenvironment);
}
((DefinitionDetailModel) detailModel).setAvailableBootenvironments(availableBootenvironments);
}
The problem is that when I click a button in the page (which is bound to an action), I get the error:
detailForm:bootenvironment1: Validation error: value is not valid.
I don't understand where the error is; the value for selectItems is a map with the object's name-field(so a string) as key and the object itself as value. Then the value for the default selected (value="#{detailModel.selectedBootenvironment1}") is a string too as you can see in the getter/setter method of the model.
Another problem (maybe related to the previous one) is that when the page first loads, the default selected value should be --Please select one--- as the getBootenvironment1() returns null, but this does not happen: another one from the list is selected.
Can you please help me understanding what/where am I doing wrong?
EDIT
I implemented the Converter as you said:
#FacesConverter( forClass = Bootenvironment.class )
public class BootenvironmentConverter implements Converter
{
#Override
public String getAsString( FacesContext context, UIComponent component, Object modelValue ) throws ConverterException
{
return String.valueOf(((Bootenvironment) modelValue).getDbId());
}
#Override
public Object getAsObject( FacesContext context, UIComponent component, String submittedValue ) throws ConverterException
{
List<Bootenvironment> bootenvironments = ... (get from DB where dbid=submittedValue)
return bootenvironments.get(0);
}
}
But now I have the following error:
java.lang.ClassCastException: java.lang.String cannot be cast to
ch.ethz.id.wai.bootrobot.bo.Bootenvironment
You will get this error when the selected item value doesn't pass the equals() test on any of the available item values.
And indeed, you've there a list of Bootenvironment item values, but you've bound the property to a String which indicates that you're relying on the Bootenvironment#toString() value being passed as submitted value and that you aren't using a normal JSF Converter at all. A String can never return true on an equals() test with an Bootenvironment object.
You'd need to provide a Converter which converts between Bootenvironment and its unique String representation. Usually, the technical ID (such as the autogenerated PK from the database) is been used as the unique String representation.
#FacesConverter(forClass=Bootenvironment.class)
public class BootenvironmentConverter implements Converter {
#Override
public void getAsString(FacesContext context, UIComponent component, Object modelValue) throws ConverterException {
// Write code to convert Bootenvironment to its unique String representation. E.g.
return String.valueOf(((Bootenvironment) modelValue).getId());
}
#Override
public void getAsObject(FacesContext context, UIComponent component, Object submittedValue) throws ConverterException {
// Write code to convert unique String representation of Bootenvironment to concrete Bootenvironment. E.g.
return someBootenvironmentService.find(Long.valueOf(submittedValue));
}
}
Finally, after implementing the converter accordingly, you should be able to fix the selectedBootenvironment1 property to be a normal property without any mess in getter/setter:
private Bootenvironment selectedBootenvironment1;
public Bootenvironment getSelectedBootenvironment1() {
return selectedBootenvironment1;
}
public void setSelectedBootenvironment1(Bootenvironment selectedBootenvironment1) {
this.selectedBootenvironment1 = selectedBootenvironment1;
}
Related
I'm sending this parameter to my struts action
cdata[1]=bar
In my action I'm interested in the index and the value.
I defined a getter/setter pair for CDATA as the OGNL documentation suggests:
public void setCdata(int index, String value){
LOG.info("setData; key="+ key +"; value="+ value);
// store index and value;
}
public String getCdata(int index){
return null; // don't really need a setter
}
This is the Exception I get:
2013-04-29 15:38:49,792 [http-apr-8080-exec-3] WARN com.opensymphony.xwork2.util.logging.commons.CommonsLogger.warn(CommonsLogger.java:60) - Error setting expression 'cdata[1]' with value '[Ljava.
lang.String;#4223d2a4'
ognl.OgnlException: target is null for setProperty(null, "1", [Ljava.lang.String;#4223d2a4)
at ognl.OgnlRuntime.setProperty(OgnlRuntime.java:2309)
at ognl.ASTProperty.setValueBody(ASTProperty.java:127)
at ognl.SimpleNode.evaluateSetValueBody(SimpleNode.java:220)
at ognl.SimpleNode.setValue(SimpleNode.java:301)
at ognl.ASTChain.setValueBody(ASTChain.java:227)
at ognl.SimpleNode.evaluateSetValueBody(SimpleNode.java:220)
at ognl.SimpleNode.setValue(SimpleNode.java:301)
at ognl.Ognl.setValue(Ognl.java:737)
...
If I define a public member variable String[] cdata = new String[1000] I don't see any exception in my log but my setter is not called either. If the member variable is private I get another exception again.
Use the following setup
List<String> cdata = new ArrayList<String>();
public List<String> getCdata() {
return cdata;
}
public void setCdata(final List<String> cdata) {
if (cdata == null) {
this.cdata = new ArrayList<String>();
} else {
this.cdata = cdata;
}
}
submit the values from JSP like cdata[1]=value etc
only requirement is to have the getters/setters. I've tested this Tomcat7 running on java 1.6. You can submit values like cdata[0], cdata[1] likewise
or else you could use a map
private Map<String, String> data = new HashMap<String, String>();
public Map<String, String> getData() {
return data;
}
public void setData(Map<String, String> data) {
this.data = data;
}
JSP can have
<s:form action="indexProperty">
<h3>Test The Map</h3>
<input type="text" name="data['0']"/>
<input type="text" name="data['1']"/>
<s:iterator value="data.entrySet()" var="aData">
<s:property value="#aData.key" />-<s:property value="#aData.value" />
</s:iterator>
<input type="submit" name="submit" value="submit"/>
</s:form>
Gets populated without a issue
My solution (rather an ugly hack):
I made my action class implement ServletRequestAware and in the action iterate over the parameter map from HttpServletRequest, fetch cdata from it and parse it for index and value
I had to change the sent parameter and encode eg cdata[999]=foobar like cdata_999_=foobar because if it looks like an array field struts requires there's a setter/getter for it in the action class.
According to the docs, OGNL provides support for indexing properties of JavaBeans: OGNL Reference Guide:
JavaBeans supports the concept of Indexed properties. Specifically this means that an object has a set of methods that follow the following pattern:
public PropertyType[] getPropertyName();
public void setPropertyName(PropertyType[] anArray);
public PropertyType getPropertyName(int index);
public void setPropertyName(int index, PropertyType value);
You didn't implement all of these methods. Also if you didn't initialize an array, the values could not be set.
You can read more about indexed properties here.
Indexed Properties
An indexed property is an array instead of a single value. In this case, the bean class provides a method for getting and setting the entire array. Here is an example for an int[] property called testGrades:
public int[] getTestGrades() {
return mTestGrades;
}
public void setTestGrades(int[] tg) {
mTestGrades = tg;
}
For indexed properties, the bean class also provides methods for getting and setting a specific element of the array.
public int getTestGrades(int index) {
return mTestGrades[index];
}
public void setTestGrades(int index, int grade) {
mTestGrades[index] = grade;
}
I'm rather new to Play Framework so I hope this is intelligible.
How can I tell play to map a form element to an Object field in the Form's class?
I have a form with a select dropdown of names of objects from my ORM. The values of the dropdown items are the ID field of the ORM objects.
The form object on the Java side has a field with the type of the ORM object, and a setter taking a string and translating it to the object, but on form submission I only get a form error "Invalid Value" indicating the translation is not taking place at all.
My template has a form component:
#helper.select(
createAccountForm("industry"),
helper.options(industries)
)
Where industries is defined in the template constructor by : industries: Map[String, String]
and consists of ID strings to User-Readable names.
My controller defines the class:
public static class CreateAccountForm {
public String name;
public Industry industry;
public void setIndustry(String industryId) {
this.industry = Industry.getIndustry(Integer.parseInt(industryId));
}
}
EDIT: I was doing the setter in the class because this answer indicated to do so, but that didn't work.
EDIT2:
Turns out the setter method was totally not the way to go for this. After banging my head a bit on trying to get an annotation working, I noticed the Formatters.SimpleFormatter and tried that out. It worked, though I don't understand why the extra block around it is necessary.
Global.java:
public class Global extends GlobalSettings {
// Yes, this block is necessary; no, I don't know why.
{
Formatters.register(Industry.class, new Formatters.SimpleFormatter<Industry>() {
#Override
public Industry parse(String industryId, Locale locale) throws ParseException {
return Industry.getIndustry(Integer.parseInt(industryId));
}
#Override
public String print(Industry industry, Locale locale) {
return industry.name;
}
});
}
}
Play is binding the form to an object for you when you use it like described in the documentation: https://github.com/playframework/Play20/wiki/JavaForms
So your controller should look like:
Form<models.Task> taskForm = form(models.Task.class).bindFromRequest();
if (taskForm.hasErrors()) {
return badRequest(views.html.tasks.create.render(taskForm));
}
Task task = taskForm.get();
The task object can have a Priority options list. And you use it in the form (view) like:
#select(editForm("priority.id"), options(Task.priorities), 'class -> "input-xlarge", '_label -> Messages("priority"), '_default -> Messages("make.choice"), 'showConstraints -> false, '_help -> "")
Notice that I am using priorities.id to tell play that a chosen value should be binded by a priority ID. And of course getting the priorities of the Tasks:
public static Map<String, String> priorities() {
LinkedHashMap<String, String> prioritiesList = new LinkedHashMap<String, String>();
List<Priority> priorities = Priority.getPrioritiesForTask("task");
for (Priority orderPrio : priorities) {
prioritiesList.put(orderPrio.getId().toString(), orderPrio.getDescription());
}
return prioritiesList;
}
I am quite new to JSF 2.0 so this may be a very simple question.
I would now like to pass a self-defined object from one page to another using h:inputHidden, so I can get it by using request.getParameter("obj01").
I have passed the whole object into the value attribute of the h:inputHidden,
however I have get the following errors:
Cannot convert com.project01.obj.web.obj01#10562017 of type class java.lang.String to class com.project01.obj.web.obj01
So I suppose I have done something wrong.
Could anyone give me some advice on it ?
Thank you very much.
You can only pass Strings via request. But there is a solution for that:
Write a converter. Some codeexample could be found here.
http://www.mkyong.com/jsf2/custom-converter-in-jsf-2-0/
EDIT:
For example I passed Objects via a SelectOneMenu.
<h:selectOneMenu id="inputX" value="#{someBean.someObject}" converter="someConverter">
<f:selectItems value="#{someBean.someObjectList}"/>
</h:selectOneMenu>
Put your converter in your faces config.
<converter>
<description>Converter - X</description>
<converter-id>someConverter</converter-id>
<converter-class>de.package.company.SomeConverter</converter-class>
</converter>
Converter:
public class SomeConverter implements Converter
{
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if (value != null)
return (YourBean) new YourBeanDAO().find(Long.parseLong(value));
return null;
}
#Override
public String getAsString(FacesContext arg0, UIComponent arg1, Object arg2) throws ConverterException {
if (arg2 != null && arg2 instanceof YourBean)
return Long.toString(((YourBean) arg2).getId());
return null;
}
}
I have a model class in the following structure:
public class User {
public String name;
public Long id;
}
public class Play {
public String name;
public User user;
}
Now i want to have a form based on Play class. So I have an editPlay view which takes Form[Play] as an input.
In the view I have a form which calls an update action on submit:
#form (routes.PlayController.update())
{..}
but I cannot find the right way to bind the user field in a way I'll receive it properly in the controller:
Form<Play> formPlay = form(Play.class).bindFromRequest();
Play playObj = formPlay.get();
According to the API, Form.Field value is always a string. Is there some other way to automatic bind an input to the User Object?
Thanks
You can make use of custom DataBinder
In the play.scla.html:
#form (routes.PlayController.update())
{
<input type="hidden" name="user" id="user" value="#play.user.id"/>
}
in your method in the controller
public static Result update()
{
// add a formatter which takes you field and convert it to the proper object
// this will be called automatically when you call bindFromRequest()
Formatters.register(User.class, new Formatters.SimpleFormatter<User>(){
#Override
public User parse(String input, Locale arg1) throws ParseException {
// here I extract It from the DB
User user = User.find.byId(new Long(input));
return user;
}
#Override
public String print(User user, Locale arg1) {
return user.id.toString();
}
});
Form<Play> formPlay = form(Play.class).bindFromRequest();
Play playObj = formPlay.get();
}
I'm not quite sure I understand your question, but basically I have been handling forms like this:
final static Form<Play> playForm = form(Play.class);
...
public static Result editPlay(){
Form<Play> newPlayForm = form(User.class).bindFromRequest();
Play newPlay = newPlayForm.get();
....
}
I serve and render the template from an action using:
return ok(play_form_template.render(playForm));
Then in the template:
#(playForm: Form[Play])
#import helper._
#helper.form(action = routes.Application.editPlay()) {
#helper.inputText(playForm("name"))
...
}
I am setting up a form using JSF (I'm pretty new at this) and I am getting a Validation Error: Value is not valid message on one of the fields. This field is actually a separate object (as I will show below) that has a custom converter.
Here is what I have (with non-relevant code removed):
I have a Citation class:
#ManagedBean(name="citation")
public class Citation {
private int id;
private Status status;
// getters and setters
}
I also have a Status class that you see referenced in the Citation class:
#ManagedBean(name="status")
public class Status {
private int id;
private String name;
// getters and setters
public List<Status> getAllStatuses() {
Session session = HibernateUtil.getCurrentSession();
session.beginTransaction();
session.clear();
Query query = session.createQuery("from Status");
List<Status> statuses = query.list();
try {
session.getTransaction().commit();
} catch (HibernateException e) {
// TODO: handle exception
session.getTransaction().rollback();
}
return statuses;
}
#Override
public boolean equals(Object obj) {
if (obj == null) return false;
if (!(obj instanceof Status)) return false;
if (this.id == ((Status)obj).getId()) {
return true;
} else {
return false;
}
}
#Override
public int hashCode() {
return this.name.hashCode();
}
}
Then for my form, I have:
<h:selectOneMenu id="citation_status" value="#{citation.status}">
<f:selectItems value="#{status.allStatuses} var="s" itemValue="#{s.id}" itemLabel="#{s.name}" />
</h:selectOneMenu>
<h:message for="citation_status" />
Lastly, for my converter, I have:
#FacesConverter(forClass=Status.class)
public class StatusConverter implements Converter {
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
// uses Hibernate to get the Status object (using a breakpoint in Eclipse, I have verified that this works)
// I can post this code if needed, but just trying to keep it short :)
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
return String.valueOf(((Status) value).getId());
}
}
Now when I get to my form and submit, I get the Validation Error next to the Status. I'm pretty new at this and thanks to #BalusC, I'm this far along.
Any help is greatly appreciated.
Validation Error: Value is not valid
In case of <h:selectOneMenu>, you will get this when error whenever the selected item does not match any of the items available in the list. I.e. selectedItem.equals(selectItem) has never returned true for any of the items.
Since it's apparently a custom object (the Status class), did you implement its Object#equals() (and #hashCode()) properly? You can if necessary let the IDE (Eclipse/Netbeans) autogenerate them.
See also:
Overriding equals and hashCode in Java
How to implement equals() in beans/entities
Update: after having a closer look at your code, it turns out that you're actually submitting #{s.id} instead of #{s} (the whole Status object). Fix the itemValue accordingly and it should work (if equals() is still doing its job properly).