Sortable - jQuery UI - Web Development with jQuery (2015)

Web Development with jQuery (2015)

Part II. jQuery UI

Chapter 13. Sortable

Chapter 12, “Draggable and Droppable,” introduced how jQuery UI provides plugins that make implementing drag-and-drop UI easy to implement. This chapter presents another jQuery UI plugin, Sortable, which enables you to make items in a list sortable, or “rearrangeable.”

In website development you might need to often sort items, and you will probably want to change the order of the items. An example would be the order in which products appear in a navigation or side menu.

Without drag-and-drop, it's still possible to give users the ability to tweak the order of items. You can offer up or down arrows for shifting items in a list, for example, but drag-and-drop sorting is the fastest, most intuitive way to implement this type of user interface.

Making a List Sortable

As you've seen throughout this book, jQuery takes more complex programming tasks and makes them easier. Sometimes, you can do a lot by adding just one line of code or even chaining one additional function call to a selection. When you experience how easy jQuery makes common programming tasks, it becomes nearly impossible to return to JavaScript programming without the convenience offered by frameworks like jQuery. In Chapter 12, you saw how making elements draggable amounts to making a selection and then making a single function call. Making a list of items sortable via drag-and-drop is just as easy—you make a selection of elements, and then you make a single function call. The function that you call in this case is called sortable(). Like the drag-and-drop examples that were presented in Chapter 12, you have the ability to tweak element sortability using fine-grained options that you can pass to the sortable() method with a JavaScript object literal. Each of the options that jQuery UI provides for the Sortable plugin is defined in detail in Appendix K, “Sortable.”

All that you need to make this possible is to include the relevant jQuery UI plugin, Sortable, make a selection with jQuery, and then chain a call to the function sortable() onto that selection. The Sortable plugin requires that you select a container element, whose immediate children will be sortable by drag-and-drop. One example of a container is a <ul> element, and the sortable children are the <li> elements contained within that element. The sortable functionality offered by the Sortable plugin works in all modern browsers: IE, Firefox, Safari, and Opera.

The following example puts the concept of sortability into context with a real-world-oriented application, where you sort files through a GUI interface, which you might use in a Content Management System (CMS), to control things such as sorting links in a sidebar or drop-down menu, or the order in which products appear in a catalog. You'll also return to this example throughout this chapter to examine other aspects of file sorting that jQuery UI provides through its Sortable plugin. You begin with the following HTML, which appears as Example 13-1 in the source materials available at www.wrox.com/go/webdevwithjquery.

<!DOCTYPE HTML>

<html xmlns='http://www.w3.org/1999/xhtml'>

<head>

<meta http-equiv='content-type'

content='application/xhtml+xml; charset=utf-8' />

<meta http-equiv='content-language' content='en-us' />

<title>Sortable</title>

<script src='../jQuery.js'></script>

<script src='../jQueryUI.js'></script>

<script src='Example 13-1.js'></script>

<link href='Example 13-1.css' rel='stylesheet' />

</head>

<body>

<ul id='finderCategoryFiles'>

<li class='finderCategoryFile'>

<div class='finderCategoryFileIcon'></div>

<h5 class='finderCategoryFileTitle'>

Using CoreImage to Resize and Change Formats on the Fly

</h5>

<div class='finderCategoryFilePath'>

<a href='/Blog/apple/CoreImage.html'>

/Blog/apple/CoreImage.html

</a>

</div>

</li>

<li class='finderCategoryFile'>

<div class='finderCategoryFileIcon'></div>

<h5 class='finderCategoryFileTitle'>

Exploring Polymorphism in PHP

</h5>

<div class='finderCategoryFilePath'>

<a href='/Blog/php/Polymorphism.html'>

/Blog/php/Polymorphism.html

</a>

</div>

</li>

<li class='finderCategoryFile'>

<div class='finderCategoryFileIcon'></div>

<h5 class='finderCategoryFileTitle'>

A PHP Shell Script for Backups

</h5>

<div class='finderCategoryFilePath'>

<a href='/Blog/php/Backup%20Script.html'>

/Blog/php/Backup Script.html

</a>

</div>

</li>

<li class='finderCategoryFile'>

<div class='finderCategoryFileIcon'></div>

<h5 class='finderCategoryFileTitle'>

HTML 5 DOCTYPE

</h5>

<div class='finderCategoryFilePath'>

<a href='/Blog/web/html5_doctype.html'>

/Blog/web/html5_doctype.html

</a>

</div>

</li>

<li class='finderCategoryFile'>

<div class='finderCategoryFileIcon'></div>

<h5 class='finderCategoryFileTitle'>

First Impressions of IE 8 Beta 2

</h5>

<div class='finderCategoryFilePath'>

<a href='/Blog/web/ie8_beta2.html'>

/Blog/web/ie8_beta2.html

</a>

</div>

</li>

</ul>

</body>

</html>

The preceding HTML is joined with the following CSS.

html,

body {

width: 100%;

height: 100%;

}

body {

font: 12px 'Lucida Grande', Arial, sans-serif;

background: rgb(189, 189, 189)

url('images/Bottom.png')

repeat-x

bottom;

color: rgb(50, 50, 50);

margin: 0;

padding: 0;

}

ul#finderCategoryFiles {

position: absolute;

top: 0;

bottom: 22px;

left: 0;

width: 300px;

border-bottom: 1px solid rgb(64, 64, 64);

border-right: 1px solid rgb(64, 64, 64);

background: #fff;

list-style: none;

margin: 0;

padding: 0;

}

li.finderCategoryFile {

clear: both;

padding: 5px 5px 10px 5px;

min-height: 48px;

width: 290px;

}

li.finderCategoryFile h5 {

font: normal 12px 'Lucida Grande', Arial, sans-serif;

margin: 0;

}

div.finderCategoryFileIcon {

float: left;

width: 48px;

height: 48px;

background: url('images/Safari Document.png')

no-repeat;

}

h5.finderCategoryFileTitle,

div.finderCategoryFilePath {

padding-left: 55px;

}

li.finderCategoryFileSelected {

background: rgb(24, 67, 243)

url('images/Selected Item.png')

repeat-x

bottom;

color: white;

}

li.finderCategoryFileSelected a {

color: lightblue;

}

Finally, this JavaScript enables sortability.

$(document).ready(

function()

{

$('li.finderCategoryFile').mousedown(

function()

{

$('li.finderCategoryFile')

.not(this)

.removeClass('finderCategoryFileSelected');

$(this).addClass('finderCategoryFileSelected');

}

);

$('ul#finderCategoryFiles').sortable();

}

);

The preceding example results in the application you see shown in Figure 13.1.

image

Figure 13.1

The preceding example is a demonstration of the jQuery UI Sortable plugin, an application that provides file sorting, which can have a variety of applications, as mentioned just prior to presenting this example.

In this example, you have five files. Each has a file icon, a title, and a clickable link to the file. This is borrowed from Mac OS X for the look and feel of an application that feels more like a native desktop application. If you were to extend this concept, you could also provide alternative templates that mirror the look and feel of other operating systems as well. A server-side language that can detect the user's operating system, combined with different style sheets for each OS, makes that a viable option, which can make your users feel more at home with your web-based application.

In the markup, you set things up so that the content can be styled with CSS. Each file item is represented as a <li> element. Because you work with a list of items, semantically speaking, it makes the most sense to set up your sortable list as a <ul> element, with each list item, <li>, representing each file.

The file icon is placed in a <div> element. You use a <div> so that you can provide the icon via the CSS background property.

The text content is wrapped within an <h5> and a <div> element so that you can control the margin and padding using block elements instead of inline elements like <span>. You can see how this is helpful in the explanation for the style sheet in this example. Then, you also gratuitously give each element class names, which makes it much easier to apply style or behavior to those specific elements, in addition to making it easier to identify the purpose of the element from the standpoint of semantics. Each class name is chosen so that it conveys the exact purpose of the element.

<li class='finderCategoryFile'>

<div class='finderCategoryFileIcon'></div>

<h5 class='finderCategoryFileTitle'>

Using CoreImage to Resize and Change Formats on the Fly

</h5>

<div class='finderCategoryFilePath'>

<a href='/Blog/apple/CoreImage.html'>

/Blog/apple/CoreImage.html

</a>

</div>

</li>

The application is designed so that sortable elements are contained in a single column that spans the left side. The column is created by using the top and bottom offset properties in tandem to imply height, which, in turn, lets you have a stretchy column that resizes fluidly with the size of the viewport.

In the style sheet, first, you give the <html> and <body> elements 100 percent width and height, and remove any default margin or padding from the <body> element. (Some browsers apply default margin; some apply default padding.)

html,

body {

width: 100%;

height: 100%;

}

body {

font: 12px 'Lucida Grande', Arial, sans-serif;

background: rgb(189, 189, 189)

url('images/Bottom.png')

repeat-x

bottom;

color: rgb(50, 50, 50);

margin: 0;

padding: 0;

}

In the next style-sheet rule, you create the left column by styling the <ul> element with the id name finderCategoryFiles so that it spans the height of the left side of the document. The declaration top: 0; combined with the declaration bottom: 22px; causes the <ul>element to span the entire height of the viewport, except for the bottom 22 pixels, which has a gradient background applied to that space. The <ul> element is given a fixed width of 300 pixels; otherwise, you would have a shrink-to-fit width because the <ul> element is absolutely positioned.

ul#finderCategoryFiles {

position: absolute;

top: 0;

bottom: 22px;

left: 0;

width: 300px;

border-bottom: 1px solid rgb(64, 64, 64);

border-right: 1px solid rgb(64, 64, 64);

background: #fff;

list-style: none;

margin: 0;

padding: 0;

}

Each <li> element first has the declaration clear: both applied, which is needed to clear the left floating of each file icon (the <div> element with class name finderCategoryFileIcon). Without this declaration, you'd have a jumbled unintelligible mess, as each <li>element tried to float up to the right of the icon of the preceding <li> element, and the <li> element preceding that one float up to the right of the icon of the <li> element before that one, and so on. The clear: both declaration cancels floating so that the icon floats to the left, and only the text content within the <li> element floats up to the right of that icon.

Each <li> element is given a fixed width of 290 pixels. You do this because when you drag a <li> element, the element loses its width and shrinks. It does that because without an explicit width, each <li> element's width is based on the parent, <ul>, element's width. When you drag an <li> element, its parent is no longer the <ul> element but the <body> element. The <li> element is moved with the mouse cursor through CSS. It is positioned absolutely, relative to the viewport, and its position is constantly updated based on where the mouse cursor is going via the jQuery UI Sortable plugin. Otherwise, as an absolutely positioned element, the <li> element would have a shrink-to-fit width, so by giving the <li> element a fixed width, you allow it to maintain its dimensions as it is dragged from one point to another. The min-height property keeps the spacing within the <li> element consistent but also allows each <li> element to expand vertically to accommodate additional text content.

li.finderCategoryFile {

clear: both;

padding: 5px 5px 10px 5px;

min-height: 48px;

width: 290px;

}

The next item of interest in the style sheet is the icon, which is defined by the following rule:

div.finderCategoryFileIcon {

float: left;

width: 48px;

height: 48px;

background: url('images/Safari Document.png')

no-repeat;

}

In the preceding rule, the <div> element is floated to the left with the declaration float: left;. That declaration causes the text content to float to the right of the icon, as explained previously. The clear: both; declaration of the previous rule cancels this declaration on each <li> element so that only the text content is affected. The icon is set as the background using the background property, and the <div> is given a width and height of 48 pixels, matching the dimensions of the background image.

The last items of interest in the style sheet define the look for selected files. That's done in the following two rules:

li.finderCategoryFileSelected {

background: rgb(24, 67, 243)

url('images/Selected Item.png')

repeat-x

bottom;

color: white;

}

li.finderCategoryFileSelected a {

color: lightblue;

}

The preceding two rules are for <li> elements with the class name finderCategoryFileSelected. This class name is dynamically added and removed from <li> elements by jQuery. This addition of this class name lets your users see which file is currently selected. Beyond providing a visual cue for selection, this also lets you implement the ability to add a Delete button, which when pressed would remove the selected item or implement some other functionality that is contingent on the selection of an element.

The JavaScript for this example is lean and to the point. The JavaScript basically does two things. It provides the ability to select an <li> element by adding and removing the class name finderCategoryFileSelected as appropriate to indicate selection. And it makes the<li> elements sortable using the jQuery UI Sortable plugin.

When the DOM is ready, the first task is to attach a mousedown event to each <li> element. You can use this event to implement an indication of which element is selected.

$('li.finderCategoryFile').mousedown(

function()

{

$('li.finderCategoryFile')

.not(this)

.removeClass('finderCategoryFileSelected');

$(this).addClass('finderCategoryFileSelected');

}

);

The script selects every <li> element with class name finderCategoryFile. The class name is added to the selection, even though as it stands, you could just select every <li> element without a class name and get the same result so that your application can be easily extended. You might bring in more functionality that involves adding <li> elements that are completely unrelated to what you're doing here. Adding the class name to the selector makes the selection more specific and gives you the ability to expand your application's functionality more effortlessly. So every <li> element with class name finderCategoryFile is selected; then the <li> element on which the mousedown event is taking place is filtered out using .not(this), and the class name finderCategoryFileSelected is removed from every <li> element, except the <li> element on which the mousedown event is taking place.

This is actually not the most efficient way to implement selection, especially if you have a long list. Selecting every <li> element is inefficient and can make your script slow if you have a lot of items in the list. So having shown you the wrong way to do a selection, a better approach is to create a variable, and every time a selection is made, store the currently selected element in that variable. The following code is what this approach looks like in the context of Example 13-1:

$(document).ready(

function()

{

var selectedFile;

$('li.finderCategoryFile').mousedown(

function()

{

if (selectedFile && selectedFile.length)

{

selectedFile.removeClass('finderCategoryFileSelected');

}

selectedFile = $(this);

selectedFile.addClass('finderCategoryFileSelected');

}

);

$('ul#finderCategoryFiles').sortable();

}

);

The selected item is stored in the variable selectedFile. When the mousedown event fires, the script first checks to see if there is an element stored in the selectedFile variable. If there is, the finderCategoryFileSelected class name is removed from that element because that element is the previously selected element.

Then the element on which the mousedown event is being fired, referenced by the this keyword, is made into a jQuery object by wrapping this in a call to the dollar sign function, and the class name finderCategoryFileSelected is added to the element on which themousedown event is being fired. This provides you with a leaner, more efficient selection API.

The last item that happens in the script (and the point of this example) is to make every <li> element sortable with a call to the sortable() method:

$('ul#finderCategoryFiles').sortable();

The next section introduces some customization into the discussion of the Sortable plugin.

Customizing Sortable

This section talks about some visual tweaks you can make to sortable lists and how you link one list to another so that you have sorting between multiple, separate lists. The jQuery UI sortable() method, like draggable() and droppable(), enables you to specify an object literal as its first argument, which enables you tweak how sorting works, in addition to providing callback functions that are executed during specific events that occur as sorting is taking place.

NOTE This section discusses just a few of the options that jQuery UI exposes for its Sortable plugin. You can find a complete list of options in Appendix K.

The first option presented is placeholder, which gives you the ability to style the placeholder that appears within a sortable list as a drag is taking place to indicate where the item will be dropped if the mouse is released. By default (refer to Figure 13.1) you can see that the placeholder is simply empty white space, sized relatively to the element being dragged. The placeholder option accepts a class name as its value, which, in turn, is applied to the placeholder element.

The second option presented describes how you can customize the element being dragged; the process for doing this can also be applied to the jQuery UI draggable() method. By default, jQuery UI displays the element the user picked for sorting as the element that the user drags, which, of course, makes sense for most scenarios. You do, however, have the option of using a completely different element for display as the drag element, if you choose. Customizing the element that's displayed during a drag is done with the helperoption. In jQuery UI, helper, as applied to drag-and-drop, whether in the Sortable plugin or the Draggable plugin, or other plugins, is the term used for the element that is displayed while a drag is taking place. The helper option takes two arguments: The first argument is the event object, and the second argument references the element the user picked for sorting. Aside from completely replacing the element displayed during the drag event, you can also use this option to simply tweak the display of the element that the user picked.

In the following example, you extend the file-sorting application that you created in Example 13-1, with some options, like the placeholder and helper options that you learned about in this section. You also add another option that gives you the ability to sort elements between multiple lists.

Using Example 13-1.html as the basis, create the following markup document as Example 13-2.html:

<!DOCTYPE HTML>

<html xmlns='http://www.w3.org/1999/xhtml'>

<head>

<meta http-equiv='content-type'

content='application/xhtml+xml; charset=utf-8' />

<meta http-equiv='content-language' content='en-us' />

<title>Sortable</title>

<script src='../jQuery.js'></script>

<script src='../jQueryUI.js'></script>

<script src='Example 13-2.js'></script>

<link href='Example 13-2.css' rel='stylesheet' />

</head>

<body>

<div id='finderCategoryFileWrapper'>

<ul id='finderCategoryFiles'>

<li class='finderCategoryFile'>

<div class='finderCategoryFileIcon'></div>

<h5 class='finderCategoryFileTitle'>

Using CoreImage to Resize and Change Formats on the Fly

</h5>

<div class='finderCategoryFilePath'>

<a href='/Blog/apple/CoreImage.html'>

/Blog/apple/CoreImage.html

</a>

</div>

</li>

<li class='finderCategoryFile'>

<div class='finderCategoryFileIcon'></div>

<h5 class='finderCategoryFileTitle'>

Exploring Polymorphism in PHP

</h5>

<div class='finderCategoryFilePath'>

<a href='/Blog/php/Polymorphism.html'>

/Blog/php/Polymorphism.html

</a>

</div>

</li>

<li class='finderCategoryFile'>

<div class='finderCategoryFileIcon'></div>

<h5 class='finderCategoryFileTitle'>

A PHP Shell Script for Backups

</h5>

<div class='finderCategoryFilePath'>

<a href='/Blog/php/Backup%20Script.html'>

/Blog/php/Backup Script.html

</a>

</div>

</li>

<li class='finderCategoryFile'>

<div class='finderCategoryFileIcon'></div>

<h5 class='finderCategoryFileTitle'>

HTML 5 DOCTYPE

</h5>

<div class='finderCategoryFilePath'>

<a href='/Blog/web/html5_doctype.html'>

/Blog/web/html5_doctype.html

</a>

</div>

</li>

<li class='finderCategoryFile'>

<div class='finderCategoryFileIcon'></div>

<h5 class='finderCategoryFileTitle'>

First Impressions of IE 8 Beta 2

</h5>

<div class='finderCategoryFilePath'>

<a href='/Blog/web/ie8_beta2.html'>

/Blog/web/ie8_beta2.html

</a>

</div>

</li>

</ul>

<ul id='finderOtherCategoryFiles'>

</ul>

</div>

</body>

</html>

Using the style sheet in Example 13-1.css, make the following modifications and save the results in a new file, as Example 13-2.css:

html,

body {

width: 100%;

height: 100%;

}

body {

font: normal 12px 'Lucida Grande', Arial, sans-serif;

background: rgb(189, 189, 189)

url('images/Bottom.png')

repeat-x

bottom;

color: rgb(50, 50, 50);

margin: 0;

padding: 0;

}

div#finderCategoryFileWrapper {

position: absolute;

top: 0;

right: 0;

bottom: 23px;

left: 0;

}

ul#finderCategoryFiles,

ul#finderOtherCategoryFiles {

float: left;

height: 100%;

width: 300px;

border-bottom: 1px solid rgb(64, 64, 64);

border-right: 1px solid rgb(64, 64, 64);

background: #fff;

list-style: none;

margin: 0;

padding: 0;

}

li.finderCategoryFile {

clear: both;

padding: 5px 5px 10px 5px;

min-height: 48px;

width: 290px;

}

li.finderCategoryFile h5 {

font: normal 12px 'Lucida Grande', Arial, sans-serif;

margin: 0;

}

div.finderCategoryFileIcon {

float: left;

width: 48px;

height: 48px;

background: url('images/Safari Document.png')

no-repeat;

}

h5.finderCategoryFileTitle,

div.finderCategoryFilePath {

padding-left: 55px;

}

li.finderCategoryFileSelected {

background: rgb(24, 67, 243)

url('images/Selected Item.png')

repeat-x

bottom;

color: white;

}

li.finderCategoryFileSelected a {

color: lightblue;

}

.finderCategoryFilePlaceholder {

background: rgb(230, 230, 230);

height: 58px;

}

Starting with the JavaScript file you created in Example 13-1.js, make the following modifications and save the new JavaScript file as Example 13-2.js:

$(document).ready(

function()

{

var selectedFile;

$('li.finderCategoryFile').mousedown(

function()

{

if (selectedFile && selectedFile.length)

{

selectedFile.removeClass('finderCategoryFileSelected');

}

selectedFile = $(this);

selectedFile.addClass('finderCategoryFileSelected');

}

);

$('ul#finderCategoryFiles').sortable({

connectWith : 'ul#finderOtherCategoryFiles',

placeholder : 'finderCategoryFilePlaceholder',

opacity : 0.8,

cursor : 'move'

});

$('ul#finderOtherCategoryFiles').sortable({

connectWith : 'ul#finderCategoryFiles',

placeholder : 'finderCategoryFilePlaceholder',

opacity : 0.8,

cursor : 'move'

});

}

);

The preceding gives you something similar to what you see in Figure 13.2.

image

Figure 13.2

In Example 13-2, you added a few options to the sortable() method and tweaked the presentation of the document to accommodate multiple lists.

<div id='finderCategoryFileWrapper'>

<ul id='finderCategoryFiles'>

The <div> element contains two <ul> elements; each, in turn, is a sortable list. Each <ul> element is also made into a column that spans the height of the <div> element. The following CSS is used to prepare the <div> element so that the <ul> elements within it can become columns.

div#finderCategoryFileWrapper {

position: absolute;

top: 0;

right: 0;

bottom: 23px;

left: 0;

}

The <div> element is positioned absolutely, and the four offset properties are used to imply width and height, causing the <div> element to take up the entire viewport, except the bottom 23 pixels. Then styles are applied to each <ul> element. Each <ul> element is floated to the left and given fixed dimensions. This styling manages to turn both <ul> elements into columns, matching the visual look and feel that you saw in Example 13-1, but also managing to work around an annoying z-index bug in old versions of IE.

ul#finderCategoryFiles,

ul#finderOtherCategoryFiles {

float: left;

height: 100%;

width: 300px;

border-bottom: 1px solid rgb(64, 64, 64);

border-right: 1px solid rgb(64, 64, 64);

background: #fff;

list-style: none;

margin: 0;

padding: 0;

}

Going to the JavaScript, the scripting portion is straightforward. The first portion of the file deals with selection; as you saw later in the explanation for Example 13-1, a variable called selectedFile is used to keep track of which file is selected. The remainder of the script sets up two sortable lists, one in each column.

$('ul#finderCategoryFiles').sortable({

connectWith : 'ul#finderOtherCategoryFiles',

placeholder : 'finderCategoryFilePlaceholder',

opacity : 0.8,

cursor : 'move'

});

$('ul#finderOtherCategoryFiles').sortable({

connectWith : 'ul#finderCategoryFiles',

placeholder : 'finderCategoryFilePlaceholder',

opacity : 0.8,

cursor : 'move'

});

The connectWith option accepts a selector as its value and enables you to connect one list to another so that you have the ability to sort items between multiple lists.

Then, the other options— placeholder, opacity, and cursor— are each used to tweak the presentation of each sortable list. The placeholder option, as you already learned, enables you to add a custom class name to the element that acts as a placeholder during sorting. The opacity option is used to control the opacity of the helper element, and it takes a standard CSS 3 opacity property value (that works in IE, too). The cursor option is used to change the cursor while the helper is being dragged, and it takes any value that the CSScursor property can take.

Also in the preceding snippet of code, the <ul> list with the id name finderCategoryFiles is connected to the <ul> list with the id name finderOtherCategoryFiles. The connectWith option specified for this list sets up a one-way connection from the first <ul> element to the second, which lets you drag items from the first list to the second, but not vice versa. To have two-way sorting, you also need to set the connectWith option on the second <ul> list, which you also see in the preceding snippet of code. Other than the connectWith option, the second <ul> element has the same options as the first <ul> element.

As mentioned previously, this section discussed only a few of the options for Sortable. All options for Sortable are documented in Appendix K.

Saving the State of Sorted Lists

The Sortable API in jQuery UI wouldn't be complete without one last detail: saving the state of a sorted list. This too is covered by the Sortable plugin. In Chapter 7, “AJAX,” you learned about jQuery's serialize() method, which automatically takes a selection of input elements for a form and serializes the data in those input elements into a string of data that you can then submit to a server-side script with an AJAX request. The Sortable plugin provides a similar mechanism for retrieving data from a sortable list. But instead of retrieving input form values, the Sortable plugin retrieves a specific attribute from each sortable element. By default, the Sortable plugin retrieves the value of the id attribute. In the context of the examples you've completed in this chapter, you'd give each <li>element an id attribute and then use the Sortable plugin's mechanism for serializing the data present in each id attribute into a string that you can pass on to an AJAX request to a server-side script, so you can save the sort. The following code snippet shows the code you'd use on the JavaScript side:

var data = $('ul').sortable(

'serialize', {

key: 'listItem[]'

}

);

In the preceding code, to serialize the data present in the id attribute of each <li> element, you call the sortable() method, with the first argument set to 'serialize'. For the second argument, you specify an object literal of options, which decide how the serialization will be done. The key option specifies the name you want to use for each query string argument. The name listItem[] is used, which in PHP and some other server-side scripts will cause the query string of sorted items to be translated into an array or hash.

In the following example you apply the concepts you've just learned to the sortable files example that you've been working on throughout this chapter. Using Example 13-2.html as the basis, copy the contents of that file into a new document, and save that document as Example 13-3.html; then add a data-path attribute to each <li> element, as you see in the following markup. Don't forget to update each file reference to Example 13-3.

<!DOCTYPE HTML>

<html xmlns='http://www.w3.org/1999/xhtml'>

<head>

<meta http-equiv='content-type'

content='application/xhtml+xml; charset=utf-8' />

<meta http-equiv='content-language' content='en-us' />

<title>Sortables</title>

<script src='../jQuery.js'></script>

<script src='../jQueryUI.js'></script>

<script src='Example 13-3.js'></script>

<link href='Example 13-3.css' rel='stylesheet' />

</head>

<body>

<div id='finderCategoryFileWrapper'>

<ul id='finderCategoryFiles'>

<li class='finderCategoryFile'

data-path='/Blog/apple/CoreImage.html'>

<div class='finderCategoryFileIcon'></div>

<h5 class='finderCategoryFileTitle'>

Using CoreImage to Resize and Change Formats on the Fly

</h5>

<div class='finderCategoryFilePath'>

<a href='/Blog/apple/CoreImage.html'>

/Blog/apple/CoreImage.html

</a>

</div>

</li>

<li class='finderCategoryFile'

data-path='/Blog/php/Polymorphism.html'>

<div class='finderCategoryFileIcon'></div>

<h5 class='finderCategoryFileTitle'>

Exploring Polymorphism in PHP

</h5>

<div class='finderCategoryFilePath'>

<a href='/Blog/php/Polymorphism.html'>

/Blog/php/Polymorphism.html

</a>

</div>

</li>

<li class='finderCategoryFile'

data-path='/Blog/php/Backup Script.html'>

<div class='finderCategoryFileIcon'></div>

<h5 class='finderCategoryFileTitle'>

A PHP Shell Script for Backups

</h5>

<div class='finderCategoryFilePath'>

<a href='/Blog/php/Backup%20Script.html'>

/Blog/php/Backup Script.html

</a>

</div>

</li>

<li class='finderCategoryFile'

data-path='/Blog/web/html5_doctype.html'>

<div class='finderCategoryFileIcon'></div>

<h5 class='finderCategoryFileTitle'>

HTML 5 DOCTYPE

</h5>

<div class='finderCategoryFilePath'>

<a href='/Blog/web/html5_doctype.html'>

/Blog/web/html5_doctype.html

</a>

</div>

</li>

<li class='finderCategoryFile'

data-path='/Blog/web/ie8_beta2.html'>

<div class='finderCategoryFileIcon'></div>

<h5 class='finderCategoryFileTitle'>

First Impressions of IE 8 Beta 2

</h5>

<div class='finderCategoryFilePath'>

<a href='/Blog/web/ie8_beta2.html'>

/Blog/web/ie8_beta2.html

</a>

</div>

</li>

</ul>

<ul id='finderOtherCategoryFiles'>

</ul>

</div>

</body>

</html>

The preceding HTML file is combined with the same CSS you used in Example 13-2.css, and then the following script is applied:

$(document).ready(

function()

{

var selectedFile;

$('li.finderCategoryFile').mousedown(

function()

{

if (selectedFile && selectedFile.length)

{

selectedFile.removeClass('finderCategoryFileSelected');

}

selectedFile = $(this);

selectedFile.addClass('finderCategoryFileSelected');

}

);

$('ul#finderCategoryFiles').sortable({

connectWith : 'ul#finderOtherCategoryFiles',

placeholder : 'finderCategoryFilePlaceholder',

opacity : 0.8,

cursor : 'move',

update : function(event, ui)

{

var data = $(this).sortable(

'serialize', {

attribute : 'data-path',

expression : /∧(.*)$/,

key : 'categoryFiles[]'

}

);

data += '&categoryId=1';

alert(data);

// Here you could go on to make an AJAX request

// to save the sorted data on the server, which

// might look like this:

//

// $.get('/path/to/server/file.php', data);

}

});

$('ul#finderOtherCategoryFiles').sortable({

connectWith : 'ul#finderCategoryFiles',

placeholder : 'finderCategoryFilePlaceholder',

opacity : 0.8,

cursor : 'move',

update : function(event, ui)

{

var data = $(this).sortable(

'serialize', {

attribute : 'data-path',

expression : /∧(.*)$/,

key : 'categoryFiles[]'

}

);

data += '&categoryId=2';

alert(data);

// Here you could go on to make an AJAX request

// to save the sorted data on the server, which

// might look like this:

//

// $.get('/path/to/server/file.php', data);

}

});

}

);

The preceding document gives you something similar to Figure 13.3.

image

Figure 13.3

In Example 13-3, you add some code that retrieves data from each <li> element. However, instead of getting data from the id attribute, which is what jQuery UI uses by default, you can get data from the custom data-path attribute.

$('ul#finderCategoryFiles').sortable({

connectWith : 'ul#finderOtherCategoryFiles',

placeholder : 'finderCategoryFilePlaceholder',

opacity : 0.8,

cursor : 'move',

update : function(event, ui)

{

var data = $(this).sortable(

'serialize', {

attribute : 'data-path',

expression : /∧(.*)$/,

key : 'categoryFiles[]'

}

);

data += '&categoryId=1';

alert(data);

// Here you could go on to make an AJAX request

// to save the sorted data on the server, which

// might look like this:

//

// $.get('/path/to/server/file.php', data);

}

});

You start this project by defining a new anonymous function that is assigned to the custom update event of the sortable plugin. The custom sortable update event fires every time you complete a sort, and it is therefore the most useful method to save the state of sorting as sorting occurs. Within the anonymous function, you retrieve data from each <li> element by calling the sortable() method again but this time with the serialize option specified in the first argument. Then, in the options you pass in the second argument to thesortable() method, you change the attribute that jQuery UI serializes data from by using the attribute option and setting the value of that option to data-path. The rest is the same: You use the expression option to retrieve the data-path attribute's entire value from beginning to end, rather than just a substring within that value. (You can use any regular expression here.) And the key option is set to categoryFiles[], which is used to name the data in the serialized string. This results in sending something like the following query string to the server side:

categoryFiles[]=/Blog/apple/CoreImage.html

&categoryFiles[]=/Blog/php/Polymorphism.html

&categoryFiles[]=/Blog/web/ie8_beta2.html

&category=1

On the server side, you have two GET arguments. The first is an array called categoryFiles; the second is an integer named category. The syntax for creating an array is that used for PHP, and, of course, you want to adjust this syntax depending on the server-side language you're actually using.

Summary

In this chapter, you learned how to make sortable lists with the jQuery UI Sortable plugin. Using the Sortable plugin, you can offer a drag-and-drop sorting API effortlessly. jQuery UI provides a plethora of options that you can use for fine-grained control.

You learned how to use options such as placeholder, cursor, and opacity to control the look and feel of a sortable list. The placeholder option takes a class name, which enables you to use CSS to customize the look of the space that's reserved for a sortable element as sorting is taking place. And you saw how the opacity and cursor options both take the same values of the CSS opacity and cursor properties.

You saw how multiple lists can be connected to each other using the connectWith option, which you provide with a selector that indicates which list you want that sortable list to exchange items with. The connectWith option creates a one-way link to another list, which means that you can drag items only to the other list, but not back to the original. To create a two-way link, you can also add the connectWith option to the other list, with a selector that references the first list.

You've also learned how to save the state of sorted lists, which is also done with the sortable() method. In the first argument, you provide the string 'serialize'. Then in the second argument, you can provide options that determine how serialization works. For example, you provide the attribute option if you want to get the value of any attribute other than the id attribute. Another option you can use is the expression option, which takes a JavaScript regular expression as its value. Then, the key option is used to name the data that's serialized.

You also learned how the update option can be provided to sortable lists, which takes a callback function that executes after a sort is completed.

Exercises

1. What method do you use to make a list sortable?

2. What kind of value do you provide to the placeholder option?

3. What is the purpose of the placeholder option?

4. If you want to change the cursor displayed as a sort is taking place, which option would you use?

5. What is the purpose of the helper option?

6. Which option do you use to connect multiple sortable lists to one another?

7. What kind of value do you provide to the connectWith option?

8. How do you save the state of a sortable list after every sort takes place?