ConfigurableComponent HOWTO.

The purpose of this document is to provide a brief description of the idea behind the configurable component and to show example use cases.

Concept

The idea behind the ConfigurableComponent originates from the common use case for components in the layout framework. There are many component compounds which basically represent one component consisting of two parts:

  1. Configurator: a part defining what, when and how to display
  2. Configurable: a part taking care of the actual displaying
The way, this behavior has been implemented in the past, was rather complex and tedious. Developers has to create two separate LayoutComponents and glue them together either using some xml attribute configuration or the layout framework's event system. The first approach statically connects the two components thus exposing implementation details to each other. The second approach completely disconnects the components and totally relies on proper event and model handling, yet again gluing them on the implementation level In addition to the issues mentioned above, using two full-scale components over and over again might have quite an impact on the overall system performance.

In order to make life easier for developers having to implement this pattern, the ConfigurationComponent has been introduced. The idea is to split the common implementation (like model and configuration changes etc.) from the actual business specifics.

How it works

As mentioned above, a configurable component consists of two parts: configuration provider (IConfigurator) and configurable part (IConfigurable). The sole purpose of the configuration provider is to provide configuration values for configuration properties and to notify the configurable part upon configuration changes. The configurable part is responsible for listening to the relevant changes and to update its contents according to the new configuration.

The only thing clients have to do is to provide the ConfigurableComponent with an IConfigurable and IConfigurator implementation containing the business logic specifics.

Example

Tired of abstract formulation already? Let's take a look at how it's done in a real application. First, we need to configure the component (just like any other) in an XML file. Since the ConfigurableComponent supports content editing, clients can also provide the appropriate command handler etc. to handle the changes.

XML Configuration

<!-- Basic properties just like for any other edit component -->
<component class="com.top_logic.project.pos.layout.configurable.ConfigurableComponent"
	name="MyConfigurableComponent"
	page="/jsp/myConfigurable.jsp"
	resPrefix="my.configurable.resource.prefix."
	buttonComponent="MyButtonComponent"
	isSecurityMaster="true"
	<!-- These are the names of the command handlers (which has to be registered in the CommandHandlerFactory on system startup) we're going to use for our editing capabilities.
	Clients might decide to leave it blank in order to suppress editing capabilities. -->
	applyHandlerName="myApplyChangesHandler"
	deleteHandlerName="myDeleteContentHandler">

	<!-- we're going to use this one to check what model object's we actually support -->	
	<inputFilter class="com.example.MyElementSelector" />
	
	<!-- This is our configuration provider implementation -->
	<configurator class="com.example.MyConfigurator"
		name="MyConfigurator"
		resPrefix="my.configurator.resource.prefix." />

	<!-- And this is the configurable implementation -->
	<configurable class="com.example.MyConfigurable"
		name="MyConfigurable"
		resPrefix="my.configurable.resource.prefix." />
</component>

<!-- Since we need the editing capability, we also provide the button component just like for any other edit component here. -->
<component class="com.example.myButtonComponent"
	target="MyConfigurableComponent"
	name="MyButtonComponent" />

Implementation

The implementation itself is pretty straight forward.

Configurator:

/*
 * Copyright (c) 2009 Business Operation Systems GmbH. All Rights Reserved
 */
package com.example;

import java.util.Arrays;
import java.util.Map;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;

import com.top_logic.layout.basic.Command;
import com.top_logic.layout.form.FormField;
import com.top_logic.layout.form.ValueListener;
import com.top_logic.layout.form.model.FormContext;
import com.top_logic.layout.form.model.SelectField;

/**
 * A class demonstrating a possible client implementation for a
 * {@link ConfigurableComponent}.
 * 
 * @history 17.12.2009 wta created
 * @author wta
 */
public class MyConfigurator extends AbstractConfigurator {

    /**
     * The name of our example property (free to rhyme here).
     */
    private static final String PROPERTY_EXAMPLE = "MyProperty";

    /**
     * We're going to attach this one to the members in order to resolve which
     * field changed their values and notify all others about configuration
     * changes.
     */
    private final ValueListener listener = new ValueListener() {

        /*
         * @see
         * com.top_logic.layout.form.ValueListener#valueChanged(com.top_logic
         * .layout.form.FormField, java.lang.Object, java.lang.Object)
         */
        public void valueChanged(final FormField field, final Object oldValue, final Object newValue) {
            // this will cause the basic implementation (AbstractConfigurator)
            // to fire the appropriate event thus notifying the appropriate IConfigurable
            // of property changes.
            MyConfigurator.this.setProperty(field.getName(), newValue);
        }
    };

    /**
     * Just create a configurator based on the abstract implementation.
     * 
     */
    public MyConfigurator(final Attributes attributes) throws SAXException {
        super(attributes);
    }

    /*
     * @see com.top_logic.layout.form.FormHandler#getApplyClosure()
     */
    public Command getApplyClosure() {
        // nothing to do here
        return null;
    }

    /*
     * @see com.top_logic.layout.form.FormHandler#getDiscardClosure()
     */
    public Command getDiscardClosure() {
        // nothing to do here
        return null;
    }

    /*
     * @see
     * com.top_logic.project.pos.layout.configurable.AbstractComponentPart#createFormContext
     * ()
     */
    @Override
    protected FormContext createFormContext() {
        // create a new form context here and fill it with members.
        final FormContext context = new FormContext("MyConfigurator", getResourcePrefix());

        final SelectField selector = new SelectField(PROPERTY_EXAMPLE, Arrays.asList("one", "two", "three"));
        context.addMember(selector);

        return context;
    }

    /*
     * @see
     * com.top_logic.project.pos.layout.configurable.AbstractConfigurator#initialize(java
     * .util.Map)
     */
    @Override
    protected void initialize(final Map properties) {
        // initial property values go here.
        properties.put(PROPERTY_EXAMPLE, "one");
    }
}

Configurable:

/*
 * Copyright (c) 2009 Business Operation Systems GmbH. All Rights Reserved
 */
package com.example;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;

import com.top_logic.layout.basic.Command;
import com.top_logic.layout.form.FormField;
import com.top_logic.layout.form.model.FormContext;
import com.top_logic.layout.form.model.StringField;

/**
 * A class demonstrating a possible client implementation for a
 * {@link ConfigurableComponent}.
 * 
 * @history 17.12.2009 wta created
 * @author wta
 */
public class MyConfigurable extends AbstractConfigurable {

    /**
     * The name of the viewer to display the example property (free to rhyme
     * here)
     */
    private static final String EXAMPLE_VIEWER = "MyViewer";

    /**
     * Just create a configurable based on the abstract implementation.
     */
    public MyConfigurable(Attributes attributes) throws SAXException {
        super(attributes);
    }

    /*
     * @see com.top_logic.project.pos.layout.configurable.IConfigurationListener#configurationChanged(com.top_logic.project.pos.layout.configurable.ConfigurationEvent)
     */
    public void configurationChanged(final ConfigurationEvent event) {
        // okay, now THAT is the one we've been waiting for!
        // here, we can check what property changed and react to changes.
        for(final String propertyName : event.getPropertyNames()) {
            // we react to changes to the example property only
            if("MyProperty".equals(propertyName)) {
                final Object value = event.getSource().getProperty(propertyName);
                getFormContext().getField(EXAMPLE_VIEWER).setValue(value.toString());
            }
        }
    }

    /*
     * @see com.top_logic.layout.form.FormHandler#getApplyClosure()
     */
    public Command getApplyClosure() {
        // nothing to do here
        return null;
    }

    /*
     * @see com.top_logic.layout.form.FormHandler#getDiscardClosure()
     */
    public Command getDiscardClosure() {
        // nothing to do here as well.
        return null;
    }

    /*
     * @see
     * com.top_logic.project.pos.layout.configurable.AbstractComponentPart#createFormContext
     * ()
     */
    @Override
    protected FormContext createFormContext() {
        // create a new form context here and fill it with members.
        final FormContext context = new FormContext("MyConfigurable", getResourcePrefix());

        final StringField field = new StringField(EXAMPLE_VIEWER);
        context.addMember(field);

        return context;
    }

    /*
     * @see
     * com.top_logic.project.pos.layout.configurable.AbstractComponentPart#inputChanged(
     * java.lang.Object, java.lang.Object)
     */
    @Override
    protected void inputChanged(final Object oldInput, final Object newInput) {
        // this one is called when the ConfigurableComponent's input changes.
        // We might want to clear our viewer?
        if(hasFormContext()) {
            final FormContext context = getFormContext();
            final FormField field = context.getField(EXAMPLE_VIEWER);
            field.setDefaultValue("INITIALIZE ME!");
            field.reset();
        }
    }
}

Element selector

/*
 * Copyright (c) 2009 Business Operation Systems GmbH. All Rights Reserved
 */
package com.example;

import com.top_logic.basic.col.Filter;

/**
 * A class demonstrating a possible client implementation for a
 * {@link ConfigurableComponent}.
 * 
 * @history 17.12.2009 wta created
 * @author wta
 */
public class MyElementSelector implements Filter {

    /*
     * @see com.top_logic.basic.col.Filter#accept(java.lang.Object)
     */
    public boolean accept(final Object anObject) {
        // life's like a box of chocolates... you never know what's inside. :)
        return true;
    }
}