Home | Projects | Tutorials | Articles | live chat | Submit Project | Big Vote
 
Ajax Projects
.NET Frameworks
Java Frameworks
PHP Frameworks
Ruby Frameworks
Other Frameworks
Cool AJAX sites
Ajax Resources
Ajax Tools
JavaScript frameworks
Partners

 Home /  Tutorials / How to Make Sortable Lists

How to Make Sortable Lists





Many web applications need to offer an interface to order items - think about categories in a weblog, articles in a CMS, wishes in an e-commerce website... The old fashion way of doing it is to offer arrows to move one item up or down in the list. The AJA

Read The Full Tutorial.
































Overview

Many web applications need to offer an interface to order items - think about categories in a weblog, articles in a CMS, wishes in an e-commerce website... The old fashion way of doing it is to offer arrows to move one item up or down in the list. The AJAX way of doing it is to allow direct drag-and-drop ordering with server support. This chapter will describe both ways, together with a few tips on the way to enhance your object model and to do complex queries with Creole.

classic and AJAX sortable lists

What you need

Data structure

For this article, the example used will be an undefined Item table - name it according to your needs. In order to be sortable, records need at least a rank field - no need for a heap here since the sorting will be done by the user, not by the computer. So the data structure (to be written in the schema.yml) is simply:

propel:
test_item:
_attributes: { phpName: Item }
id:
name:        varchar(255)
rank:        { type: integer, required: true }

Make sure you build your model once the data structure is defined by typing in a command line interface:

$ symfony propel-build-model

You will also need a database with the same structure. The fastest way of doing it is to call:

$ symfony propel-build-sql
$ symfony propel-insert-sql

Extending the model

Before thinking about the user interactions, make sure you have a way to retrieve items by rank, to get the list of items ordered by rank, and to get the current maximum rank, by adding the following methods to the lib/model/ItemPeer.php:

static function retrieveByRank($rank = 1)
{
$c = new Criteria;
$c->add(self::RANK, $rank);
return self::doSelectOne($c);
}
 
static function getAllByRank()
{
$c = new Criteria;
$c->addAscendingOrderByColumn(self::RANK);
return self::doSelect($c);
}
 
static function getMaxRank()
{
$con = Propel::getConnection(self::DATABASE_NAME);
$sql = 'SELECT MAX('.self::RANK.') AS max FROM '.self::TABLE_NAME;
$stmt = $con->prepareStatement($sql);
$rs = $stmt->executeQuery();
 
$rs->next();
return $rs->getInt('max');
}

These methods will be of great use for both sorting interfaces. If you need more information about the way the Object Model handles database queries in symfony, check out the basic CRUD chapter of the Propel user guide.

There are two more method that needs to be added to the lib/model/Item.php class. They won't be needed here, but you will probably need them in a real world application, where you will also add and delete items to your table:

public function save($con = null)
{
// New records need to be initialized with rank = maxRank +1
if(!$this->getId())
{
$con = Propel::getConnection(ItemPeer::DATABASE_NAME);
try
{
$con->begin();
 
$this->setRank(ItemPeer::getMaxRank()+1);
parent::save();
 
$con->commit();
}
catch (Exception $e)
{
$con->rollback();
throw $e;
}
}
else
{
parent::save();
}
}
 
public function delete($con = null)
{
$con = Propel::getConnection(PagePeer::DATABASE_NAME);
try
{
$con->begin();
 
// decrease all the ranks of the page records of the same category with higher rank 
$sql = 'UPDATE '.ItemPeer::TABLE_NAME.' SET '.ItemPeer::RANK.' = '.ItemPeer::RANK.' - 1 WHERE '.ItemPeer::RANK.' > '.$this->getRank();
$con->executeQuery($sql);
// delete the item
parent::delete();
 
$con->commit();
}
catch (Exception $e)
{
$con->rollback();
throw $e;
}
}

Additions and deletions of records have to be managed carefully for the integrity of the rank field, that's why the save() and delete() methods are to be specialized. Because these methods do complex read/write operations and create a risk of concurrency issues, these operations are enclosed in a transaction (refer to the Propel documentation for more details about transactions in symfony).

Preparing the module

The interactions described in this tutorial will take place in a item module. Initialize it by calling (assuming you have a frontend application):

$ symfony init-module frontend item

Make sure your web server configuration is OK by testing the access to this new module via your favorite browser. Here is the URL that you should check if you follow this tutorial with a sandbox:

http://localhost/sf_sandbox/web/frontend_dev.php/item

Finally, if you want to test the ordering of items, you will need... items. Create a bunch of test items, either via a CRUD interface or a population file.

Now that everything is ready, let's get started.

Classic sortable list

A classic sortable list is a list for which each item has a control to change its order. First, create the action and template that display the list:

// add to modules/item/actions/actions.class.php
public function executeList()
{
$this->items = ItemPeer::getAllByRank();
$this->max_rank = ItemPeer::getMaxRank();
}
 
// create a template listSuccess.php in modules/item/templates/
<h1>Ordered list of items</h1>
<ul>
<?php foreach($items as $item): ?>
<li>
<?php
echo $item->getName().' ';
if($item->getRank() > 0):
echo link_to('Move up ', 'item/up?id='.$item->getId());
endif;
if($item->getRank() != $max_rank):
echo link_to('Move down', 'item/down?id='.$item->getId());
endif;
?>
</li>
<?php endforeach ?>
</ul>

The links to move an item up or down are displayed only when the reordering is possible. This means that the first item cannot be moved further up, and the last item cannot be moved further down. Check that the page displays correctly:

http://localhost/sf_sandbox/web/frontend_dev.php/item/list

Now, it's time to look into the item/up and item/down action. The up action is supposed to decrease the rank of the page given as a parameter, and to increase the rank of the previous page. The down action is supposed to increase the rank of the page given as parameter, and to decrease the rank of the following page. As they both do two write operations in the database, these actions should use a transaction.

The two actions have a very similar logic, and if you want to keep D.R.Y., you'd better find a smart way to write them without repeating any code. This is done by adding a swapWith() method to the Item.php model class:

public function swapWith($item)
{
$con = Propel::getConnection(ItemPeer::DATABASE_NAME);
try
{
$con->begin();
 
$rank = $this->getRank();
$this->setRank($item->getRank());
$this->save();
$item->setRank($rank);
$item->save();
 
$con->commit();
}
catch (Exception $e)
{
$con->rollback();
throw $e;
}
}

Then, the up and down actions become pretty simple:

public function executeUp()
{
$item = ItemPeer::retrieveByPk($this->getRequestParameter('id'));
$this->forward404Unless($item);
$previous_item = ItemPeer::retrieveByRank($item->getRank() - 1);
$this->forward404Unless($previous_item);
$item->swapWith($previous_item);
 
$this->redirect('item/list');
}
 
public function executeDown()
{
$item = ItemPeer::retrieveByPk($this->getRequestParameter('id'));
$this->forward404Unless($item);
$next_item = ItemPeer::retrieveByRank($item->getRank() + 1);
$this->forward404Unless($next_item);
$item->swapWith($next_item);
 
$this->redirect('item/list');
}
Read Full Tutorial
neon tabela Says:
Wed Oct 07, 2009 4:05 pm



Your site is very easy in terms of expression and open. I think everyone who enters your site is very gratifying, but also sharing a very nice opportunity to give …

Leave Your Comment

Name (Required)
Mail (will not be published) (required)
Website
AddThis Social Bookmark Button
Top Projects
MSN Web Messenger
MessengerFX
ebuddy
e-messenger
ILoveIM
AJAX file upload
You Tube
KoolIM.com
Meebo
Ajax.NET Professional
Tutorials
AJAX in Java with DWR
SAML Single Sign On for Google Apps in .NET and DotNetNuke
qooxdoo 0.8: new GUI toolkit and tool chain
Two CSS Background Images on a Single Website
Auto Failover for CDN based Javascript
Design DHTML without HTML or CSS
Ajax
Showing an Alert or Message Box using ASP.NET AJAX
Get rid of the IE iframe “click”
Digg-like Ajax Voting