Preventing click events on a Flex Accordion header
Posted 31 July 2009 by Russ in Adobe Flex. Tags: accordion, events.
Intercepting Flex’s Accordion navigation isn’t something that the Accordion provides out of the box, but with a few simple classes it’s easy to prevent clicks, prompt for confirmation, or whatever else you need to do…
Flex takes care of switching the views in an Accordion when you click on the bars of the Accordion, but what if you want to detect that user activity and do something else? Maybe you want to disable the navigation if a form inside one of the accordion children has failed validation, or you want a user to confirm they don’t want to save the changes they’ve made in that form.
Below are two examples to achieve this. For both examples, we’ll simply enable and disable each item in the Accordion, which will enable or disable the navigation of the Accordion as appropiate.
Understanding the Accordion
Both of these examples make use of the Accordion’s headerRenderer class to achieve what we’re looking for. The default for an Accordion is a Button class and as Button is a subclass of Container, we know that it has a property called data which is a reference to the related object. In the case of the Accordion that related object is the child object you add to the Accordion. In other words if you add one VBox to an Accordion, that VBox is referenced in the data property of the header bar that appears in the Accordion.
A common way to disable a view is to set the enabled property to false, so what we’ll doing in both examples is setting the enabled property of each child object of the Accordion and our headerRenderer will follow suit. To test this I’ve created a little class just to toggle the enabled property of an object on click:
package com.russback.examples
{
import flash.events.MouseEvent;
import mx.controls.Button;
import mx.core.Container;
public class ExampleButtonControl extends Button
{
/**
* Target container linked to this ExampleButtonControl
*/
public var target:Container;
/**
* Constructor
*/
public function ExampleButtonControl()
{
super();
}
/**
* Toggles the enabled property of the target Container
*/
override protected function clickHandler(event:MouseEvent):void
{
super.clickHandler(event);
target.enabled = ! target.enabled;
}
}
}
Example 1: using BindingUtils
The first example is simple: we just need to bind the enabled property of each header to the related Accordion child so we have a class that extends the Button control. This overrides the default createChildren() method to bind the enabled property of the Button to the enabled property of it’s data object:
package com.russback
{
import flash.events.MouseEvent;
import mx.binding.utils.BindingUtils;
import mx.controls.Button;
public class BoundAccordionHeader extends Button
{
/**
* Constructor
*/
public function BoundAccordionHeader()
{
super();
}
/**
* Overrides default createChild() method
* Binds the enabled property to the data's enabled property
*/
override protected function createChildren():void
{
super.createChildren();
BindingUtils.bindProperty(this, "enabled", data, "enabled");
}
}
}
Example 1 in action
The MXML for this couldn’t be simpler. We simply create an Accordion and set it’s headerRenderer property to be our BoundAccordionHeader class. The only other objects in this example are three of our ExampleButtonControl objects that simply toggle the enabled state of each child in the Accordion. Job done.
<?xml version="1.0" encoding="utf-8"?>
<mx:Application
xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:russback="com.russback.*"
layout="vertical"
viewSourceURL="srcview/index.html"
>
<mx:HBox width="100%">
<russback:ExampleButtonControl label="Toggle Child 1" target="{child1}" />
<russback:ExampleButtonControl label="Toggle Child 2" target="{child2}" />
<russback:ExampleButtonControl label="Toggle Child 3" target="{child3}" />
</mx:HBox>
<mx:Accordion height="100%" width="100%" headerRenderer="com.russback.BoundAccordionHeader" >
<mx:VBox id="child1" label="Child 1" />
<mx:VBox id="child2" label="Child 2" />
<mx:VBox id="child3" label="Child 3" />
</mx:Accordion>
</mx:Application>
You can download and edit the source code, or right-click on the demo below and choose View Source to see how it all fits together.
You need Flash Player 10 to view this content. You can download this for free from the Adobe website. If you’re using a feed reader you could try loading this post in your web browser.
Example 2: Events
Example 1 is great if you just need a simple binding but what if you need to handle those changes to the enabled property? This next example uses a custom event class to enable you to handle events at the Accordion level. A use for this might be to have the Accordion enabled but only to allow navigation in certain circumstances, such as if a form in one of the child objects has been validated successfully.
A custom event
For this example I’ve created a custom event, called AccordionHeaderEvent. This is a simple extension of the Event class that has a static constant for the event type and a container property. We’ll fire one of these events each time an Accordion child changes it’s enabled property and use the event’s container property to hold a reference to the child object involved.
package com.russback.events
{
import flash.events.Event;
import mx.core.Container;
public class AccordionHeaderEvent extends Event
{
/**
* Event types
*/
public static const UPDATE_HEADER:String = "accordionHeaderEventUpdateHeader";
/**
* Container object associated with this event
* NB event bubbles and can be cancelled
*/
public var container:Container;
/**
* Constructor sets this event's container property
*/
public function AccordionHeaderEvent(type:String, container:Container, bubbles:Boolean=true, cancelable:Boolean=true)
{
super(type, bubbles, cancelable);
this.container = container;
}
}
}
Custom headerRenderer component
This is a simple class that only has three methods. The first is the obligatory constructor that just invokes the super class, the next is an override of the createChildren() method that sets the enabled property of the button according to the enabled property of the button’s data property. This data property is assigned by the Accordion class and is a reference to the child object in the Accordion (a VBox for example).
package com.russback.extensions
{
import com.russback.events.AccordionHeaderEvent;
import flash.events.MouseEvent;
import mx.controls.Button;
public class StateEnabledAccordionHeader extends Button
{
/**
* Constructor
*/
public function StateEnabledAccordionHeader()
{
super();
}
/**
* Sets the enabled property according to the data's enabled property
*/
override protected function createChildren():void
{
super.createChildren();
if (data.enabled is Boolean)
{
enabled = data.enabled;
}
}
Next we override the clickHandler() method. All we do here is stop the event from bubbling if the button has been disabled. And that’s it for this example.
/**
* Prevents the CLICK MouseEvent propogating if this object is disabled
*/
override protected function clickHandler(event:MouseEvent):void
{
if (! enabled)
{
event.stopImmediatePropagation();
}
}
}
}
This is enough to disable an Accordion item when it creates, but we should really extend this further so that we can enable and disable each item according to user behaviour or data values. For this we need to look at the child objects we’re going to add to the Accordion.
Child objects of the Accordion
For this example I’m just using three VBox objects but this approach applies to any class that extends the Container class. For now I’ve just created a custom class that extends VBox, called ExampleAccordionChild and it just has one override function.
package com.russback.examples
{
import com.russback.events.AccordionHeaderEvent;
import mx.containers.VBox;
import mx.core.Container;
public class ExampleAccordionChild extends VBox
{
/**
* Constructor
*/
public function ExampleAccordionChild()
{
super();
}
/**
* Fires an UPDATE_HEADER AccordionHeaderEvent after setting the enabled property,
* passing this class as the event's container property
*/
override public function set enabled(value:Boolean):void
{
super.enabled = value;
var accordionHeaderEvent:AccordionHeaderEvent = new AccordionHeaderEvent(AccordionHeaderEvent.UPDATE_HEADER, this as Container);
dispatchEvent(accordionHeaderEvent);
}
}
}
After the constructor we override the enabled() setter function to fire a new AccordionHeaderEvent with itself as the container argument. This will fire an event every time the enabled property of this object is changed and as our event is set to bubble, we’ll be able to capture this in the Accordion, which we can look at now.
The custom Accordion component
I’ve created an extension of the Accordion classs called StateEnabledAccordion. The constructor sets the headerRenderer property for the Accordion by setting up a new ClassFactory object which has the StateEnabledAccordionHeader class as it’s argument, and adds an event listener for the custom event we’ve created.
package com.russback.extensions
{
import com.russback.events.AccordionHeaderEvent;
import flash.display.DisplayObject;
import mx.containers.Accordion;
import mx.controls.Alert;
import mx.core.ClassFactory;
public class StateEnabledAccordion extends Accordion
{
/**
* Constructor adds headerRenderer and event listener
*/
public function StateEnabledAccordion()
{
super();
headerRenderer = new ClassFactory(StateEnabledAccordionHeader);
addEventListener(AccordionHeaderEvent.UPDATE_HEADER, accordionHeaderEventHandler);
}
Then we have our event handler function that handles an UPDATE_HEADER AccordionHeaderEvent by using the container reference in the event to locate the child object that fired the event. We then get the Accordion’s header object for that child, set its enabled property, and cancel the event as we don’t need it to bubble any further. I’ve also added an Alert here just to demonstrate the fact that you can work with this here to do whatever you want.
/**
* Event handler sets the enabled property of the header element associated
* with the event container object, and stops the event from bubbling further
*/
private function accordionHeaderEventHandler(event:AccordionHeaderEvent):void
{
var childIndex:int = getChildIndex(event.container);
var child:DisplayObject = getChildAt(childIndex);
Alert.show("Received from " + child, "AccordionHeaderEvent.UPDATE_HEADER received");
getHeaderAt(childIndex).enabled = event.container.enabled;
event.stopImmediatePropagation();
}
}
}
Example 2 in action
Creating the custom Accordion for this excample isn’t much more complex that exampl 1. We just create a new StateEnabledAccordion which contains as many ExampleAccordionChild child objects as we need. This method relies on having the child objects fire an event whenever their enabled property changes so you’d need to make sure you add that override to any child object you use.
<?xml version="1.0" encoding="utf-8"?>
<mx:Application
xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:examples="com.russback.examples.*"
xmlns:extensions="com.russback.extensions.*"
layout="vertical"
viewSourceURL="srcview/index.html"
>
<mx:HBox width="100%">
<examples:ExampleButtonControl label="Toggle Child 1" target="{child1}" />
<examples:ExampleButtonControl label="Toggle Child 2" target="{child2}" />
<examples:ExampleButtonControl label="Toggle Child 3" target="{child3}" />
</mx:HBox>
<extensions:StateEnabledAccordion height="100%" width="100%" >
<examples:ExampleAccordionChild id="child1" label="Child 1" />
<examples:ExampleAccordionChild id="child2" label="Child 2" />
<examples:ExampleAccordionChild id="child3" label="Child 3" />
</extensions:StateEnabledAccordion>
</mx:Application>
You can download and edit the source code, or right-click on the demo below and choose View Source to see how it all fits together.
You need Flash Player 10 to view this content. You can download this for free from the Adobe website. If you’re using a feed reader you could try loading this post in your web browser.


J. Gordon
14. Aug, 2009
I’m currently building a website with a spry accordion with integrated rollovers as my main navigation source but I’m have some trouble with it. The problem is that the accordion always returns to the top panel (the ‘home’ panel) every time I click to a new page. I was wandering if it is possible to keep the panel (with the content) open, related to the open page without it returning the the default top panel position.
I would appreciate the help.
Russ
15. Aug, 2009
Hey. I haven’t worked with Spry, preferring instead to use the jQuery library. However I would investigate the same approach regardless of the library.
You need some way to establish which accordion element to have expanded by default. You could either do this by checking the URL in the browser’s location object, or by using the location’s hash (#panel-3 or something similar).
It’s difficult to comment without seeing your working example though.
aYo
19. Oct, 2009
NIce one Russ