2

I am working on a ColdFusion 8 training application where I'm making some AJAX requests (without any libraries such as jQuery) to support a very basic CRUD application. The high level architecture includes a CFM view, a CFC with remote access methods which receive the AJAX requests, and a CFC which acts as a model and has all of the database queries. For just retrieving data that doesn't require any sort of bind variables (like getting all rows from the table), the AJAX queries are working fine. When I try to post anything to the CFC middle layer, however, I'm getting errors about the values I'm looking for being undefined in the Form scope (which from my understanding is where post parameters will be stored). I even dissected the requests with Tamper Data and verified that the names and values of the post parameters are as I expect them to be.

Here is an example of the JS AJAX requests:

    function addLocation(locToAdd) {
            var thisAccess = new AccessStruct("POST", "jsontest.cfc?method=addNewLocation", getLocations, "newLoc=" + JSON.stringify(locToAdd));
            accessWrapper("addLoc", thisAccess);

    function accessWrapper(action, accessDef) {
            var ajaxRequest = new XMLHttpRequest();
            var nextStep;
            if (action != "getLocs") {
                nextStep = getLocations;
            } else {
                nextStep = buildTable;
            }

            ajaxRequest.onreadystatechange = function() { // using a closure so that the callback can
                if (ajaxRequest.readyState == 4) {        //   examine the request and nextStep
                    if (ajaxRequest.status == 200) {
                        if (nextStep != null) {
                            nextStep(ajaxRequest);
                        }
                        return true;
                    } else {
                        alert("Request Could Not Be Completed; Errors On Page");
                        return false;
                    }
                }
            }
            ajaxRequest.open(accessDef.method, accessDef.url, true);
            ajaxRequest.send("newLoc=" + accessDef.params);
    }


    function Loc(locCode, parLocCode, name, addrL1, addrL2,
                            city, stateProv, postal, locTypeCode) {
            this.locCode     = locCode;
            this.parLocCode  = parLocCode;
            this.name        = name;
            this.addrL1      = addrL1;
            this.addrL2      = addrL2;
            this.city        = city;
            this.stateProv   = stateProv;
            this.postal      = postal;
            this.locTypeCode = locTypeCode;
        }

        function AccessStruct(method, url, nextStep, params) {
            this.method   = method;
            this.url      = url;
            this.nextStep = nextStep;
            this.params   = params;
        }

Essentially what's happening on the page is that a table populated by all the location (loc) records is being rendered for a "user". There is a form to add a new user, and when they click the add button, a Loc structure is created containing the information they entered and is passed to the addLocation function. This creates an Access structure, which will include the request URL, method, the name of a function to act as a callback, and any post parameters. This is all passed into the accessWrapper function, which will create the XMLHttpRequest and process the AJAX request. I used a closure for the onreadystatechange callback function so that it could be aware of the XMLHttpRequest object and the callback function defined in the Access structure (this callback function will be generally be used to refresh the view table after a record is added, deleted, or edited).

Here is the cffunction within the middle-layer CFC where the problem is being reported from. I won't bother to post the DAO CFC as that has been tested elsewhere and is not even being reached during this process (because it's failing at the middle [or controller] level)

 <cffunction name="addNewLocation" output="false" access="remote">
    <cfset var deserializedLocation = "">
    <cfscript>
        deserializedLocation = DeserializeJSON(Form.newLoc);
    </cfscript> 
    <cfobject component="locationDAO" name="locationDAOObj">
    <cfinvoke
        component="#locationDAOObj#"
        method="addLocation">
        <cfinvokeargument name="code" value="#deserializedLocation.locCode#">
        <cfinvokeargument name="parentCode" value="#deserializedLocation.parLocCode#">
        <cfinvokeargument name="name" value="#deserializedLocation.name#">
        <cfinvokeargument name="addr1" value="#deserializedLocation.addrL1#">
        <cfinvokeargument name="addr2" value="#deserializedLocation.addrL2#">
        <cfinvokeargument name="city" value="#deserializedLocation.city#">
        <cfinvokeargument name="stateProv" value="#deserializedLocation.stateProv#">
        <cfinvokeargument name="postal" value="#deserializedLocation.postal#">
        <cfinvokeargument name="locationType" value="#deserializedLocation.locTypeCode#">
    </cfinvoke>
</cffunction>

The error in the request response is: 500 Element NEWLOC is undefined in FORM

Like I said before, I've checked the request in Tamper Data, and it looks fine there. Thanks in advance for any help you great folks might be able to offer!

Haz
  • 2,539
  • 1
  • 18
  • 20

2 Answers2

4

There absolutely is a FORM scope when you do an Ajax post to a CFC.

This example POSTs form data via Ajax to a CFC function with no arguments and returns the JSON format of the FORM scope. Yes, you should have arguments to document, specify required/not required and data type, but they're not mandatory.

Is there any reason you aren't using jQuery? It would probably make your life much easier.

There must be something wrong with how you're sending the form data to the Ajax call. If you use FireBug to watch your Ajax calls, you can see the POSTed parameters.

HTML

<html>
    <head>
        <title>Ajax POST to CFC</title>
        <script src="//ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
        <script type="text/javascript" src="test.js">
    </head>
    <body>

        <form id="foo" action="" method="post">

            <input type="text" id="a" name="a" value="Hello" />
            <br />
            <input type="text" id="b" name="b" value="Goodbye" />
            <br />

            <textarea id="data" cols="30" rows="10" disabled="true"></textarea>
            <br />
            <input type="button" id="btnSubmit" value="Do Ajax!" />

        </form>

    </body>

</html>

JavaScript

$(document).ready(function() {
    $('#btnSubmit').on('click', function() {
        $.ajax({
            asynch : true,
            type : 'POST',
            dataType : 'json',
            url : 'test.cfc?method=testing&returnformat=json',
            data : {
                a : $('#a').val(),
                b : $('#b').val()
            },
            success : function(data, textStatus) {
                $('#data').val(JSON.stringify(data));
            }
        });
    });
});

CFC

<cfcomponent>
    <cffunction name="testing" access="remote" output="false" returntype="string">
        <cfreturn serializeJSON( form ) />
    </cffunction>> 
</cfcomponent>


Old School, no jQuery, just plain ol' JavaScript

I found a simple example of an Ajax POST without jQuery here: http://www.openjs.com/articles/ajax_xmlhttp_using_post.php

HTML
Remove the jQuery SCRIPT tag, change the other SCRIPT to test-nojq.js and change the submit button to add an onclick event.

<input type="button" id="btnSubmit" value="Do Ajax!" onclick="doSubmit();" />

JavaScript: test-nojq.js

function doSubmit(){
    var http = new XMLHttpRequest();
    var url = "test.cfc";
    var params = "method=testing&returnformat=json";
        params += "&a=" + document.getElementById('a').value;
        params += "&b=" + document.getElementById('b').value;
    http.open("POST", url, true);
    //Send the proper header information along with the request
    http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    http.setRequestHeader("Content-length", params.length);
    http.setRequestHeader("Connection", "close");
    http.onreadystatechange = function() {//Call a function when the state changes.
        if(http.readyState == 4 && http.status == 200) {
            document.getElementById('data').value = http.responseText;
        }
    }
    http.send(params);
}

Adrian J. Moreno
  • 14,350
  • 1
  • 37
  • 44
  • I've used both Firebug and Tamper Data to verify that the post parameters are there, with their proper names and values. I understand that this would be much easier using jQuery, believe me I love jQuery; I'm simply doing this with raw JS for the learning experience (joke's on me I guess :) ). – Haz Jul 13 '12 at 15:18
  • Updated with a no-jQuery version of the code. Also added output="false" to the CFFUNCTION to remove white space in the returned data. – Adrian J. Moreno Jul 13 '12 at 15:44
  • Looks basically like what I've been doing; I've even tried a few different content-types to see if that was the issue, but it hasn't made a difference yet. I just rerouted the call to a cfm with a cfdump statement to examine the form scope, but it showed an exmpty structure... – Haz Jul 13 '12 at 18:17
0

Make newLoc into an argument and it should work.

<cffunction name="addNewLocation" output="false" access="remote">
  <cfargument name="newLoc">
  ...

</cffunction>

update: not sure why I encountered no form scope one time calling a remote method. Anyway, it isn't true but the rest of the answer should still hold true.

Henry
  • 32,689
  • 19
  • 120
  • 221
  • Why would there not be a form scope? Earlier I tried turning it into an argument on the cffunction, and I was getting an error about the argument being required but not passed in. However, if instead of using post parameters I appended newLoc to the URL, it was able to be picked up as a cfargument, however I would prefer not to use that approach. – Haz Jul 12 '12 at 22:23
  • 1
    when you're posting to `.cfc?method=addNewLocation` with the form field `newLoc`, value of that should be mapped to the argument by CF. – Henry Jul 12 '12 at 22:37
  • I will try that again tomorrow (do not have a CF license at home) and post the results. Hopefully I just made an error when I did it with a cfargument earlier. – Haz Jul 12 '12 at 23:04
  • 1
    @Haz FYI, developer license is free, just install CF as Developer Edition during the setup. – Henry Jul 12 '12 at 23:45
  • Ok, so I tried using the cfargument again this morning. I received a 500 response with the message "Variable NEWLOC is undefined". – Haz Jul 13 '12 at 13:04
  • More interesting stuff: If I append the values to the URL instead of sending them as POST parameters, they get picked up as cfargument... So I can get it to work by doing that, but it's not how I'd like it to work and I'd still like to figure out why my original code isn't working. – Haz Jul 13 '12 at 13:15
  • typically cfc's are accessed via GET not POST. after the "method=", additional params are passed in as arguments - so Henry's right... but something may be wrong with your URL. Tease out what's actually being called with Firebug. It should look like ... something.cfc?method=addNewLocation&newLoc=*somefancy json string*. – Mark A Kruger Jul 13 '12 at 13:33
  • I'm not sure if it's your ajax code, we use jQuery and POST works just as well as GET. I agree with Mark here, try debugging with your browser debugging tool. – Henry Jul 13 '12 at 14:32
  • Here's an example using POST with jQuery: http://stackoverflow.com/questions/8336819/how-can-i-send-javascript-object-to-a-remote-cfc-component – Henry Jul 13 '12 at 14:40