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>
<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?!



If Object.isInCollection("ThisObjectName") then . . .
I suppose I could just do an Object.finished() or something?
<cfloop condition "#Object.isLast()#">
...
Hard to think of anything that doesn't start with "not", like notFinished().
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!!!
or "Object.EOC()" and your cfloop would be:
<cfloop condition="#NOT Object.EOC()#">
...means you would keep away from ye olde double negatives...
<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...
Of course, this makes it look more like ASP, but I think that is true any time that you are doing iterators anyway.
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!!!
So you'd just do:
<cfloop condition="#oMyCollection.next()#">
--
Adam
<cfloop condition="NOT Object.pastFirst() AND NOT Object.pastLast()">
...
<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...
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.
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>
@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!
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.