Slide Sorter
Create a view for sorting images (and potentially other content) which are displayed in a grid layout, where by each item occupies a single cell rather than a whole row. This uses a modified version of the javascript that powers the drag and drop functionality of folder_contents.
Introduction
Brief description of the motivation and implementation.
The site this is used for has folderish content types that can contain multiple images, for which the users requested a simple method for sorting.
First Attempt
Initially, I simply customized folder_contents to additionally display an icon of the image, and make use of the existing Ajax-based drag and drop reordering. This fell short in that the icons were too small to be able to accurately distinguish one from the other.
Second Attempt
Next, I further customized folder_contents using the thumbnail view and also providing links to larger views that would be displayed in a dialog box. This solution has the drawback that the drag and drop functionality is only usable when all the images fit in the window. When a folder contains more than 5-6 images, the user would have to drag and drop an image as far as is viewable, then stop, scroll the window further and repeat the drag and drop process. When moving an image from the bottom to the top of a list of 30, this quickly became unusable.
Aside: Although this template is not used primarily for sorting, it is used for edting (renaming, rotating) multiple images at once.
Final Solution
The ultimate solution would resemble iPhoto's sorting capabilities, in that all the images would be displayed in a table-like view, such that each data cell would contain an image, which could be re-ordered by dragging to any position in the grid. This requires a new page template that positions the images with css rather than a table, and a modified version of the dragdropreorder.js used by folder_contents. I also added the ability to double-click on an image to view the full-size version in a dialog box.
Get this working on my site
Quick instructions of how to get this slide sorter functionality working on my site
- Add each of the attached source files to the custom folder or a products skin folder
- Add an action to the folder content type, with:
- Title = Slide Sorter
- id = slide_sorter
- URL = string:$object_url/slide_sorter
- category = folder
- Navigate to your folder containing many images
- Click on the Slide Sorter tab
- Drag and drop images to sort
- Double click an image to see bigger view
Create slide sorter view
Describes the process and choices made when creating the slide sorter view
Table vs Divs
In the folder_contents table, each row is draggable, and so each element had a common parent of the <table> or <tbody> tag. However, if we use a table with each image in a <td>, all the draggable element would not have the same parent (unless the table consisted of one row);
Therefore, I decided to use CSS to make the images display in a grid rather than an html table element, since this would mean all draggable elements could have the same parent node in the element tree.
Keeping all draggable elements inside the same parent element means that we have less to customize of the javascript used by folder_contents.
Also, the use of the CSS float property to position these elements means that the number of columns cam be gracefully determined by the size of the window.
Key components
There a few parts to the template which are necessary for the javascript to work.
- The parent element (the one containing all of the sortable items), must have the id= 'slide-sortable'
- Each of the sortable elements must have the class= 'sortable-cell'
- Each sortable element must also have a unique ID (ex. id = 'folder-content-item-my_img')
Use with custom image types
To use this with other content types, simply change the filter used in the call to getFolderContents, from {'portal_type':'Image'} to something more suitable.
Unrequired Extra
On each sortable element, I also include the attribute ondblclick, which calls a javascript function to open a dialog box of a larger view of the image. This particular javascript function (written by John Gardner), resizes the pop-up window to the size of the image. For this functionality, one must include this 'ondblclk' attribute, aswell as the script element that calls openpopup.js. Of course none of this is required for the primary sorting functionality.
Note: Instead of explicitly including the javascript files using the 'script' elements in this template, they can also be included using the portal_javascripts tool.
This is the source code for the slide sorter template:
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US"
lang="en-US"
metal:use-macro="here/main_template/macros/master"
i18n:domain="plone">
<body>
<div metal:fill-slot="main">
<tal:protect tal:condition="python: not checkPermission('List folder contents', here)"
tal:replace="here/raiseUnauthorized" />
<metal:main_macro define-macro="main">
<metal:pic_sorter_macro define-macro="pic_sorter"
tal:define="imgs python:context.getFolderContents({'portal_type':'Image'});">
<h1 class="documentFirstHeading">Slide Sorter</h1>
<script type="text/javascript" src="openpopup.js"></script>
<script type="text/javascript" src="dragdropslidesorter.js"></script>
<tal:albumsimages tal:condition="imgs">
<div id="slide-sortable"
class="listing"
width="100%">
<tal:items tal:repeat="item imgs">
<tal:itemdefs tal:define="index repeat/item/index;
numCols python:3;
sameRow python:index % numCols;">
<span tal:define="oddrow repeat/item/odd;
item_url item/getURL|item/absolute_url;
item_id item/getId;
item_title_or_id item/pretty_title_or_id;
item_description item/Description;
oddEven python:test(oddrow, 'even', 'odd');
large_view string:$item_url/image_large;"
tal:attributes="class string:$oddEven sortable-cell;
id string:folder-contents-item-${item_id};
ondblclick string:return openPopup('$large_view',
'$item_title_or_id');" >
<img src="" alt='' tal:attributes="src string:${item_url}/image_thumb;
alt item_title_or_id;
title item_description" />
<span tal:content="item_title_or_id" />
</span>
</tal:itemdefs>
</tal:items>
<div class="visualClear"><!-- --></div>
</div>
</tal:albumsimages>
<p class="discreet"
i18n:domain="atcontenttypes"
i18n:translate="text_no_albums_uploaded"
tal:condition="python:not imgs">
No images uploaded yet.
</p>
</metal:pic_sorter_macro>
</metal:main_macro>
</div>
</body>
Create javascript for drag and drop sorting
Modify the javascript used by folder_contents to allow sorting of individual cells in our grid
The main difference between this javascript file and the one used for folder_contents, is that I replaced the swapElement function with an insertElement function.
When sorting the rows in folder_contents, as you drag an item up or down, it swaps location with the next item it comes to; However, with a two dimensional grid layout, this works fine when you drag an item along the same row, but if you drag it up to the next row, it swaps postion with that item. This is not the desired affect, as items jump around the screen.
This "bug" can also been demonstrated in folder_contents by selecting an item to drag, dragging it outside of the table, and then back into the table at a different point. The item where you re-enter the table then gets swapped with the position of the dragged-item. This is only a visual affect, but is a little confusing.
Instead of swapping elements, we simply want to insert the element we are dragging, and bump the other items along.
Below is a snippet of the dragdropslidesorter.js, showing the new insertElement function:
<snip>
dndSlideSorter.insertElement = function(child1, child2) {
// child1 = target (current location of cursor)
// child2 = item being dragged
var parent = child1.parentNode;
var children = parent.childNodes;
var items = new Array();
// get index of target and dragged item
var target_pos, drag_pos = 0;
for (var i = 0; i < children.length; i++) {
var node = children[i];
items[i] = node
if (node.id) {
removeClassName(node, "even");
removeClassName(node, "odd");
if (node.id == child1.id)
target_pos = i;
if (node.id == child2.id)
drag_pos = i;
}
}
// move dragged item to index of target (don't swap, just insert)
// swapping meant that with new layout, index 1 could get swapped with
// 7 and images visually jump around rather than insert
if (drag_pos > target_pos)
{
// insert dragged
items.splice(target_pos,0,items[drag_pos]);
// delete original
items.splice(drag_pos+1,1);
}
else{
// insert dragged
items.splice(target_pos+1,0,items[drag_pos]);
// delete original
items.splice(drag_pos,1);
}
Sarissa.clearChildNodes(parent);
var pos = 0;
for (var i = 0; i < items.length; i++) {
var node = parent.appendChild(items[i]);
if (node.id) {
if (pos % 2)
addClassName(node, "even");
else
addClassName(node, "odd");
pos++;
}
}
}
<snip>
The other major change between the original dragdropreorder.js and this one, is to fetch our draggable items identified by the id #slide-sortable and class .sortable-cell rather than by the table elements.
Add styles
In order for the images to appear in a grid, we must add some Cascading Style Sheet (CSS) declarations
To make the images appear as if they are positioned with a table, we use the CSS float property.
Below is the complete style sheet used:
/* ######################## */
/* ## Slide Sorter styles ## */
/* ######################## */
#slide-sortable {
border: 1px solid #ccc;
}
span.sortable-cell {
float: left;
height: 185px;
width: 143px;
margin: 0em;
padding: 0px 6px 0px 9px;
text-align: center;
background-image: url('&dtml-portal_url;/polaroid-single.png');
background-repeat: no-repeat;
}
span.sortable-cell img {
border: 1px solid #ccc;
display: block;
margin: auto;
margin-top: 30px;
}
#slide-sortable span.dragging {
background-color: yellow;
background-image: url('');
}
.dragging img {
border:1px solid black;
}
/* ######################## */
/* ## Slide Sorter styles ## */
/* ######################## */
Conclusion
Lessons learnt
We all know that Plone has lots of awesome functionality built in. However, if your very specific use case is not covered, it is not hard to customize something similar to get exactly what you need.
This tutorial describes the process taken to build a drag and drop slide sorter, starting with the folder_content's drag and drop functionality as a base.
I started by fiddling with the existing page template. When it became apparent that this was not enough, I looked to make a few small changes to the javascript. The majority (if not all) of the core Plone code is very well written and easy to understand. By digging around a little, it is easy to find exactly what you are after.



