AJAX calls in CakePHP: The JSON Response
The controller and HTTP response are now working correctly, so it is time to display the response message to the client. We will achieve this by utilizing jQuery to output our JSON. The plan is to show a success message if the JSON passed or display a list of errors if it has failed. To display the messages we need to create our HTML elements that will hold the responses.
<div id="responseSuccess" class="responseBox" style="display: none"></div>
<ul id="responseError" class="responseBox" style="display: none"></ul>
Before we can write our Javascript to handle the JSON, we need to first inspect the JSON response. Our JSON "data" message can come in 3 possible formats: a simple string, an array containing the error messages, or an object containing the error messages / post data with their key values. Below are a couple examples of how our JSON response could be formatted:
// Single string error
{"success":false,"data":"No username/password","code":-1}
// An array containing the errors
{"success":false,"data":["No username","No password"],"code":-1}
// Errors / Post data and their key
{"success":false,"data":{"username":"No username","password":"No password"},"code":-1}
{"success":true,"data":{"username":"Miles","password":"p4ssw0rd"}}
Now this poses a significant problem. If our response data can be sent as 3 possible formats, we need to write our Javascript to be able to handle all possible variations. We can do this by creating a loop for the arrays/objects and store the values in a custom array, and for the single string just return the message. The following code block can do just that, and all we need to do is place it within the "success" parameter of our AJAX call.
function login() {
var data = $("#UserAddForm").serialize();
$.ajax({
type: "post",
url: "/ajax/login/",
data: data,
dataType: "json",
success: function(response, status) {
// Response was a success
if (response.success) {
$("#responseSuccess").html(response.data).slideDown();
// Response contains errors
} else {
var errors = [];
if (typeof(response.data) == ("object" || "array")) {
$.each(response.data, function(key, value) {
var text = isNaN(key) ? key + ": " + value : value;
errors.push("<li>"+ text +"</li>");
});
} else {
errors.push("<li>"+ response.data +"</li>");
}
errors = errors.join("\n");
$("#responseError").html(errors).slideDown();
}
}
});
return false;
}
Pretty simple right? We can improve on this code by adding a setTimeout() that will remove the response box after 5 seconds. Additionally we can add the AJAX error argument that is called if the AJAX mechanism fails as a whole.
// Remove box after 5 seconds
setTimeout(function() {
$(".responseBox").slideUp();
}, 5000);
error: function(XMLHttpRequest, textStatus, errorThrown) {
$("#responseError").html("<li>An unexpected error has occurred.</li>").slideDown();
}
Our Javascript is now complete, but we could improve on it yet again. Say you have multiple AJAX calls that all utilize the same type of callback mechanism. It would be a major pain and waste of time to write all that code over and over again. What we can do is separate the code into 3 separate functions, all handling certain aspects of the logic. Our final code may look something like the following.
/**
* Fire an AJAX call to login the user
*/
function login() {
var data = $("#UserAddForm").serialize();
$.ajax({
type: "post",
url: "/ajax/login/",
data: data,
dataType: "json",
success: function(response, status) {
handleCallback(response, status);
},
error: function(XMLHttpRequest, textStatus, errorThrown) {
handleError(XMLHttpRequest, textStatus, errorThrown);
}
});
return false;
}
/**
* Handle the AJAX callbacks
*/
function handleCallback(response, status) {
// Response was a success
if (response.success) {
$("#responseSuccess").html(response.data).slideDown();
// Response contains errors
} else {
var errors = [];
if (typeof(response.data) == ("object" || "array")) {
$.each(response.data, function(key, value) {
var text = isNaN(key) ? key + ": " + value : value;
errors.push("<li>"+ text +"</li>");
});
} else {
errors.push("<li>"+ response.data +"</li>");
}
errors = errors.join("\n");
$("#responseError").html(errors).slideDown();
}
// Remove box after 5 seconds
setTimeout(function() {
$(".responseBox").slideUp();
}, 5000);
return false;
}
/**
* Handle an AJAX failure
*/
function handleError(XMLHttpRequest, textStatus, errorThrown) {
$("#responseError").html("<li>An unexpected error has occurred.</li>").slideDown();
}
Now there are multiple ways for your to handle a JSON response, but the approach above is a pretty straight forward implementation of displaying a success or failure message. Be sure that when you write your AJAX requests, that the $.ajax() function has a dataType of json, or your JSON response will completely fail.