9 Replies Latest reply on Mar 17, 2014 1:23 PM by rgg1880

    Overriding Spark DataGrid item renderer's prepare method - renderer's child is initially null

    rgg1880

      I am currently using the 4.12.0 SDK.  I have a Spark DataGrid setup that makes use of an externally-defined itemRenderer:

       

      <s:DataGrid id="dgEquipment"
                  width="100%" height="100%"
                  doubleClickEnabled="true"
                  creationComplete="init()" doubleClick="popTab(event)">
        <s:columns>
          <s:ArrayList>
            <s:GridColumn itemRenderer="renderers.equipment.IconRenderer"
                          dataField="EXISTING"
                          width="22"/>
         …

       

      The data provider is set programmatically after a remote call has returned a result.

       

      I have the renderer setup as follows:

       

      <?xml version="1.0" encoding="utf-8"?>
      <s:GridItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
                          xmlns:s="library://ns.adobe.com/flex/spark"
                          xmlns:mx="library://ns.adobe.com/flex/mx"
                          dataChange="init()" remove="dispose()">
        <s:layout>
          <s:VerticalLayout horizontalAlign="center" verticalAlign="middle"/>
        </s:layout>
        
        <fx:Script>
          <![CDATA[
            import mx.controls.Menu;
            import mx.events.MenuEvent;
            
            import spark.components.DataGrid;
            
            [Bindable]
            [Embed(source="../../../assets/images/Icon 1.png")]
            private var ico1:Class;
            [Bindable]
            [Embed(source="../../../assets/images/Icon 2.png")]
            private var ico2:Class;
            [Bindable]
            [Embed(source="../../../assets/images/Icon 3.png")]
            private var ico3:Class;
            
            private var isExisting:Boolean;
            private var popUp:Menu;
            
            private function init():void
            {
              if (data)
                isExisting = data.EXISTING == 1;
            }
            
            private function dispose():void
            {
              if (popUp)
              {
                popUp.removeEventListener(MenuEvent.ITEM_CLICK, popUp_click);
                
                popUp = null;
              }
              
              if (imgActions)
              {
                imgActions.removeEventListener(MouseEvent.CLICK, image_click);
                imgActions = null;
              }
            }
            
            override public function prepare(hasBeenRecycled:Boolean):void
            {
              if (data)
              {
                if ((data.TYPE == "A" || data.TYPE == "B") && !data.X && !data.Y)
                {
                  disableLink();
                  
                  imgActions.source = ico3;
                  
                  imgActions.toolTip = "Blah blah.";
                }
                else if (data.TYPE == "C" || data.TYPE == "D")
                  disableLink();
                else if (isExisting)
                {
                  imgActions.source = ico1;  //******************************            imgActions.toolTip = "More blah blah.";
                  imgActions.addEventListener(MouseEvent.CLICK, image_click);
                }
                else
                {
                  imgActions.source = ico2;
                  imgActions.addEventListener(MouseEvent.CLICK, image_click);
                  imgActions.toolTip = "Even more blah blah.";
                  
                  initPopUp();
                }
              }
            }
            
            private function initPopUp():void
            {
              …
            }
            
            private function popUp_click(event:MenuEvent):void
            {
              …
            }
            
            private function image_click(event:MouseEvent):void
            {
              …
            }
            
            …
            
            private function disableLink():void
            {
              …
            }
          ]]>
        </fx:Script>
      
        <s:Image id="imgActions"
                 height="18" width="18"/>
        
      </s:GridItemRenderer>

       

      When the code reaches the line where I have added a comment full of asterisks, I get the following error:

       

      TypeError: Error #1009: Cannot access a property or method of a null object reference.

          at renderers.equipment::IconRenderer/prepare()[C:\…\renderers\equipment\IconRenderer.mxml:81 ]

          at spark.components.gridClasses::GridViewLayout/initializeItemRenderer()[/Users/justinmclean /Documents/ApacheFlex4.12.0/frameworks/projects/spark/src/spark/components/gridClasses/Gri dViewLayout.as:1808]

          at spark.components.gridClasses::GridViewLayout/createTypicalItemRenderer()[/Users/justinmcl ean/Documents/ApacheFlex4.12.0/frameworks/projects/spark/src/spark/components/gridClasses/ GridViewLayout.as:1243]

          at spark.components.gridClasses::GridViewLayout/updateTypicalCellSizes()[/Users/justinmclean /Documents/ApacheFlex4.12.0/frameworks/projects/spark/src/spark/components/gridClasses/Gri dViewLayout.as:1374]

          at spark.components.gridClasses::GridViewLayout/measure()[/Users/justinmclean/Documents/Apac heFlex4.12.0/frameworks/projects/spark/src/spark/components/gridClasses/GridViewLayout.as: 875]

          at spark.components.supportClasses::GroupBase/measure()[/Users/justinmclean/Documents/Apache Flex4.12.0/frameworks/projects/spark/src/spark/components/supportClasses/GroupBase.as:1156 ]

          at mx.core::UIComponent/http://www.adobe.com/2006/flex/mx/internal::measureSizes()[/Users/justinmclean/Documents/ApacheFlex4.12.0/frameworks/projects/framework/src/mx/cor e/UIComponent.as:9038]

          at mx.core::UIComponent/validateSize()[/Users/justinmclean/Documents/ApacheFlex4.12.0/framew orks/projects/framework/src/mx/core/UIComponent.as:8962]

          at spark.components::Group/validateSize()[/Users/justinmclean/Documents/ApacheFlex4.12.0/fra meworks/projects/spark/src/spark/components/Group.as:1074]

          at mx.managers::LayoutManager/validateSize()[/Users/justinmclean/Documents/ApacheFlex4.12.0/ frameworks/projects/framework/src/mx/managers/LayoutManager.as:673]

          at mx.managers::LayoutManager/doPhasedInstantiation()[/Users/justinmclean/Documents/ApacheFl ex4.12.0/frameworks/projects/framework/src/mx/managers/LayoutManager.as:824]

          at mx.managers::LayoutManager/doPhasedInstantiationCallback()[/Users/justinmclean/Documents/ ApacheFlex4.12.0/frameworks/projects/framework/src/mx/managers/LayoutManager.as:1188]

       

      Running the debugger shows that this occurs with the first item in the data provider.  If I alter the prepare method to check for the existence of imgActions before doing anything, everything works fine after the first item.  So I'll have one row in the DataGrid with a missing icon, and all the rest will have icons.

       

      So the question is, is it normal for prepare to run before any children of the item renderer are created?  If so, how should I handle this?

       

      Many thanks in advance.

        • 1. Re: Overriding Spark DataGrid item renderer's prepare method - renderer's child is initially null
          rgg1880 Level 1

          A little more info.  I added some event handlers to the renderer and the image (for events that I thought would be relevant), and here is the order of events based on trace statements within the handlers:

           

          griditemrenderer1_addedHandler

          griditemrenderer1_addedToStageHandler

          griditemrenderer1_preinitializeHandler

          imgActions_addedHandler

          griditemrenderer1_addedHandler

          imgActions_addedToStageHandler

          imgActions_preinitializeHandler

          imgActions_addedHandler

          griditemrenderer1_addedHandler

          imgActions_initializeHandler

          griditemrenderer1_elementAddHandler

          imgActions_addHandler

          griditemrenderer1_initializeHandler

          griditemrenderer1_addHandler

          prepare called

          imgActions_resizeHandler

          griditemrenderer1_resizeHandler

          imgActions_creationCompleteHandler

          imgActions_updateCompleteHandler

          griditemrenderer1_creationCompleteHandler

          griditemrenderer1_updateCompleteHandler

          griditemrenderer1_removeHandler

          griditemrenderer1_addedHandler

          griditemrenderer1_addedToStageHandler

          imgActions_addedToStageHandler

          griditemrenderer1_addHandler

          griditemrenderer1_dataChangeHandlerTypeError: Error #1009: Cannot access a property or method of a null object reference.

           

          prepare called

              at renderers.equipment::IconRenderer/prepare()[C:\…\renderers\equipment\IconRenderer.mxml:91 ]

              …

          imgActions_renderHandler

          griditemrenderer1_renderHandler

          • 2. Re: Overriding Spark DataGrid item renderer's prepare method - renderer's child is initially null
            rgg1880 Level 1

            A little more info.

             

            First, imgActions is in existance before the addedToStage event occurs.

             

            Second, when the error occurs, the debugger shows the following:

             

            imgActionsnull
            this.getChildAt(0).idimgActions

             

            So apparently the image exists, but it also doesn't.

             

            Message was edited by: rgg1880 (added a table for values)

             

            Message was edited by: rgg1880 (add the first info item)

            • 3. Re: Overriding Spark DataGrid item renderer's prepare method - renderer's child is initially null
              Flex harUI Adobe Employee

              You can try binding.  Declare a bindable variable:

               

              private var imgsrc:String;

               

              Set imgsrc in your code instead of imgactions

               

              Bind imgactions:

              
                <s:Image id="imgActions" source="{imgsrc}"
                         height="18" width="18"/>
              
              
              

               

              • 4. Re: Overriding Spark DataGrid item renderer's prepare method - renderer's child is initially null
                rgg1880 Level 1

                Just that one change will still leave me with the same error on the next line, where I set the image's tool tip.  I could use binding for that as well, but the line after that one (in certain situations) adds an event listener to the image.  Almost any path through the prepare method changes the image's properties, which won't work when the image suddenly becomes null.  Also, I thought writing out a prepare method was supposed to be a more efficient alternative to binding, but it pretty much defeats the purpose if the method is called when the renderer's children aren't available.

                • 5. Re: Overriding Spark DataGrid item renderer's prepare method - renderer's child is initially null
                  Flex harUI Adobe Employee

                  You're right that binding has extra overhead.  The AS equivalent is to use try/catch to catch exceptions or check for null values before assigning.

                  • 6. Re: Overriding Spark DataGrid item renderer's prepare method - renderer's child is initially null
                    rgg1880 Level 1

                    If at the start of the prepare method I check to make sure that data isn't null and that imgActions isn't null, it will, of course, avoid any errors.  But even though prepare initially works for the first item in my DataGrid, it eventually fails for that same item, and it doesn't fix itself.  Then all the other rows turn out as expected.  I'm currently using quite a hack: at the beginning of prepare, if data isn't null, I declare a local variable of type Image.  I set it to imgActions if it exists; if it doesn't exist and the first child of the renderer has an id of "imgActions," I set the variable to that child instead.  And so far it has worked.  It just makes a programmer feel like an English teacher reading an essay full of botched grammar.

                    • 8. Re: Overriding Spark DataGrid item renderer's prepare method - renderer's child is initially null
                      rgg1880 Level 1

                      I got it down to this, and everything is working fine:

                      <!--?xml version="1.0" encoding="utf-8"?-->
                      <s:GridItemRenderer 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 spark.components.Image;
                            
                            [Bindable]
                            [Embed(source="../../../assets/images/Icon 1.png")]
                            private var ico1:Class;
                            [Bindable]
                            [Embed(source="../../../assets/images/Icon 2.png")]
                            private var ico2:Class;
                            [Bindable]
                            [Embed(source="../../../assets/images/Icon 3.png")]
                            private var ico3:Class;
                            
                            override public function prepare(hasBeenRecycled:Boolean):void
                            {
                              if (data)
                              {
                                if ((data.TYPE == "A" || data.TYPE == "B") && !data.X && !data.Y)
                                {
                                  imgActions.source = ico3;
                                  imgActions.toolTip = "Blah blah.";
                                }
                                else if (data.EXISTING == 1)
                                {
                                  imgActions.source = ico1;
                                  imgActions.toolTip = "More blah blah.";
                                }
                                else
                                {
                                  imgActions.source = ico2;
                                  imgActions.toolTip = "Even more blah blah.";
                                }
                              }
                            }
                          ]]>
                        </fx:Script>
                        
                        <s:Image id="imgActions"
                                 height="18" width="18"/>
                      </s:GridItemRenderer>

                       

                      I'm guessing the issue was with my disableLink() function, which modified some of imgActions' properties and (I assume) forced it to redraw.

                      • 9. Re: Overriding Spark DataGrid item renderer's prepare method - renderer's child is initially null
                        rgg1880 Level 1

                        Actually, the disableLink() function doesn't run before the null exception occurs.  I did, however, set a handler for the "remove" event on the GridItemRenderer, and that handler sets the image to null.  Apparently the renderer is removed before it's actually used, and when it's brought back, the image is still null.