Advertisement

A jQuery UI and .Net Image Organizer

by

Over the course of this tutorial we'll look at how to create a simple image organizer that lets users reorder a series of images; this functionality could be useful on any kind of image-based site where users have a collection of images that they have uploaded or otherwise added to their profile or account. We'll use .net to retrieve and store the order of images in a SQL database on the server, and jQuery UI to handle the reordering of the images on the client.

Image Organizer

Getting Started

The page we create will be of the type aspx; we can create and edit these files with a simple text editor if necessary, but it's far more efficient to use a proper .Net IDE. Visual Web Developer Express from Microsoft is a great .Net IDE and it's completely free; grab a copy now from http://www.microsoft.com/express/Web/. It can be downloaded as part of the Web Platform; you can choose a range of different products when you download it, for the purposes of this tutorial we'll be using the following components:

  • Visual Web Developer Express 2008
  • SQL Server Express 2008 (with SQL Server 2008 Management Studio Express)

The Web Platform is actually pretty good and gives you access to a wide range of popular web applications and frameworks, such as dotNetNuke, Joomla, Umbraco and many others, and the platform installer downloads and configures everything you need. It'll take a little while to download and install, so while it's doing its thing we can set up a development area; create a new folder and call it image_organiser, then inside this folder create two new folders and call them js and css.

You should also grab a copy of the latest release of jQuery UI; head over to the download builder at http://jqueryui.com/download and make sure the following components at the left of the page are checked:

  • Core
  • Widget
  • Mouse
  • Sortable

A theme isn't required but make sure version 1.8 is selected at the right of the page and then hit the download button. Once the archive has downloaded open it up and copy the following files from the js folder in the archive to the js folder we just created:

  • jquery-1.4.2.min.js
  • jquery-ui-1.8.custom.min.js

We also make use of Doug Crockford's excellent JSON utility, which can be downloaded from http://www.JSON.org/json2.js. Save a copy of this file to our js folder and be sure to remove the alert from the top of the file.

Once the platform installer has finished, fire up Visual Web Developer Express and go to File » Open Web Site and then choose the image_organiser project folder that we just created. You'll get a prompt asking whether to upgrade the site to use .net 3.5; choose Yes.


Creating a Database

We'll create a new database and table for this example; open the SQL Server Management Studio and connect to the local instance of SQL Server (it will be called something like COMPUTERNAME\SQLEXPRESS). To create a new database right-click on the Databases folder and choose New Database. In the dialog that appears set the Database name to image_organiser and then click Ok. You should then see the new database listed in the left pane of the manager.

We now need to create a new table within our new database; expand the new database, then right-click on the Tables folder and choose New table. The management console will give you a couple of extra panels; one showing the table columns and one showing the table properties. Add three columns to the table, the first should have the name src and be of type varchar(50), the second should have the name alt and also be of type varchar(50). The final column is called [order] and is of the type int. Only the alt column should allow null values.

Click the disk icon on the toolbar and choose the name images. When you expand the Tables folder in the Object Explorer on the left, the new table should be listed. In a full implementation, each user of the application would have their own set of images, and there would no doubt be other tables in the database for usernames and passwords and other information associated with the user. For the purpose of this tutorial, imagine that we're a single authenticated user manipulating our own set of images.

Now we need to populate the table with some data; right-click on the new table and choose Edit Top 200 Rows; the console will change again so that you have an editable view of the table. An id column is inserted into the table automatically; in this example I've simply used a zero-based index number for the values in this column, but this should match the file names of the images in use. Use the data shown here:


The aspx File

To create a new aspx page, right click the root of the site in the Solution Explorer at the right of the application and choose Add New Item. In the dialog that appears choose Web Form in the top section and Visual C# in the Language select box. Click Add.

This will give a new page called Default.aspx, which will open up in the IDE automatically. The new page is listed in the Solution Explorer at the right and it has a plus icon beside it indicating that it contains something. For those of you that have never worked with .Net before, it contains the code-behind aspx.cs file which we can use to add the server-side logic for the page a little later on.

The aspx file will have a few elements inside it already, including a <form>; add the following code within the <form> element:

<div id="outerWrap">
	<div id="left">
		<h1>Image Organiser</h1>
		<p>Re-order the images by dragging an image to a new location. Your changes will be saved automatically.</p>
	</div>
	<div id="images"></div>
</div>

We've got a simple outer container with two <div> elements inside it; one holds some brief instructions while the other will be used to hold the sortable image elements. In order to populate the images container with the images from the database we can use the handy .Net Repeater control; add the following code inside the images container:

<asp:Repeater id="imageRepeat" runat="server">
	<ItemTemplate>
      		<li id="<%# DataBinder.Eval(Container.DataItem, "id") %>">
			<img src="<%# DataBinder.Eval(Container.DataItem, "src") %>" alt="<%# DataBinder.Eval(Container.DataItem, "alt") %>" />
		</li>
	</ItemTemplate>
</asp:Repeater>

We use the <asp:Repeater> element whichrepeater control we just added to the page. When you open up the Default.aspx.cs file you'll see that there are several items in the file already; there are a series of using directives at the top of the file which indicate to the server the namespaces of the .Net components that are required by the aspx file. As well as those included in the file, we'll also need to add the following:

using System.Data;
using System.Data.SqlClient;

Following this we have a class definition and a Page_Load event handler which we can use to execute server-side code when the aspx page loads. Within this event handler add the following code:

//define connection
SqlConnection dbCon = new SqlConnection("Server=DESKTOP\\SQLEXPRESS;UID=sa;PWD=your_password;Database=image_organiser");
        
//define query
string sSQL = "Select * from images";

//define command
SqlCommand cmd = new SqlCommand(sSQL, dbCon);

//open connection
dbCon.Open();  

//read data
SqlDataReader ds = cmd.ExecuteReader(); 

//bind to repeater
imageRepeat.DataSource = ds;  
imageRepeat.DataBind();

//close connection
dbCon.Close();

The code is very straight-forward, let's walk through it; we define a new SqlConnection using the variable dbCon. The value of this variable is the connection string we use to connect to the database and consists of the server name, user name (sa is the default), password and database name. Don't forget to replace your_password in the above code with the password you set when installing SQL.

Next we define our query, which in this case is just to select everything in the database using the * wildcard. We also store the SqlCommand in a variable which consists of the query and the connection. Following this we can then open the connection with the Open() method and read the data into a SqlDataReader variable with the ExecuteReader() method called on the SqlCommand.

Lastly we bind the data to our repeater control by setting the ds variable as the repeater's DataSource and calling the DataBind() method on it, before finally closing the database connection. We don't need to select the repeater control, we can just refer to it directly using the ID we specified in the aspx page. The first stage of our code is now complete, the repeater will display an <li> and <img> for each row of our database. It'll look a little bland at this point however so let's add some basic styling.


Styling the Page

To add a new style sheet to the site right-click on the css folder in the Solution Explorer at the right and choose Add New Item; select Style Sheet in the top pane of the dialog and set the Name field to image_organiser.css, then hit Add. The new file will automatically open in the IDE; add the following code to it:

#outerWrap { width:1004px; margin:auto; position:relative; background-color:#eee; border:1px solid #999; }
#outerWrap:after { content:"."; display:block; visibility:hidden; clear:both; }
h1 { font:italic normal 24px Georgia, Serif; text-align:center; margin:10px 0; }
p { margin:0; font:12px Arial, Sans-serif; padding:0 10px; }
#left { width:218px; float:left; }
#images { margin:0; padding:0; float:left; width:786px; }
#images li { list-style-type:none; float:left; cursor:move; margin:10px 10px 0 0; width:250px; height:250px; border:1px solid #999; }
#images .vacant { border:3px dotted #66d164; width:246px; height:246px; background-color:#fff; }
.success, .failure { margin:0 0 0 10px; padding:4px 0 4px 26px; position:absolute; bottom:18px; font-weight:bold; }
.success { background:url('../img/tick.png') no-repeat 0 1px; color:#12751c; }
.failure { background:url('../img/cross.png') no-repeat 0 0; color:#861b16; }

These basic styles simply lay the page out in the format we want for this example. There's nothing really important here, any of it could easily be changed to suit other requirements. Don't forget to link to the new stylesheet in the <head> of the page with the following:

<link rel="stylesheet" type="text/css" href="css/image_organiser.css" />

At this point, the page should now appear like this when it first loads in the browser:

You can view the page by right-clicking the aspx file in the Solution Explorer and choosing View in browser. This will use the IDE's built-in web server to display the page.


Making the Images Sortable

The point to the page is to make the images sortable so that the user can reorder them, to do this we need to link to the jQuery UI files in our js folder; add the following <script> tags directly before the closing </body> tag:

<script type="text/javascript" src="js/jquery-1.4.2.js"></script>
<script type="text/javascript" src="js/jquery-ui-1.8.custom.min.js"></script>

Making the images sortable is extremely easy; after the above <script> elements add the following code:

<script type="text/javascript">
	$(function() {

		//make li sortable
		$("#images").sortable({
			placeholder: "vacant",
			update: function(e, ui) {

				//code to save new order      			
                  
	      		}
      		});
	});
</script>

All we do is call the sortable() method on the container of the items we would like to be able to sort. We supply a configuration object to the method specifying the class name that should be applied to the empty slot that the item being sorted can be dropped into using the placeholder option, and a callback function that should be executed whenever a sort occurs and the order of the items changes. When we run the page at this point, we should find that the images are sortable and that our vacant styles are applied:


Saving the New Order

All we need to do now in the main .aspx file is send the new order of the images to the server whenever the images are sorted; replace the comment in the update callback with the following code:

//create vars
var orderArray = [], wrap = {};

//reset 'saved' message
$(".success", $("#left")).remove();

//process each image
$("#images img").each(function(i) {
                        
	//build img object
	var imgObj = {
		"id": $(this).parent().attr("id").split("_")[1],
		"order": i + 1
	};

	//add object to array
	orderArray.push(imgObj);
});

//wrap in object
wrap.d = orderArray;

//pass to server
$.ajax({
	type: "POST",
	url: "WebService.asmx/updateOrder",
	data: JSON.stringify(wrap),
	contentType: "application/json; charset=utf-8",
	success: function(data) {
		if (data.d === "saved") {
			$("<p>").text("New order saved!")
				.addClass("success").appendTo("#left");
		} else {
			$("<p>").text("Save failed")
				.addClass("failure").appendTo("#left");
		}
	}
});

Let's look at what this code does; first we create a couple of variables which we'll need later on in the script, the first is an array literal, the second an object literal. We then remove any success messages that may be present from previous sort interactions. We then process each of the images in the image grid using jQuery's each() method, which will execute the anonymous function we specify once for each image in the list. This function is automatically passed an index number for the current item, which we need to make use of.

Within this function we create a new object literal and give it two properties; the id of the current image, and the index number of the current each() iteration. We then insert this object into the array we created a moment ago. Once we have done this for each image on the page we insert the array into a wrapping object. This object will be passed to the server, which is done using jQuery's low-level ajax() method.

We need to use the ajax() method instead of, say, the post() or getJSON() methods, because we need to specify the contentType in order for the server to process the data correctly. We set the request type to POST, specify the server-side file with the name of the method that will handle the request as a query string parameter. We also pass in our prepared wrap object. In order to convert the object fully into JSON syntax we use the stringify() method of the json2.js file.

We also specify a success handler which will be executed once the request is completed; we can see the string returned by the server by accessing the data passed back to this success handler. The actual string will be contained in a propery of the data object labelled d. Data returned to a page via AJAX in .Net is usually accessed via a d object in this way.

We can add a different message and class name to the page depending on whether the server indicates the request was a success or failure. You can test this and see the different messages by using Firebug to change the id attribute of one of the image containers to a value that doesn't exist in the database, and then sorting an image. This is how our messages should appear:


The Active Server Method File

To receive the JSON object passed to the server via AJAX following a sort interaction we can use an asmx file; right-click the root of the site in the Solution Explorer and choose Add New Item. In the dialog that appears choose Web Service in the top section and Visual C# in the Language select box, then click Add.

This will give you a new WebService.asmx file in your site, but the code-behind for this file will go into an automatically created folder called App_code. We don't need to update the asmx file at all, everything will be done in the code-behind WebService.asmx.cs file. Open it up and you'll see that there is already of lot of code in the file; change it so that the file in its entirety appears as follows:

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Web;
using System.Web.Services;
using System.Web.Script.Services;

/// <summary>
/// Receives and saves new order of images
/// </summary>

[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]

// To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line. 
[System.Web.Script.Services.ScriptService]

public class WebService : System.Web.Services.WebService {

    public class ImageDTO
    {
        public string id { get; set; }
        public int order { get; set; }
    }

    [WebMethod]
    public string updateOrder(List<ImageDTO> d)
    {
        //define connection
        SqlConnection dbCon = new SqlConnection("Server=DESKTOP\\SQLEXPRESS;UID=sa;PWD=your_password;Database=image_organiser");
        
        //process JSON object
        foreach (ImageDTO img in d)
        {
            //define procedure
            string sSQL = "Update images set [order] = " + img.order + "where id = " + img.id;

            try
            {
                //open connection
                dbCon.Open();

                //update data
                cmd.ExecuteNonQuery();

                //close connection
                dbCon.Close();
            }
            catch (SqlException ex)
            {
                return "failed";
            }
        }        

        //success!
        return "saved";
       
    }
    
}

We need to add several namespaces to the using section at the top of the file in order to work with our SQL database. We'll also need to ensure we uncomment the line that allows our web service to be called from the script in the main aspx page (it's clearly marked with a comment in the default version of the file).

Within the WebService class we need to add a new class that represents each of the inner objects within the array passed to the web service. We do this with the ImageDTO class and give each object id and order properties and assign getter and setter methods for working with the values of these properties.

Next comes the method that is called from our script; the updateOrder web method. This method receives the d object which we cast as a list of ImageDTO objects; we'll then be able to use the methods defined in our class to access each property.

We define the connection information needed to connect to our database and then process each object in our ImageDTO list. We extract the new order and the id of the image and use this to update the order column for the corresponding row in the MSSQL table.

This code is relatively similar to the code we used to get the information out of the database on page load, we just use a different connection string and use the ExecuteNonQuery() method instead of ExecuteReader() because we're updating the database instead of just reading from it. We also wrap our connection execution in a try...catch statement and either output the string failed or saved depending on whether the update succeeds.


Summary

We used the c# flavour of .Net combined with jQuery UI in this tutorial to create a page that remembers the order of images on it and allows the images to be reordered according the whims and desires of the visitor to the page. In this example it is a simple page but don't forget that in a proper implementation this would probably be accessible only by the authenticated user; each user would have access to his or her own images and be able to sort them, while the images would be fixed on the publicly accessible version of the page.

We didn't do any sanitization of the data being passed into the server-side file that updates the database; although the user doesn't enter the data in a text field, the outgoing request from the page could easily be manipulated in order to send malicious code to the server. The danger of this kind of attack would be limited as we would probably only be allowing sorting in the first place to registered, authenticated users. Although security is beyond the scope of this tutorial, it should always be a primary concern when dealing with live code.

Advertisement