Ajax Delete with CakePHP and jQuery
In this post I'm going to describe how you can use Ajax using the jQuery javascript library to delete items without a refresh. This is quite an easy enhancement to achieve and will help with the usability of your applications. It isn't CakePHP specific but in this example I'm using the framework to delete items from the main index.
I'm a big fan of progressive enhancement and so the delete action will still work without Javascript enabled but this will improve the user experience a little bit by keeping the user on the same page and will display a smooth fade animation to get rid of the table row.
The View
Usually your index.ctp will display a table of all the items for that Controller with edit/delete links for each one. What I usually do is add a class to each of the delete links so that I can target these with jQuery. In this case I've added the confirm_delete class, on a usabilty side of things I popup a javascript confirm box asking whether they really want to delete the item in case they hit the link by accident.
<?php
// simple HTML link with a class of 'confirm_delete'
echo $html->link('Delete',array('action'=>'delete',$item['Item']['id']),array('class'=>'confirm_delete'));
?>
Include jQuery
Download jQuery and create another file which will hold all your custom Javascript code in the js folder in the webroot. Once done include these files in your default layout file. On new projects I copy the default from /cake/libs/view/default.ctp to /app/views/layouts/default.ctp and then start to modifiy the copied one.
// include the javascript in default.ctp <script type="text/javascript" src="/js/jquery-1.3.2.min.js"></script> <script type="text/javascript" src="/js/jquery-common.js"></script>
Javascript
Just a quick explanation of what the below code, the source is fairly well commented so you shouldn't have a problem understanding what each bit does. First assign a click handler to anything with a "confirm_delete" class, ask the user whether they are sure they want to delete the Item. If they want to go ahead with the delete send an Ajax request to the link "href". The dataType is set to "json" so we'll be expecting the returned data to be in that format. If the delete was a success the script simply fades out the table row and displays a message to the user.
Just a quick note incase the user doesn't have Javascript or has it disabled. The delete action will function normally but without all the Javascript goodness so from their perspective they're not missing anything. Progressive enhancement is usually the best way to proceed. Get the script working without Javascript and then use it to enhance the user experience.
// file: /app/webroot/js/jquery-common.js
// on dom ready
$(document).ready(function(){
// class exists
if($('.confirm_delete').length) {
// add click handler
$('.confirm_delete').click(function(){
// ask for confirmation
var result = confirm('Are you sure you want to delete this?');
// show loading image
$('.ajax_loader').show();
$('#flashMessage').fadeOut();
// get parent row
var row = $(this).parents('tr');
// do ajax request
if(result) {
$.ajax({
type:"POST",
url:$(this).attr('href'),
data:"ajax=1",
dataType: "json",
success:function(response){
// hide loading image
$('.ajax_loader').hide();
// hide table row on success
if(response.success == true) {
row.fadeOut();
}
// show respsonse message
if( response.msg ) {
$('#ajax_msg').html( response.msg ).show();
} else {
$('#ajax_msg').html( "<p id='flashMessage' class='flash_bad'>An unexpected error has occured, please refresh and try again</p>" ).show();
}
}
});
}
return false;
});
}
});
Controller
In your Controller don't forget to include the RequestHandler component, it will help us ascertain if an Ajax request is being used. Have a look at the "admin_delete" method below. The default message and class is setup so that it can used in either the data sent via JSON or by the "setFlash" method. A quick check is done to ensure that the Item id has been passed and that it's numeric. The Item is then retrieved from the database to ensure it exists and then the Item is deleted. The message and class variables are then updated with the result of the delete action.
The magic really happens in the next part, we check to see if the request being sent is an Ajax request, if it is we set the Controller's "autoRender" and "layout" variables to false. This ensures that the method doesn't automatically output anything other than the JSON response. Data is encoded using "json_encode" function that will say if the action was successful along with a message. The script is then stopped using "exit" and then the Javascript code will take over and parse the response.
If the request to the action wasn't Ajax then we can assume that the person doesn't have Javascript and we have to deal with it via the old school method, a la simply refreshing the page and set a flash message with the result of the delete operation.
// include the RequestHandler component at the top of your Controller
var $components = array('RequestHandler');
/**
* Delete a List
* @param int $id
*/
function admin_delete($id=null) {
// set default class & message for setFlash
$class = 'flash_bad';
$msg = 'Invalid List Id';
// check id is valid
if($id!=null && is_numeric($id)) {
// get the Item
$item = $this->Item->read(null,$id);
// check Item is valid
if(!empty($item)) {
// try deleting the item
if($this->Item->delete($id)) {
$class = 'flash_good';
$msg = 'Your Item was successfully deleted';
} else {
$msg = 'There was a problem deleting your Item, please try again';
}
}
}
// output JSON on AJAX request
if($this->RequestHandler->isAjax()) {
$this->autoRender = $this->layout = false;
echo json_encode(array('success'=>($class=='flash_bad') ? FALSE : TRUE,'msg'=>"<p id='flashMessage' class='{$class}'>{$msg}</p>"));
exit;
}
// set flash message & redirect
$this->Session->setFlash($msg,'default',array('class'=>$class));
$this->redirect(array('action'=>'index'));
}
Wrapping Up
Hopefully everything here should make sense, using jQuery with CakePHP is pretty straight forward but may just take a while getting to grips with everything. If you have any questions or comments just drop me an email or leave your feedback using the form at the bottom.
Comments
majna (12/02/2010 - 03:09)
Make controller code clearer by moving all "view" related logic and markup to hmm... view?
Controller should only echo eg. json encoded (boolean) $success variable, and error message for i18n apps.
Do not call exit()! You'll break all calbacks like Model::beforeDelete()
$this->autoRender = false is just fine.
Matt Curry (12/02/2010 - 09:00)
Nice post James. One thing:
If you set the url to "url:$(this).attr('href') + ".json" you can create a /views/items/json/admin_delete.ctp view and not have to output and exit in the controller. It's a little cleaner and not much extra work.
Also why the "if($('.confirm_delete').length) {" part in the jQuery? It'll just skip over the event attach if there is no .confirm_delete.
James (14/02/2010 - 22:43)
@majna: Good point on moving the view stuff but I was thinking it was a little overkill for a simple message, but I don't see a problem with calling "exit" in this instance. Any callbacks won't be broken as this isn't in the Model, it's in the Controller and is only used for user interaction. If the request is an Ajax one there's no need to continue at the end and is just in place as a precaution.
@Matt: Also a good point on creating the JSON specific view, similar to what "majna" is saying. Don't know why I do the if statement in the Javascript, old habits I suppose but it's good to know in the future. Great work on your website btw, really good resource for CakePHP and a funny read too!
zack (19/02/2010 - 01:35)
nice post.
where i create the .ajax_loader ,#flashMessage ?
for me it don't work yet. when i clik the delete button it show the 'Are you sure you want to delete this?' then i click nothing happen. but when i refresh the page i see the item deleted
Jacque (19/02/2010 - 03:38)
hi james,
i do everything
in the js file
type:"POST",
url:$(this).attr('href'),
data:"ajax=1",
dataType: "json",
when i remove the ( (,) at the end of the url:$(this).attr('href'), )
it show the 'Your Item was successfully deleted' without show the confirm box
if i let the , at the end i have the confirm box. but when i delete the item i don't have the message 'Your Item was successfully deleted' even if i refresh.
the ajax call don't work.
Jacque (19/02/2010 - 03:45)
i aslo noticed that my
app\controllers\components
is empty why ? i download the cake package from the official website
James (20/02/2010 - 03:57)
@zack: The .ajax_loader & #flashMessage are in your index view. Not sure why it didn't work for you. Go through the article again and make sure everything is working.
@Jacque: You have to leave the ',' in to work it work. Also use Firebug to see the actual Ajax request in the browser and see what is being sent and recieved. The 'components' directy is empty by default so nothing to worry about there.
Adriano (28/02/2010 - 14:11)
A few comments:
- A div with id = ajax_msg must be added to the index.ctp file for the messages to show. Preferably with "style='display:none;' at first.
- The .ajax_loader class is supposed to load a spinner or something. What should it be appended to?
- You are overriding the flashMessage div that appears automagically when Session has a message to display.
- Obviously, Lists aren't going to work for everyone.
- The Javascript and Ajax helpers must be specified in the controller, in that order AFAIK
- It is more cakeish to add the javascript files using the Javascript helper, so that you get automatic path resolution and other goodies.
- The default delete action as baked already has a confirmation and the message there is more complete (it states at least the ID), so I bypassed the confirmation in your js.
James (03/03/2010 - 23:45)
@Adriano: good points, thanks for taking the time to comment. I usually put the .ajax_loader in the view and hide it so I know visually where it's going to go but you could append it via javascript wherever you'd like. I don't usually bother with Cake's inbuild Javascript and Ajax helpers because I use jQuery.
Ryan P. (13/03/2010 - 08:38)
I know this is probably not the right forum for this kind of question, but I can't for the life of me get CakePHP to properly call Ajax. This seems like a great example, but when I try to implement it, it works in that it deletes a record, but none of the Ajax works--it's as if I don't have Javascript, but I know that I do b/c I have other localhost cake apps that work fine with Ajax (somebody else set those up for me). Can anybody point me to a tutorial with more detail on setting up an environment to work with Cake and Ajax? Thanks!
neel (01/05/2010 - 07:30)
Thanks for this simple and interesting tutorial. i used it on my projects.
stve (05/11/2010 - 13:35)
Ryan P. Check your ie zone secuirty. XMLHTTPRequest is an option somewhere and you need it for AJAX to work (hence the X in the acronym)