Update: Solution to disappearing data in your itemRenderer

In a previous post I explained how a rendering bug in Flex was causing items to disappear in an itemRenderer. In the example below, you can see that when you scroll, the items in the list disappear.  This is caused by the varying heights of the elements.  If you explicitly set the height for each element or set variableRowHeights to false, then the problem goes away.





What was causing the problem was apparent but the solution was not.  Thankfully, Olivier Lalonde was nice enough to shed some light on an easy fix.  The itemRenderer needs to be validated as the user scrolls.  So, as Olivier suggests, you simply need to listen for the dataChange event and execute the validateNow() function.

You can see in the example below that the list now has varying row heights… and the data no longer disappears. NICE!  The key point to focus on here is the following line: <mx:VBox dataChange=”validateNow()” verticalGap=”0″>

<mx:List

dataProvider=”{myData.item}
variableRowHeight=”true
alternatingItemColors=”['#FFFFFF','#E6EEF3']
width=”100%
height=”100%>

 

<mx:itemRenderer>

<mx:Component>

<mx:VBox verticalGap=”0” dataChange=”validateNow()”>

<mx:HBox>

<mx:Label text=”{data.Label}” fontWeight=”bold/>

<mx:Label text=”{data.date}” color=”#999999/>

</mx:HBox>
<mx:Text text=”{data.description}” width=”100%/>

</mx:VBox>

</mx:Component>

</mx:itemRenderer>

 

</mx:List>








Tags: , , ,

20 Responses to “Update: Solution to disappearing data in your itemRenderer”

  1. Tim Erwin Says:

    I tried your solution and I see its working nicely for you, but I couldn’t get it to work. I am very frustrated by this problem. I am using the AdvancedDataGrid:

    <mx:AdvancedDataGrid allowMultipleSelection="false" selectable="false" displayItemsExpanded="true" alternatingItemColors="[0xffffff,0xe0e0e0]" width="900" height="80%">
                   <mx:dataProvider>
                      <mx:HierarchicalData source="{ax}" />
                   </mx:dataProvider>
                   <mx:columns>
                      <mx:AdvancedDataGridColumn dataField="@ReqNum"    headerText="Number" width="100" />
                      <mx:AdvancedDataGridColumn dataField="@ReqText" headerText="Description" width="400">
                         <mx:itemRenderer>
                            <mx:Component>
                               <mx:VBox verticalGap="0" height="100">
                                  <mx:Text styleName="softLabel" text="{data.@ReqText}" width="100%" />
                                  <mx:Text styleName="softLabel" text="{data.@ReqNotes}" color="blue" width="100%" />
                               </mx:VBox>
                            </mx:Component>
                         </mx:itemRenderer>
                      </mx:AdvancedDataGridColumn>
                      <mx:AdvancedDataGridColumn headerText="Response" />
                   </mx:columns>
    
                </mx:AdvancedDataGrid> 
    
  2. Tim Erwin Says:

    P.S> in that example I use an explicit height, but I tried it with validateNow( ) too

  3. Kalen Gibbons Says:

    Hi Tim,
    It looks like the problem is that you’re missing the “variableRowHeight” property on your AdvancedDataGrid. You need to set variableRowHeight to true for the grid to allow dynamic row heights. Furthermore, if you have a long string that you want to wrap and take up multiple lines, you’ll have to set “wordWrap” to true as well.

    <mx:AdvancedDataGrid variableRowHeight="true" wordWrap="true" … ></mx:AdvancedDataGrid>

  4. Tim Erwin Says:

    Thanks so much for your email response! I tried that solution but I wasn’t able to get that to work for me — but I was able to fix the problem by using an explicit width on the mx:component — now it looks like it works perfectly like your example =)

    Here is example code:

    <mx:AdvancedDataGrid sortExpertMode="true" variableRowHeight="true" selectable="false" displayItemsExpanded="true" alternatingItemColors="[0xffffff,0xe0e0e0]" height="80%">
                   <mx:dataProvider>
                      <mx:HierarchicalData source="{ax}" />
                   </mx:dataProvider>
                   <mx:columns>
                      <mx:AdvancedDataGridColumn dataField="@ReqNum"    headerText="Number" width="100" />
                      <mx:AdvancedDataGridColumn dataField="@ReqText" headerText="Description" width="350">
                         <mx:itemRenderer>
                            <mx:Component>
                               <mx:VBox dataChange="validateNow( );" width="350" verticalScrollPolicy="off" horizontalScrollPolicy="off">
                                  <mx:Text styleName="softLabel" text="{data.@ReqText}" width="{width}" paddingLeft="10" paddingRight="10" />
                                  <mx:Text styleName="softLabel" text="{data.@ReqNotes}" width="{width}" color="blue" paddingLeft="10" paddingRight="10" />
                               </mx:VBox>
                            </mx:Component>
                         </mx:itemRenderer>
                      </mx:AdvancedDataGridColumn>
                      <mx:AdvancedDataGridColumn headerText="Response" width="300">
    
                      </mx:AdvancedDataGridColumn>
                   </mx:columns>
    
                </mx:AdvancedDataGrid> 
    
  5. Tim Erwin Says:

    Sorry, I am forgetting things =( The original example I sent you was jacked up, I think I messed it up in my frustration and posted it wrong. I think I had tried variableRowHeight — it was just messing up all the heights until I put the explicit width as I said.

    Thanks again

  6. Jose Says:

    Hello! I just got the same problem and I’m not able to fix it! The fact is that I’ve got a list within some custom components. Each component has to states: short and large. When I go from short to large I happens to me the same problem. Do you have any idea? I tried the validateNow() method, setting all the width to straight number etc…

    If you want I can send you my code, the website location is http://josegranja.site40.net/bin-debug/newWebsite.html so you can know what the trouble is. Any help would be really apreciate :)

    kind regards,

    jose

  7. Kalen Gibbons Says:

    Hi Jose,
    Thanks for sending the link. I can see that there are multiple symptoms here. The most obvious appears when you expand the first node and scroll down, and all of a sudden the bottom node is expanded and the first one isn’t. This is something that I hope to write about soon, and it’s a result of how List components recycle their children. You can read more about it here: http://www.adobe.com/devnet/flex/articles/itemrenderers_pt1.html

    What you’ll want to do is store the status of each node (eg. opened or closed) in an array in the parent document. Then, on the dataChange event check to see if the node should be expanded or not. Here is an example of how I’ve used this technique in the past to track whether a checkbox has been selected or not:

    <mx:Script>
    	<![CDATA[
    
    		[Bindable]
    		public var selectedCheckboxes:ArrayCollection = new ArrayCollection();
    
    		public function toggleCheckbox(item:Object):void{
    			var index:int = selectedCheckboxes.getItemIndex(item);
    
    			if(index != -1){
    				selectedCheckboxes.removeItemAt(index);
    			}else{
    				selectedCheckboxes.addItem(item);
    			}
    		}
    
    		public function validateCheckbox(item:Object):Boolean{
    			var index:int = selectedCheckboxes.getItemIndex(item);
    
    			if(index != -1){
    				return true;
    			}else{
    				return false;
    			}
    		}
    
    	]]>
    </mx:Script>
    
    <!-- List would be here .... -->
    <mx:itemRenderer>
    	<mx:Component>
    		<mx:CheckBox click="outerDocument.toggleCheckbox(data);"
    			dataChange="this.selected=outerDocument.validateCheckbox(data)"/>
    	</mx:Component>
    </mx:itemRenderer>
    

    I hope this helps. Go ahead and send me your code, to Kalen[dot]Gibbons[at]gmail[dot]com, and I’ll take a look at it for you.

    Thanks,
    Kalen Gibbons

  8. Arianapicy Says:

    Nice ! :) .. Thanks buddy..

  9. James Says:

    Awesome – this sorted my itemrenderer issues out in a snap.

  10. Robert J. Gijsbertsen Says:

    This is Nobel Prize material, thanks a lot!

  11. jose miguel Says:

    hi, i have tried this solution in a custom item Renderer (extended from TreeItemRenderer), but i cannot get it to work:

    public class BaseTreeItemRenderer extends TreeItemRenderer {

    private var itemIcon:Image;
    private var legendIcon:Image;

    public function BaseTreeItemRenderer() {
    super();

    itemIcon = new Image();
    itemIcon.width = 16;
    itemIcon.height = 16;
    itemIcon.scaleContent = true;
    itemIcon.visible = false;

    legendIcon = new Image();
    legendIcon.width = 25;
    legendIcon.height = 25;
    legendIcon.scaleContent = true;
    legendIcon.visible = false;

    this.addEventListener(FlexEvent.DATA_CHANGE, dataChangeHandler);
    }

    private function dataChangeHandler(event:FlexEvent):void {
    validateNow();
    }

    override protected function commitProperties( ): void {
    super.commitProperties( );
    if ((data != null) && (data is TreeNode)) {
    var node:TreeNode = data as TreeNode;
    getControl().selected = node.selected;
    }
    }

    override protected function measure():void {
    super.measure();
    var w:Number = data ? (data as BaseNode).indent : 0;

    if ( disclosureIcon )
    w += disclosureIcon.width;

    if ( icon )
    w += icon.measuredWidth;

    if ( label.width < 4 || label.height measuredHeight )
    measuredHeight = icon.measuredHeight;
    }
    }

    override protected function createChildren(): void {
    super.createChildren();
    addChild(itemIcon);
    addChild(legendIcon);
    }

    override protected function updateDisplayList( unscaledWidth: Number, unscaledHeight: Number ): void {
    super.updateDisplayList( unscaledWidth, unscaledHeight );
    var node:TreeNode = data as TreeNode;
    var startx:Number = data ? (data as BaseNode).indent : 0;
    var xIncrement:Number = 3;

    if ( disclosureIcon ) {
    disclosureIcon.x = startx;
    startx = disclosureIcon.x + disclosureIcon.width;
    disclosureIcon.setActualSize( disclosureIcon.width, disclosureIcon.height );
    }

    var labelFormat:TextFormat = new TextFormat();

    if ( icon ) {
    if ((node) && (node.level == 2)) {
    icon.visible = false;
    legendIcon.visible = false;

    labelFormat.italic = true;
    label.setTextFormat(labelFormat);

    itemIcon.source = (data as BaseNode).iconSource;
    itemIcon.visible = true;
    itemIcon.x = startx + xIncrement;
    startx = itemIcon.x + itemIcon.width;
    } else if (data is LegendNode) {
    icon.visible = false;
    itemIcon.visible = false;

    legendIcon.source = (data as BaseNode).iconSource;
    legendIcon.visible = true;
    legendIcon.visible = true;
    legendIcon.x = startx + xIncrement;
    startx = legendIcon.x + legendIcon.width;
    } else {
    labelFormat.bold = true;
    labelFormat.size = 11;
    label.setTextFormat(labelFormat);

    itemIcon.visible = false;
    legendIcon.visible = false;
    icon.x = startx + xIncrement;
    startx = icon.x + icon.measuredWidth;
    icon.setActualSize(icon.measuredWidth, icon.measuredHeight);
    }
    }

    if (getControl()) {
    if ((node) && (node.level == 2)) {
    getControl().x = startx + xIncrement;
    startx = getControl().x + getControl().measuredWidth;
    getControl().setActualSize(getControl().measuredWidth, getControl().measuredHeight);
    } else {
    getControl().visible = false;
    }
    }
    label.x = startx + xIncrement;
    label.setActualSize(unscaledWidth – startx, measuredHeight );
    }

    protected function getControl():Button { // this is overriden by some renderers wich also inherit from this one
    return null;
    }

    }
    this item renderer creates a check or radio button next to the label of the item; the class that inherit from this one pass the created checkbox or radio button via the getControl method

    as you see, i have placed your solution in the dataChangeHandler but is not working; if i scroll the tree the checkbox or radio button still disappears

  12. BethL Says:

    Thank You for this simple solution! I’ve been racking my brain for 3 hours now on why my radio buttons kept losing the data when scrolling down. I tried lots of painful things and it was as simple as using validateNow() in the datagrid!
    Thank You!!!

  13. timjowers Says:

    No luck here yet. I used Flexnaut’s code for custom item renderers in a tree or list: http://flexnaut.blogspot.com/2009/10/extensible-list-item-renderers.html. Then I tried to vary the item renders. It works fine on the first expansion. It works fine as long as no scrolling occurs. Once scrolling occurs, then the nodes jump around and their layout is lost as well as the height of their node in the tree. That is, one node type is a datagrid and the other is the node type in Flexnaut’s example: an ActorRenderer. Here’s the custom item renderer with the two node types. These are selected against in the creationComplete method (as there the “data” can be checked and used to select the node type.)

    So much for using differing ItemRenderers in the same tree. I guess I’ll play with show/hide, states, or height=0 techniques.

  14. Joe Says:

    This didn’t work for me in my ActionScript class, but I came up with something similar that did the trick.


    override public function set data(value:Object):void
    {
    super.data = value;
    validateNow();
    }

    I’m not sure what the difference is between calling validateNow this way versus calling it in the data change event listener, but this worked for me while the event listener method didn’t.

  15. FlexBoz Says:

    Hello Kalen,

    I have having problems with checkboxes in my itemrenderer being checked/unchecked when scrolling in my datagrid. Grateful if you could have a look.

    My Flex application is as such:

    <!–

    –>

    And my XML file is as such:

    Bet I am missing something in the code, but dont know what. Pls help

  16. Kalen Gibbons Says:

    Hi FlexBoz,
    This is a very common problem. The problem is that itemRenderers in Lists are recycled, meaning that Flex does not create a instance of the itemRenderer for every object in your data. Instead it only creates enough for what is visible in the list at one time. These itemRenderers are then reused when a user scrolls down the list and the data is just swapped out. For a full explanation check out this article: http://www.adobe.com/devnet/flex/articles/itemrenderers_pt1.html.

    So… because the itemRenderers are re-used you can’t store instance data in them, like the state of a checkbox. Instead you need to store that information somewhere outside the renderer, typically in the data that’s being passed into the renderer. You can simply add a property to the data that will store whether the checkbox has been checked or not and then override the set data function in the itemRenderer. In the set data function you can look at the data, and manually check or uncheck that checkbox. The same article above expands on this concept a bit in the second part of the article.
    http://www.adobe.com/devnet/flex/articles/itemrenderers_pt2.html

  17. Cory Says:

    “selectable = false” fixes this too

  18. Cory Says:

    “selectable = false” on the that is.

  19. Cory Says:

    Kalen – if what you are saying is correct, then if I override my “set data” field in my itemRenderer and call invalidateProperties(), and then explicitly reset all of my data in the itemrenderer, this issue wouldn’t happen. But it still does. This has to be a bug in flex, does it not?

  20. Patrick Says:

    Don’t usually post these but when my ADG content started disappearing, I was afraid it was going to be a battle… VariableRowHeight wins!!!

Leave a Reply