By Peter Bell

A Simple Iterator Question . . .

OK, so I'm having an unreasonably hard time with this. I want to do an iterator that can be called using CF tags (in and html template) and I'm trying to come up with a non-hacky solution.

Right now I have object.first(), object.next() and object.hasMore(), and my display code is something like:

<cfset Object.first() />
<cfloop condition="#Object.hasMore()#">

DISPLAY HERE

<cfset Object.next()>
</cfloop>

Of course, though I don't want to only loop while hasMore() is true, as for the last record hasMore() is false, so unless I compare to (Recordcount + 1) which is a little hacky (and what I did in the past) it comes up displaying one record short.

I must use cf tags (not cf script) and am trying to think of a clean name the condition method currently called hasMore() which would accurately describe what is going on.

Any ideas?!

Comments
Object.isInCollection() ?
# Posted By Steve Bryant | 2/20/07 4:56 PM
Hmm, maybe. Kind of sounds like a search to me though - I'd expect that to be something like:

If Object.isInCollection("ThisObjectName") then . . .

I suppose I could just do an Object.finished() or something?
# Posted By Peter Bell | 2/20/07 4:59 PM
Why not just do an Object.isLast() that returns a boolean? It is still a little clunky but...

<cfloop condition "#Object.isLast()#">
...
# Posted By Ron Alexander | 2/20/07 5:13 PM
Good point. finished() sounds like it would return a false where hasMore() returns true though.

Hard to think of anything that doesn't start with "not", like notFinished().
# Posted By Steve Bryant | 2/20/07 5:14 PM
Hi Ron,

That was my first solution way back. Problem is that it is misleading for same reason as hasMore(). The truth is that I don't want to do while isLast - I want to do while BEYOND isLast otherwise if I did what my language suggested, if I had four records, I'd display the first three and then quit. Easy to fix with a plus 1 hack, but not very good programming (although that is how my IBO has worked to date).

Steve - actually, I kind of like that. I'll just put the NOT into the conditional:

<cfset Object.first()>
<cfloop condition="NOT #Object.finished()#">

do whatever

<cfset Object.next()>
</cfloop>

That actually works for me - thanks Steve!!!
# Posted By Peter Bell | 2/20/07 5:23 PM
How about "Object.notEOC()" meaning "End Of Collection"?

or "Object.EOC()" and your cfloop would be:
<cfloop condition="#NOT Object.EOC()#">

...means you would keep away from ye olde double negatives...
# Posted By David Harris | 2/20/07 5:23 PM
When I took a crack at rebuilding your IBO after it was first released, I settled on "IsRecord()". Of course, this suffers from the same "search-esque" sound that IsInCollection() has??
# Posted By Ben Nadel | 2/20/07 5:26 PM
What I meant to type was:

<cfloop condition="NOT #Object.isLast()">
...

It even matches Sun's idea of how to do it: http://java.sun.com/javase/6/docs/api/java/sql/Res...
I guess that could make it good or bad for you depending on how you feel about Java.

Plus somebody smart once said it was a good way to deal with iterating business objects:
http://www.pbell.com/index.cfm/2006/9/17/Partially...
# Posted By Ron Alexander | 2/20/07 5:27 PM
I actually like David's idea best. EOC has already been used for exactly this purpose.

Of course, this makes it look more like ASP, but I think that is true any time that you are doing iterators anyway.
# Posted By Steve Bryant | 2/20/07 5:28 PM
Peter -- Gotcha. Steve's solution looks pretty good and is very readable.
# Posted By Ron Alexander | 2/20/07 5:30 PM
Hi Ron,

Yeah - I liked the concept of isLast(), but it just doesn't work in a CF for loop. Actually, I came really close to implementing this using Sean's closures library but because it is doing some templating stuff for each record I decided it wasn't best use case (that and having to write to hard drive behind the scenes!). Thanks for finding the old link - what can I say - things change quickly round these parts!!!

David, that is also a really good solution. finished and EOC are both to me communicating the same intent, so I'm going to go with the less computery sounding "finished" which I think expresses intent slightly more directly (what we care after all is that you keep going until you are finished - describing it as being end of collection is simply sharing the implementation detail as to how we know we are finished). But I agree with Steve that it is a cool approach and probably just a matter of preference.

I guess other words would be something like done() or completed(), but finished() works for me for now. Many thanks all for playing!!!
# Posted By Peter Bell | 2/20/07 5:36 PM
@Ben, Yeah, firstly it sounds a little searchy and secondly it sounds a little railsy and while I like DHH, I don't love his naming choices! Still, I think it is better than my original cut, so let me know what you think about finished() and if you come up with anything you like even more!
# Posted By Peter Bell | 2/20/07 5:38 PM
And Ron, funnily I got both the isLast() and hasMore() from Java - just happens that they don't work well with a cfloop condition.
# Posted By Peter Bell | 2/20/07 5:39 PM
Shouldn't next() return false if there is no "next" item in the collection?

So you'd just do:

<cfloop condition="#oMyCollection.next()#">

--
Adam
# Posted By Adam Cameron | 2/20/07 5:56 PM
Peter -- What if you want to implement Object.last() and/or Object.prev()? Then will finished mean you are at either end of the Object or will it only mean you are at the hind end. Maybe pastFirst() and pastLast()
<cfloop condition="NOT Object.pastFirst() AND NOT Object.pastLast()">
...
# Posted By Ron Alexander | 2/20/07 5:58 PM
condition="foo.hasNext() or foo.isLast"
# Posted By Daniel Roberts | 2/20/07 6:52 PM
Peter, isn't this really more of an issue with where your Object.next() call is? Seems like it would be easy to do this:

<cfloop condition="#Object.hasMore()#">
<cfset Object.next() />

DISPLAY HERE

</cfloop>

Or did I miss an objection to this approach in one of your myriad other posts? I think this also has the advantage of being clearer if your iterator is empty: Object.first() is problematic with an empty iterator...
# Posted By Christopher Bradford | 2/20/07 7:25 PM
I think the simple solution is to do away with requiring the first() method to initialize the collection. The collection could be automatically initialized to it upon a call to object.next() as Christopher mentioned. You only use first() if you want to return to the first record, for some reason. In either case, it removes some of the semantics from having to "know" you need to call first() before doing anything else.

I don't know if I've missed something, but that seems nice to me.

In addition, I think the finished() has the same problem as hasMore. When you are going to visit the "next" record at the end of the loop, it will still be "finished" on the next iteration.
# Posted By Sam | 2/20/07 8:14 PM
I'd vote for the Java Iterator interface which seems to make sense: http://java.sun.com/j2se/1.4.2/docs/api/java/util/...

In this interface, hasNext() returns True as long as the next() call would return something. This is True before you start iterating ( when next() will return the first element) and just before the last element. In this interface next() would throw an exception if there is nothing left to return, not return a false as suggested by Adam.

To agree with CB in the last post, your example then becomes:

<!--- not needed as the first call to next() returns the first element
<cfset Object.first() />
--->
<cfloop condition="#Object.hasNext()#">
<cfset Object.next()>
DISPLAY HERE
</cfloop>
# Posted By Chip Temm | 2/20/07 8:42 PM
@Ron, Yep - thought about that, but still sounded a little hacky even though accurate.

@Daniel, Sorry - see above - doesn't work the way the code is currently structured.

@Christopher, Yes and no. Reason I couldn't put it upfront was my obejct.first was taking me to object 1, so putting next at top would take me straight to object 2, so I'd see objects 2, 3, and 4 but not 1!

After all the input above and some serious help from Mark Mandell over IM, have come to the following solution:

rename .first as .reset - setting pointer to "record 0"
use my next() loop as both command and query (usually not best practice, but OK in this case_

<cfset Object.reset()>
<cfloop condition="#Object.next()#">

DO SOMETHING

</cfloop>

This would work just fine and simplify the interface. Condition both advances to next and reports whether succeeded. Let me play with this and check it works out!
# Posted By Peter Bell | 2/20/07 9:12 PM
Sam/Chip,

I have gone this way - collapsing hasNext() and next() into a single call which I get isn't generally good from a command/query separation perspective, but I can live with the hack for the brevity in this use case :->

I still needed a reset() method as potentially I might want to loop through the same collection multiple times in a page request (once to display errors, another time to display form fields, or once to validate and another time to display or whatever) so I did have to leave that in, but making it a reset() rather than a misleadingly named first() (as it takes me to BEFORE the first record so the first next() will take me to record # 1) improved the solution.
# Posted By Peter Bell | 2/20/07 9:24 PM
MANY THANKS EVERYONE FOR ALL THE GREAT INPUT!!!!!
# Posted By Peter Bell | 2/20/07 9:25 PM
BlogCFC was created by Raymond Camden. This blog is running version 5.005.