5

The app: A JavaScript function listens for changes on form elements (input & select), and posts the data to a CFC method that assigns them to a Session struct. The struct is returned, making the form data usable for the life of the session. The app is adapted from code at Raymond Camden's Using a server, or session storage, to persist form values.

Issue: The original CFC code is written in CFScript. Because we're on ColdFusion 8, I get an error when the method is called. So, I translated the method into ColdFusion tag syntax and stopped getting that error. In Chrome's Dev Tools, I can see data passing to the CFC via the JSON object each time I enter something into a form element. So I know the JavaScript function is working. And even though I'm not getting any return errors, there are some behaviors that lead me to believe that my translation is incorrect. E.g., the dump of the session struct only displays the last input element entered, rather than all of them (as is the case in Ray's demo).

Here's the original CFScript version and then my tag translation. In addition to any comments about where my translation is wrong, I'd love to have an explanation of this line, <cfset s.name = [s[name]] />, particularly the [s[name]] construct, since I'm not able to articulate what's happening there. Thanks.

script syntax:

component {
    remote void function preserveSession(string awardData) {
        if(!isJSON(arguments.awardData)) return;
        arguments.awardData = deserializeJSON(arguments.awardData);

        //convert the array into a name based struct
        var s = {};
        for(var i=1; i<=arrayLen(arguments.awardData); i++) {
            var name = arguments.awardData[i].name;
            if(!structKeyExists(s, name)) {
                s[name] = arguments.awardData[i].value;    
            } else {
                //convert into an array
                if(!isArray(s[name])) {
                    s[name] = [s[name]];
                }
                arrayAppend(s[name], arguments.awardData[i].value);
            }    
        }
        session.awardFormData = s;    
    }
}

tag syntax:

<cfcomponent>
    <cffunction name="preserveSession" access="remote" returntype="void" output="no">

        <cfargument name="awardData" type="string" />

        <cfset var s = {} />

        <cfif NOT isJSON(arguments.awardData)>
            <cfreturn />
        </cfif>

        <cfset arguments.awardData = #deserializeJSON(arguments.awardData)# />

        <cfloop index="i" from="1" to="#arrayLen(arguments.awardData)#">
            <cfset name = #arguments.awardData[i].name# />

            <cfif NOT structKeyExists(s, name)>
                <cfset s.name = #arguments.awardData[i].value# />   
            <cfelse>
                <cfif NOT isArray(s.name) >
                    <cfset s.name = [s[name]] />
                </cfif>
                <cfset arrayAppend(s.name, arguments.awardData[i].value) />
            </cfif>
        </cfloop>

        <cfset session.awardFormData = s />

        <cfreturn />
    </cffunction>
</cfcomponent>
Miguel-F
  • 13,450
  • 6
  • 38
  • 63
rickp
  • 65
  • 2

2 Answers2

5

First off, you don't really need to translate all of that. CF8 doesn't support component/function in cfscript but otherwise you are good to use it as is:

<cfcomponent>
  <cffunction name="preserveSession" access="remote" returntype="void" output="no">
    <cfargument name="awardData" type="string" />
      <cfscript>
        var s = {};
        var name = '';
        var i = 0;
        if(!isJSON(arguments.awardData)) return false;
        arguments.awardData = deserializeJSON(arguments.awardData);
        for(i=1; i<=arrayLen(arguments.awardData); i++) {
          name = arguments.awardData[i].name;
          if(!structKeyExists(s, name)) {
            s[name] = arguments.awardData[i].value;
          } else {
            if(!isArray(s[name])) {
              s[name] = [s[name]];
            }
            arrayAppend(s[name], arguments.awardData[i].value);
          }
        }
        session.awardFormData = s;
        return true;
    </cfscript>
  </cffunction>
</cfcomponent>

Try simply rewrapping the guts like this rather than rewriting and see if you get farther.

To explain the notation generally: The s[name] addresses the structure of s by key of the value stored in name; this is sometimes called array notation. It equivalent to dot notation of s.theValueStoredInName where the name is known. To address a structure by a variable name dynamically it is easiest to do this via array notation.

Same thing for the nested set: The line s[name] = [s[name]] is setting a key with value of what is stored in name to the value of what is stored in the named key of s[name]. By wrapping that set in [], it is as a type of array.

This will help clarify what is happening here specifically:

If something with that name-key has not already been assigned as a struct, it gets stored as a struct (simple name-key value). If it has, then what ever is in the name-key value gets converted to an array with that name-key. Then if it is a successive pass of a 2nd item (which it will be, the if! is just checking if this array conversion has already occurred at least once), then the 2nd item is appended to that array which has the key of the same name.

Personally, I would just always directly set to an array for simplicity. Then you always know how to address the values in storage.

williambq
  • 1,125
  • 7
  • 12
  • One thing you need to do in CF8 if i recall is move your var's to the top of the script block. But otherwise the cfscript should support all modern object notation as is. CF9 lets you var anywhere as hoisting evidently now occurs in CF9+. I will edit that to reflect this. – williambq Jun 05 '13 at 19:29
  • 1
    @ williambq - Might be better to add that last part as an [edit](http://stackoverflow.com/posts/16948078/edit), so it is more visible. Then delete the comment. – Leigh Jun 05 '13 at 19:54
  • 1
    I had to change a few details, e.g., move the loop's 'var i=1;' clause up to the top of the script, and remove the closing 'return true;' clause (because I've declared a 'returntype=void'). And whoo hoo, I'm now getting all the form elements in my session dump. Thanks! – rickp Jun 05 '13 at 20:31
  • 1
    And thanks to both of you for the explanation of s[name] = [s[name]]; It's making more sense. – rickp Jun 05 '13 at 20:33
2

<cfset s.name = ... />

You need to access the key names dynamically. The code above uses the same static key (ie name) every time. So you end up overwriting the previous value each time you loop. That is why you end up with only one value - the last one.

 <!--- this creates a dynamic key named "apple" (correct) --->
 <cfset name = "apple" />
 <cfset s[name] = "..." />

 <!--- this creates a key literally named "name" (wrong) --->
 <cfset name = "apple" />
 <cfset s.name = "..." />

To fix it, everywhere you are using s.name, replace it with s[name]. Also, do not forget to var scope all of your function local variables.

Update:

williambq's response also raises a good point. You do not have to convert it all to cfml. It simpler to wrap the bulk of it in cfscript tags.

Community
  • 1
  • 1
Leigh
  • 28,765
  • 10
  • 55
  • 103