Introduction and Motivation

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:

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:

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.

Basic principles

Overview

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()

{

    ...

}

 

Interface IComponentAdapterBinding

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:

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.

Simple (but powerful...) Example

In the demo workplace there is an example that shows the power of adapter binding objects:

Implementation if IComponentAdapterBinding

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:

Usage in Layout / Page Bean

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");

    }

 

}

 

Advantages of using Adapter Binding

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.

Example

In the example above the following attributes are dynamically managed:

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.

Default classes

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)

{

    ...

}

 

Annotation-based implementations

Idea

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.

Example

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.

Annotation-based base classes

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.

Do not hesitate to develop your framework...!

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.

Valid values of a COMBOFIELD

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.

Where to keep the “value”

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.

Alternative technologies

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.

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:

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.