Thursday, 17 October 2013

CFML: listEach() on CFLib

G'day:
This one cropped up because of something Rob Glover mentioned in passing on Twitter, which I gave a fairly perfunctory response to.

Scott more helpfully pointed out that converting the list to an array and looping that is a good approach. I agree.

However this got me thinking: a list-iterating UDF would be handy, and CFLib doesn't seem to have one, so I knocked one together:

public array function listEach(required string list, required function callback, string delimiters=","){
    var arr = listToArray(list, delimiters);
    var arrLen = arrayLen(arr);
    var result = [];
    for (var index=1; index <= arrLen; index++){
        arrayAppend(result, callBack(argumentCollection=arguments, index=index, element=arr[index], length=arrLen));
    }
    return result;
}

There's nothing exciting here, it just takes a list, loops over it, and passes each element to a callback to process it. It'll return an updated array if that's helpful. I chose an array instead of a list because it's more useful, and allows non-simple values to be returned.

This follows the precedent set in my recent arraySetEach() UDF, passing all the passed-in args to the callback, plus the position the iterating index is at, and the value, and in this case the length of the list being processed.

It can be used both to create a new, transformed list:

numbers = "tahi|rua|toru|wha";

function _ucase(){
    return ucase(element);
}

upperCasedNumbers = listEach(numbers, _ucase, "|");
writeOutput(serializeJson(upperCasedNumbers));

Output:

["TAHI","RUA","TORU","WHA"]

Or just as a looping construct. Here we turn the list into an <ol>:

function toOL(){
    if (index == 1){
        writeOutput("<ol>");
    }
    writeOutput('<li id="index_#index#">#element#</li>');
    if (index == length){
        writeOutput("</ol>");
    }
}


listEach(numbers, toOL, "|");

Which outputs mark-up:

<ol>
    <li id="index_1">tahi</li>
    <li id="index_2">rua</li>
    <li id="index_3">toru</li>
    <li id="index_4">wha</li>
</ol>

So that was two examples: using it to create an updated data structure, or simply as a looping construct. Actually the toOL() UDF would probably be better done in tags, given we're outputting mark-up:

<cffunction name="toOL">
    <cfif index EQ 1>
        <ul>
    </cfif>
    <cfoutput><li id="index_#index#">#element#</li></cfoutput>
    <cfif index EQ length>
        </ul>
    </cfif>
</cffunction>

For the UDF I'll submit to CFLib (as above) the code is ColdFusion 10 / Railo 4.x only, but Rob was using CF8. However with a few cosmetic changes, this all works fine on CF8 too:
function listEach(list, callback, delimiters){
    var arr        = false;
    var arrLen    = false;
    var result    = [];
    var index    = 0; 

    if (!structKeyExists(arguments, "delimiters")){
        delimiters = ",";
    }

    arr = listToArray(list, delimiters);
    arrLen = arrayLen(arr);

    for (index=1; index <= arrLen; index++){
        arrayAppend(result, callBack(argumentCollection=arguments, index=index, element=arr[index], length=arrLen));
    }
    return result;
}

I just needed to change the function definition slightly, do my VARs at the top, and scope my arguments specifically in some places in the calling code:

function toOL(){
    if (arguments.index == 1){
        writeOutput("<ol>");
    }
    writeOutput('<li id="index_#arguments.index#">#arguments.element#</li>');
    if (arguments.index == arguments.length){
        writeOutput("</ol>");
    }
}

And that was it.

It's important to remember that people get confused when talking about closures and callbacks, and often use the word interchangably. And closure is only a facet of function expressions, and only available from CF10 / Railo 4.x. However being able to use callbacks has existed in CFML since CF5, and before Railo even existed.

None of this is terribly exciting, but maybe someone will find it helpful. Shrug.

Righto.

--
Adam