A component - e.g. a FIELD - has many attributes. Some of them are fix – some of them are variable.
By default each attribute of a component, which is relevant to be used, is defined in the layout definition:
If it is a fix attribute then the value is directly defined
If it is a dynamic attribute then a server side property is bound using an expression
Example:
<t:field width=”100”
font=”weight:bold”
text=”#{d.XyzUI.firstName}”
enabled=”#{d.XyzUI.firstNameEnabled}”/>
If working with typical forms then there are quite a lot of attributes per component which you want to control dynamically.
Example: if the field contains some error value, it should be painted accordingly using the BGPAINT attribute – and the error text should we visible in the TOOLTIP attribute.
Consequence: more and more attributes need to be bounds to more and more server-side property definitions.
<t:field width=”100”
font=”weight:bold”
text=”#{d.XyzUI.firstName}”
enabled=”#{d.XyzUI.firstNameEnabled}”
bgpaint=”#{d.XyzUI.firstNameBgpaint}”
tooltip=”#{d.XyzuI.firstNameTooltip}”/>
Because a typical form contains more than one field and because you tpyically define a couple of forms, this becomes a bit of a nightmare to maintain...!
There are several ways out:
Definition of Macros
Definition of own PageBean-components wrapping the FIELD
...and: working with “Adapter Binding”
Working with “Adapter Binding” is the most lightweight approach and is the one requiring the lowest level of framework-thinking that you have to apply on top of the components. Please take a look into the annotation-based implementations of adapter bindings after reading about the basic principles – which again simplify the usage of adapter bindings.
“Adapter Binding” - of course... - is mentioned in the Developer's Guide! But: it is rarely used though being straight forward and powerful. This is the reason for adding this additional documentation.
Instead of assigning values attribute by attribute by attribute you only define one attribute ADAPTERBINDING – defining an expression that refers to an instance of interface IComponentAdapterBinding:
Layout:
<t:fields adapterbinding=”#{d.XyzUI.firstNameAdapter}/>
Code:
public IComponentAdapterBinding getFirstNameAdapter()
{
...
}
The interface looks as follows:
public interface IComponentAdapterBinding
extends Serializable
{
public Set<String> getFixAttributeNames();
public Set<String> getDynamicAttributeNames();
public void setAttributeValue(String attributeName, Object value);
public Class getAttibuteType(String attributeName);
public Object getAttributeValue(String attributeName);
public void onAction(ActionEvent event);
}
The interface includes:
getFixAttributeNames()/getDynamicAttributeNames – which returns these attribute names which do not change their value during the life-cycle of the component – and these attribute names for which the value changes during the life-cycle
setAttributeValue(...), getAttributeType(...) and getAttributValue(...) to receive and to set the value of the attribute
onAction(...) to process action events that are executed on this component.
Why is there an explicit separation between fix attribute names and dynamic attribute names? - It is because of performance: fix attributes are only picked once, then they are not picked with every roundtrip, because their value does not change.
The method “getAttributeType(...)” is only used for these attributes which are dynamic ones and which are the ones that are set by the component. It may return null for all other attributes. It is used to convert a value coming from the user interface into the correct format before passing it into the “setAttribugeValue(...)” method.
In the demo workplace there is an example that shows the power of adapter binding objects:
The implementation of the IComponentAdapterBinding is implemented as inner class within this demo – but of course could/should be implemented as stand alone class:
public static class MyFieldAdapter
implements IComponentAdapterBinding, ICCComponentProperties
{
static Set<String> MYFIELDADAPTER_FIXATTRIBUTES;
static Set<String> MYFIELDADAPTER_DYNATTRIBUTES;
static
{
MYFIELDADAPTER_FIXATTRIBUTES = new HashSet<String>();
MYFIELDADAPTER_FIXATTRIBUTES.add(ATT_labeltext);
MYFIELDADAPTER_FIXATTRIBUTES.add(ATT_align);
MYFIELDADAPTER_DYNATTRIBUTES = new HashSet<String>();
MYFIELDADAPTER_DYNATTRIBUTES.add(ATT_text);
MYFIELDADAPTER_DYNATTRIBUTES.add(ATT_bgpaint);
MYFIELDADAPTER_DYNATTRIBUTES.add(ATT_tooltip);
}
String i_labelText = null;
String i_align = null;
boolean i_containsError = false;
String i_errorMessage = null;
String i_text = "";
// methods if IComponentAdapterBinding
public Set<String> getFixAttributeNames() { return MYFIELDADAPTER_FIXATTRIBUTES; }
public Set<String> getDynamicAttributeNames() { return MYFIELDADAPTER_DYNATTRIBUTES; }
public MyFieldAdapter(String labelText, String align)
{
i_labelText = labelText;
i_align = align;
}
public Class getAttibuteType(String attributeName)
{
if ("text".equals(attributeName))
return String.class;
else
throw new Error("The attribute " + attributeName + " is not supported!");
}
public Object getAttributeValue(String attributeName)
{
if (ATT_text.equals(attributeName))
return i_text;
else if (ATT_bgpaint.equals(attributeName))
{
if (i_containsError == true)
return "error()";
else
return null;
}
else if (ATT_align.equals(attributeName))
return i_align;
else if (ATT_labeltext.equals(attributeName))
return i_labelText;
else if (ATT_tooltip.equals(attributeName))
{
if (i_containsError == true)
return i_errorMessage;
else
return null;
}
else
throw new Error("The attribute " + attributeName + " is not supported!");
}
public void setAttributeValue(String attributeName, Object value)
{
if (ATT_text.equals(attributeName))
i_text = (String)value;
else
throw new Error("The attribute " + attributeName + " is not supported!");
}
public void onAction(ActionEvent event) {}
// normal access methods
public void resetError() { i_containsError = false; }
public void notifyError(String message)
{
i_containsError = true;
i_errorMessage = message;
}
public String getText() { return i_text; }
public void setText(String text) { i_text = text; }
}
Some comments:
Interface ICCComponentProperties contains the String constants for all attribute names of CaptainCasa components. This is where “ATT_xxx” are coming from. - Of course you could directly write the name of the attribute as well (e.g. “text” instead of ATT_text) – but it just looks a bit better using the constants... ;-)
The TEXT-attribute of the component is directly managed in the corresponding “i_text” member. Other attributes (such as BGPAINT) are not directly related to a member variable but are derived from other variables (e.g. there is a boolean member indicating that there is an error in the value).
The layout of the example looks as follows:
<t:rowdemobodypane id="g_1"
objectbinding="#{d.DemoAdapterBindingSimple}" rowdistance="5">
<t:row id="g_2">
<t:field id="g_3"
adapterbinding="#{d.DemoAdapterBindingSimple.firstName}"
width="200" />
</t:row>
<t:row id="g_4">
<t:field id="g_5"
adapterbinding="#{d.DemoAdapterBindingSimple.lastName}"
width="200" />
</t:row>
<t:row id="g_6">
<t:button id="g_7"
actionListener="#{d.DemoAdapterBindingSimple.onCheck}"
text="Check" width="100+" />
</t:row>
</t:rowdemobodypane>
You see: in addition to the attribute ADAPTERBINDING you still can use other attributes (in this case: WIDTH), which are not included in the adapter binding implementation.
The implementation of the Page Bean is:
package workplace;
...
...
@CCGenClass (expressionBase="#{d.DemoAdapterBindingSimple}")
public class DemoAdapterBindingSimple
extends PageBean
implements Serializable
{
...
...
// ------------------------------------------------------------------------
// members
// ------------------------------------------------------------------------
MyFieldAdapter m_firstName = new MyFieldAdapter("First name","left");
MyFieldAdapter m_lastName = new MyFieldAdapter("Last name", "left");
// ------------------------------------------------------------------------
// constructors
// ------------------------------------------------------------------------
public DemoAdapterBindingSimple()
{
m_firstName.setText("Cap");
m_lastName.setText("Casa");
}
// ------------------------------------------------------------------------
// public usage
// ------------------------------------------------------------------------
public MyFieldAdapter getFirstName() { return m_firstName; }
public MyFieldAdapter getLastName() { return m_lastName; }
public void onCheck(ActionEvent event)
{
m_firstName.i_containsError = false;
m_lastName.i_containsError = false;
// some "checks"
if (m_firstName.getText().length() < 4)
m_firstName.notifyError("Please define a value with more than 4 characters");
if (m_lastName.getText().length() < 4)
m_lastName.notifyError("Please define a value with more than 4 characters");
}
}
The big advantage of using adapter binding is that you may extend the adapter binding by implementing new attributes of components without having to update each occurrence of the component in all of your layout definitions.
In the example above the following attributes are dynamically managed:
TEXT, BGPAING, ENABLED, TOOLTIP
Now assume, that you also want to mange the attribute ENABLED – in order to control on logic side, if a corresponding FIELD is enabled or disabled.
All you have to do is: you have to take over the new attribute into the adapter implementation...
public static class MyFieldAdapter
implements IComponentAdapterBinding, ICCComponentProperties
{
...
static
{
...
MYFIELDADAPTER_DYNATTRIBUTES.add(ATT_enabled);
...
}
...
boolean i_enabled = true;
...
public Object getAttributeValue(String attributeName)
{
...
else if (ATT_enabled.equals(attributeName))
return i_enabled;
...
}
...
public void enable() { i_enabled = true; }
public void disable() { i_enabled = false; }
}
...that's it! All fields that use this adapter binding now automatically can be enabled/disabled from your page bean logic.
There is a default class “ComponentAdapterBindingBase” which contains some convenience functions.
The names of the fix and dynamic attributes are passed by corresponding arrays into the constructor:
public ComponentAdapterBindingBase(String[] fixAttributeNames,
String[] dynamicAttributeNames)
{
...
}
public ComponentAdapterBindingBase(String[] fixAttributeNames,
String[] dynamicAttributeNames,
Class[] dynamicAttributeTypes)
{
...
}
And there is a listener management for the onAction-methods, so that users of an adapter binding instance can register for events:
public void addActionListener(IActionListenerListener listener)
{
...
}
public void removeActionListener(IActionListenerListener listener)
{
...
}
The methods of IComponentAdapterBinding are quite generic...
public Set<String> getFixAttributeNames();
public Set<String> getDynamicAttributeNames();
public void setAttributeValue(String attributeName, Object value);
public Class getAttibuteType(String attributeName);
public Object getAttributeValue(String attributeName);
...and were explicitly implemented in the example code of the previous chapters.
CaptainCasa now provides for some default implementations which automatically derives this generic information by using annotations.
The following class is a component adapter implementation using annotations:
public class FieldAdapter extends ComponentAdapterByAnnotation
{
String m_label;
String m_value;
boolean i_containsError;
String i_errorText;
public FieldAdapter(String label)
{
m_label = label;
}
@ComponentAttribute(type=ComponentAttributeType.FIX,attribute="labeltext")
public String getLabel() { return m_label; }
@ComponentAttribute(attribute="text")
public String getValue() { return m_value; }
public void setValue(String value) { m_value = value; }
@ComponentAttribute
public String getBgpaint()
{
if (i_containsError)
return "error()";
else
return null;
}
@ComponentAttribute
public String getTooltip()
{
if (i_containsError == true)
return i_errorText;
else
return null;
}
public void indicateError(String text)
{
i_containsError = true;
i_errorText = text;
}
public void resetError()
{
i_containsError = false;
i_errorText = null;
}
}
The annotations describe how component attributes are bound to properties of the FieldAdapter-class. Example: the component attribute FIELD-”labeltext” is bound to the property “label” (with the get-ter “getLabel()”).
With the annotation you can explicitly define the component attribute's name and the information if its content is fix or dynamic.
Please both read more information the Developer's Guide (“Adapter Binding”) and view the demos in the Demo Workplace.
The core class of the annotation-based adapter binding is the class “ComponentAdapterByAnnotation”.
Please also check the class “ComponentAdapterByAnnotationForBeanProperty” - which in additions simplified the binding of a component attribute (e.g. the FIELD-text) to a property of a bean instance.
There is no magic around the concept of adapter binding objects: the object takes over the setting/getting of attribute values for one component, so that it is the programmed counter part of the optical component – that's it!
Adapter binding objects are the ones which can be very powerful objects in which core parts of a component's default logic is part of.
In the following we will list some examples just to give you some idea.
The following show a simple implementation of a component adapter which manages the opening of a valid-values-dialog for a COMBOFIELD component – based on reading a property file which is configured in the constructor of the adapter:
public class MyComboFieldAdapter
extends ComponentAdapterBindingBase
implements ICCComponentProperties
{
static final String[] FIXATTRIBUTES = new String[] {};
static final String[] DYNATTRIBUTES = new String[] {ATT_text,ATT_enabled};
static final Class[] DYNCLASSES = new Class[] {String.class,null};
String i_resourceName;
String i_text;
boolean i_enabled = true;
public MyComboFieldAdapter(String resourceName)
{
super(FIXATTRIBUTES,DYNATTRIBUTES,DYNCLASSES);
i_resourceName = resourceName;
addActionListener(new IActionListenerListener()
{
@Override
public void reactOnActionListenerInvoked(IComponentAdapterBinding ab, ActionEvent event)
{
if (event instanceof BaseActionEventValueHelp)
openValueHelp();
}
});
}
@Override
public Object getAttributeValue(String attribute)
{
if (ATT_text.equals(attribute))
return i_text;
if (ATT_enabled.equals(attribute))
return i_enabled;
throw new Error("Unimplemented attribute: " + attribute);
}
@Override
public void setAttributeValue(String attribute, Object value)
{
if (ATT_text.equals(attribute))
i_text = (String)value;
throw new Error("Unimplemented attribute: " + attribute);
}
protected void openValueHelp()
{
try
{
// open resource (property file) and pass back results
InputStream is = new ClassloaderReader(true).readFileIntoInputStream(i_resourceName,true);
Properties ps = new Properties();
ps.load(is);
IdTextSelection idts = IdTextSelection.createInstance();
Enumeration<Object> keyEnum = ps.keys();
while (keyEnum.hasMoreElements())
{
String key = (String)keyEnum.nextElement();
String value = ps.getProperty(key);
idts.addLine(key,value);
}
idts.sortItemsById();
idts.setCallBack(new ISetId()
{
@Override
public void setId(String value)
{
i_text = value;
}
});
}
catch (Throwable t)
{
Statusbar.outputAlert("Problem when reading resource: " + i_resourceName);
}
}
public String getText() { return i_text; }
public void setText(String text) { i_text = text; }
public boolean isEnabled() { return i_enabled; }
public void setEnabled(boolean enabled) { i_enabled = enabled; }
}
The implementation of a concrete scenario...
...is quite simple as result. The layout is:
<t:rowdemobodypane id="g_2"
objectbinding="#{d.DemoAdapterBindingValidValues}">
<t:row id="g_3">
<t:combofield id="g_4"
adapterbinding="#{d.DemoAdapterBindingValidValues.countryAdapter}"
labeltext="Country" width="200" />
</t:row>
<t:row id="g_5">
<t:combofield id="g_6"
adapterbinding="#{d.DemoAdapterBindingValidValues.departmentAdapter}"
labeltext="Department" width="200" />
</t:row>
</t:rowdemobodypane>
The code is:
package workplace;
...
@CCGenClass (expressionBase="#{d.DemoAdapterBindingValidValues}")
public class DemoAdapterBindingValidValues
extends PageBean
implements Serializable
{
// ------------------------------------------------------------------------
// members
// ------------------------------------------------------------------------
MyComboFieldAdapter m_countryAdapter = new MyComboFieldAdapter("/workplace/resources/demo_countries.properties");
MyComboFieldAdapter m_departmentAdapter = new MyComboFieldAdapter("/workplace/resources/demo_departments.properties");
// ------------------------------------------------------------------------
// constructors & initialization
// ------------------------------------------------------------------------
public DemoAdapterBindingValidValues(IWorkpageDispatcher workpageDispatcher)
{
super(workpageDispatcher);
}
public String getPageName() { return "/workplace/demoadapterbindingvalidvalues.jsp"; }
public String getRootExpressionUsedInPage() { return "#{d.DemoAdapterBindingValidValues}"; }
// ------------------------------------------------------------------------
// public usage
// ------------------------------------------------------------------------
public MyComboFieldAdapter getCountryAdapter() { return m_countryAdapter; }
public MyComboFieldAdapter getDepartmentAdapter() { return m_departmentAdapter; }
}
In the same way that property files are referenced by configuration in this example you can e.g. implement a database connection to read the valid values from.
In the examples so far the actual value of the component was always kept in some own member variable of the component adapter class:
public class MyComboFieldAdapter
extends ComponentAdapterBindingBase
implements ICCComponentProperties
{
...
String i_text;
...
@Override
public Object getAttributeValue(String attribute)
{
if (ATT_text.equals(attribute))
return i_text;
...
}
...
Of course this is just one possibility. An other possibility is to reference a Pojo object and access the value via reflection:
public class MyComboFieldAdapter
extends ComponentAdapterBindingBase
implements ICCComponentProperties
{
...
Object i_bean; // the pojo bean
String i_beanPropertyName; // the property of the bean
...
public MyComboFieldAdapter(Object bean,
String beanPropertyName,
String resourceName)
{
super(FIXATTRIBUTES,DYNATTRIBUTES,DYNCLASSES);
i_resourceName = resourceName;
i_bean = bean;
i_beanPropertyName = beanPropertyName;
...
}
...
@Override
public Object getAttributeValue(String attribute)
{
if (ATT_text.equals(attribute))
return (String)findBeanPropertyValue(bean,beanPropertyName);
...
private Object findBeanPropertyValue(Object bean, String beanPropertyName)
{
...
// dynamicallyp pick value via reflection
...
}
}
...
Referencing the value in this way is often useful when pojo-beans are read e.g. from persistence level and should be managed within the user interface in a way that changes to the data (e.g. by user input) are directly set into the pojo's property.
There are two alternatives which you can use in order to efficiently embed CaptainCasa components into your framework – with avoiding the “hell of expression-assignments” within your layout definitions.
Usage of “Macros”: Macros multiply out expressions within a component. Result: by defining one attribute's expression all the other's are automatically generated and do not have to be set manually.
Usage of “Page Beans” to shrink wrap components. Instead of directly e.g. working with a FIELD you build some FieldPageBean which manages all these aspects of the FIELD that you want to manage. The developer does not work with CaptainCasa native components anymore but works with page beans only.
When comparing “adapter binding” with “Macros” then both technologies achieve the same result: working with components in a framework scenario is simplified drastically. From functional point of view both do the same in a different way – there is no functional advantage for the one or the other.
When comparing to “Page Beans” (including page bean components) then page beans offer a greater flexibility because they are able to abstract from the concrete CaptainCasa control. Example:
With using adapter binding then the developer chooses e.g. a COMBOFIELD to be placed in the layout, and as consequence selects a corresponding adapter in the code.
With using page beans, the page bean itself could decide (by internally using DYNAMICCONTENT) if to render a RADIOBUTTON-group if there are only few values, or if to render a COMBOBOX for some values or if to render a COMBOFIELD for a lot of values.
Greater flexibility of page beans comes with more effort of course – because you first have to define your page bean layer on top of CaptainCasa components, and because you do not allow application developers to directly use the CaptainCasa controls but always use the wrapping page beans.