Dragging, dropping and sorting, with jQuery UI

I spy a nice opportunity to document some jQuery UI functionality I’ve been playing with this afternoon, for a soon-to-be-revealed client project. The project needs photo galleries, and as these need to be tied into the other custom post types and taxonomies, I’m quickly rolling my own so I can completely control the experience for admins and viewers. This is not so much a tutorial, as a quick glance through the code from the back of a galloping horse.

Here’s a quick screen capture of the organiser in action.

As you can see, the photos are in two rows: the top row is the “feeder”, these are photos which are available but not yet included in the gallery; the bottom row is the “organiser”, these photos are in the gallery. The site admin needs to be able to drag photos from the top “feeder” row into the organiser, to add them to the gallery, and drag them from the bottoms “organiser” row back to the feeder, to remove them from the gallery. Further the admin should be able to drag the photos within the organiser to specify the order of photos within the gallery.

I’m using the jQuery UI Javascript library, which does all the heavy lifting for me (thanks guys) leaving me with only a few lines of code to write… that said, it was a little tricky getting the interactions between the various Draggable, Droppable and Sortable functionalities all playing nicely together. Here’s that code, as it is at the moment:

The HTML:

<div id="gallery_photos">

<div id="gallery_feeder" class="ui-droppable">

<div class="photo photo-750 ui-draggable">

<img width="120" height="120" src="/images/IMG_1211-120x120.jpg">

</div>

<div class="photo photo-749 ui-draggable">

<img width="120" height="120" src="/images/IMG_2527-120x120.jpg">

</div>

<div class="photo photo-748 ui-draggable">

<img width="120" height="120" src="/images/6295963633_4e80160dd2_o_2-120x120.jpg">

</div>

<div class="photo photo-747 ui-draggable">

<img width="120" height="120" src="/images/IMG_4171-120x120.jpg">

</div>

<div class="photo photo-746 ui-draggable">

<img width="120" height="120" src="/images/IMG_1199-120x120.jpg">

</div>

</div>

<div id="gallery_organiser" class="ui-sortable">

<div class="photo photo-744">

<img width="120" height="120" src="/images/IMG_3545-120x120.jpg">

</div>

<div class="photo photo-743">

<img width="120" height="120" src="/images/IMG_0375-120x120.jpg">

</div>

<div class="photo photo-742">

<img width="120" height="120" src="/images/IMG_0593-120x120.jpg">

</div>

</div>

</div>

Note that each IMG element is wrapped in a DIV element, which has a class (it’s a class and not an ID, as things are getting duplicated and cloned and I don’t want to end up with having several elements on the same page with the same ID) of “photo-[some ID number]”. We’ll come back to that later.

Here’s the Javascript:

( function( $ ) {

var band_gallery = {

/**
* Let's get this show on the road.
*
* @return void
**/
init : function() {

var feeder_drag_args = {
connectToSortable: '#gallery_organiser', // This makes the organiser a drop target for the feeder, no need for droppable
cursor: 'move',
helper: 'clone', // The helper needs to be clone, to have connectToSortable work correctly
start: function( event, ui ) { // Fires when something is picked up from the feeder
$( this ).animate( { opacity: 0.5 } );
},
stop: function() {
$( '#gallery_feeder .photo' ).not( '.photo-used' ).css( { opacity: 1 } );
}
};

$('#gallery_feeder div.photo').draggable( feeder_drag_args );

$('#gallery_organiser').sortable( { // Sortable extends draggable, no need to make it draggable too
cursor: 'move',
receive: function( event, ui ) { // Fires when something is received into the organiser (implicitly from the feeder)
ui.item.animate( { opacity: 0.25 } ).addClass( 'photo-used' ).draggable( 'destroy' ); // Fade the source item in the feeder
$( '.photo', this ).css( { opacity: 1 } ); // Ensure all the organiser items are full opacity
}
} );

$("#gallery_feeder").droppable( {
accept: '#gallery_organiser div.photo', // The feeder is a drop target for the organiser
drop: function( event, ui) { // Fires when something is dropped on the feeder (implicitly from the organiser)
var existing, photo_num;
// Find and remove any existing copy of this object from the feeder
photo_num = ui.draggable.attr( 'class' ).match( /photo-([0-9]+)/ )[1];
if ( existing = $( '#gallery_feeder .photo-' + photo_num ) )
existing.fadeOut( function() { $( this ).remove(); } );
// Clone the dragged object and place it in the feeder
ui.draggable
.clone()
.appendTo( $( this ) )
.css( { position: 'relative', top: 0, left: 0 } )
.draggable( feeder_drag_args );
// Remove the dragged object from the organiser
ui.draggable
.remove();
}
} );

}
};

$( document ).ready( band_gallery.init );

} )( jQuery );

(Deep breath, are you ready?) Here’s a quick rush through the main points of the code:

The photos in the Organiser as defined as Sortable, the functionality here is draggability plus the ability to reorder the objects by dragging and dropping them within the organiser. The photos in the Feeder are defined as Draggable, this means you can drag them them (so far, so obvious). In order to drag photos from the Feeder to the Organiser, we define the Feeder as connected to the Sortable Organiser using connectToSortable, this means you can drag an object from the Feeder into the Organiser and it’s immediately sortable, you can even drag from the feeder into the middle of the organiser and the sorting just magically begins (so cool).

Still with me? So you can now drag photos from the Feeder to the Organiser. Any photos in the Organiser or over the Organiser are automagically sorted. Now…

The Feeder is defined as a Droppable area accepting photos dragged from the Organiser, so now the admin can remove photos by dragging from the Organiser (containing the photos shown in the gallery) to the Feeder. By default, nothing actually happens when a photo from the Organiser is dropped on the Feeder, so I had to write some custom code here which clones the dragged object into the Feeder and removes it from the Organiser. It’s here I’m using that “photo-[some ID number]” class, to find and remove any duplicate photos from the Feeder area when a new photo gets dropped in.

As a hint to the user as to whether you’ve used an image, there’s some little animations with opacity so when you drag a photo from the Feeder it fades slightly and when a photo in the feeder is in use in the Organiser it fades out even further.

Finally just a note that this is tested in Safari only, as we know the client uses that browser for admin and uses jQuery v1.7.2 and jQuery UI v1.8.20 – if you try it with other browsers and other versions your mileage may vary!

Good luck!

Join the Conversation

1 Comment

  1. Hi Simon,

    Great code and explanation, thanks! I got an an example working on Chrome no problem
    but am having a bug trying to work with the resulting id’s from the Gallery Organizer:

    Uncaught TypeError: Cannot read property ‘options’ of undefined

    Most of the jQeury scripts seem to load except for:
    a._mouseCapture._mouseDistanceMet._mouseDelayMet._mouseUpDelegate

    Any advice would be most appreciated!

    Thanks,

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.