Drag and drop category management with CakePHP

Today's article is going to walk you through creating a slick drag and drop with AJAX category management system.

CakePHP offers a really nice built-in tree management. In fact, at a bare minimum you simply need to create a table with 2 extra columns, tell your model to act like a "tree" and rather than doing a find('all') you do a generatetreelist() or a find('threaded') and CakePHP takes care of the rest.

After doing a quick test, I was quite impressed with what CakePHP did for me, but I was not satisified. I wanted to create a really slick category management system that I can re-use and show off. Well, in this tutorial I go about 90% of the way. The only thing I didn't have time to finish was, rather than redrawing my tree through AJAX, use DHTML and dynamically update my tree after dragging and dropping. Don't worry, I plan to finish this with a part two soon.

I know it's not the most beautiful system in the word. But, I hope it drives the point home of how much potential there is with this system. To create all of the code below and do a bit of testing, it took about 3 hours total! A normal category management system of unlimited sub categories would probably take me a couple of days AND there is no way it could match the "coolness" factor of this application.

Also, in case the screen shots are not quite clear. To create a new category, type the name in the text box and drag the red rectangle above to where you want to place it. The category it will be placed in will be highlighted in yellow. If you wish to move a category or entire branch, simply drag it and move it to the new category.

Ok, let's move on to the actual code. The first thing to do is create our categories table:

If you've ever built a category system, the first three columns should look familiar. The key columns here though are the fourth and fifth columns, the lft and rght. CakePHP automatically deals with these columns for us whenever we save or delete data. For a detailed explaination, view this document from Mysql: http://dev.mysql.com/tech-resources/articles/hierarchical-data.html

Now that our table is created, the next thing I did was bake my model, controller, and views. After I baked all three, I had to update and remove a few things. First off our model, the simplest part of the process:

As you will see, nothing to special is going on inside of our controller. I've removed the redirects on successful save for the add and edit. I didn't include deleting in this example, but it certainly would not be a lot of work to add it in. I've done two other things. The first is, if I pass in an isAjax parameter, I set the layout to ajax. This prevents the entire layout from re-drawing with each Ajax call. The second thing I did, was change my find('all') to find('threaded'). If we used the generatetreelist() instead, it creates a flat array with an identifier for each child level, this is why we use threaded because it creates a recursive array that makes it easier in code to navigate through.

For simplicities sake during the creation of this article, I have placed the CSS and Javascript all in one file. I would normally segregate these items.

So what's happening? First up, when the document is done loading, we create our droppable elements. We also call our function to create our draggable elements - both our single draggable element for new categories and all of our current categories in case we would like to re-position them in our tree.

Inside our droppable code, in the drop function we do a couple of important things. One, get the parent_id of where we dropped it. Two, get the text value of our category to save as our new name. Three, determine if we are adding or editing. If we are adding, we post to the add page with just those two pieces of data. If we are editing, we also need to parse out our current id and post all three pieces of data to our edit page.

Finally, when our post is done, we re-draw our tree through AJAX. We also destroy and re-initialize our draggable elements. In part two I plan to not re-draw our tree in AJAX and instead dynamically move or create the elements.

The last file that you need to see is our recursive element to draw our categories. This file goes in app/views/elements/draw_category.ctp:

It's job is quite simple. It loops through all categories, writes it to the screen and calls itself with the child elements. That's it for today, I hope you have found this article as much fun as I had writing it!