Xpages ObjectData Custom Control: Mixing compositeData with text - xpages

Would like to make a custom control for my DataObjects which would have two properties, the javaClass and the javaModel.
So if I have a java class names acme.com.model.Person, the javaClass property would be acme.com.model.Person and the javaModel property would be Person.
I started to build my custom control but only got a few things put in before I ran into syntactical problems.
The real problem is createObject. I don't understand how I can replace the presently hard-coded "Person" in the createObject with my compositeData values. Is this even possible?
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core"
xmlns:xe="http://www.ibm.com/xsp/coreex">
<xp:this.data>
<xe:objectData
saveObject="#{javascript:compositeData.javaModel + 'save()'}"
var="${javascript:compositeData.javaModel}">
<xe:this.createObject><![CDATA[#{javascript:var Person = new com.scoular.model.Person();
var unid = context.getUrlParameter("key")
if (unid != "") {
Person.loadByUnid(unid);
viewScope.put("readOnly","Yes");
} else {
Person.create();
viewScope.put("readOnly","No");
}
return Person;}]]></xe:this.createObject>
</xe:objectData>
</xp:this.data>
</xp:view>

Just as you might try to de-couple your business logic from your "UI" (XPages markup), you could move your "create" code to a constructor method to the Person class, one which:
calls out to check for the URL Parameter of "key"
sets the key to a property (making this a bit more bean-like, optional, but likely a good idea)
invokes the loadByUnid(String) method
and puts the appropriate readOnly value into viewScope
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
// constructor method
public Person(){
Map<String, Object> reqParm = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap();
String unid = (String) reqParm.get("key");
Map<String, Object> vwScope = ExtLibUtil.getViewScope();
if (unid != "") {
this.loadByUnid(unid);
vwScope.put("readOnly","Yes");
} else {
this.create();
viewScope.put("readOnly","No");
}
}
//...
}
Then your createObject block would look more like:
<xe:this.createObject><![CDATA[#{javascript:return new com.scoular.model.Person();}]]></xe:this.createObject>
This should shift enough of the specifics off of the markup layer so as to be far more reusable across specific classes, provided each self-construct like that.
As for the general mixing compositeData with text, you should be passing an object reference so for the example of the save method above, I think you should be able to access it more via compositeData.javaModel.save();, provided the save method exists in the object referenced by compositeData.javaModel. I don't think appending the string of a method will work, but I can't say I've tried it that way.

Related

Defining the date pattern via a properties file in XPages

I want to define the date pattern in my application on a global level. Therefor I would like to use a properties file. Since I am already using one e.g. to set the default number of rows for a repeat control I added a ne wkey-value pair:
date_format_date_only=yyyy-MM-dd
In my application I have set the date format via convertors e.g.:
<xp:this.converter>
<xp:convertDateTime pattern="yyyy-MM-dd" type="date" />
</xp:this.converter>
But when I set it to
<xp:this.converter>
<xp:convertDateTime pattern="${application.date_format_date_only}" />
</xp:this.converter>
It is not working. The date is displayed as Feb 26, 2018. Application is the variable I have set via a Theme design element:
<resources>
<bundle src="/application.properties" var="application" />
</resources>
Am I overlooking something?
I notice that when I add the bundle directly to the XPage the pattern works but what is the use of a Theme design element then?
I do certain things through theme definition but not all. There are some restrictions that apply - read timing on theme properties evaluations.
I think your need would be the one of avoiding declaring the resource bundle in every page you use. Am I right?
In that case my advice is to tap into JSF mechanics more.
I personally set up a request scoped bean that I call MessageBean:
public class MessageBean implements Serializable {
private static final long serialVersionUID = 1L;
private ApplicationEx application;
private Locale locale;
private ResourceBundle app;
private ResourceBundle error;
private ResourceBundle log;
public void setApplication(Application application) {
this.application = (ApplicationEx) application;
}
public void setLanguage(String language) {
locale = new Locale(language);
}
public ResourceBundle getApp() {
if (app == null) {
app = getResourceBundle("app");
}
return app;
}
public ResourceBundle getError() {
if (error == null) {
error = getResourceBundle("error");
}
return error;
}
public ResourceBundle getLog() {
if (log == null) {
log = getResourceBundle("log");
}
return log;
}
private ResourceBundle getResourceBundle(String name) {
try {
return application.getResourceBundle("/WEB-INF/i18n/" + name + ".properties", locale);
} catch (IOException e) {
throw new FacesException(e);
}
}
}
In the faces-config.xml:
<managed-bean>
<managed-bean-name>msg</managed-bean-name>
<managed-bean-class>mypackage.MessageBean
</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
<managed-property>
<property-name>application</property-name>
<value>#{facesContext.application}</value>
</managed-property>
<managed-property>
<property-name>language</property-name>
<value>#{facesContext.viewRoot.locale.language}</value>
</managed-property>
</managed-bean>
What this bean does for me is to wire up different property files - these are located under /WEB-INF/i18n/ - (which I split according to their domain of competence 'app' = general app messages, 'error' = error messages etc), and arrange them nicely under one single root: msg.
In other words I can declare anywhere in the page ${msg.app.hello} for the app resource bundle or ${msg.error.sorry} for the error resource bundle.
I don't have to declare any resource on the page, that's the magic of the beans. You don't use them, they are not created. You want to use them, the framework creates them and gives them to you automatically.
I notice that when I add the bundle directly to the XPage the pattern works but what is the use of a Theme design element then?
I believe the intention behind themes was to "define the look and feel of an application" (source) and I think this is reflected in the fact that resource bundles defined on a theme file only become available to an XPage during the beforeRenderResponse phase. So, as you've discovered, any properties that you want to access using a bundle declared on a theme can only be accessed via run time bindings and not on Page Load - and as Converters can't use run time bindings...
A couple of quick and simple SSJS workarounds to avoid adding the bundle directly to each XPage you build are:
Put the property in xsp.properties and access it using context.getProperty('foo') or, if you fall on the side of the debate that prefers not to add your own key-value pairs to xsp.properties
use context.bundle("application.properties").getString("foo") instead.
#shillem's answer about writing it all in Java and using a bean is probably more elegant and better practice - especially if you're using a controller framework in your applications - but the above will work.

Sample for for an XSP Object data source that is writable

I'm struggling with the Object datasource in an XPage:
<xp:this.data>
<xe:objectData var="demo" ignoreRequestParams="true"
readonly="false" scope="view"
createObject="#{javascript:return new demo.SampleBean();}">
</xe:objectData>
</xp:this.data>
When I execute save(); on the XPage in SSJS I get the error:
Error saving data source demo
The save method has not been implemented in the data source
The class is rather simple:
package demo;
import java.io.Serializable;
public class SampleBean implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private String job;
public String getName() {
return this.name;
}
public String getJob() {
return this.job;
}
public void setName(final String name) {
this.name = name;
}
public void setJob(final String job) {
this.job = job;
}
public void dummyAction() {
System.out.println("Here shall be logic");
}
}
I tried to add a public void save(); method to the class, but that doesn't do the trick.
So I'm looking for a sample of an Object datasource
You've defined the createObject attribute for the data source, but you haven't specified the saveObject attribute... that's what the error message you're getting ("save method has not been implemented") is referring to.
So, for example, if you want your dummyAction() method to run when a save is triggered, try this:
<xp:this.data>
<xe:objectData var="demo" ignoreRequestParams="true"
readonly="false" scope="view"
createObject="#{javascript:return new demo.SampleBean();}"
saveObject="#{javascript:return value.dummyAction();}">
</xe:objectData>
</xp:this.data>
When the saveObject attribute is specified as a SSJS method binding, the variable value is bound to the data object, and then the method binding is invoked. So you can either pass value to some other object to handle the business logic of saving the object, or you can use a syntax of value.anyMethod() to keep the business logic of object serialization internal to the object itself.
NOTE: whatever logic you do use in this method, you'll want to return a boolean (not void), so that a return value of false can be treated as a cancellation, just like the standard Domino document data source does.

Managed property to an Object in a list from a bean

I would do something with a Managed Bean but I dont' find a solution
To explain what I will do I will show a small example:
I have created a Object Data with the following structure
public class Data implements Serializable{
private static final long serialVersionUID = 5156829783321214340L;
String value="";
public Data() {
}
public String getValue() {
return value;
}
void setValue(String data) {
this. value = data;
}
}
As you can see ist a simple dataholder with one property
now I created a secound Object whitch will be my bean it only holds a list of Data Objects
public class Databean implements Serializable{
private static final long serialVersionUID = 9205700558419738494L;
private ArrayList<Data> datalist;
public Databean()
{
datalist = new ArrayList<Data>();
Data newItem;
for (int i=0; i<5; i++) {
newItem = new Data();
datalist.add(newItem);
}
}
public ArrayList<Data> getDatalist() {
return datalist;
}
public void setDatalist(ArrayList<Data> datalist) {
this.datalist = datalist;
}
}
The Declaration in the Faces-config to publish the bean is no Problem
<managed-bean>
<managed-bean-name>managedBean</managed-bean-name>
<managed-bean-class>de.itwu.Databean</managed-bean-class>
<managed-bean-scope>view</managed-bean-scope>
</managed-bean>
So now to my problem:
I would like to create a Managed Property or something else to make a connection to an inputtext
in a repreat control e.g:
<xp:repeat value="#{managedBean.datalist}" var="rowData">
<xp:inputText id="inputText1" defaultValue="#{rowData.value}"></xp:inputText>
</xp:repeat>
does anyone have an Idea how this could work?
So exmaple corrected but it doesen't work the Ich ich set Datualt values in the Data-Object they are shown. But when I edit the values in the Inputtextfields they are not automatically written back to the Object. I Thing the Problem is the Daclaration in the Faces-Config. Ideas?
The variable assigned in the repeat to var (rowData) will contain an instance of your Data class. To bind each input control to the value field you refer to that property. Because you have a getValue() and setValue() defined a value binding will be created and you will be able to edit the content. If only a getValue() method is defined a method binding is created and the field will not be editable.
<xp:repeat value="#{managedBean.datalist}" var="rowData">
<xp:inputText id="inputText1" value="#{rowData.value}"></xp:inputText>
</xp:repeat>
Your binding is wrong.
<xp:repeat value="#{managedBean.datalist}" var="rowData">
<xp:inputText id="inputText1" defaultValue="#{rowData.value}"></xp:inputText>
</xp:repeat>
rowData contains Data object, which populates getter/setter for field value, not datavalue.

Adding Custom Attributes to Primefaces Autocomplete Component in JSF

I asked about pass through attributes in a different question and found I could create a custom renderer for the <p:autocomplete> component but the problem is my custom renderer would be used for every p:autocomplete in my project (site-wide). Therefore I have elected to create a custom component which extends org.primefaces.component.autocomplete.AutoComplete and adds the necessary attributes to the text box.
My initial thought was to add a constructor but it doesn't seem to work because the attribute map is null at this point:
#FacesComponent("com.mycomponents.SiteSearch")
public class SiteSearch extends AutoComplete {
public SiteSearch() {
Map<String,Object> attrs = getAttributes();
attrs.put("x-webkit-speech", null);
attrs.put("x-webkit-grammer", "builtin:search");
attrs.put("onwebkitspeechchange", "this.form.submit();");
attrs.put("placeholder", "Enter a Search Term");
}
}
My other thought was leave this custom component empty (empty class) and then specify a custom renderer that extends org.primefaces.component.autocomplete.AutoCompleteRenderer and modify the attributes there.
After all is said and done, I just need a way to keep these attributes separate to this one text box so just putting a custom renderer on the p:autoComplete is not going to work (unless maybe I can use renderType= attribute for this one p:autoComplete?).
If you need a specific component which uses a different renderer than <p:autoComplete> then you really can't go around creating a custom component with its own family and component type. You can still just extend the PrimeFaces AutoComplete (and its renderer) to save some boilerplate code.
In the custom component, you need to provide getters for those attributes. You could as good specify setters as well, this way you can always override the default values from in the view side. Those getters/setters should in turn delegate to StateHelper.
There's only a little problem with x-webkit-* attributes. The - is an illegal character in Java identifiers. So you have to rename the getters/setters and change the renderer somewhat as the standard renderer relies on the component property name being exactly the same as the tag attribute name. Update: I understand that x-webkit-speech should just be rendered as is (so, no getter/setter necessary) and that x-webkit-grammer is actually a typo, it should be x-webkit-grammar.
Here's how the SiteSearch component can look like:
#FacesComponent(SiteSearch.COMPONENT_TYPE)
public class SiteSearch extends AutoComplete {
public static final String COMPONENT_FAMILY = "com.example";
public static final String COMPONENT_TYPE = "com.example.SiteSearch";
private enum PropertyKeys {
grammar, onspeechchange, placeholder
}
#Override
public String getFamily() {
return COMPONENT_FAMILY;
}
#Override
public String getRendererType() {
return SiteSearchRenderer.RENDERER_TYPE;
}
public String getGrammar() {
return (String) getStateHelper().eval(PropertyKeys.grammar, "builtin:search");
}
public void setGrammar(String grammar) {
getStateHelper().put(PropertyKeys.grammar, grammar);
}
public String getOnspeechchange() {
return (String) getStateHelper().eval(PropertyKeys.onspeechchange, "submit()");
}
public void setOnspeechchange(String onspeechchange) {
getStateHelper().put(PropertyKeys.onspeechchange, onspeechchange);
}
public String getPlaceholder() {
return (String) getStateHelper().eval(PropertyKeys.placeholder, "Enter a Search Term");
}
public void setPlaceholder(String placeholder) {
getStateHelper().put(PropertyKeys.placeholder, placeholder);
}
}
Please note that the getters have all default values specified. If the eval() returns null, then the default value will be returned instead. I have also neutralized the attribute names somewhat so that it can be reused for any future non-webkit browsers by just modifying the renderer accordingly.
And here's how the SiteSearchRenderer renderer should look like for the above component:
#FacesRenderer(
componentFamily=SiteSearch.COMPONENT_FAMILY,
rendererType=SiteSearchRenderer.RENDERER_TYPE
)
public class SiteSearchRenderer extends AutoCompleteRenderer {
public static final String RENDERER_TYPE = "com.example.SiteSearchRenderer";
#Override
protected void renderPassThruAttributes(FacesContext facesContext, UIComponent component, String[] attrs) throws IOException {
ResponseWriter writer = facesContext.getResponseWriter();
writer.writeAttribute("x-webkit-speech", "x-webkit-speech", null);
writer.writeAttribute("x-webkit-grammar", component.getAttributes().get("grammar"), "grammar");
writer.writeAttribute("onwebkitspeechchange", component.getAttributes().get("onspeechchange"), "onspeechchange");
writer.writeAttribute("placeholder", component.getAttributes().get("placeholder"), "placeholder");
super.renderPassThruAttributes(facesContext, component, attrs);
}
}
To use it in the view, we of course need to register it as a tag. Create a /WEB-INF/my.taglib.xml file:
<?xml version="1.0" encoding="UTF-8"?>
<facelet-taglib
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facelettaglibrary_2_0.xsd"
version="2.0"
>
<namespace>http://example.com/ui</namespace>
<tag>
<tag-name>siteSearch</tag-name>
<component>
<component-type>com.example.SiteSearch</component-type>
<renderer-type>com.example.SiteSearchRenderer</renderer-type>
</component>
</tag>
</facelet-taglib>
Note that you don't need a <renderer> in faces-config.xml for this anymore. The #FacesRenderer annotation can just do its job on real custom components. So remove the <renderer> entry in faces-config.xml which you created based on your previous question.
Now tell JSF that you've got a custom taglib by the following context param in web.xml:
<context-param>
<param-name>javax.faces.FACELETS_LIBRARIES</param-name>
<param-value>/WEB-INF/my.taglib.xml</param-value>
</context-param>
Finally you can use it as follows:
<html ... xmlns:my="http://example.com/ui">
...
<my:siteSearch />
You can even specify additional attributes which will override the defaults set in the component:
<my:siteSearch grammar="builtin:language" onspeechchange="alert('peek-a-boo')" placeholder="Search" />
For IDE autocomplete on attributes, you'd need to specify every one as a separate <attribute> in the <tag> declaration in the my.taglib.xml.

JSF selectManyCheckbox

I'm having a hard time with a selectManyCheckbox.
Basically what I am doing is loading a List of Categories in a selectManyCheckbox type controller (have done this either with a List or with a List with convertEntity). My problem is with the selected elements (value="#{cardListProvider.categoriesHolder.selectedCategories}"). After some reading I understand it also has to be a List, but what kind? And how can I set the selected categories? I'm not saving them in DB but I need to run some action in the bean with them!
Here's what I have:
<h:selectManyCheckbox id="supportCategoryCardFilter"
value="#{cardListProvider.categoriesHolder.selectedCategories}" styleClass="greyText" required="false" >
<s:selectItems var="filterList" value="#{cardListProvider.categoriesList}" label="#{filterList.label}" />
<a:support id="supportCategoryCardFilter2" event="onchange"
reRender="someHolder, categoriesPanel" eventsQueue="onchange" action="#{cardListProvider.findCards(cardListProvider.categoriesHolder.selectedCategories)}" />
</h:selectManyCheckbox>
I have wasted several hours with this... Can anyone help me?
Thank you
You can bind to a String[] array like so:
public class CheckSelector {
private String[] chosen;
public String[] getChosen() { return chosen; }
public void setChosen(String[] chosen) { this.chosen = chosen; }
public SelectItem[] getChoices() {
return new SelectItem[] { new SelectItem("1"), new SelectItem("2"),
new SelectItem("3") };
}
}
The value of the selectManyCheckbox should be bound to chosen. Alternatively, you can use a List:
public class CheckSelector {
private List<String> chosen;
public List<String> getChosen() { return chosen; }
public void setChosen(List<String> chosen) { this.chosen = chosen; }
public List<SelectItem> getChoices() {
return Arrays.asList(new SelectItem("1"), new SelectItem("2"),
new SelectItem("3"));
}
}
The exact rules for value support are listed in the javadoc:
If the component has an attached Converter, use it.
If not, look for a ValueExpression for value (if any). The ValueExpression must point to something that is:
An array of primitives (such as int[]). Look up the registered by-class Converter for this primitive type.
An array of objects (such as Integer[] or String[]). Look up the registered by-class Converter for the underlying element type.
A java.util.List. Assume that the element type is java.lang.String, so no conversion is required.
If for any reason a Converter cannot be found, assume the type to be a String array.
I see you are using Seam so no need to use Strings or any primitive type, you can bind directly to List. You just have to add another tag inside your selectManyCheckbox component which is and it will automatically do everything.
Better than doing all by yourself, check Seam documentation
http://docs.jboss.org/seam/2.2.0.GA/reference/en-US/html/controls.html#d0e28378

Resources