11 Replies Latest reply on Oct 3, 2006 12:57 PM by Mr Black

    Array of Structures problem

    BINPA Level 1
      I am trying to create a chronological event calendar from events drawn from three different queries. The following code creates the array and populates it properly:

      <CFSET EventArray=ArrayNew(1)>
      <CFSET EventInfo=StructNew()>
      <CFSET ArrIndex=1>

      <CFLOOP QUERY="EventQuery1">
      <CFSET EventInfo.ID=EventQuery1.ID>
      <CFSET EventInfo.StartDate=EventQuery1.StartDate>
      <CFSET EventInfo.EventType=EventQuery1.EventType>
      <CFSET EventArray[ArrIndex]=EventInfo>
      <CFSET ArrIndex=ArrIndex+1>
      </CFLOOP>

      <CFLOOP QUERY="EventQuery2">
      <CFSET EventInfo.ID=EventQuery2.ID>
      <CFSET EventInfo.StartDate=EventQuery2.StartDate>
      <CFSET EventInfo.EventType=EventQuery2.EventType>
      <CFSET EventArray[ArrIndex]=EventInfo>
      <CFSET ArrIndex=ArrIndex+1>
      </CFLOOP>

      <CFLOOP QUERY="EventQuery3">
      <CFSET EventInfo.ID=EventQuery3.ID>
      <CFSET EventInfo.StartDate=EventQuery3.StartDate>
      <CFSET EventInfo.EventType=EventQuery3.EventType>
      <CFSET EventArray[ArrIndex]=EventInfo>
      <CFSET ArrIndex=ArrIndex+1>
      </CFLOOP>

      The problem I'm having is that when I try to loop through the fully populated array, I can only retrieve data from the last array entry. For example, the following code will only display the StartDate of the last event read:

      <CFSET LoopCount = ArrIndex-1>

      <CFLOOP CONDITION="LoopCount GREATER THAN OR EQUAL TO 1">
      <CFOUTPUT>#LoopCount#</CFOUTPUT> &nbsp
      <CFOUTPUT>#EventArray[LoopCount].StartDate#</CFOUTPUT>
      <CFSET LoopCount=LoopCount-1>
      </CFLOOP>

      How can I get this loop to read all array entries? My ultimate goal is to display all events chronologically by StartDate.
        • 1. Re: Array of Structures problem
          Dan Bracuk Level 5
          From a syntax perspective, when you loop through a query using cfloop (as opposed to cfoutput), you have to specfify the rownumber. The syntax is queryname.fieldname[rownumber]

          Given your ultimate goal, your approach seems way too complicated. The fact that you need 3 queries seems suspicious, but, if they really are necessary, you could alway use Q of Q to combine them into one query. Then you probably would not need an array of structures.
          • 2. Re: Array of Structures problem
            BINPA Level 1
            The loop that is causing me problems is over an array (of structs), not a query.

            I using a single query to retrieve the data, but ran into problems with that approach, too. I tried to compare the EventInfo.EventType elements with know valid constants, but could never get the results I expected. I figured that the table type I'm using (DBase IV) added some null characters, but haven't been able to prove it.
            • 3. Re: Array of Structures problem
              davidsimms Level 1
              BINPA:

              Dan's right that a query of queries seems like it might be more logical here, but in any event, you're overwriting your array values for each CFLOOP tag. In other words, if you want output from EventQuery2 to append to the array (rather than to overwrite values just set by EventQuery1), you need to ensure that you're dynamically generating a new index position for each iteration, of each CFLOOP occurence in the code block. Ditto for output from EventQuery3 overwriting values set by EventQuery2 (which explains why you can only output values set by EventQuery3).

              • 4. Re: Array of Structures problem
                BINPA Level 1
                davidsimms:

                I thought the lines:

                <CFSET EventArray[ArrIndex]=EventInfo>
                <CFSET ArrIndex=ArrIndex+1>


                at the end of each loop would be enough to ensure that each array index position was unique. Is there something else I need?
                • 5. Re: Array of Structures problem
                  Level 7
                  What do you get if you <cfdump var="#EventArray#"> after you are done
                  building the array.
                  • 6. Re: Array of Structures problem
                    Level 7
                    You may also be running into an pass by reference problem.

                    When you create the structure with the <cfset EventInfo = StructNew()>
                    This creates a structure, assignes it some memory and references the
                    variable name 'eventInfo' to that memory location.

                    When you then Assign the eventinfo to an element of the eventarray, it
                    just assigns another pointer to the same memory. So all your eventArray
                    elements point to the same EventInfo memory location which has the last
                    set of data in it. I suspect you will see this when you dump the array.

                    Try this
                    <CFSET EventArray[ArrIndex]=dupliate(EventInfo)>
                    • 7. Re: Array of Structures problem
                      MikerRoo Level 1
                      Any time you find yourself repeating blocks of code, alarm bells should go off on your head.
                      Think modular code and functions! (The previous arguments about Q of Q, etc., are good too.)

                      Look at the attached code. It:
                      (1) Works (avoids the pass by reference issue)
                      (2) Is shorter.
                      (3) Is modular -- meaning it's easier to maintain.

                      • 8. Re: Array of Structures problem
                        Level 7
                        <CFFUNCTION name="AddEventQryToArray" returntype="array">
                        <CFARGUMENT name="aTargetArray" type="array" required="yes">
                        <CFARGUMENT name="qSrcQry" type="query" required="yes"
                        hint="Must
                        contain columns: ID, StartDate, and EventType">

                        <CFSET var zTmpStruct = StructNew()><!--- create struct here --->

                        <CFLOOP query="arguments.qSrcQry">
                        <CFSCRIPT>
                        zTmpStruct.ID = arguments.qSrcQry.ID;
                        zTmpStruct.StartDate = arguments.qSrcQry.StartDate;
                        zTmpStruct.EventType = arguments.qSrcQry.EventType;
                        ArrayAppend (arguments.aTargetArray, zTmpStruct);
                        </CFSCRIPT>
                        </CFLOOP>

                        <CFRETURN arguments.aTargetArray>
                        </CFFUNCTION>

                        Just to be a bit nitpicky, the function code provide by MikerRoo avoided
                        the pass by reference problem by defining a new struct with the
                        structNew function for each iteration of the loop inside the function.
                        Which is the way I would have done it as well.

                        But, in order to avoid doing real work, I wanted to point out that with
                        the small change provide above, this User Defined Function (UDF) would
                        suffer a similar pass by reference issue. So it was MikerRoo's choice
                        of where to create a new structure, rather then his choice of doing it
                        in a UDF that resolved the pass by reference issue.

                        All other points about using an UDF are very valid.



                        • 9. Re: Array of Structures problem
                          Level 7
                          > From a syntax perspective, when you loop through a query using cfloop (as
                          > opposed to cfoutput), you have to specfify the rownumber.

                          No you do not.

                          --
                          Adam
                          • 10. Re: Array of Structures problem
                            Level 7
                            On Mon, 02 Oct 2006 13:14:07 -0700, Ian Skinner wrote:

                            > You may also be running into an pass by reference problem.

                            Yep. That's what it looks like to me.

                            > Try this
                            > <CFSET EventArray[ArrIndex]=dupliate(EventInfo)>

                            Typo aside, that's pretty much it, I reckon. Although I'd dispense with
                            maintaining the ArrIndex variable, which can be replaced with simply using
                            arrayAppend(). The counter seems to be serving no purpose other than
                            pointing to the end of the array: CF already knows where that is, so
                            there's no need to keep track of it separately.

                            I concur with everyone who says the whole construct (ie: the originally
                            posted code) looks a bit clunky though.

                            Why are you building an array of identically-keyed structs out of a query,
                            instead of leaving it as a query? As someone else said, if you MUST have
                            three separate queries (doubtful), then just QoQ them together. Better
                            yet, UNION them at database level.

                            --
                            Adam
                            • 11. Re: Array of Structures problem
                              Mr Black Level 1
                              This is the result of "smart" Cold Fusion design, where everything is passed by value, except structures...

                              Just change your code slightly. Move StructNew() inside the loop like this:

                              <CFSET ArrIndex=1>
                              <CFLOOP .. .. .>
                              <CFSET EventArray[ArrIndex]=StructNew()>
                              <CFSET EventArray[ArrIndex].ID=EventQuery1.ID>
                              . . . . . . . . . . . . . . . .
                              <CFSET ArrIndex=ArrIndex+1>
                              </CFLOOP>