Question

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>
Was it helpful?

Solution

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.

OTHER TIPS

<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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top