A quick example of a photo gallery using the standard SDK SliderIn a world of visuals (well at least our little world of RIA development), this is freakishly basic. [Edit: After trying to get this to show up as an inline swf using WP-SWFObject, I gave up on it since it refuses to put up the content, so instead it'll have to be a retarded link for now.]

So essentially while it works, the control doesn’t tell you anything about what you’re about to see, other than an index of the photo which lets face it isn’t that helpful. Knowing previously there is a reference property to the sliderDataTipClass, I began to look up how to implement my own. Essentially all I want is something relatively basic that shows a scaled-down thumbnail of the image (you can get a lot more detail out of it once you know the basic mechanics of how to set one up).

Kudos to Adobe for releasing the source for the SDK instead of making us machete our way through a compiled swc. This is invaluable when the API just doesn’t cut it, after all it’s only helpful tip:

sliderDataTipClass property
sliderDataTipClass:Class [read-write] A reference to the class to use for the data tip. The default value is SliderDataTip.

Shame on them for what I’m about to talk about next. My guess is it probably was a really good idea that just got shelved until some later release, but behold it wasn’t ever updated / improved upon in Flex 3. Kudos to guys like Doug and countless others who put in the time towards developing components, API’s, SDK’s in an open source forum. Since I didn’t want to have to monkey hack the official API I started from the FlexLib Slider. It’s still technically a monkey hack, but not quite as much so since it stands a better chance of acceptance / rejection based on it’s usefulness to the developers at large instead of going through the rounds of acceptance within the Adobe engineering world. And as for me personally I don’t really care, I use it and that’s all I’ll ever worry about if anyone else finds it useful so be it.

Why Creating your own sliderDataTipClass will Never Work (with the SDK):

The problem starts with the base Slider class sliderDataTipClass property (line 919) in that the setter only calls invalidateProperties, this of itself is not the issue but it’s a part of the root manifested problem. The core of the problem moves into the onThumPress (line 2001) event handler when the instance of the reference class is generated and displayed.

  • Problem #1. The instance is added directly to the systemManager.toolTipChildren (ToolTipManagerImpl) display stack. This means, even though the instance is an interface of IToolTip, no tooltip events will be generated for it (because ToolTipManager.createToolTip is not called), and since the Slider class doesn’t generate events related to the dataTip this means it’s not very customizable.
    mx_internal function onThumbPress(thumb:Object):void
    {        if (showDataTip)
    {
    if (!dataTip)
    {
    dataTip = SliderDataTip(new sliderDataTipClass());
    systemManager.toolTipChildren.addChild(dataTip);

    var dataTipStyleName:String = getStyle(“dataTipStyleName”);
    if (dataTipStyleName)
    {
    dataTip.styleName = dataTipStyleName;
    }
    }

    var formattedVal:String;
    if (_dataTipFormatFunction != null)
    formattedVal = this._dataTipFormatFunction(getValueFromX(thumb.xPosition));
    else
    formattedVal = dataFormatter.formatPrecision(String(getValueFromX(thumb.xPosition)),
    getStyle(“dataTipPrecision”));

    dataTip.text = formattedVal;

    //dataTip.text = String(getValueFromX(thumb.xPosition));

    // Tool tip has been freshly created and new text assigned to it.
    // Hence force a validation so that we can set the
    // size required to show the text completely.
    dataTip.validateNow();
    dataTip.setActualSize(dataTip.getExplicitOrMeasuredWidth(),dataTip.getExplicitOrMeasuredHeight());
    positionDataTip(thumb);
    }
    keyInteraction = false;

    var event:SliderEvent = new SliderEvent(SliderEvent.THUMB_PRESS);
    event.thumbIndex = thumb.thumbIndex;
    dispatchEvent(event);
    }

  • Problem #2. The sliderDataTipClass is a composite extension of ToolTip which means not only is it forced to have a textfield but that’s all it will ever have, since the press event handler directly manipulates the text update. In it’s current form, you couldn’t suppress it even if you wanted to (a dataTip class say with just an image?).

So the next best thing is to do is surgically graft in your own. Starting with the ExtendedSlider base in FlexLib, I modified the onThumbPress and onThumbMove event handlers to dispatch the following events related to the datatip:

[Event(name="dataTipCreate", type="flexlib.events.ExtendedSliderEvent")]
[Event(name="dataTipShown", type="flexlib.events.ExtendedSliderEvent")]
[Event(name="dataTipHide", type="flexlib.events.ExtendedSliderEvent")]
[Event(name="dataTipEnd", type="flexlib.events.ExtendedSliderEvent")]
[Event(name="dataTipUpdate", type="flexlib.events.ExtendedSliderEvent")]

This closely models the sorts of events you’d expect from a tootip, but with one important distinction which is the ExtendedSliderEvent.DATA_TIP_UPDATE. This is broadcast during the thumbMove so that any owner component (of the slider) can modify all subcomponents of your custom datatip. I also improved upon in the onThumbPress / thumbMove routine to detect if the slider has an active listener for the update. This assumes if it does, then it’s a custom class that wants its own way of modifying the tip. If not then it provides a default behavior where the tip.text gets set.

In the FlexLib Slider version, the same method gets modified to (the onThumbMove isn’t shown, but the principle is the same):

override mx_internal function onThumbPress(thumb:Object):void
{
// Here we jack up the counter, see the code for onThumbRelease to see how we use this
counter++;

if (showDataTip)
{
var dataTip:SliderDataTip = dataTips[thumb.thumbIndex];

if (!dataTip)
{
dataTip = SliderDataTip(new sliderDataTipClass());
dataTips[thumb.thumbIndex] = dataTip;

var value:Number = getValueFromX(thumb.xPosition);
this.dispatchEvent(new ExtendedSliderEvent(ExtendedSliderEvent.DATA_TIP_CREATE, false, false, dataTip, thumb.thumbIndex, value));

systemManager.toolTipChildren.addChild(dataTip);
this.dispatchEvent(new ExtendedSliderEvent(ExtendedSliderEvent.DATA_TIP_SHOWN, false, false, dataTip, thumb.thumbIndex, value));
var dataTipStyleName:String = getStyle(“dataTipStyleName”);
if (dataTipStyleName)
{
dataTip.styleName = dataTipStyleName;
}
}

var formattedVal:String;
if (_dataTipFormatFunction != null)
formattedVal = this._dataTipFormatFunction(getValueFromX(thumb.xPosition));
else
formattedVal = dataFormatter.formatPrecision(String(getValueFromX(thumb.xPosition)),
getStyle(“dataTipPrecision”));

if (!this.hasEventListener(ExtendedSliderEvent.DATA_TIP_UPDATE)) {
dataTip.text = formattedVal;
}
this.dispatchEvent(new ExtendedSliderEvent(ExtendedSliderEvent.DATA_TIP_UPDATE, false, false, dataTip, thumb.thumbIndex, value));

//dataTip.text = String(getValueFromX(thumb.xPosition));

// Tool tip has been freshly created and new text assigned to it.
// Hence force a validation so that we can set the
// size required to show the text completely.
dataTip.validateNow();
dataTip.setActualSize(dataTip.getExplicitOrMeasuredWidth(),dataTip.getExplicitOrMeasuredHeight());
positionDataTip(thumb);
}
keyInteraction = false;

var event:SliderEvent = new SliderEvent(SliderEvent.THUMB_PRESS);
event.thumbIndex = thumb.thumbIndex;
dispatchEvent(event);
}

The Mechanics of creating a custom datatip:

In the example AlbumPreviewToolTip below, you’ll see it’s just about as easy as creating any other custom component. There are several protected methods you’ll want to override namely (additional property exposure is up to you in what you create):

  • createChildren
  • committProperties
  • measure
  • updateDisplayList

I won’t go into too much detail as to each of these, assuming you’ll already know how, or can find several sources out there that go into it at length. But the basics of it is, you’ll need to measure and re-layout any subcomponents especially if they are allowed to use measuredWidth / Height. In my example you’ll probably notice two ‘tricks’ I used. 1 Since I wanted to use a textfield for the tip, instead of going through the motions of creating a label, since the tooltip has one already embedded, I re-parented it to my vbox control. Secondly, since I’m using images and updating the source, the datatip will be created and destroyed every time the user interacts with it and there is no caching of any of the internals. This essentially forces the image to re-issue a URLRequest (if the source is http, everything in the example is embedded for simplicity). To speed up the display, I use a ResourceManager to cache the images for me, and if that source matches an id it can be recycled to add that item as a child in my tip. This gives the appearance of near instantaneous update to the tip. Just remember the caching has to be done outside the tip class (it’s destroyed everytime).

Example:
An Updated Photo Gallery Control with a custom DataTip embellished with a few effects

It’s pretty simple, after all it’s just an example but now hopefully you get the mechanics of how to create one on your own with whatever visuals you want to create as your UI.

Downloads:

Modified: flexlib.controls.sliderClasses.ExtendedSlider
Added: flexlib.events.ExtendedSliderEvent

Example: AlbumPreviewToolTip
Additional Resource: ResourceManager Used to maintain a cached reference of created Images