Friday, May 16, 2008

In Place Editing: Extended

Background



A long time ago (a couple years seems like forever these days), Flex guru Ely Greenfield created and posted a collection of In Place Editing Controls that each basically use composition to combine some form of read-only control, such as Label or Text, with an editable control, such as TextInput or TextArea.

The editable and non-editable controls are mutually exclusive (e.g. only one is shown at a time) and are both driven by the same data so that they function as a single component with 2 distinct views or modes. Each IPE control has an editable attribute that defines which view should be shown. To make it the hotness, Ely also includes a FlipBitmap transition between edit and non-edit modes.

In this post, I will describe a few techniques I used (including some minor tweaks to his controls) to support more complex form layouts with in place editing. A lot of this work was done in collaboration with my close friend and colleague, Frankie Loscavio, who has been my inspiration to get started blogging!

IPE In Action



So, recently I was working on a project that was suited for this type of functionality and I decided to dust off Ely's IPE controls and give them try. For the most part, the controls worked as I had expected/remembered, however, the biggest problem I ran into was that I couldn't independently size the editable and non-editable controls.

So lets use an example. Here are 3 IPETextInput fields with a width set to 140. In both editable and non-editable modes, the width is the same, fixed value.

Note: Red dotted lines indicate component bounds for purpose of illustrating fixed dimension.

ipe1.png


Now, this is fine as long as you have all of your fields on separate lines, but my project had a very dense form with a lot of information, so space was at a premium. What I wanted to happen was to have the read-only mode show information collapsed down to its actual size and with related information on the same line. By contrast. the edit mode would show the form fields at the requisite larger sizes, which typically required them to be on separate lines.

Layout Concepts



Read Only Layout



Typically, the optimal layout for read-only data is naturally condensed, often with additional delimiters between particular fields that indicate their relationship rather than their actual value.

The best example is probably an address. The city, state and zip code are each distinct fields; however, they are typically formatted together in one line:

Atlanta, GA 30319-1234

The aspects to note here are that all fields are on the same line and are sized to the dimension of their content (text), and that various delimiters exist between fields based on existence of adjacent fields. For example, the hyphen in the zip code should only appear if both parts (5 + 4) of the zip code exist.

A simpler example could be a person's name:

Ryan Patrick Swanson

In read only view, the layout only requires that the fields are on the same line and collapsed to the size of the text.

Editing Layout



The optimal layout for an editable form is quite different from the read-only layout. Each input field is usually a fixed dimension that is large enough to display the typical length of data. Also, to be aesthetically pleasing, the fields will usually be sized to line up with each other. This often means that fields that would be together on the same line when in read-only layout are on separate lines for editing to allow for a larger input field.

First Step: Box is your friend



Many people I talk to are familiar with HBox and VBox, but often aren't aware that they are just specific-use subclasses of the Box class, which has a direction attribute that can be changed at runtime to switch the axis of layout for the Box's child components. If you haven't guessed it yet, this is how we solve half our problem.

So now we take our previous example put the 3 controls into a Box that will switch direction from vertical to horizontal as the form changes from editable to non-editable.

ipe3.png


...and the code that would accomplish this:

[Bindable] private var editing:Boolean = false;
...
<mx:Box horizontalGap="0" verticalGap="0" direction="{(editing) ? 'vertical' : 'horizontal'}">
<ipeControls:IPETextInput id="firstNameField" editable="{editing}"
text="{contact.firstName}" />
<ipeControls:IPETextInput id="middleNameField" editable="{editing}"
text="{contact.middleName}" />
<ipeControls:IPETextInput id="lastNameField" editable="{editing}"
text="{contact.lastName}" />
</mx:Box>


Second Step: measureText() is also your Friend



For many years and in working with many interface technologies, I have wished I had a relatively simple and valid way to determine the dimension that text will be rendered with a control that has some (arbitrary and unknown to me) styling applied to it. Props to Adobe on this one, Flex has it and after really digging into the underlying source code, it appears to be done in a way that I approve of (if you know me, this doesn't happen too often)!

UIComponent includes 2 functions for measuring text in the context of the stylesheets applied to the class that extends UIComponent: measureText() and measureHTMLText().

In working with Frankie Loscavio on a similar problem regarding measurement of styled text, he initially discovered these functions and we have since found a number of uses for them. For more information on measureText(), please read his blog posting.

So the solution to our problem is to simply set (through bindings) the width of our components conditionally (based on an editing flag) between a fixed size for editing and a measured size for read-only.

<ipeControls:IPETextInput id="firstNameField" editable="{editing}"
text="{contact.firstName}"
width="{(editing) ? 150 :
Math.max(firstNameField.label.measureText(firstNameField.text).width + 4, 1)}" />


Note: I have found I sometimes need to add an offset (4 in the example above) to the measured value, and I am assuming this may be due to padding or some other aspect of the component that is not included in the measurement.

Final Product



So combining these concepts and adding a little polish, here is a simple example of a contact form with name and address information. Full source code is included (right click -> View Source).




Example

Flex Project

Cheers,
Ryan

1 comment:

Unknown said...

Hi Ryan,

Thanks for the excellent component.

I have few questions on IPE

How can I apply styles to textArea, TextInput & Text.

And also for textArea, how can I detect the width or height, because measureText() doesn't work for textArea, but works for textInput.Am I missing anything?

What about Images, can we also update the images or this yet to be done.

Regards
Nazeer B
India