Creating Attribute Editors and Validators

You can write components that provide a user interface for editing your control's property attributes, and for validating new attribute values. This topic provides an introduction to building components that expose property attribute values for editing and that validate the new values. For a sample control that implements these features, see the CustomerData control. You'll find this sample in the propEditor package of the ControlFeatures project in the ControlDevKit sample application.

This topic assumes some familiarity with properties and their definitions in an annotation XML file. For an introduction to control properties, see Control Properties Overview.

Validating Properties

You can provide validation at both the property annotation level and for individual attributes for which you have defined a custom type. Providing a validator can be handy when a property requires a specific combination of attribute values. It's also useful when an attribute's value is complex, perhaps even having its own syntax (as with an SQL expression for a Database control method).

WebLogic Workshop provides validation interfaces that you can implement for validating properties and their attributes.

Implementing a Validation Interface

The two interfaces are very similar. They both define two methods whose names are the same, but whose parameters differ for obvious reasons; a ValidateTag implementation is designed to receive a complete tag, with potentially many attributes; a ValidateAttribute implementation receives a single attribute. In general, your implementation of either method should validate the incoming values, then return null if they're valid. If they're not valid, your implementation should return an array of objects that implement the Issue interface. Your implementation of Issue provides messages that the IDE can present to the user.

The two methods defined in these interfaces reflect the fact that the IDE is designed to validate at two stages: compile-time and edit-time.

While it's undoubtedly more convenient to implement both compile-time and edit-time validation in the same class, it's not necessary. A class for compile-time validation and one for edit-time validation are invoked at in different ways by the IDE, as described below.

Connecting a Validator to the IDE

How you make your validator class available to the IDE differs depending on the circumstances under which validation is being done.

Building a Custom Editor

You can build a user interface that is designed specifically to edit the value of a particular property attribute. This can be useful when an attribute's value is potentially long (requiring more space than the Property Editor can provide), or requires some "builder" style editor to compose it. In fact, there are many reasons why you might want to create your own editor.

The Java control API provides two interfaces that define the methods required by a custom editor class. Your custom editor implements one of these to provide a means for the IDE to interact with your user interface, get the value set the by user, and so on.

Choosing an interface to implement is a matter of simplicity versus customization. AttributeEditorSimple, the simpler alternative, is the one to use when your user interface will consist of a single panel. If your user interface will be made up of multiple panels or dialogs (as with a wizard), you should implement AttributeEditorWizard.

Implementing AttributeEditorSimple

The AttributeEditorSimple interface is the basic approach, and will probably suit most needs. When you implement this interface, your user interface will be fit into a dialog owned by the IDE. Your user interface need not provide an OK or Cancel button because these are in the dialog into which your user interface is placed.

Note: The dialog created will be made as big as needed, up to the size of the IDE.

Here are the methods it exposes, roughly in the order they'll be called by the IDE.

Implementing AttributeEditorWizard

Implement the AttributeEditorWizard interface when you want to manage the dialog (or dialogs) containing your attribute editor user interface. This interface defines one method, getDialog, which is called by the IDE to get the Dialog class that defines your user interface. (Note that the getEditorComponent method is not called.) You should use the Frame object received as a parameter of getDialog when constructing your Dialog. This helps to ensure that your dialog is displayed properly with respect to the IDE. When the user clicks OK in your dialog, the IDE calls your implementation of the AttributeEditorSimple.onFinish() method.

Connecting an Editor to the IDE

Once you have written the user interface for your attribute editor, you connect it to the IDE using your implementation of an editor support class. As described in Controlling Appearance and Handling Actions in Design View, an editor support class handles user actions such as double-clicking a method arrow in Design View or clicking the ellipses next to an attribute value in the Property Editor.

An editor support class provides specifics for several "behaviors" defined by the IDE, one of which corresponds to a user's request to edit an attribute value. The constant representing this behavior is EditorSupport.BEHAVIOR_ATTRIBUTE_EDITOR.

The following example describes how you might use an editor support class to connect your custom editor to the IDE.

Putting It All Together

The code in the following example is based on the CustomerData sample control available with the Control Developers Kit. For the complete working version, see the ControlDevKit sample application; there, look for the propEditor folder of the ControlFeatures project.

Validation Class Example

The basic set of tasks for a validation class is simple: receive the values to validate, validate them, and return messages to the user if validation fails (or null if it succeeds). The following code shows a simple attribute validation class.

// Implement the ValidateAttribute interface.
public class CustIdValidator implements ValidateAttribute
{ 
    // Define legal values for the attribute.
    int legalValues[] = { 987654, 987655, 987658, 987659 };    

    /* 
     * Implement the Issue interface as a way to send messages back
     * to the IDE when the value is invalid. The IDE will extract 
     * information from this implementation and present it to the user.
     */
    static class CustIdIssue implements Issue
    {
        String _message;
        private CustIdIssue(String message) { _message = message; }
        public boolean isError() { return true; }
        public String getDescription() { return _message; }
        public String getPrescription() 
        { 
            return "Provide one of the following values: " +
             "987654, 987655, 987658, 987659. that's all"; 
        } 
    }
    
    // Called by the attribute editor.
    public Issue[] validateId(String value)
    {
         return validateDuringCompile(null, value, null);   
    }

    /*
     * Receive the attribute type name as defined in the annotation XML file. Also
     * receive the value that the user is trying to set for this attribute.
     * The context variable, unused in this example, would be used to store 
     * context information that could accumulate and be inspected at validation time.
     */
    public Issue[] validateDuringCompile(String attributeType, String value, Map context)
    {
        // Initialize the Issue array for return to the IDE.
        Issue[] issues = new Issue[1];

        int val;
        try 
        {
            val = Integer.parseInt(value);
        }
        catch(Exception e)
        {
            issues[0] = new CustIdIssue(e.getMessage());
            return issues;
        }

        // Find out if the value received is one of the allowed values.
        for(int i = 0; i < legalValues.length; i++)
        {
            // If the value is okay, return null: no issues here.
            if(legalValues[i] == val)
            {
                return null;
            }
        }            

        /* Otherwise, the value must be invalid. Put in instance of the 
         * Issue interface implementation into the array and send it to
         * the IDE.
         */
        issues[0] = new CustIdIssue("Unsupported value error");
        return issues;
    }

    public Issue[] validateDuringEdit(String attributeType, String value)
    {
        return null;
    }
}

Custom Editor Example

The purpose of a custom editor is to provide a user interface for editing an attribute's value, and to provide a way for the IDE to get the UI and the edited value. This example defines both the user interface and the IDE hooks in the same class, but those could easily have been in separate classes.

package propEditor.ide; 

import java.util.StringTokenizer;
import java.awt.BorderLayout;
import java.awt.Component;
import javax.swing.JTextField;
import javax.swing.JLabel;
import javax.swing.JFormattedTextField;
import javax.swing.JPanel;
import com.bea.ide.control.EditorSupport;
import javax.swing.JTextPane;
import com.bea.ide.control.AttributeEditorSimple;
import com.bea.control.Issue;
import com.bea.control.DefaultIssue;

/*
 * Represents an attribute editing/validation dialog for the
 * customer-id attribute. 
 */
public class CustomerIdEditorSimple extends JPanel implements AttributeEditorSimple
{ 
    private JTextField m_field;
    
    /*
     * Constructs a dialog using the existing value for 
     * the customer-id attribute. Note that this constructor
     * is called by the getEditorComponent method in this class.
     */
    public CustomerIdEditorSimple(String origValue)
    {
        super(new BorderLayout());
        m_field = new JTextField(origValue);
        String messageText = "This sample supports the following values: \n" + 
                             "987654    987655 \n" +
                             "987658    987659 \n"
                             +"that's all \n";
        JTextPane valueMessage = new JTextPane();
        valueMessage.setText(messageText);
        valueMessage.setBackground(null);
        valueMessage.setEditable(false);
        this.add(m_field, BorderLayout.SOUTH);
        this.add(valueMessage, BorderLayout.NORTH);
     
    }

    // No special formatting is needed for this value.
    public JFormattedTextField.AbstractFormatter getFormatter()
    {
        return null;
    }

    /*
     * Returns the component to use for the customer-id
     * editing dialog. The IDE calls this method to display
     * the dialog. This class's constructor is used to 
     * contain the user interface code.
     * 
     * Note that an implementation of the AttributeEditorWizard
     * (as a more customizable alternative) would implement the
     * getDialog method, rather than this one, to return user interface.
     */
    public Component getEditorComponent()
    {
        return this;
    }

    /*
     * Provides a way for the IDE to retrieve the newly
     * entered attribute value. The getText method below is
     * exposed by the JTextField class. The user puts the value in,
     * this code gets it out so the IDE can retrieve it.
     */
    public String getNewAttributeValue()
    {
        return m_field.getText();
    }

    /*
     * Called by the IDE when the user clicks OK. This
     * method calls the validator code above to 
     * validate the attribute value, ensuring that it is
     * one of the allowable values.
     */
    public Issue[] onFinish()
    {
        CustIdValidator cIdV = new CustIdValidator();
        return cIdV.validateId(getNewAttributeValue());  
    }
} 
  

Editor Support Example

The editor support class acts as the signpost, directing the IDE to specific code for each behavior. This implementation handles two behaviors (although one of them is for illustration only): a request for a custom editor and a request for a validator.

package propEditor.ide; 

import com.bea.ide.control.ControlBehaviorContext;
import com.bea.ide.control.EditorSupport;
import com.bea.ide.control.ControlAttribute;
import com.bea.ide.control.DefaultEditorSupport;


/*
 * Represents support for actions in the IDE. In particular, this
 * class provides code that executes when the user clicks the ... in the 
 * Property Editor to edit the customer-id attribute.
 */
public class CustomerDataEditorSupport extends DefaultEditorSupport
{ 
    public Object getBehavior(String behavior, ControlBehaviorContext ctx)
    {
        /* 
         * When the user clicks the ellipses in the Property Editor, the IDE
         * sends a request for specifics on the BEHAVIOR_ATTRIBUTE_EDITOR
         * behavior. This code handles that request, return an object that 
         * implements AttributeEditorSimple and provides UI for the custom editor.
         */
        if (behavior.equals(EditorSupport.BEHAVIOR_ATTRIBUTE_EDITOR))
        {
            if (ctx instanceof ControlAttribute && 
                ((ControlAttribute)ctx).getName().equals("customer-id"))
            {
                return new CustomerIdEditorSimple(((ControlAttribute)ctx).getValue());
            }            
        }        

        /*
         * This behavior request won't be received because validation will 
         * occur in the context of the custom editor referred to above. 
         * But the code is included here for illustration purposes. It would
         * be called if a custom editor were not provided and the user attempted
         * to set a new value in the Property Editor. The CustIdValidator class
         * implements ValidateAttribute.
         */
        if (behavior.equals(EditorSupport.BEHAVIOR_ATTRIBUTE_VALIDATOR))
        {
            if (ctx instanceof ControlAttribute && 
                ((ControlAttribute)ctx).getName().equals("customer-id"))
            {
                return new CustIdValidator("customer-id", 
                    ((ControlAttribute)ctx).getValue());
            }            
        }        
        // Return the default implementation for other behaviors.
        return super.getBehavior(behavior, ctx);
    }     
}

Related Topics

None.