Velocity Tools
  1. Velocity Tools
  2. VELTOOLS-124

LoopTool fails to retrieve $loop.index, $loop.first etc for the last element in a collection.

    Details

    • Type: Bug Bug
    • Status: Resolved
    • Priority: Major Major
    • Resolution: Fixed
    • Affects Version/s: 2.x
    • Fix Version/s: 2.0, 2.x
    • Component/s: GenericTools
    • Labels:
      None
    • Environment:
      velocity-tools-2.0-beta4 with velocity-1.6.2

      Description

      $loop.index, $loop.first and other LoopTool methods (that depend on iterator) return null for the last element in a collection.

      Try to run:
      #set( $list = [1..3] )
      #foreach($i in $loop.watch($list))
      i[$

      {loop.index}]=$i;
      #end

      The result is:
      i[0]=1; i[1]=2; i[${loop.index}

      ]=3;

      I tried to investigate it a little, and seems like the problem is inside "cacheNext(boolean popWhenDone)".

      First it checks if (!iterator.hasNext()) and if not then removes this iterator from the stack. But later in this method it retrieves next element from a collection "this.next = iterator.next();". Seems like it causes removing iterator too early, when calling $

      {loop.index}

      on the last element the iterator is already removed.

      Thanks.

        Activity

        Hide
        Nathan Bubna added a comment -

        Unfortunately, there is no alternative time to pop the last iterator from the stack. There is no way to have the #foreach directive notify the LoopTool that #end was reached. It's just not possible without hacking Velocity Engine itself.

        However, Velocity 1.7 will soon have a beta release and hopefully a final release shortly thereafter. With that release is the advent of the $foreach "control" object. That will provide $foreach.count $foreach.index $foreach.last $foreach.first and $foreach.hasNext to all #foreach loops. It will also provide access to $foreach.parent and $foreach.parent.parent and so on up to $foreach.topmost to provide access to all those properties in nested situations. Oh, and the #break directive is upgraded to accept a #break( $foreach.parent ) argument and so on.

        All that is to say that the LoopTool will soon be only useful for the sync, skip and stop condition features, which are not affected by this bug.

        Still, in the short term, i've got a solution for those wishing to always have accurate index, count, etc properties. I've provided access to the underlying ManagedIterator wrapper:

        #foreach( $i in $loop.watch([1..3]) )
        #set( $this = $loop.this )
        i[$this.index]=$i;
        #end

        The $loop.this reference will always provide the ManagedIterator from the most recent $loop.watch() call. Along with this, i have actually "fixed" this specific bug in some situations by using that "last" iterator reference. So, in simple situations like the example you gave, things should work fine. However, in nested loop situations, things get messy once you start getting beyond the first #end on the final iteration(s) of the nested #foreach calls. Your only reliable bet in that situation is to grab the $loop.this reference at the top of each #foreach and use those to get what you need or to otherwise not reference $loop properties directly after the first #end.

        Though, again, for index/count/first/last/hasNext properties, the only proper solution is the upcoming $foreach reference in Velocity 1.7. Once $foreach is well established, those properties of $loop should probably just be removed.

        Show
        Nathan Bubna added a comment - Unfortunately, there is no alternative time to pop the last iterator from the stack. There is no way to have the #foreach directive notify the LoopTool that #end was reached. It's just not possible without hacking Velocity Engine itself. However, Velocity 1.7 will soon have a beta release and hopefully a final release shortly thereafter. With that release is the advent of the $foreach "control" object. That will provide $foreach.count $foreach.index $foreach.last $foreach.first and $foreach.hasNext to all #foreach loops. It will also provide access to $foreach.parent and $foreach.parent.parent and so on up to $foreach.topmost to provide access to all those properties in nested situations. Oh, and the #break directive is upgraded to accept a #break( $foreach.parent ) argument and so on. All that is to say that the LoopTool will soon be only useful for the sync, skip and stop condition features, which are not affected by this bug. Still, in the short term, i've got a solution for those wishing to always have accurate index, count, etc properties. I've provided access to the underlying ManagedIterator wrapper: #foreach( $i in $loop.watch( [1..3] ) ) #set( $this = $loop.this ) i [$this.index] =$i; #end The $loop.this reference will always provide the ManagedIterator from the most recent $loop.watch() call. Along with this, i have actually "fixed" this specific bug in some situations by using that "last" iterator reference. So, in simple situations like the example you gave, things should work fine. However, in nested loop situations, things get messy once you start getting beyond the first #end on the final iteration(s) of the nested #foreach calls. Your only reliable bet in that situation is to grab the $loop.this reference at the top of each #foreach and use those to get what you need or to otherwise not reference $loop properties directly after the first #end. Though, again, for index/count/first/last/hasNext properties, the only proper solution is the upcoming $foreach reference in Velocity 1.7. Once $foreach is well established, those properties of $loop should probably just be removed.

          People

          • Assignee:
            Unassigned
            Reporter:
            Sergiy Kovalchuk
          • Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved:

              Development