Just a heads up, I am using Java and Spring for a web app.
I have an object (objectBean) that contains an EnumSet (enumSet) of type EnumInnerObject as an attribute. I am passing this object as a bean from my controller to my .jsp view. I use the following .jsp code to bind the checkboxes:
<form:form commandName="objectBean" name="whatever" action="./save.htm" method="post">
<form:checkboxes items="${allOptions}" path="enumSet" />
</form:form>
Here is my controller initbinder:
#InitBinder
protected void initBinder(WebDataBinder binder) throws Exception{
binder.registerCustomEditor(EnumSet.class, "enumSet", new CustomCollectionEditor(Collection.class){
protected Object convertElement(Object element){
if(element instanceof String){
EnumInnerObject enumInnerObject= EnumInnerObject.valueOf((String)element);
return enumInnerObject;
}
return null;
}
});
In the controller, I pass allOptions (separate from my bean), and this contains all EnumInnerObject options, so all the checkboxes are displayed. "enumSet" is the EnumSet attribute with the appropriate values contained (if the value is contained in EnumSet, then it automatically checks the correct box in "allOptions"). All of this works and the .jsp correctly shows the correct checked boxes. However, the problem is when I submit the page to save. I get the following error:
java.lang.IllegalArgumentException: Cannot convert value of type [java.lang.String[]] to required type [java.util.EnumSet] for property 'enumSet': PropertyEditor [com.example.controller.MyController$1] returned inappropriate value]
I have a feeling I have to modify the InitBinder to get the form submit to work. Any ideas??
Thanks!
Frankly speaking, I can hardly imagine how this idea is gonna work: EnumSet collection is designed to store the values of enums, but at the moment it is constructed is needs to know the number of elements in that enum (= size of universe it its terms).
CustomCollectionEditor is passed a collection class as it's constructor argument, so it need to create this collection and it will fail for above reason. More over CustomCollectionEditor supports only the limited amount of target collections (ArrayList, TreeSet, LinkedHashSet, see CustomCollectionEditor#createCollection()).
In order not to overcomplicate things I suggest you to use common collections, rather than EnumSet. Otherwise you need to write your own property editor. The implementation won't be difficult, something like:
binder.registerCustomEditor(EnumSet.class, "enumSet",
new PropertyEditorSupport() {
#Override
public void setValue(Object value) {
EnumSet<EnumInnerObject> set = EnumSet.noneOf(EnumInnerObject.class);
for (String val: (String[]) value) {
set.add(EnumInnerObject.valueOf(val));
}
super.setValue(set);
}
});
Related
Use case: system administrator stores a Freemarker template in a database which is used (by Spring Boot REST API) to present information stored by system users (respondents) in a locale-aware way to a different user type (reviewer).
A respondent's response might be stored in this sort of object (or in lists of this sort of object, in the event a question posed to the respondent is expected to have multiple answers):
// snip
import com.fasterxml.jackson.databind.node.ObjectNode;
// more imports snipped
public class LanguageStringMap {
private Map<Language, String> languageStringMap;
public LanguageStringMap(ObjectNode languageMapNode) {
// snip of code instantiating a LanguageStringMap from JSON
}
public void put(Language language, String value) {
if (value.length() == 0)
throw new IllegalArgumentException(String.format(
"value for language '%s' of zero length", language.getCode()));
languageStringMap.put(language, value);
}
public String get(Language language) { return languageStringMap.get(language); }
}
What I think I want to do is write an ObjectWrapper that maps instances of LanguageStringMap to a string (obtained by calling the get() method with a language derived from the Locale requested by the reviewer's browser and set in the template's settings). This presents a cleaner user experience to the system administrator than making the uploaded template contain a bunch of template method calls would.
To do this, my object wrapper needs to access a template setting. I have perused the pertinent Freemarker documentation, but I am still unclear on how to do this or if it is even possible.
I think it would be a mistake to try to implement this with resource bundles uploaded to the database alongside the templates, but that is a consideration.
Typically you simply put the locale specific string into the data-model before the template is processed, along with all the other variables. In that case no ObjectWrapper customization is needed. But if you have to use an ObjectWrapper-based solution, then you can get the locale inside an ObjectWrapper method (like in the override of DefaultObjectWrapper.handleUnknownType) with Environment.getCurrentEnvironment().getLocale().
I have built a custom control which renders an arraylist of java objects via a repeat control. Via the property definition I can provide which fields from the underlying java object I want to display. In the back-end I read this value e.g. via
obj[compositeData.columnField1]
This works well with static data, but sometimes I want to format the before rendering e.g. when the field contains a notesname and I only want to display the commonname.
I am wondering how I could set up something like that.
Now I am only passing the field name, which will be picked up by the cc to read the value.
E.g. for the jQuery DataTables plugin you can define a render function for a column and use the data variable of that column within that render function (e.g. build an anchor link or button in that column).
Can I provide something similar for SSJS e.g. I pass the render function as text (or object?) and in the back-end it will be transformed to ssjs.
Functions in JavaScript are first class citizens on objects. Presume you created a JS object with all the rendering functions and stored it in one of the scopes. E.g. viewScope.renderFunctions. Then hand over the name of the render function and inside use something like:
var renderfunc = viewScope.renderFunctions[funcname];
var result = renderfunc(rawdata);
return result;
That should do the trick
I found an answer here on stackoverflow. The eventhandler for my button looks as followed:
<xp:eventHandler event="onclick" submit="true"
refreshMode="partial" refreshId="pnlContainer"
action="#{javascript:if (compositeData.actionButton.action) if (!compositeData.actionButton.action.call()) return;}">
</xp:eventHandler>
For my custom control I have set up a property:
<property>
<property-name>action</property-name>
<property-class>javax.faces.el.MethodBinding</property-class>
<property-extension>
<designer-extension>
<editor>com.ibm.workplace.designer.ide.xfaces.internal.editors.MethodBindingEditor</editor>
</designer-extension>
</property-extension>
<description>ssjs that action button must perform</description>
</property>
Make sure the class and editor are as above.
Then the property on the xpage containing the custom control contains the ssjs:
action="#{javascript:removeSelected}"
This is a function that resides in a SSJS script library. The key here is not to provide ANY parameters of parantheses (!!!)
The SSJS function is as followed:
function removeSelected(){
var accessList = sessionScope.get("removalList");
var nsf_committee = datasources["COM1_DB_FILEPATH"];
var db:NotesDatabase = session.getDatabase(session.getServerName(), nsf_committee);
for (var i = 0; i < accessList.length; i++) {
var doc:NotesDocument = db.getDocumentByUNID(accessList[i]);
if (null != doc){
doc.remove(true);
accessList.remove(accessList[i]);
}
}
}
(here I remove documents from the database. the unid id's reside in a an arraylist. the array list is set via a checkbox group for each row in my repeat control.)
Considering you would likely want to re-use the component in many contexts of your app I would approach the problem with an helper class and interface
public class FieldManager {
public static interface FieldDetail {
String getName();
void getAction();
}
private List<FieldDetail> fieldDetails = new List<FieldDetail>();
public FieldManager() {
}
public FieldManager(List<FieldDetail> fieldDetails) {
this.fieldDetails.addAll(fieldDetails);
}
public void addFieldDetail(FieldDetail fieldDetail) {
this.fieldDetails.add(fieldDetail);
}
public List<FieldDetail> getFieldDetails() {
return fieldDetails;
}
}
With this succinct definition you could now be able to implement the FieldDetail interface with a generic class or alternative specialized classes.
Then in the custom control you set a property as value or whatever, the <property-class> tag will be FieldManager and in the custom control you would know how everything plays out because the class FieldManager and the FieldDetail are the contract for you.
<xp:repeat disableOutputTag="true"
value="#{compositeData.value.fieldDetails}" var="fieldDetail">
<xp:link text="#{fieldDetail.name}">
<xp:eventHandler event="onclick" submit="true"
refreshMode="partial" refreshId="pnlContainer"
action="#{fieldDetail.action}">
</xp:eventHandler>
</xp:link>
</xp:repeat>
Or whatever your code might be. Anyway, that's the gist of it.
By default Grails renders List in XML with a <list> element tag at its root. Likewise it renders Map with <map>. I would like to control the name of the root element.
If I'm returning an ArrayList of User, then I'd like to see:
<users>
<user>...</user>
<user>...</user>
</users>
How can I achieve the above? Here are the Requirements:
Easy to apply this serialization for 50+ domain classes
Abstracted from developers so no explicit coding is required during rendering domain objects (i.e., when render() or respond() is invoked, an ArrayList is still passed in, no explicit casting/converting like as MyNewType)
Able to handle the edge case of an empty list (should return <users/>)
Nice-to-haves:
If this formula can be applied to Map as well, great :)
I have been semi-successful in achieving the goals above, except I don't know how to account for the empty list case. I implemented my own ObjectMarshaller which renders all objects of type List. So long as the list contains one element, I can check the element's type and determine what the plural tag name should be (User => users). But if the list is empty, and since Java generics are by erasure (unless that's different in Groovy?) then I have no way to properly name an empty list other than defaulting to something like <list/>, which is not acceptable.
Some resources that I've been through:
http://www.cacoethes.co.uk/blog/groovyandgrails/dry-json-and-xml-with-grails
http://grails.1312388.n4.nabble.com/Custom-XML-Marshaller-change-the-root-element-name-td4649949.html
http://jwicz.wordpress.com/2011/07/11/grails-custom-xml-marshaller/
http://mrhaki.blogspot.com/2013/11/grails-goodness-register-custom.html
http://manbuildswebsite.com/2010/02/15/rendering-json-in-grails-part-3-customise-your-json-with-object-marshallers/
A way to achieve this is to write a subclass for the CollectionMarshaller class and register it in our Grails application. We can for example register a custom implementation in BootStrap.groovy with the following code:
import org.codehaus.groovy.grails.web.converters.marshaller.xml.CollectionMarshaller
import grails.converters.XML
class BootStrap {
def init = { servletContext ->
// Register custom collection marshaller for List with User instances.
// The root element name is set to users.
XML.registerObjectMarshaller(new CollectionMarshaller() {
#Override
public boolean supports(Object object) {
object instanceof List<User>
}
#Override
String getElementName(final Object o) {
'users'
}
})
}
}
To make this work for more domain classes we might get a reference to all domain classes in BootStrap.groovy and loop through them to configure custom CollectionMarshaller instances.
For maps you can extend MapMarshaller
Also described in http://mrhaki.blogspot.com/2014/02/grails-goodness-customize-root-element.html
I've got in my .tml file something like this:
<t:beaneditform t:id="adForm" object="editableAd"
reorder="actiontype,shops,movies,streams,widgets" ....
My question is how to access (refer) actionType, which is an Enum (and in fact SELECT) in .java file? I just need to handle event when user changes the value of this select (dropdown), obviously before submitting the form itself.
If something like this would work for me...
#OnEvent(component = "adForm.actionType", value=EventConstants.VALUE_CHANGED)
public void actionTypeValueChanged(String value) {
log.info("value is: " + value);
}
To be updated with the changed value in a Select html component on the client side, have a tapestry select component in your template file with a t:zone attribute (i.e. in your case it could point to any dummy zone, this is only needed to be set correctly if you need to update a zone when a value is changed)
Also set the t:value attribute to your enum variable in your page\component java file, usually this variable will be annotated with tapestry's #Property.
Example:
<t:select t:id="myEnumVariable" t:zone="dummyZone" t:value="myEnumVariable"/>
myEnumVariable is used to refer to your class's variable AND to act as an ID (i.e. the actual string myEnumVariable is used as an id), this is not necessary, but it's more readable and maintainable that way)
public class MyClass{
#Property
private MyEnum myEnumVariable;
#OnEvent(component = "myEnumVariable", value=EventConstants.VALUE_CHANGED)
public void actionTypeValueChanged(**MyEnum** newValue) {
this.myEnumVariable = newValue; // <<<<<<
log.info("value is: " + myEnumVariable );
}
}
If you don't mind using the ChenilleKit framework for tapestry you could try using the
framework's OnEvent mixin.
You 'll find the example on the link I share but basically you add two attributes the select tag:
<t:select t:id="myselect" ... t:mixins="ck/OnEvent" t:event="change" />
then you add the event handler on your java class:
#OnEvent(component="myselect", value='change')
public void onChangeDoSomething(String value) {
hope that helps, by the way I think Muhammad's answer is equally correct (and doesn't requires the use of an extra framework).
I have a Java Hashmap object which stores a key value pair in form of org.w3c.dom.Element: java.util.ArrayList. after populating the HashMap, I put it in Freemarker context along with List of org.w3c.dom.Element.
When I try to get the List object using below code snippet
<#list ElementList as key>
<#assign fh =ElemmentListMap>
${fh[key]}
</#list>
It says ${fh[key]} is undefined. Please let me how to get the solution for it or feel free to give another solution.
Update: Starting from 2.3.22, you should just use ?api to work this around. See this answer, or this FAQ entry for details.
The old answer:
The root of the problem here is that the FreeMarker Template Language doesn't have an equivalent of the Map type of Java. Instead it has a type called "hash", which is similar to a Map, but it only supports string keys (variable names, originally). So when it sees fh[key], it will assume that key has a string value (in this case that will be the result of key.toString() due to how POJO-s are wrapped), and hence will not find the item. So the solution would be not using the [] operator for non-string keys, but the Java API, Map.get(Object key).
And the rest is outdated...
But you will run into yet another obstacle if you try to do that: the default ObjectWrapper doesn't expose the methods of Map-s since the string keys of the Map already populate the same name-space. I hate the default ObjectWrapper for various other reasons too, so I always set up FreeMarker like this:
BeansWrapper wrapper = new BeansWrapper();
wrapper.setSimpleMapWrapper(true);
cfg.setObjectWrapper(wrapper);
With this wrapper you can use Map-s both as myMap[someStringKey] or as myMap(whateverKey). So in your case it would be ${fh(key)}. Again, this doesn't work with the default ObjectWrapper.
To make things worse, I see you are using W3C DOM here. The above wrapper doesn't wrap Node-s automatically in a way so that you could use the FreeMarker XML features. So if you need that, then unless you always wrap DOM node manually, you will have to extend BeansWrapper to recognize DOM Node-s. That's simple to do luckily:
public class MyObjectWrapper extends freemarker.ext.beans.BeansWrapper {
public TemplateModel wrap(Object obj) throws TemplateModelException {
if (obj instanceof org.w3c.dom.Node) {
return freemarker.ext.dom.NodeModel.wrap((org.w3c.dom.Node) obj);
} else {
return super.wrap(obj);
}
}
}
The last thing to consider if the environment you are using FreeMarker in already has a custom ObjectWrapper. In that case, you better try to extend that somehow...
Your java code should be:
Map map = new HashMap();
List list = new ArrayList();
list.add("red");
list.add("green");
list.add("blue");
map.put("array_list", list);
And the freemarker template should be:
<#list array_list as i>
${i}
</#list>
Output:
red
green
blue