Dynamically bind an Edit Box in a custom control t

2020-06-20 07:40发布

问题:

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>

回答1:

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

  <xp:this.value><![CDATA[${javascript:"#{"+compositeData.BindTo+"}"}]]></xp:this.value>

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:

 <xc:myComponent BindTo="document1.subject"></xc:myComponent>
 <xc:myComponent BindTo="viewScope.someVariable"></xc:myComponent>
 <xc:myComponent BindTo="myBean.Color"></xc:myComponent>

Let us know how that works for you!



回答2:

Make sure that one of the following is true about the object that you're passing to the Custom Control:

  1. It really is a bean (specifically, follows bean conventions). For example, if you'll be binding to the database property, the object should have (at a minimum) a getDatabase method that returns the current value of the database property; if the property is not read-only, the class should also have a setDatabase method that accepts the new value.
  2. It's a DataObject: your object implements the com.ibm.xsp.model.DataObject interface. The generic getValue and setValue methods this interface requires you to implement will be used to, respectively, retrieve and update values for any property, including database.
  3. It's a Map. Any property name you bind to will be treated as a key for the map.
  4. It's an instance of 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.