Drag, drop and sort images with Prototype and Script.aculo.us

a fresh how-to guide with the help of the powerful Javascript framework named Prototype and its glamourous sidekick Script.aculo.us

1. Demo: "Drag, drop and sort images with Ajax and Prototype & Script.aculo.us"

Learn how to drag, drop and sort images and save this order to the database
by using the Javascript Prototype with Script.aculo.us libraries.

2. Introduction

2.1. What is Prototype? Toggle View

2.2. You will need:

  1. to download the Prototype Javascript framework
  2. to download the Script.aculo.us libraries, a collection of Web 2.0 style JavaScript libraries that help web developers to easily add visual and ajax effects to projects. These libraries depend on Prototype. The download also includes a copy of Prototype.
  3. Preferably a (local) server which runs PHP & MySQL. As we will save the order the way we sorted the images. I strongly recommend XAMPP. XAMPP is an easy to install Apache distribution containing MySQL, PHP and Perl. XAMPP is really very easy to install and to use - just download, extract and start.

3. The front-end code to initialize the drag, drop and sortable list

Save as: index.php
<?php
// Include your file which makes a connection to your database
include('include/class.database.php');        
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Drag, drop and sort images with Javascript's Prototype</title>
    
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <meta http-equiv="pragma" content="no-cache" />
    
    <script type="text/javascript" src="js/scriptaculous/prototype.js"></script>
    <script type="text/javascript" src="js/scriptaculous/scriptaculous.js"></script>
    <script type="text/javascript">
    //<![CDATA[
        document.observe('dom:loaded', function() {
            var changeEffect;
            Sortable.create('sortlist', { tag: 'img', overlap:'horizontal',constraint:false, 
                onChange: function(item) {
                    var list = Sortable.options(item).element;
                    $('changeNotification').update(Sortable.serialize(list).escapeHTML());
                    if(changeEffect) changeEffect.cancel();
                    changeEffect = new Effect.Highlight('changeNotification', {restoreColor:"transparent" });
                },
                    
                onUpdate: function() {
                    new Ajax.Request("saveImageOrder.php", {
                        method: "post",
                        onLoading: function(){$('activityIndicator').show()},
                        onLoaded: function(){$('activityIndicator').hide()},
                        parameters: { data: Sortable.serialize("sortlist") }
                    });
                }				
            });
        });
    // ]]>
    </script>	
    	
    <style>
    <!--
    #sortlist {
        width: 360px;
        margin: 0 auto;
        padding: 20px;
        margin-bottom: 20px;
        border: 1px solid #a5a1a1;
        text-align:center;

        }
    #sortlist img.sorting {
        float: left;
        margin: 4px;
        padding: 4px;
        border: 1px solid #ccc;
        }
        
    #sortlist img.sorting:hover { 
        float: left;
        margin: 4px;
        padding: 4px;
        border: 1px solid #666; 
        cursor: move;
        }
    -->
    </style>
</head>

<body>
    <div id="changeNotification"> </div>
    <div id="sortlist">
        // if you have a database with images stored
        <?php
            $sql = mysql_query("SELECT * FROM tut_sortImages ORDER BY orderId");
            while ($row = mysql_fetch_array($sql)) {
                print '<img class="sorting" alt="'.$row['imageCaption'].'" id="pictureId_'.$row['imageId'].'" src="'.$row['imageUrl'].'" />';
            }
        ?> 
        
        // write this if you don't have a database...
        <img class="sorting" alt="MGM Las Vegas" id="pictureId_1" src="images/tn_1.jpg" />
        <img class="sorting" alt="Hurricane Truck" id="pictureId_2" src="images/tn_2.jpg" />
        <img class="sorting" alt="Grand Canyon Rainbow" id="pictureId_3" src="images/tn_3.jpg" />
        <img class="sorting" alt="Yosemite" id="pictureId_4" src="images/tn_4.jpg" />
        <img class="sorting" alt="California Wine County" id="pictureId_5" src="images/tn_5.jpg" />
        <img class="sorting" alt="Desert road" id="pictureId_6" src="images/tn_6.jpg" />
        <img class="sorting" alt="West Hollywood home" id="pictureId_7" src="images/tn_7.jpg" />
        <img class="sorting" alt="Highway 1 Chipmunk" id="pictureId_8" src="images/tn_8.jpg" />
        <img class="sorting" alt="San Francisco fire trucks" id="pictureId_9" src="images/tn_9.jpg" />    	  
    </div>
</body>
</html>
		

The Javascript explained

<script type="text/javascript" src="js/scriptaculous/prototype.js"></script>
<script type="text/javascript" src="js/scriptaculous/scriptaculous.js"></script>
<script type="text/javascript">
//<![CDATA[
    document.observe('dom:loaded', function() {
        var changeEffect;
        Sortable.create('sortlist', { tag: 'img', overlap:'horizontal', constraint:false, 
            onChange: function(item) {
                var list = Sortable.options(item).element;
                $('changeNotification').update(Sortable.serialize(list).escapeHTML());
                if(changeEffect) changeEffect.cancel();
                changeEffect = new Effect.Highlight('changeNotification', {restoreColor:"transparent" });
            },
                
            onUpdate: function(list) {
                new Ajax.Request("saveImageOrder.php", {
                    method: "post",
                    onLoading: function(){$('activityIndicator').show()},
                    onLoaded: function(){$('activityIndicator').hide()},
                    parameters: { data: Sortable.serialize(list) }
                });
            }				
        });
    });
// ]]>
</script>	
    
<script type="text/javascript" src="js/scriptaculous/prototype.js"></script>
<script type="text/javascript" src="js/scriptaculous/scriptaculous.js"></script>
Make sure that the path is correct and that Prototype is loaded before Script.aculo.us.
document.observe('dom:loaded', function() { });
With this code we make sure this code only executes when the document is loaded: it makes the DOM available to us.
var changeEffect;
We declare a variable by using the keyword var that we will use later. Not doing this will result in an error.
Sortable.create('sortlist', { tag: 'img', overlap:'horizontal', constraint:false, 
Actually making a sortable list is very easy with Prototype. In fact you can do it as easy as this: Sortable.create(element [,options]) or translated to our example: Sortable.create('sortlist'). The default behaviour of sorting applies to li-elements. But you can sort any element you want, like div, p, img and so on. So, to override this default setting we use the [,options] of this function.

Use the tag option to specify the type of sortable elements within the container. Like I said, this defaults to li-elements. In our example we use images, so we set this option to the element img.

In this example we set the overlap option to horizontal. This controls the point at which a reordering is triggered. Defaults to vertical.

Next we set the constraint option to false so when we move the image around we can move freely within our container. Again, this defaults to vertical.

Should it interest you, here's a list of all available options for Sortable.create().
onChange: function(item) {
    var list = Sortable.options(item).element;
    $('changeNotification').update(Sortable.serialize(list).escapeHTML());
    if(changeEffect) changeEffect.cancel();
    changeEffect = new Effect.Highlight('changeNotification', {restoreColor:"transparent" });
},

The Sortable.create() function takes two callbacks in the options paramater: onChange and onUpdate. First we declare onChange: function(): it's a function that will be called whenever the sort order changes while dragging. It has the affected (or dragged, in this example) element as its parameter. So this explanation should make this onChange: function(item) clear.

Next we define a new variable called list, and we assign it the element that is currently being dragged. It dynamically retrieves the container for the element we pass.

This line $('changeNotification').update(Sortable.serialize(list).escapeHTML()); is for debugging purposes, and will display the string that will be passed over to our file saveImageOrder.php, ready for storing the order in our database.

The two following lines involve visual effects for our debugging line. The last line, changeEffect = new Effect.Highlight('changeNotification', {restoreColor:"transparent" }); makes sure that any highlight finishes with a transparency. This is good for people doing very fast sorting, so instead of whatever degree of yellowness it was in when the latest onChange() callback happened it finishes with a transparent background. Mind the comma at the end, saying that something follows.

onUpdate: function(list) {
    new Ajax.Request("saveImageOrder.php", {
        method: "post",
        onLoading: function(){$('activityIndicator').show()},
        onLoaded: function(){$('activityIndicator').hide()},
        parameters: { data: Sortable.serialize(list) }
    });
}	

Next is our second callback. Here, we finally make the Ajax request to put the data to our back-end file which saves it to a database. Combining Ajax.request with Sortable.serialize does the trick.

We start off by declaring the built-in function onUpdate: function() {}. That's the function that will be called upon the termination of a drag operation that results in a change in element order. One very important thing to notice is that it takes our variable list, created in the previous callback onChange, as an argument. This way we can still use that variable and the value it's holding for this callback.

Next we start a new instance of the object Ajax.Request by using the keyword new.

The first argument it takes is the URL, next are the options. (Tip: most of the time you can recognize the options of a function as they are placed between { })

The value of the method option for sending the data will be post (as opposed to get).

The next two lines starting with onLoading and onLoaded are two callbacks that are built-in into the Ajax.Request object. Should you be interested in what the other callbacks are: read more about Prototype's AJAX object callbacks.
These two lines show and hide the activityIndicator element. Within this element there's a typical Ajax spinner, and its sole function is to give visual feedback to the user.

More importantly is the last line: parameters: { data: Sortable.serialize(list) }. The code Sortable.serialize(element [,options]) is particularly useful for storing the current ordering in a database. It produces an URL-encoded string, which is stored in the variable data in our example, of the current ordering based on the id attributes of both the container element and the ordered elements. The default behaviour is as follows, try to understand this bit of important information:

Now that we covered the front-end code, let's head to the back-end.

4. The back-end code to process and save the order of the sortable list

Save as: include/constants.php
<?php       
/* DEFINE CONSTANTS */

define('DB_HOST', '127.0.0.1');
define('DB_NAME', 'your database name');
define('DB_USER', 'your database username');
define('DB_PASS', 'your database password');        
?>
Save as: include/class.database.php
<?php       
include("constants.php");

class database {
	var $connection;
	
	function database() {
		$this->connection = mysql_connect(DB_HOST, DB_USER, DB_PASS) or die(mysql_error());
		mysql_select_db(DB_NAME, $this->connection) or die(mysql_error());
	}

	
}
$db = new database;	     
?>        
        
Just a quick note: this is just a part of the my class.database.php. Normally other database related functions, as looping through database results, go here.
Save as: saveImageOrder.php
		<?php
        	// This is a class which connect to the database
            include('include/class.database.php');
            
            parse_str($_POST['data']);
            for ($i = 0; $i < count($sortlist); $i++) {
                $sql = mysql_query("UPDATE tut_sortImages SET orderId = $i WHERE imageId = $sortlist[$i]") or die(mysql_error());
                if ($sql) print 'Updating order went well'.$i;
            }
            sleep(1);
		?>
		

The function sleep(int seconds) delays our answer, giving the impression to the user that the we're very busy processing your form, while in fact without this function we get a response in a blink of an eye.

5. Demo: "Drag, drop and sort images with Javascript's Prototype and Script.aculo.us"

6. How did we do for "Drag, drop and sort images with Javascript Prototype"?

+422 564 votes

Bookmark and Share

Was this tutorial:

The information you were looking for?

More Ajax tutorials with Prototype

  • In this tutorial we will get data and do a live update on our page. You can use it as a real-time visitor counter, stock exchange ticker (boring...), live scores from a game. In fact anything that needs live updating. For this tutorial we'll make a live visitor counter. Hell yeah!
    Rating: +1487

  • In this short tutorial you will learn how to automatically expand or contract a textarea while typing. This is handy when you want to restrict space in your contact form layout but still give the user access to this fine piece of usability. I've added some extra functionality like highlighting a field when it's focused and sending the form with Ajax to our back-end. Enjoy!
    Rating: +393

  • In this short tutorial we use the Prototype library to send forms to our back-end for processing.
    Rating: +265

  • In this tutorial we will toggle elements, paragraphs for example, by their selector ID and by their position in the DOM (if for some reason their ID is unknown, like dynamically inserted content).
    Rating: +163

  • In this tutorial we will use the Prototype javascript framework to retrieve and display comments made on a news item without refreshing the browser. In fact you can get any data you want, but for the purpose of this tutorial we'll stick with retrieving comments.
    Rating: +134

  • Learn how to build an Ajax Star Rating System using Javascript's powerful Prototype framework.
    Rating: +72

  • In this tutorial we will retrieve and display pages without refreshing the browser.
    Rating: +47

  • In this short tutorial you will learn how to stop default link, form or any other element's behaviour when it is clicked. (like a link or a submit button)
    Rating: +41

Make a tutorial suggestion

I would like to see a tutorial about:
 Submit suggestion