Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JSON returned from remote CFC function is out of order

I have a remote CFC that returns a structure. It is called using cfajaxproxy. I want the JSON returned to be in order i.e. first into the structure first into the JSON object. However, the JSON that is returned is in a mixed up order.

Here's the remote function.

<cfcomponent displayname="validation" hint="">
    <cffunction name="validateForm" displayname="validateForm" hint="" access="remote" verifyClient="yes" returntype="struct">

        <cfargument name="formVals" type="struct" required="yes">

        <cfset errors = StructNew()>

        <cfif formVals.project neq "project">
              <cfset errors["project"] = "Invalid project name." />
        </cfif>

        <cfif Len(formVals.description) eq 0>
             <cfset errors["description"] = "Please enter a description." />
        </cfif>

        <cfif StructIsEmpty(errors)>
            <cfset errors["message"]["type"] = "success">
            <cfset errors["message"]["text"] = "Client and server-side validation passed successfully.">
            <cfset errors["areErrors"] = false>
        <cfelse>
            <cfset errors["message"]["type"] = "validation">
            <cfset errors["message"]["text"] = "Please fix the errors, and resubmit.">
            <cfset errors["areErrors"] = true>
        </cfif>

        <cfreturn errors />

    </cffunction>
</cfcomponent>

This is the cfajaxproxy that I have set at the top of my form page.

<cfajaxproxy cfc="validation" jsclassname="validation">

Here's the call made to the remote function in the onSubmit handler of my form.

var v = new validation();
v.setHTTPMethod("POST");
var errors = v.validateForm(o);

Here's the data (o variable above) that is sent to the function in the post request.

{"formVals":{"project":"","description":""}}

Here's the JSON response returned from the function.

{"message":{"text":"Please fix the errors, and resubmit.","type":"validation"},"description":"Please enter a description.","project":"Invalid project name.","areErrors":true}

I want the response to be in the same order as the structure was created which would look like this.

{"project":"Invalid project name.","description":"Please enter a description.","message":{"text":"Please fix the errors, and resubmit.","type":"validation"},"areErrors":true}

That way when I iterate over the response I can set the focus to the first form field with an error in it.

var focusSet = false;

$.each(errors, function(key, val){
    //alert(key + ': ' + val);
    if(key != 'message' && key != 'areErrors') {
        var fi = $('#' + key).parents('.formItem').filter(':first');
        fi.addClass("inError");
        fi.find('.err').filter(':first').html(val);
        if(!focusSet) {
            $('#' + key).focus();
            focusSet = true;
        }
    }
});

Right now this places focus in the second field of the form, description, instead of in the project field.

like image 430
Brandon Avatar asked Sep 27 '11 16:09

Brandon


4 Answers

The keys of a ColdFusion struct are never stored in any particular order. However, I found one post that shows how you can create a java LinkedHashMap (which is the java underneath a CF Struct) to store and retrieve keys in a specific order.

<cfset pets = CreateObject("java", "java.util.LinkedHashMap").init()>
<cfscript>
pets["Cat Name"] = "Leo";
pets["Dog Name"] = "Meatball";
pets["Fish Name"] = "Lil Fish";
pets["Bird Name"] = "PePe";
pets["Snake Name"] = "Sizzle";
</cfscript>
<cfloop collection="#pets#" item="key" >
    <cfoutput>
    #key#: #pets[key]#<br/>
    </cfoutput>
</cfloop>

EDIT: Dan's solution (array instead of struct) would probably be much easier.

like image 158
Adrian J. Moreno Avatar answered Nov 03 '22 05:11

Adrian J. Moreno


You can't (easily) control the order of the returned JSON structure data unless you manually build the string to return that data. If you must be reliant on an order, then you need to return your errors in an array instead of a structure. You can even return an array of error structures, and CF will maintain the correct array order.

I would return your data like so:

<cfcomponent displayname="validation" hint="">
    <cffunction name="validateForm" displayname="validateForm" hint="" access="remote" verifyClient="yes" returntype="struct">

        <cfargument name="formVals" type="struct" required="yes">

        <cfset var retVal = StructNew() />
        <cfset var tempError = StructNew() />
        <cfset retVal.errors = ArrayNew(1) />

        <cfif formVals.project neq "project">
            <cfset tempError["key"] = "project" />
            <cfset tempError["message"] = "Invalid project name." />
            <cfset ArrayAppend(retVal.errors, Duplicate(tempError)) />
        </cfif>

        <cfif Len(formVals.description) eq 0>
            <cfset tempError["key"] = "description" />
            <cfset tempError["message"] = "Please enter a description." />
            <cfset ArrayAppend(retVal.errors, Duplicate(tempError)) />
        </cfif>

        <cfif ArrayIsEmpty(retVal.Errors)>
            <cfset retVal["message"]["type"] = "success" />
            <cfset retVal["message"]["text"] = "Client and server-side validation passed successfully.">
            <cfset retVal["areErrors"] = false>
        <cfelse>
            <cfset retVal["message"]["type"] = "validation">
            <cfset retVal["message"]["text"] = "Please fix the errors, and resubmit.">
            <cfset retVal["areErrors"] = true>
        </cfif>

        <cfreturn retVal />

    </cffunction>
</cfcomponent>

This would give you a separate array of errors to loop over, instead of dealing with your base message and areErrors keys at the same time as your errors. Break them out into a separate entity altogether, and you'll have an easier time of looping through them on the client side.

like image 26
Dan Short Avatar answered Nov 03 '22 04:11

Dan Short


struct is not ordered in CFML (it's just a Hashmap-like collection).

If you want ordered struct, use

struct function orderedStructNew()
{
    return createObject("java","java.util.LinkedHashMap").init();
}
like image 3
Henry Avatar answered Nov 03 '22 04:11

Henry


The easiest way if you want something to stay in order use an Array instead of a structure.

like image 2
Sam Farmer Avatar answered Nov 03 '22 03:11

Sam Farmer