Apr 05

cakephp.gifI spent some time working with a few of the “cool” features of CakePHP this week.

I again noted that Cake is a capable PHP framework in ways that aren’t obvious at first glance. In fact, I found myself fairly impressed by their AJAX and Script.aculo.us support.

For the particular application I was working on, a drag & drop interface was going to be particularly handy. Having spent time previously implementing drag & drop interfaces with Script.aculo.us, Yahoo UI, and Rico, I was hoping the process would be a bit easier using Script.aculo.us as an extension on top of Cake and I was pleasantly surprised with the results. I walked away with a functional drag & drop interface in an amazingly short amount of time with very little coding.

Important Note: A CakePHP beginner should be able to get this tutorial working, but I am still assuming that you have a basic understanding of Cake in order to implement this tutorial properly. If, not please see this article to get started.

In this example, we’ll use a very simplified page of widgets that are “draggable” to change their order and save the changes to a MySQL database in real time.

Make sure you have a recent version of CakePHP (I used version 1.1.14.4603). The document root of your web server should probably be set to:

SERVER-PATH/CAKE-ROOT/app/webroot/

Now, you just need to grab the latest stable version of Script.aculo.us (I used v1.7.0 for this example). For my set up, I extracted the files in the following manner:

CAKE-ROOT/app/webroot/js/scriptaculous/lib/prototype.js
CAKE-ROOT/app/webroot/js/scriptaculous/src/scriptaculous.js (and remaining “src” files)

Also, for this example, I used a MySQL database called “widgets” with a table called “widgets” that contained three fields: “id”, “title”, and “order”.

Here’s The Code:
———————————————————-
Widgets Controller
[ --controllers/widgets_controller.php ]
class WidgetsController extends AppController
{

var $name = ‘Widgets’;/** These are the needed helpers **/
var $helpers = array(‘html’, ‘javascript’, ‘ajax’);

/** Implements the index view **/
function index() {
$this->pageTitle = ‘Widgets Index’;

//status message to be used later
$this->set(‘status’, ‘Widget Ordering Succesfully Saved!’);

//assign widgets to view sorted by the ‘order’ field
$this->set(‘widgets’, $this->Widget->findAll(null,null,’order’,null,null));
}

/** Receives ajax request from index **/
function order()
{
//loop through the data sent via the ajax call
foreach ($this->params['form']['widgets'] as $order => $id)
{
$data['Widget']['order'] = $order;
$this->Widget->id = $id;
if($this->Widget->saveField(‘order’,$order)) {
//we have success!
} else {
//deal with possible errors!
}
}
$this->autoRender=false;
}

}
?>

/*
The data comes to your order action in this format:
Array (
[0] => 19
[1] => 16
[2] => 7 )
where the array indexes ([0],[1],[2]) are the “orderings” and the array values (19,16,7) are the actual “id’s” of each question.
*/


Widget Model

[ --models/widget.php ]
<?php
class Widget extends AppModel
{

var $name = ‘Widget’;

}
?>

Index View
[ --views/widgets/index.thtml ]
<?php
if (isset($javascript)) {
echo $javascript->link(‘scriptaculous/lib/prototype.js’);
echo $javascript->link(‘scriptaculous/src/scriptaculous.js’);
}
?>
<div id=”status” style=”display: none;”><?php echo $status; ?></div>

<ul id=”widgets”>
<?php foreach ($widgets as $row): ?>
<?php echo ‘<li id=”widget_’ . $row['Widget']['id'] . ‘”>’ . $row['Widget']['title'] . ‘</li>’; ?>
<?php endforeach; ?>
</ul>

<?php echo $ajax->sortable(‘widgets’, array(‘url’=>’order’, ‘before’=>”Element.hide(‘status’);”, ‘complete’=>”Element.show(‘status’);”)); ?>

———————————————————-

Take special note of the “before” and “complete” ajax helpers to show and hide the status message. Read more about them here under the heading “AJAX”.

And that’s it! You now have a drag & drop interface that will serialize your form data and save it to the database quickly and easily. Isn’t that a little too easy?

A few important side tips:

- Use Firebug to do your debugging. It’s an invaluable tool that you should use if you don’t already.
- For debugging Cake errors, make sure you have debug level 2 set in [core.php] for some additional help.

If you have questions or comments about this tutorial, feel free to leave a comment at the bottom and I’ll do my best to answer what I can. Remember, this was intended to be a basic tutorial with as much elegance as possible while still remaining clear to differing skill levels. I’m also fairly new to CakePHP myself, so feel free to offer suggestions to make the code better!

I was going to offer a zip download of the source code, but I’d rather you type it out or copy & paste– maybe you’ll learn something in the process.

35 Responses to “CakePHP: Sortable AJAX Drag & Drops – The Basics”

  1. Anonymous Says:

    I feel like such an idiot when I read your page. :)

  2. Anonymous Says:

    I don't know why you're using query where you can simply use Model::save.
    $data['Widget']['order'] = $order;
    $this->Widget->id = $id;
    if($this->Widget->save($data)) {..}
    Also your saveOrder doesn't return any value so how'd you know that is was successful or not.

  3. Dustin Weber Says:

    Yeah, good point. I corrected the code. Thanks for the help!
    - Dustin Weber

  4. Anonymous Says:

    Since we're saving only one field, we can simplify it even more:
    $this->Widget->id = $id;
    if($this->Widget->saveField('order',$order)){
    }

  5. Dustin Weber Says:

    Again, you got me! I updated the code once more.
    I really thought I should use saveField originally.. but for one reason or another I totally forgot to put it this example.
    Good catch, I appreciate the help again!
    - Dustin Weber

  6. Drag and Drop ordering « CakePHP findings Says:

    [...] http://dustinweber.com/cakephp/cakephp-sortable-ajax-drag-drops-the-basics/ [...]

  7. Mike Says:

    Very nicely explained, I read through it a couple of times and was then able to include it in my own pages. A little bit of CSS would round this out nicely too.

  8. Mat Says:

    It does seems a very useful tutorial, but I do not get a thing: in the view it is necessary to insert a form?

  9. Andras Says:

    Couldn’t be easier, worked great after a few minutes

    Thank you,
    Andras

  10. Ryan Says:

    I liked the examples, but they could definitely use some better formatting.

  11. MRashed Says:

    Im new to cakePHP
    and have some questions
    regarding the $ajax->sortable function. In the
    cakePHP manual regarding the $id it reads: …specified by DOM element ID $id… In your example the id of the lists is “widget_[id]” but in the $ajax->sortable you use “widgets” as your id. I’m pretty confused right now, cause it works but I don’t know why and I think just copying and pasing won’t help me much in understanding this awesome framework

  12. Ben Says:

    Thank you so much for this quick rundown. It’s always good to see overview posts like this that give a new user like myself some direction. Thanks again!

  13. esion Says:

    Thanks!
    This script is easy and quick to
    - find
    - implement

    So cool.

  14. Jacob Says:

    Pretty cool, but i would really enjoy a demo of this…
    Jacob

  15. vibeesh Says:

    It is very useful.

  16. James Says:

    Extremely Helpful.
    Thanks!

  17. James Says:

    I have everything you have up here working except for the automagical status update.

    Do you have any ideas. Here is what I am doing.

    if($this->Widget->saveField(’order’,$order)) {
    $this->set(‘status’, ‘Widgets have been reordered’);
    $this->render(‘status’, ‘ajax’);
    } else {
    $this->set(‘status’, ‘Something went south’);
    $this->render(‘status’, ‘ajax’);
    }

  18. Jeremy Says:

    @Dustin Thank you for the great tutorial!

    @MRashed The sortable id is referring to the unordered list (ul) id of widgets. The widget_{id} is the id for each of the list items (li).

    @Mike & Ryan You are able to style the list based on your needs, but here is a little something to help in your css:
    ul#widgets{list-style-type:none;}
    ul#widgets li{border:1px solid #000; cursor:move;}

    @James I am not sure but I think the problem is that you are trying to render in your order function. Also you don’t need all that because in the index function you set your success message, and in your view the status is hidden then when the order function is complete the status is shown.

  19. Troy Says:

    Great tutorial! I managed to get it working. Now I’m trying to add more functionality. I wanted to have another list that I could drag from to add elements to the sortable list. I would like this ‘from’ list to stay static (I don’t want elements removed from the list when they are accepted by the sortable).

    I’m close. I just created a new list of $ajax-drag() elements with ‘revert’=>true to create the “from” list. I also added ‘containment’=>”['sortable_list','from_list']” to the original sortable.

    I can’t figure out how to keep the “from_list” static. It reverts if the element isn’t dragged into the “sortable_list”.

    Seems like this should be a common functionality. I think of a shopping cart. I wouldn’t want my catalog entries disappearing when someone drops them into their cart.

    Any suggestions?

    Thanks,
    Troy

  20. Christian Says:

    Thanks a lot!! I’m a rookie in CakePHP, downloaded it first time yesterday. Yet I managed to follow your helpful tutorial and make it work :-)

    I just had one problem; WP tweaks the output of single and double quotes, so I had to debug some PHP errors before I got it up. Lazy me didn’t read the code thoroughly enough before paste – save – load page… :-)

  21. Yogeshs Says:

    Its nice tutorial and working fine for me.
    As I m knew to cakephp so I would like to know if I have to show data in Field 1Field 2Field 3
    How do apply this drag n drop sorting here above.
    Please reply asap ..I need it urgently :(
    Thanks in advance ..

  22. Perkster Says:

    You rock! Thanks for a great easy to learn article.

  23. Karl Says:

    Nice tutorial, thanks.

    I was scratching my head for a bit because I copied some elements of your code which then didn’t work – for anyone having the same issue it is because the single and double quotes were not rendering as ‘normal’ quotes inside my editor and so the strings were breaking. This is on FF3/Mac, don’t know if the same is true of other browsers/OSs

  24. Dustin Weber Says:

    EVERYONE

    This post is very old and probably unusable at this point. CakePHP has changed immensely since I wrote this. I would recommend you learn version 1.2 at this point (it is in RC2) and you’ll have a much easier go of it!

    Thanks,

    Dustin Weber

  25. brooks Says:

    Excellent. Dustin, I’m using 1.2 and it seems you’ve either updated the code or something, but it works great.

    Thanks for helping the new guy (me)!

  26. Dustin Weber Says:

    Yeah, now that I looked at the code; it actually seems like there isn’t anything deprecated in there after all. Isn’t that nice?!

  27. Sebastien Galarneau Says:

    Wow very nice code…
    But i wanted the order to be the same when i came back on the page… ??!

    thks a lot

  28. Loren Says:

    I couldn’t get this to work at first; I got a “Sortable not defined” javascript error, because as is, this code causes the script tags for scriptaculous and prototype to appear AFTER the document head.

    I got it to work by simply adding “,false” at the end of the $javascript->link statements, like this;

    if (isset($javascript)) {
    echo $javascript->link(’scriptaculous/lib/prototype.js’, false);
    echo $javascript->link(’scriptaculous/src/scriptaculous.js’, false);
    }

    works fine now.

    Cheers!

    Loren

  29. Tomas Kupka Says:

    Thanks, it works great!! with cake 1.2

  30. ray Says:

    hi,

    i have the drag and drop sorting working but its not saving the order.

    in firebug this is what is coming up in the post

    widgets[] 2
    widgets[] 3

    for some reason the ID is coming up empty.. can someone help me?

    i have checked the code many times….no idea what the problem could be…

  31. ray Says:

    i need some help…

    my function order() is not getting called…so the drag and drop is working but not getting saved….

    here is my code in the view:

    sortable(‘widgets’, array(‘url’=>’order’, ‘before’=>”Element.hide(‘status’);”, ‘complete’=>”Element.show(‘status’);”)); ?>

    set(‘status’, ‘Artist Ordering Succesfully Saved!’);

    //assign widgets to view sorted by the ‘order’ field
    $this->set(‘widgets’, $this->Widget->findAll(null,null,’order’,null,null));

    /** Receives ajax request from index **/

    }// end of index

    function order()
    {

    //loop through the data sent via the ajax call
    foreach ($this->params['form']['widgets'] as $order => $id)
    {
    $data['Widget']['order'] = $order;

    $this->Widget->id = $id;
    $this->Widget->saveField(‘order’,$order);
    //we have success!

    } // foreach loop

    } // end of function order

    }

  32. Felix Says:

    Hi, I have exactly the same problem as ray, everything appears to be working but the order function just doesn’t get called.

    Can any one suggest what the problem could be?

    Thanks,
    Felix

  33. varun prakash Says:

    hardly i need a completed example with demo since i am a new-bee to cakephp i wanna finish this task send me soon thanks in advance

  34. Stuart Palmer Says:

    Has anyone got this working in Cake 1.3? I have been trying but get this error message:

    Warning (512): SQL Error: 1064: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ‘findAll’ at line 1 [CORE/cake/libs/model/datasources/dbo_source.php

    I am wondering if the findAll command has been depreciated or something?

    Thanks

  35. MaverickMayur Says:

    I was wondering, if anyone has tumbled across an issue while implementing this solution on a paginated list?
    I used the scriptaculous drag and drop, it updates the order successfully if done on the same page. However, if you move onto the next page, it resets the array index updates the order, thereby creating duplicate ‘order’ values in the database.

    Any ideas?

Leave a Reply