I've read a number of excellent posts and articles about dynamically binding fields in a custom control, but they have all assumed a document data source.
I want to allow the possibility of a managed bean data source. I tried setting the property type to com.ibm.xsp.model.DataSource
or com.ibm.xsp.extlib.model.ObjectDataSource
and neither of those work with the following xml:
<xp:inputText
id="input"
value="${compositeData.dsn[compositeData.fieldName]}"
>
</xp:inputText>
Where the control is used I have passed in custom data like so:
<xc:input
dsn="#{issue}"
fieldName="Database"
>
</xc:input>
For my test purpose, I have a managed bean named issue
and I called my field Database
. I would normally bind to #{issue.Database}
but I can't figure out how to do that dynamically. Ideally, I would like to support document data sources as well, but if I can't do both then I need to bind to the managed bean.
Edit: The problem seems to be the array-notation. If I hardcode my value to #{issue.Database}
it works but if I hardcode it to #{issue[Database]}
it fails. So the question is if there is an alternative representation of dot-notation. I don't have time today, but I wonder if instead of separating dsn and fieldName if I just passed #{issue} into dsn and used that as my data binding would that work? I will try that when I get a chance.
Edit2: As it appears the problem may be related to the bean I'm using, I'll post the code for that here.
AbstractMapModel
public abstract class AbstractMapModel implements Serializable, DataObject {
private static final long serialVersionUID = 1L;
private Map<Object, Object> values;
public Class<?> getType(final Object key) {
Class<?> result = null;
if (getValues().containsKey(key)) {
Object value = getValues().get(key);
if (value != null) {
result = value.getClass();
}
}
return result;
}
protected Map<Object, Object> getValues() {
if (values == null) {
values = new HashMap<Object, Object>();
}
return values;
}
public Object getValue(final Object key) {
return getValues().get(key);
}
public boolean isReadOnly(final Object key) {
return false;
}
public void setValue(final Object key, final Object value) {
getValues().put(key, value);
}
}
AbstractDocumentMapModel
public abstract class AbstractDocumentMapModel extends AbstractMapModel {
private static final long serialVersionUID = 1L;
private String unid;
public AbstractDocumentMapModel() {
String documentId = ExtLibUtil.readParameter(FacesContext
.getCurrentInstance(), "id");
if (StringUtil.isNotEmpty(documentId)) {
load(documentId);
}
}
protected abstract String getFormName();
public String getUnid() {
return unid;
}
public void setUnid(String unid) {
this.unid = unid;
}
public void load(final String unid) {
setUnid(unid);
Document doc = null;
try {
if (StringUtil.isNotEmpty(getUnid())) {
doc = ExtLibUtil.getCurrentDatabase().getDocumentByUNID(
getUnid());
DominoDocument wrappedDoc = DominoDocument.wrap(doc
.getParentDatabase().getFilePath(), // databaseName
doc, // Document
null, // computeWithForm
null, // concurrencyMode
false, // allowDeleteDocs
null, // saveLinksAs
null // webQuerySaveAgent
);
for (Object eachItem : doc.getItems()) {
if (eachItem instanceof Item) {
Item item = (Item) eachItem;
String itemName = item.getName();
if (!("$UpdatedBy".equalsIgnoreCase(itemName) || "$Revisions"
.equalsIgnoreCase(itemName))) {
setValue(item.getName(), wrappedDoc.getValue(item
.getName()));
}
DominoUtil.incinerate(eachItem);
}
}
}
} catch (Throwable t) {
t.printStackTrace();
} finally {
DominoUtil.incinerate(doc);
}
}
protected boolean postSave() {
return true;
}
protected boolean querySave() {
return true;
}
public boolean save() {
boolean result = false;
if (querySave()) {
Document doc = null;
try {
if (StringUtil.isEmpty(getUnid())) {
doc = ExtLibUtil.getCurrentDatabase().createDocument();
setUnid(doc.getUniversalID());
doc.replaceItemValue("Form", getFormName());
} else {
doc = ExtLibUtil.getCurrentDatabase().getDocumentByUNID(
getUnid());
}
for (Entry<Object, Object> entry : getValues().entrySet()) {
String itemName = entry.getKey().toString();
doc.replaceItemValue(itemName, DominoUtil
.toDominoFriendly(entry.getValue()));
}
if (doc.save()) {
result = postSave();
}
} catch (Throwable t) {
t.printStackTrace();
} finally {
DominoUtil.incinerate(doc);
}
}
return result;
}
}
IssueModel
public class IssueModel extends AbstractDocumentMapModel implements
Serializable {
private static final long serialVersionUID = 1L;
@Override
protected String getFormName() {
return "frmIssue";
}
@Override
protected boolean querySave() {
return super.querySave();
}
@Override
public boolean isReadOnly(final Object key) {
boolean result = super.isReadOnly(key);
/**
* Implement read only logic here as follows
*
* if ("jobTitle".equalsIgnoreCase((String) key)) { if
* (!ExtLibUtil.getXspContext().getUser().getRoles().contains("[HR]")) {
* result = true; } }
*/
return result;
}
}
ccFieldset
<?xml version="1.0" encoding="UTF-8"?>
<xp:view
xmlns:xp="http://www.ibm.com/xsp/core"
>
<div
class="form-group"
>
<xp:label
id="label"
for="input"
value="${compositeData.label.text}"
>
<xp:this.styleClass><![CDATA[${javascript:styleClass = "control-label col-" + compositeData.sz + "-" + compositeData.label.columns;
return styleClass;}]]></xp:this.styleClass>
</xp:label>
<xp:div>
<xp:this.styleClass><![CDATA[${javascript:styleClass = "col-" + compositeData.sz + "-" + compositeData.input.columns;
return styleClass;}]]></xp:this.styleClass>
<xp:inputText
id="input"
>
<xp:this.value><![CDATA[${javascript:"#{"+compositeData.BindTo+"}"}]]></xp:this.value>
<xp:this.styleClass><![CDATA[${javascript:styleClass = "input-" + compositeData.sz;
return styleClass;}]]></xp:this.styleClass>
</xp:inputText>
</xp:div>
</div>
</xp:view>
Working field in xpage
<div
class="form-group"
>
<xp:label
value="Database"
id="database_Label1"
for="database1"
styleClass="col-sm-2 control-label"
>
</xp:label>
<div
class="col-sm-6"
>
<xp:inputText
value="#{issue.Database}"
id="database1"
styleClass="input-sm"
>
</xp:inputText>
</div>
</div>
Not working ccFieldset in xpage
<xc:fieldset sz="md">
<xc:this.input>
<xc:input
columns="10"
bindTo="issue.Database"
>
</xc:input>
</xc:this.input>
<xc:this.label>
<xc:label
columns="2"
text="test"
>
</xc:label>
</xc:this.label>
</xc:fieldset>
The trick is to hand over a String as parameter that would be the EL you want to use. Let's say you have a parameter bindTo as String with the value myBean.Color
The $ will evaluate first and replace the compositeData with the actual value. The beauty of the approach: works for any EL. Document Bean etc., so your custom control doesn't need to make any assumptions on its container.
So you could call your component with all sorts of bindings:
Let us know how that works for you!
Make sure that one of the following is true about the object that you're passing to the Custom Control:
database
property, the object should have (at a minimum) agetDatabase
method that returns the current value of thedatabase
property; if the property is not read-only, the class should also have asetDatabase
method that accepts the new value.com.ibm.xsp.model.DataObject
interface. The genericgetValue
andsetValue
methods this interface requires you to implement will be used to, respectively, retrieve and update values for any property, includingdatabase
.com.ibm.jscript.types.FBSObject
. This is the underlying Java class that all SSJS object literals ({ }
) become when the SSJS string is parsed at runtime. Be wary of using this type in managed beans, because these objects are not serializable.As long as the object you're passing in is one of these four, the EL syntax listed in your question will be valid. Set the property type to be either the actual class name for the object you're passing or a base class it extends (or interface it implements). Or, to ensure it will accept anything, just set the property type to
object
.But remember, declaring a class as a managed bean in the
faces-config.xml
only "manages" a variable name and scope... this declaration doesn't actually make your Java class a bean. Unless it conforms to bean conventions, it's not a bean. If it does conform to bean conventions, it's a bean, whether or not you declare it as a managed bean. This distinction is a source of much confusion in the XPages community, so I just wanted to belabor that again in this context.