1

I have a list that uses a itemRendererFunction to get custom item renderers. Is there a way to only reevaluate one item in the list and get different item renderer?

imagine code like this :

there are two item renderers ItemRendererOne.mxml and ItemRendererTwo.mxml

Data.as

package {

    public class Data {

        private var _data : Boolean = false;

        [Bindable]
        public function get data():Boolean {
            return _data;
        }

        public function set data(value:Boolean) : void {
            _data = value;
        }

        public function Data(value : Boolean) {
            _data = value;
        }
    }
}

and main app is

<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" 
                       xmlns:s="library://ns.adobe.com/flex/spark" 
                       xmlns:mx="library://ns.adobe.com/flex/mx" >
    <fx:Script>
        <![CDATA[
            import mx.collections.ArrayList;

            private var dataProvider : ArrayList = new ArrayList([new Data(true), new Data(false)]); 

            public function getItemRenderer(data : Object) : IFactory {
                if (data.data) {
                    return new ClassFactory(ItemRendererOne);
                } else {
                    return new ClassFactory(ItemRendererTwo);
                }
            }

            protected function button1_clickHandler(event : MouseEvent) : void {
                var data : Data = dataProvider.getItemAt(0) as Data;
                data.data = !data.data;
            }

        ]]>
    </fx:Script>

    <s:VGroup>
        <s:List dataProvider="{dataProvider}" itemRendererFunction="{getItemRenderer}" />

        <s:Button click="button1_clickHandler(event)" />
    </s:VGroup>

</s:WindowedApplication>

Basically when someone clicks on the button, I want getItemRenderer to be called again and the item in the list is updated with new item renderer.

Sang Park
  • 382
  • 2
  • 17
  • Not positive about this so I'm not posting it as an answer, but have you tried myListId.invalidateList() believe this will cause it to call the itemRendererFunction again for each element in the list. – shaunhusain Jan 30 '12 at 22:42
  • (I'm guessing you mean invalidateDisplayList()) doesn't seem to work. only way I got item renderer to refresh was to reset the data provider entirely. but that causes all item to be redraw and caused a flicker which I do not want – Sang Park Jan 30 '12 at 22:50
  • I actually did mean invalidateList() but I'm also coming from a mostly Flex 3 perspective, believe that was an available method on datagrid (if not it must have been advanced data grid) but it caused the items in the list to get invalidation called on them I believe, had helped us in the past with similar scenarios. If you still haven't found a viable solution respond again and I'll try to build this out and figure out what needs to be done. – shaunhusain Feb 01 '12 at 19:29
  • yeah it looks like there is invalidateList in Flex 3, but not in Flex 4. I ended up doing something similar to the solution below which worked out ok. – Sang Park Feb 01 '12 at 23:24

1 Answers1

1

Not sure about flex 4 as i mostly work in flex 3 but the set up should be close.
Basically, let the render handle the rendering.

Your list should look something like this.

There are 3 very important overrides you can do that will control the way a renderer can react to data.
1) set data is for when new data is assigned
2) createChildren will allow you to dynamically create children. I believe this is the function you are looking for in conjunction with set data
3) commitProperties this is where you do you assigning data to the children that are hard coded like my label example or the dynamically created children.

What I have done in the past is similar.
I test the data agains the current layout if it isn't the correct layout I rebuild it.
So in set data method I test if layout is wrong I call createChildren().
I suppose you could test the data structure on the old data before calling super.data in set data, but I didn't have good results like that.

myRenderer.mxml

<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml" >
    <mx:Script>
      <![CDATA[
        override public function set data(val:Object):void{
          super.data = val;
          if( !val ){
            return;
          }
          // test data here if current layout is wrong rebuild it by calling createchildren
        }
        override protected function createChildren():void{
          super.createChildren()
          if( this.data == null ){
            return;
          }
          // create your data driven layout here
        }   
        override protected function commitProperties():void{
          super.commitProperties();
          if ( data == null ){
            return;
          }
        }

       ]]>
    </mx:Script>
    <mx:Label id="myLabel" text="{data.someLabelVar} />
</mx:VBox>
The_asMan
  • 6,364
  • 4
  • 23
  • 34
  • I was thinking about doing this. Technically this wouldn't be too hard if I used multiple states. However my item renderes are pretty complex, written in actionscript, and uses custom layout... whole reason I went with AS item renderer is to make it light and i think adding all that logic into the item renderer would negate the benefit.. – Sang Park Jan 30 '12 at 23:59
  • you use your item renderer to create your classes(ItemRendererOne and ItemRendererTwo) and add them as children to the renderer :) Its easier then you think you already have half the work done. Oh yeah and no need for states. – The_asMan Jan 31 '12 at 00:29
  • hmm interesting.. that would work... but it does introduce another layer of UIComponent. Also that way, each item renderer will have all of my custom renderers which seems redundant. – Sang Park Jan 31 '12 at 00:37
  • You should only have 1 item renderer. this renderer will load a different "view" dependant on data. – The_asMan Jan 31 '12 at 00:41
  • yeah i ended up doing something like this.. I'm still not a big fan of having multiple views within the item renderer. but it allows better object pooling and makes my life easier. What I don't understand is why you are calling createChildren(). you should override createChildren() to create the sub components but you shouldn't have to call it manually. or am I missing something here? – Sang Park Feb 01 '12 at 23:33
  • Generally I always call the super function unless I specifically know what that function does internally. In this case commitProperties would be called on the VBox. It wont hurt to call it. Also in this case I don't want to override the function I just want to add on to the functionality of it, by overriding it then calling super I achieve that. Basically unless you want to halt the default behavior of a function you should "always" call super. – The_asMan Feb 02 '12 at 16:31
  • No calling super is perfectly fine and is required for my case. What i was asking is why you want to call createChildren() from set data. u mention that you want to call createChildren() in set data in order to rebuild the layout which doesn't sound right to me. You should update the layout in commitProperties() like how state transition is applied. in fact, if you look at the documentation of createChildren(), it specifically states that it should not be called directly. – Sang Park Feb 02 '12 at 19:18
  • In this case the children/view will be changed dependent on the current data provided. For example when your data structure changes you need to instantiate a different view at this time you have to recreate the children of the item renderer. The children of the renderer are based off of the data provided. That is why you call create children from the data set method. – The_asMan Feb 02 '12 at 21:00
  • Yes you are correct you shouldn't call it directly I know of a couple reasons why you shouldn't however you have no choice since you have to create new children with different data structures. – The_asMan Feb 02 '12 at 21:03
  • but wouldn't proper way be creating all children in createChildren() and then add/remove from display list on commitProperties? It almost sounds like you destroy current child view and create (or recreate) new child view whenever the data changes. I actually don't even override the create children. I invalidateProperties() when data is changed and swap the views out in commitProperties() and each views are created on demand using getters. – Sang Park Feb 02 '12 at 21:19
  • Very good point looks like we both learned something here :) now I must go update old code :( – The_asMan Feb 02 '12 at 21:44