Tue, 29 May 2007

Drag 'n drop tutorial with the CakePHP 1.2 Ajax helper, Prototype framework and Scriptaculous library

Introduction

During the development of my thesis I wanted to create a drag 'n drop interface. But I never did anything like that, I never used CakePHP's Ajax helper and neither made I ever use of more advanced functionalities of Scriptaculous/Prototype. Hell I even never touched Ajax before this!

Although there are some basic CakePHP/Ajax tutorials out there, I still had a hard time because some knowledge about Ajax (in CakePHP) was assumed in all of those. After a lot of googling I even found a tutorial called CakePHP: Sortable AJAX Drag & Drops - The Basics
"Perfect!" I thought, until after staring at the article for a long while and I started to notice nowhere in the article "$ajax->drag", "$ajax->drop" or "$ajax->dropRemote" is used. (those are calls on the CakePHP Ajax helper to enhance objects to become draggable, or to become a dropbox where draggables can be dropped into). So the only more or less suited tutorial about drag 'n drop was actually about sorting and didn't use the drag/drop function calls at all. Even though it contains very useful information.

Long story short: I finally got it working (thanks to Krazylegz and kristofer and possibly others too, it has been a while so I may forget someone ;-), and learned a lot in the process. I will share what I learned with you guys so that hopefully it's a bit easier for you then what I had to go through.



::Read from here

Prerequisites

CakePHP. I used 1.2 but I think this will work on 1.1 too (because the Ajax helper is in there for a long time. it's even fully documented in the 1.1 manual)
Scriptaculous & Protoype libraries. See the cake manual on how to install these. Don't close this page after installing the libs, because it also explains the possibilities of the Ajax helper and is a reference that you will need! The api reference reveals even more possibilities, but the former page should do.

Let's get started!

Instead of showing a really simple example, I'll give a skeleton example based on a webstore-scenario. You can have multiple articles (draggables) and 2 droppables: a cart and a thrashbin. Dropping an article in the cart adds the article to your order (which I usually keep in the session) and updates the view accordingly, dragging it from the cart into the thrashbin removes it from the session and updates the view too.
I've left out the basics such as sql, models and controller business logic, and focused on the Ajax specifics.

The view-code can be combined in different elements but for simplicity let's assume there is only 1 view file.
Keep in mind that each object where you want to assign some functionality to (such as draggable, droppable) must have a unique DOM id. It doesn't matter if it's a div or span or p or whatever, as long as it has a unique id.

The view looks like this:

<?php foreach($articles as $article): ?>
<div id="<?php echo $article['Article']['id']; ?>">
   <?php echo $article['Article']['description']; ?>
</div>
<?php echo $ajax->drag($article['Article']['id'],array('revert'=>true)); ?>
/*
   the revert thing will make the draggable return to it's original position.  Set this to false and the object will stay where you put it.
   This does not have anything to do with a function call being made btw, that's the job for the draggables
*/
<?php endforeach; ?>


<div id="cart">
   <?php echo $this->requestAction('/controller/showcart'); ?> // this call is not necessary.  You can use it to fill the <div> with some content to start with
</div>

<div id="thrashbin">
</div>

<?php
   echo $ajax->dropRemote('cart',null,array('url' => '/controller/addarticle/','with'=>'{draggedid:element.id}','update'=>'cart'));
   echo $ajax->dropRemote('thrashbin',null,array('url' => '/controller/removearticle/','with'=>'{draggedid:element.id}','update'=>'cart'));
?>
/*
   these two calls make objects with DOM id's 'cart' and 'thrashbin' droppable.  When an object is dropped into them, the actions defined by 'url' are called.
   The 'with'=>'{draggedid:element.id} passes the id of the dropped element to the function that is called (on the background, no page refresh or anything like that!).
   The update thing makes sure that the entire output of the requestAction call is displayed inside the <div id="cart"></div>.
   So that means that the page will update the part of the page which is specified (dom id 'cart') when the output from the requestAction call is received
*/

The controller would have functions like this:

<?php

// this function can be used to show initial contents of the cart.
function showcart(){
    $this->set('articles',false); /* no articles bought yet */
    $this->render('cart');
    // (read on to find out more about this view)
}

function addarticle(){
/*
   The DOM id of the element that has been dragged into here can be found in $this->params['form']['draggedid'].
   You can use that id to do something like $this->Article->findById() to add the article to the session.
   At the end of this function the entire contents of the <strong>view</strong> file will be returned (and rendered inside the <div> with DOM id 'cart'.)
   The layout won't be rendered, only the view, since this is an Ajax call (the difference is autodetected by Cakes RequestHandler) 
   You could do something like this:
*/
   $this->set('articles', $all_bought_articles_from_the_session);
   $this->render('cart');
}

// this function is similar to the one above but instead removes one article from the session and renders the same view ;-)
function removearticle(){
}
?>

The view controller/cart.ctp could look something like this:

<ul>
<?php foreach($articles as $artikel):?>
<li><?php echo $article['Article']['id']; ?></li>
<?php endforeach; ?>
</ul>

<?php if(!$articles) echo "No articles in the cart!"; ?>

There you have it. All explanations are inside the sources, I hope it's clear enough. If not, just ask :-)

Common misconceptions

From reading many (outdated?) articles about Ajax in cake I noted a lot of information that was not correct. Here they are:

  • It is not necessary to explicity pass the 'ajax' layout as an argument to the render call. Eg you don't need to do this: $this->render('cart', 'ajax'); You can do $this->render('cart'); (or even no explicit render call at all, if the view file is the same as the action name of your controller) Cakes RequestHandler automatically detects when it's dealing with an ajax call, and in that case won't render the layout, only the view.
  • It is not necessary to declare the UTF-8 character encoding in your layout or view
  • You don't need $this->autoRender =false; in the controller. Unless maybe you absolutely don't want to render anything.

The end

There you have it. I hope everything is clear. If not, ask on !

For those who don't have it already, I really recommend The firebug firefox extension. Not only is it a great aid when designing, it's also perfectly suited for debugging Ajax calls. It can show you exactly what happens when you drag an item in a droppable, what request is being made, what is being returned, etc.
This is explained in this movie:Introduction to Debugging AJAX Application with Firebug

Comments

Sorry about the title of my tutorial (on the Weber Report). What you said makes sense. It really isn't a drag & drop interface so much as a sortable interface. When I get some time, I'll try to fix that.

Anyways, good write up on a 'true' drag & drop. Keep up the good work!

Dustin Weber
http://www.dustinweber.com

Thanks for the tutorial! I finally got it working now

No problem Dustin, your tutorial was an interesting read nonetheless ;-) And a good starting point.

Anonymous: you're welcome :-)

Hi Dieter!

It seems that beside Gentoo and DT, we also have CakePHP as common interest. Unbelievable, isn't it? :P
Drag and drop seems very interesting to me, maybe I'll use it in a future project some day. Right now I'm working on some distributed application for school, so no time for sideprojects unfortunately...

Good luck with your thesis!

Lol, now you mention it :D
I suddenly noticed the CakePHP favicon on your blog :p

it's very useful,thanks

Clear and concise. I finally get it!

Thanks!

The different between $this->render('cart', 'ajax'); and $this->render('cart');(or just nothing) is that $this->render('cart'); will use the default layout of the controller, while $this->render('cart', 'ajax'); will use the ajax layout. The default layout usually consists of full HTML (with etc..), while the ajax layout do not. But of course, you can always define the default layout for your controller to suit your need.

KL, try adding RequestHandler to your use variable, it will detect you're using ajax and use the correct layout automatically. (ajax layout: no extra markup, just the view)

Thank u very much for this tutorial.
It helps me to understand the ajax helpers drag & drop.

This is how a tutorial should be,
short, to the point and working ...

Thanks

Can you provide a source code for this tutorial

Hi!

Can you provide a working example of this please?

Thanks

The code is ready to be integrated into a CakePHP project. Of course you need Cakephp (and php) skills to do that, but that's not what this article is for

your site shouldn't be horizontally for long, you should of set a overflow property on your code boxes i think that was the problem, good thing i've got firebug and can do some on the fly cssing to your site others wise i would of left in a heard beat.

good tutorial tho ;)

Thanks, good point. I'll fix it

Where can I find CakePHP - is this a class of PHP functions or javascript

Interezante artículo amigo. muy util para los que recien nos iniciamos en cake. y tratamos de trabajar con Ajax....


Name:


E-mail:


URL:


Comment:


What is the first name of the guy blogging here?


This comment form is pretty crude. Make sure mandatory fields are entered correctly.
Basic html tags (a,i,b, etc) are allowed, others are sanitized