JSON returned from remote CFC function is out of order
Asked Answered
A

4

6

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.

Aden answered 27/9, 2011 at 16:22 Comment(1)
Possible duplicate of #4516176Coricoriaceous
C
7

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.

Charlatan answered 27/9, 2011 at 16:33 Comment(1)
If I was only using the remote validation function through ajax calls I would've probably gone with Dan's approach and returned the errors in an array. However, I also call the function on the server in case they have JS disabled. Returning the errors in a structure rather than an array works better with my error display code. Using a LinkedHashMap solved the problem; the returned JSON was in the same order as the items were added.Aden
H
5

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.

Hit answered 27/9, 2011 at 16:32 Comment(3)
Let me know if you hit any snags. I typed the code up directly in the answer, and didn't actually run it to test it. However, everything should be good to go.Hit
There is one problem I found with your code. The tempError struct only gets added to the array once and keeps getting overwritten so that only the last error appears in the error array. Moving tempError = StructNew() inside of each cfif block fixes this.Aden
Gotcha, I've corrected that with a Duplicate function so you don't have to reinit the variable every trip through.Hit
T
3

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();
}
Thi answered 27/9, 2011 at 16:34 Comment(0)
G
2

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

Gstring answered 27/9, 2011 at 16:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.