Bubble ItemRenderer Events in Flex

Often times I find myself needing to bubble events from an itemRenderer to the parent control.   This is especially important when using a CairngormEventDispatcher.  Events should be used by the views, rather than having them nested within the itemRenderer.  This is commandment #10 on Jesse Warden’s post 10 Tips For Working With Cairngorm (or the way Jesse writes about it, 10 Thinks I Love to Hate About Cairngorm). 

I try, when time permits, to adhere to this practice as it can be a big pain in the ass to dig down deep within your structure hunting for events. This also makes the code more reusable as you are not having to customize itemRenderers when they need to pass different events, this can be handled in the view instead, nice an neat.

For my example I extended a List control to have a new event “menuClicked”.  

List:

package com.mister.controls
{
    import mx.controls.List;

    [Event(name="menuClick", type="mx.events.MenuEvent")]

    public class List extends mx.controls.List
    {
        public function List()
        {
            super();
        }
    }
}

I used an MXML itemRenderer instead of a class, just because I like to have the layout ease that comes with MXML.  

ItemRenderer:

<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" width="100%" height="24">
    <mx:Script>
        <![CDATA[
            import mx.events.MenuEvent;
            import mx.controls.Menu;

            private function createMenu(event:Event):void
            {

                  var menu:Menu = Menu.createMenu(menuButton, menuData, false);
                      menu.labelField="@label";
                      menu.addEventListener(MenuEvent.ITEM_CLICK, bubbleMenuEvent);

                   var point:Point = new Point();
                       point.x = menuButton.x;
                    point.y = menuButton.y;
                        point = this.localToGlobal(point);

                menu.show(point.x, point.y);
            }

            private function bubbleMenuEvent(event : MenuEvent):void
            {
                var e : MenuEvent = new MenuEvent("menuClick", true, true, null, null, event.item, this, data.label);
                dispatchEvent(e);
            }

        ]]>
    </mx:Script>

    <mx:XML id="menuData">
        <root>
            <menuitem label="Edit" data="edit"/>
            <menuitem label="Delete" data="delete"/>
            <menuitem label="Save" data="save"/>
           </root>
    </mx:XML>

    <mx:Label text="{data.label}" verticalCenter="0" x="10"/>
    <mx:Button id="menuButton" click="createMenu(event)" verticalCenter="0" right="10" width="54" label="menu"/>

</mx:Canvas>

I made an clickable event using a Menu control and bubbled the event to the view of my application.    

Application:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
    creationComplete="initApp()" viewSourceURL="srcview/index.html">

    <mx:Script>
        <![CDATA[
            import mx.controls.Alert;
            import mx.events.MenuEvent;
            import mx.collections.ArrayCollection;

             private var listArray:Array=[
                 {label: "Label One", data: 1},
                 {label: "Label Two", data: 2},
                 {label: "Label Three", data: 3},
                 {label: "Label Four", data: 4},
                 {label: "Label Five", data: 5}];

             [Bindable]
            public var dp:ArrayCollection;

            public function initApp():void
            {
                dp = new ArrayCollection(listArray);
            }

            private function handleMenuEvent(event : MenuEvent):void
            {
                Alert.show("Menu Item Clicked : " + event.item.@data + " and List Row Label : " + event.label);
            }
        ]]>
    </mx:Script>

    <controls:List dataProvider="{dp}" menuClick="handleMenuEvent(event)"
        itemRenderer="com.mister.custom.BubbleItemRenderer"
        width="450" height="240" horizontalCenter="0" top="50"
        xmlns:controls="com.mister.controls.*"/>

    <mx:Label y="24" text="Bubble Event from List ItemRenderer" horizontalCenter="0" fontWeight="bold"/>

</mx:Application>

This example is simple, but you can see how you can extend this to types of itemRenderers.

View Source (right-click) and Example

Download Example

-Mister

24 Comments

  1. It’s a great example, but I still have a question. I understand that inside BubbleItemRender.mxml there is a method “bubbleMenuEvent(event : MenuEvent)” which is responsible for actually bubbling up the menu event up to the parent List control by calling “dispatchEvent(e)”.

    However, my question is how does the event handler inside BubbleListRender.mxml, “handleMenuEvent(event:MenuEvent)” not get confused?

    For example, there are two events being fired, one is fired and handled by “bubbleMenuEvent(event:MenuEvent)” which then fires another event that is handled by “handleMenuEvent()”. But how does the List control inside the parent container know only to catch the second event which is dispatched by bubbleMenuEvent function??

    I hope my question makes sense. Thanks for your help.

    –Deven

  2. It depends on the scope. The bubbleMenuEvent is scoped to the ItemRenderer, broadcasting from the ItemRenderer goes to the scope of the MXML file containing the List control.

  3. ahhh…I see. Inside the mxml for the List control you specifically say that the list control will only listen to MenuEvent.MENU_CLICK by writing the code:

    ————————————————————–

    On a similar, but related question:

    Instead of using the mxml to connect the event handler function with the List control to listen for MenuEvents, I tried using the below ActionScript, inside of the initApp() function:

    [AS]
    this.thanksMisterList.addEventListner(MenuEvent.MENU_CLICK, handleMenuEvent);
    [/AS]
    BUT, the above code then gives me a compile error:
    Error 1119: Access of possibly undefined property MENU_CLICK through a
    reference with a static type Class.

    I’m new to Flex, so I still get a little mixed up when I should use AS vs. MXML, but I always like to learn how to do a particular task in both methods. I’m just curious why it works in MXML but not using the equivalent AS.

    btw, I’ve been reading a lot of flex blogs, but you have my favorite Flex related blog. Thanks Mister!

    –Deven

  4. ahhh…I see. Inside the mxml for the List control you specifically say that the list control will only listen to MenuEvent.MENU_CLICK by writing the code:

    ————————————————————–

    On a similar, but related question:

    Instead of using the mxml to connect the event handler function with the List control to listen for MenuEvents, I tried using the below ActionScript, inside of the initApp() function:

    this.thanksMisterList.addEventListner(MenuEvent.MENU_CLICK, handleMenuEvent);

    BUT, the above code then gives me a compile error:
    Error 1119: Access of possibly undefined property MENU_CLICK through a
    reference with a static type Class.

    I’m new to Flex, so I still get a little mixed up when I should use AS vs. MXML, but I always like to learn how to do a particular task in both methods. I’m just curious why it works in MXML but not using the equivalent AS.

    btw, I’ve been reading a lot of flex blogs, but you have my favorite Flex related blog. Thanks Mister!

    –Deven

  5. hello! I found this extremely useful. HOWEVER I have a problem perhaps you can help me with. I am not using MXML AT ALL…all my code is AS3.

    I use your List.as for my lists. Here is a sample list I create;

    [as]
    var newList1:List = new List();
    newList1.id = “newList1”;
    newList1.dataProvider = dataSetInstance[0][‘locationsValues’];
    newList1.allowMultipleSelection = true;
    newList1.width = 200;
    newList1.height = 400;
    newList1.x = 10;
    newList1.y = 20;
    newList1.variableRowHeight = true;
    newList1.wordWrap = true;
    newList1.verticalScrollPolicy = “on”;
    newList1.boxChecked = “handleCheckBoxEvent(event)” ;
    [/as]

    BUT I get an error:

    “1119: Access of possibly undefined property boxChecked through a reference with static type FormComponents:List”

    Any idea how to solve this?

    Again I am using the same List.as except I just changed two parts, the package name and teh Event statement:

    [as]
    package FormComponents
    {
    import mx.controls.List;

    // does this mean its listening for an event???
    [Event(name=”boxChecked”, type=”flash.events.Event”)]

    public class List extends mx.controls.List
    {
    public function List()
    {
    super();
    }
    }
    }
    [/as]

    Thank you!

  6. The other problem I think I’m having is how to dispatch the correct type of event. I have an itemREnderer that consisists of a chekbox that has an event listener for when a box is checked. From within that eventListener function I have:

    [as]
    private function onChangeHandler(event:Event):void
    {
    super.data.isSelected = !super.data.isSelected;

    //var e : MenuEvent = new MenuEvent(“menuClick”, true, true, null, null, event.item, this, data.label);
    // ATTEMPT TO DISPATCH EVENT HERE AND TRY TO SEND THE CHECKBOX DATA TO THE MAIN APP
    var e:Event = new Event(“boxChecked”, true);
    dispatchEvent(e);
    }
    [/as]

    I am just not sure which event object to use and which event class to import or how to send the data back to the main app.

    Thanks!

  7. Hey. I was excited to find your post about listening to events dispatched from a custom itemRenderer. However, I haven’t gotten it to work yet. I notice this post is a few years old, I’ve never used Flex 2, I am assuming this example would still work with Flex 3?
    Anyway, I’ve followed your steps, extending the TileList and adding the Event metaTag, assigning the itemRenderer and eventHandler within the mxml, dispatching the event within the itemRenderer class. But my eventHandler wont fire. I still can’t seem to get the ‘listening’ part to work. Any ideas or other ‘gotchas’ you can think of?
    Still seems wierd that the itemRenderer dispatches the event, but the owner/TileList has the event metaTag and the eventHandler. If i could get it to work i would be very happy.

  8. @ darren_db, this is still a valid method that I use in Flex 3, so the example should still work. Maybe you can post your code so we can see the issue and I can test it from my end.

  9. Awesome, thanks for your reply.
    So my situation is basically the same, but instead of a List i am extending a TileList.

    Here’s the component that contains the extended TileList (BuilderPageMX.mxml):
    [as]

    [/as]

    Here’s the extended TileList component:
    [as]
    package com.ast.ltrbox.views
    {
    import mx.controls.TileList;

    [Event(name=”imageSize”, type=”com.ast.ltrbox.events.ThumbnailEvent”)]
    public class ThumbnailTileList extends TileList
    {
    public function ThumbnailTileList()
    {
    super();
    }

    }
    }

    [/as]

    Here’s the itemRenderer mxml (code-behind, sorry):
    [as]

    [/as]
    Here’s the itemRenderer class:
    [as]
    package com.ast.ltrbox.views {

    import flash.display.DisplayObjectContainer;
    import mx.controls.Image;
    import com.ast.ltrbox.events.ThumbnailEvent;
    import mx.controls.SWFLoader;
    import flash.events.Event;

    /**
    * Uses as ItemRenderer for TileList’s
    */

    public class TextureThumbnail extends Image {

    protected function onComplete_img(p_event:Event):void{
    trace(“onComplete_img()”);

    var imgWidth:uint = this.contentWidth;
    var imgHeight:uint = this.contentHeight;
    var imgIndex:uint = this.data.index;
    var owner:DisplayObjectContainer = this.owner;

    // Dispatch Event
    dispatchEvent( new ThumbnailEvent(ThumbnailEvent.IMAGE_SIZE, imgWidth, imgHeight, imgIndex, owner));
    }

    }
    }
    [/as]

    And finally, not sure its needed but here’s my Event class:
    [as]
    package com.ast.ltrbox.events {

    import flash.display.DisplayObjectContainer;
    import flash.events.Event;

    public class ThumbnailEvent extends Event {
    // Define static constants for Event.type.
    public static const IMAGE_SIZE:String = “imageSize”;

    // Define a public variable to pass data from Broadcaster to Listener
    public var imgWidth:uint;
    public var imgHeight:uint;
    public var imgIndex:uint;
    public var imgOwner:DisplayObjectContainer;

    // Public constructor.
    public function ThumbnailEvent(type:String, p_imgWidth:uint, p_imgHeight:uint, p_imgIndex:uint, p_imgOwner:DisplayObjectContainer) {

    // Call the constructor of the superclass.
    super(type);
    trace(“ThumbnailEvent()”);
    // Set the new property.
    this.imgWidth = p_imgWidth;
    this.imgHeight = p_imgHeight;
    this.imgIndex = p_imgIndex;
    this.imgOwner = p_imgOwner;
    }

    // Override the inherited clone() method.
    override public function clone():Event {
    return new ThumbnailEvent(this.type, this.imgWidth, this.imgHeight, this.imgIndex, this.imgOwner);
    }

    }
    }
    [/as]

    Hopefully its just a matter of an extra pair of eyes but I don’t see why its not working… I appreciate your help! Sorry i don’t have files to share, but this is sort of a snippet from a large app. That should be all the relevant code, if not I’ll post again, I’m on this issue until i resolve it so…
    Thanks, again,

    d

  10. Crap, ok, my mxml tags broke themselves… sorry for the long and messy posts.
    Here’s the mxml again:
    Here’s the component that contains the extended TileList (BuilderPageMX.mxml):
    [as]
    ;
    ;
    ;

    [/as]

    And Here’s the itemRenderer mxml (code-behind, sorry):
    [as]

    [/as]

  11. Ah Ha! OK, it was my bad. Another dev/friend helped me see it. So in my custom event i wasn’t declaring bubbles=true. I added that, but it still didn’t work. Because I also then forgot to pass bubbles to the super in the constructor. So once bubbles was a part of the constructor signature for my custom event, AND i passed bubbles to the super, I instantly had listeners firing! Awesome…thanks! (And thanks, David C.!)

  12. Hi Mister,
    I try to bubble up the itemRenderer event. I have an itemRenderer in my datagrid column. I follow every step from your example. But when I invoke MyDataGrid in my application, I got this error:
    ” Could not resolve to a component implementation.”

    First I am not sure if MyDataGrid should be a MyDataGrid.as file or should be MyDataGrid.mxml file. I choose MyDataGrid.mxml(I think I used MyDataGrid.as also and got the same error).

    Here is the MyDataGrid.mxml file looks like:
    [as]
    package com.view.urlValidation
    {
    import mx.controls.DataGrid;

    [Event(name=”downloadBtnClick”,type=”com.event.SemaEvent”)]
    public class MyDataGrid extends mx.controls.DataGrid
    {
    public function MyDataGrid()
    {
    super();
    }
    }
    }
    [/as]
    And the Application.mxml:

    [as]

    [/as]
    My custom event has set the bubble to true as darren_db said. Could you tell if where I make the mistake and can not bubble us the event. Thanks so much.

    1. @Jas, My guess is you have some issue with your package path names, you have a package like com.view.urlValidation.FilesDownloadRenderer, but inside FilesDownloadRenderer you don’t have the same “package com.view.urlValidation”.

  13. Hi Mister,
    I fixed the package and the error is gone. Many thanks. But the next error is:
    Cannot resolve attribute ‘downloadBtnClick’ for component type mx.controls.dataGridClasses.DataGridColumn.

    The ‘downloadBtnClick’ attribute is used in the “mx:DataGridColumn”.

    And it is defined in the MyDataGrid file:
    [Event(name=”downloadBtnClick”,type=”com.event.SemaEvent”)]
    public class MyDataGrid extends mx.controls.DataGrid
    {

    And the event dipatch in the itemrederer as:

    private function onDownloadBtnClick(data : Object):void {
    var e:SemaEvent = new SemaEvent(“downloadBtnClick”, data, true); // true: it is a bubble up event
    dispatchEvent(e);
    }
    I can not see the problem. Thanks for helping again.

  14. Hi Mister,
    I finally solved the problem I posted last yesterday by playing around. There are two things that I fixed:
    1) I changed the MyDataGrid.as file to extend DataGridColumn instead of extend DataGrid, since the itemRederer is in the DataGridColumn. so the error I got (in last post) was gone.

    package com.view.urlValidation
    {
    import mx.controls.DataGrid;
    import mx.controls.dataGridClasses.DataGridColumn;;

    [Event(name=”downloadBtnClick”,type=”com.event.SemaEvent”)]
    public class MyDataGrid extends mx.controls.dataGridClasses.DataGridColumn
    {
    public function MyDataGrid ()
    {
    super();
    }
    }
    }

    2) I added this line in the init() of the application.mxml file:
    this.addEventListener(“downloadBtnClick”, onDownloadBtnClick1); //this is the event listener of the FilesDownloadRenderer
    And the bubble up event works! 🙂
    Thanks so much to Mister to solve the first error for me first, and this tutorial 🙂

  15. Great and helpful post. thanks.

    However, after I have read another one’s post, http://weblogs.macromedia.com/pent/archives/2008/03/itemrenderers_p_2.html

    i just simply made some comparison. Do you think, it is more simple and better to write the code below in the renderer class?


    [as]
    function onClick(event:MouseEvent){
    var evt:SomeBusinessEvent = new SomeBusinessEvent(ONE_TYPE); // with bubbles as false.
    evt.somedata = somedata_in_renderer;
    // listData.owner refers to the List that employs this item renderer
    listData.owner.dispatchEvent(evt);

    }
    [/as]

    There are few advantages.

    1. no need to customize List, it is innocent.

    2. no need to use advanced Event's bubbling mechanism. (for I have not fully get it yet.. blow my mind)

    3. the event should be of business, rather than of UI.

    My 2 cents.

  16. Hi Mister,
    I am able to follow your code on a data grid using Jas’ comment. However, as soon as I start using an AdvancedDataGrid, it falls apart. I tried to attach the listener to the rendererProviders themselves, but they don’t have the right implementation to listen for events. So I tried putting the event listeners on the columns like in Jas’ example. However, none of the events are being picked up.

    Code: http://snipt.org/okgi

  17. On using EventListeners inside ItemRenderers – I am finding that they never garbage collect. The application uses up more and more memory because there is still a reference to your ItemRenderer long after you’ve closed whatever was using it.

    I am looking for a way to remove EventListeners from ItemRenderers when I am done with them (user closes window or whatever). I tried using weakReference=true when creating the EventListener and that did not help.

    I wonder if you or any of your commenters have any ideas about this.

Comments are closed.