Use Sortable.create to initialize a sortable element.
script.aculo.us V1.0 and later.
Sortable.create('id_of_container',[options]);
Options are:
| Option | since | Default | Description |
|---|---|---|---|
| tag | V1.0 | li | Sets the kind of tag (of the child elements of the container) that will be made sortable. For UL and OL containers, this is ‘LI’, you have to provide the tag kind for other sorts of child tags. |
| only | V1.0 | (none) | Further restricts the selection of child elements to only encompass elements with the given CSS class (or, if you provide an array of strings, on any of the classes). |
| overlap | V1.0 | vertical | Either ‘vertical’ or ‘horizontal’. For floating sortables or horizontal lists, choose ‘horizontal’. Vertical lists should use ‘vertical’. |
| constraint | V1.0 | vertical | Restricts the movement of Draggables, see the constraint option of Draggables. |
| containment | V1.0 | (only within container) | Enables dragging and dropping between Sortables. Takes an array of elements or element-ids (of the containers). Important note: To ensure that two way dragging between containers is possible, place all Sortable.create calls after the container elements. |
| format | ? | /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/ |
? |
| handle | V1.0 | (none) | Makes the created Draggables use handles, see the handle option on Draggables. |
| hoverclass | V1.1b1 | (none) | Gives the created Droppables a hoverclass (see there). |
| ghosting | V1.5 | false | If set to true, dragged elements of the Sortable will be cloned and appear as “ghost”, i.e. a representation of their original element, instead of directly dragging the original element. See below for more details. |
| dropOnEmpty | V1.5 | false | If set to true, the Sortable container will be made into a Droppable, that can receive a Draggable (as according to the containment rules) as a child element when there are no more elements inside. |
| scroll | V1.5.2 | (none) | When the sortable is contained in an element with style overflow:scroll, this value can be set to the ID of that container (or the container’s DOM object). The scroll position of the container will now move along when the sortable is dragged out of the viewable area. The container must have overflow:scroll set to include scroll bars. Does not yet work for scrolling the entire document. To get this to work correctly, include this line in your code before creating the sortable: Position.includeScrollOffsets = true; Update: Scrolling the whole document does work (at least on IE7 and Firefox). Use scroll: window |
| scrollSensitivity | ? | 20 | Will start scrolling when element is x pixels from the bottom, where x is the scrollSensitivity. |
| scrollSpeed | ? | 15 | Will scroll the element in increments of scrollSpeed pixels. |
| tree | V1.6.1 | false | If true, sets sortable functionality to elements listed in treeTag |
| treeTag | V1.6.1 | ul | The element type tree nodes are contained in. |
You can provide the following callbacks in the options parameter:
| Callback | since | Description |
|---|---|---|
| onChange | V1.0 | Called whenever the sort order changes while dragging. When dragging from one Sortable to another, the callback is called once on each Sortable. Gets the affected element as its parameter. |
| onUpdate | V1.0 | Called when the drag ends and the Sortable’s order is changed in any way. When dragging from one Sortable to another, the callback is called once on each Sortable. Gets the container as its parameter. Note that the id attributes of the elements contained in the Sortable must be named as described in Sortable.serialize |
(todo)
Important: You can use Sortable.create on any container element that contains Block Elements, with the exception of TABLE, THEAD, TBODY and TR. This is a technical restriction with current browsers.
A sortable nested somewhere inside a table won’t work well under IE unless the table has a “position:relative” style. If you use the css display: table property, sortable lists will work a little, but doesn’t allow true drag and drop of the elements.
If you want your sortable list to be scrollable, wrap the list in a div and set the div to scrollable as apposed to making the ul element scrollable. Also, in IE you must set “position:relative” on the scrollable div.
Got it working using tbody as container and TR as the sortables (IE6 (pc) and Firefox (mac/pc).
A call to Sortable.create implicitly calls on Sortable.destroy if the referenced element was already a Sortable.
Dynamic Sortable Tutorial With PHP – saves your order with an Ajax call so the order is saved for future visits
Working Example
Have a look at this page for how to modify scriptaculous to have a drop zone marker.
Tankut Koray
Further down this page you will find a working practical example of sortables in action with the following features:
DisasterMan at 01:33 PM (02/19)
How can the new scroll option be used to scroll the entire window while dragging?
scroll: window should work
See also http://www.arcknowledge.com/gmane.comp.lang.ruby.rails.spinoffs/2006-03/msg00408.html
P.S. do not put ‘window’ in quotes. This should be a reference to the relevant window object, not the id of some element within that window’s document.
——
Yes, I’d like to know this, too. Is it possible? – Tom
It’s possible to achieve, though not (to my knowledge) built-in. See Scroll window when dragging Scriptaculous Sortables to edges for one solution. —HenrikN
I have these lists that are server-side sorted, so I don’t want to be able to reorder their elements—but I do want to be able to drag and drop list elements between lists. What’s the best way to go about this? Draggable doesn’t seem to be quite what I need. —DC
How can I get a sortable element to clone itself so that it leaves a copy of itself from its point of origin when it’s dragged to another sortable list? Is there a function in the library that I can use. —nal
nal—My solution (which may be over-kill for you but suited my needs) was to dynamically regenerate the source list each time they used it with an On Mouse Over event. In my case, part of the concern was only giving them applicable options in the source list, so dynamical generation was the way to go. In your case, you would just always generate the same list, but would thereby replace anything they’d swiped in a previous round. —MarkusQ
Has anybody gotten the containment option to work? I want to be able to drag from sortable a to sortable b, but not from b to a. It seems that regardless of what I put into containment, I can drag from either to the other. I also restricted it with only:, but that doesn’t help either. Thoughts? Is there a better forum for questions like this? Can’t seem to find one on the Google. —wzph
To answer my own question, I deleted and recreated the sortable on each change. It seems like this should be unnecessary, and I may be overlooking something, but that’s how I got it to work. Hope that helps—wzph
hey wzph, if you don’t want sortable B to drag into sortable A, just set a dropOn Empty:false, as an attribute on the sortable.create object
Sortable.create('thelistA',{containment:['thelistA','thelistB'], dropOnEmpty:false, constraint:false});
Sortable.create('thelistB',{containment:['thelistA','thelistB'], dropOnEmpty:true});
I’m attempting to pass a dynamic list of elements when I use the containment: flag but I can’t seem to get it too work. I’ve tried to pass an array, a list, an object, heck even and array of objects via cssQuery but no dice. Any suggestions?—biohaz
I’ve successfully used Sortable on table rows – the TBODY is my container, and use “tag:’tr’” as an option – works in IE and Firefox. —bb
Containment has worked well for me. For a three column setup with divs named left, middle, and right, here is my javascript code:
Sortable.create("left",
{dropOnEmpty:true,tag:'div',containment:["left","middle","right"],constraint:false, ghosting: true});
Sortable.create("middle",
{dropOnEmpty:true,tag:'div',containment:["left","middle","right"],constraint:false, ghosting: true});
Sortable.create("right",
{dropOnEmpty:true,tag:'div',containment:["left","middle","right"],constraint:false, ghosting: true});
Any DIVs inside the three div columns can easily be moved back and forth between each column. My complete testing code is as follows.
I wanted to know how we can limit the number of draggables can one sortable contain. I want to limit to 2 draggables to one sortable. – thanks
Same question. Any ideas?—darky
Hi all!
One question: how can put back to original order if I made a query to the server (to save the new order) but server raise an error? Thanks! (Garito)
My question: Are there plans for handling multiple selections?
Is it possible to dynamically add a new element (eg DIV) to an existing Sortable and have that new element act as though it was part of the initial set of divs in Sortable.create? The following snippet works to the point of adding the new element to the existing sortable (in this case the sortable’s ID is ‘puzzle’), but it does not inheret any of the snapping, alignment, etc., it just plops down unaligned where ever it’s dropped. – MD
element = Builder.node('div',{id:'NewPuzzlePiece',className:'ARTIST',style:'float:left'},[Builder.node('img',{src:'puzzle7.jpg'})]);
$('puzzle').appendChild(element);
new Draggable(element);
MD: Just repeat the same call you made initially to Sortable.create, with the exact same parameters as before, once you have appended the new div in the correct place. This will implicitly call Sortable.destroy first, and then you’ll have a fresh Sortable object which incorporates your new element. (Ben Bodien)
Is there a way, having first utilized Sortable.serialize to get an array of the sortable’s ID’s, but then re-ordering that array, and applying it back to the sortable? (Think sorting alphabetically by ID’s) – MD
MD, try this:
Sortable.setSequence ('sortme',Sortable.sequence('sortme').sort());hoverclass option does not seem to work with sortable.create. The option is passed to Droppable, but seems to be broken somewhere on its way :( – TS
I just found the same problem and the error was with CSS, not sortable. I applied a class to all the LI elements and avoided cascading of styles for them and worked nicely. – Hector
For my application I needed to modify sortable lists frequently. As MD above found (and it states in the docs here) you need to call Sortable.create again on the changed list. I tend to use behaviour.js (google it, not got link to hand now) to attach events to elements with certain tags or classes. Then whenever I change the dom significantly I call Behaviour.apply() and it does all the Sortable.creates etc. used by the page. – Les
I have the same problem as MD when I insert divs into a sortable dynamically and I have been trying to use behaviour as you suggested but can not get it to work, I would love to see your behaviour code.
- Jarvis
Jarvis, hope this helps
// this code goes in your startup function called by onload()
var myrules={
'div#page ul' : function(element) {
Sortable.create(element);
},
'div#page ul li : function(element) { Event.observe(element,'click',myonclickfunction,true);
}
}
Behaviour.register(myrules);
// then every time you update the dom in any way that adds or
// removes then recreates elements that need to be sortable just call Behaviour.apply();
// the Behaviour rules above are different from those I found whilst googling for Behaviour;
// they all use it to attache onclick handlers. I use Behaviour only as a convenient way to cycle
// through the dom re-applying behaviours whenever I want.
//So if I want to attach events I use Event.observe(element,...) within a behaviour rule
- Les
Here’s a solution to getting the list to update in Rails after you add an item to the list:
http://www.ruby-forum.com/topic/55632#38715
-Marcus
Question: Any way to cancel the onclick event that fires when you drop the Draggable? I want to be able to sort a list of anchor tags (a href) but the link fires when I let go.
Answer: It’s not exactly pretty, byt you can do something like this:
1. Somewhere in your code, add this snippet of Java Script:
sortableReordered = false;
2. Add this to your onUpdate handler for the sortable:
sortableReordered = true;
3. Then, add an onclick handler for each link:
Event.observe('linkName', 'click', function(event) { checkSortable(event); });
4. Add this Java Script function:
function checkSortable(event)
{
if (sortableReordered) {
Event.stop(event);
sortableReordered = false;
}
}
- Jeremy
Just noticed an unpublished option of Sortable, the “tree” option. It looks for grandchildren nodes of type ‘tag’. There is a small bug, the block of code containing it needs a ”.bind(this)”, then it works properly. I need this for TR tags, since my firefox is inserting TBODY tags automatically.
- John
Is there any way to achieve something like two lists. One which should be sortable and one static except that its elements are draggable. When one of the items of the static list is moved to the sortable list it should behave like it behaves when both are sortable (e.g. displace all the ones below the point where the cursor points to). Except when the mouse is released, then the dragged item should return to its old place, and a new created item (a clone, so to speak) in the dynamic sortable list appear.
To sum it up, is it possible to modify the current 2 cross-contained sortable lists system, so that only duplicated items of one list are stored in the other one?
- Jonas
If the containing element has it’s overflow property set to anything but "visible" and I try to drag something out of the list, the draggables are dragged underneath the “border” ( the hidden area of the container) and thus become partly or completely invisible. Any ideas to prevent this from happening? I think, a future solution might be to immediately (on startDrag) detach the draggable element from its container and attach it to something like body, but this will involve a lot of changes, I guess.
- Julian
The options to this function are provided using JSON notation. When I ad an onUpdate callback function I have to use to following javascript
Sortable.create('id_of_container', {"onUpdate":update});
Sortable.create('id_of_container', {"onUpdate":"update"});
I have several droppables between which I am successfully dragging sortables using ‘containment:’. However, for formatting purposes I would really like to fix the number of sortables that are in each of the droppables. I.e. when dragging from one droppable to another, I would like the sortable whose position the dragged sortable replaces to effectively ‘shift one spot down’ – with any sortable then exceeding the maximum number of sortables then going into the next droppable. To illustrate:
—————————————————-
1 |—A—| |—B—| |—C—|
—————————————————-
2 |—D—| |—E—| |—F—|
—————————————————-
where 1 and 2 are the droppables and A-F are the sortables should, for example, become:
—————————————————-
1 |—A—| |—E—| |—B—|
—————————————————-
2 |—C—| |—D—| |—F—|
—————————————————-
after E is dragged onto B’s spot. Were the sortables all of a fixed and identical size, I would not have this problem (and would just use a single droppable); however, in my case not only are the sortables all likely to be different sizes, but they can also be dynamically resized by the user. This can really screw up the formatting unless carefully managed.
Is there any way that anyone can think of doing this? Any help would be dearly appreciated.
—Andrei
serialize: function(element) {
element = $(element);
var options = Object.extend(Sortable.options(element), arguments[1] || {});
var name = encodeURIComponent(
(arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
if (options.tree) {
return Sortable.tree(element, arguments[1]).children.map( function (item) {
return [name + Sortable._constructIndex(item) + "=" +
encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
}).flatten().join('&');
} else {
return Sortable.sequence(element, arguments[1]).map( function(item, index) {
return name + "[" + index + "]=" + encodeURIComponent(item);
}).join('&');
}
}
items[0]=firstItemID&items[1]=secondItemID&...
function move_li(list_id,li_id,dir){
obj = document.getElementById(list_id); // get parent list
CN = obj.childNodes; // get nodes
x = 0;
while(x < CN.length){ // loop through elements for the desired one
if(CN[x].id == li_id){
new_obj = CN[x].cloneNode(true); //create copy of node
break; // End the loop since we found the element
}else{
x++;
}
}
if(new_obj){
if(dir == 'down'){ // Count up, as the higher the number, the lower on the page
y = x + 1;
while(y < CN.length){ // loop trhough elements from past the point of the desired element
if(CN[y].tagName == 'LI'){ // check if node is the right kind
old_obj = CN[y].cloneNode(true);
break; // End the loop
}else{
y++;
}
}
}
if(dir == 'up'){ // Count down, as the lower the number, the higher on the page
if(x > 0){
y = x - 1;
while(y >= 0){ // loop trhough elements from past the point of the desired element
if(CN[y].tagName == 'LI'){ // check if node is the right kind
old_obj = CN[y].cloneNode(true);
break; // End the loop
}else{
y--;
}
}
}
}
if(old_obj){ // if there is an object to replace, replace it.
obj.replaceChild(new_obj,CN[y]);
obj.replaceChild(old_obj,CN[x]);
}
}
}
The previous proposal on up / down buttons seemed inefficient, as the list elements are cloned and therefore recreated every time the list is re-ordered. hence memory usage can be decreased by reordering only the sequence. in the next paragraphs i provide the code snippets. a full working example of my solution can be found on my webpage. – berny
/*
moves an element in a drag and drop list one position up
*/
function moveElementUpforList(list, key) {
var sequence=Sortable.sequence(list);
var newsequence=[];
var reordered=false;
//move only, if there is more than one element in the list
if (sequence.length>1) for (var j=0; j<sequence.length; j++) {
//move, if not already first element, the element is not null
if (j>0 &&
sequence[j].length>0 &&
sequence[j]==key) {
var temp=newsequence[j-1];
newsequence[j-1]=key;
newsequence[j]=temp;
reordered=true;
}
//if element not found, just copy array
else {
newsequence[j]=sequence[j];
}
}
if (reordered) Sortable.setSequence(list,newsequence);
return reordered;
}
}
/*
moves an element in a drag and drop list one position down
*/
function moveElementDownforList(list, key) {
var sequence=Sortable.sequence(list);
var newsequence=[];
var reordered=false;
//move, if not already last element, the element is not null
if (sequence.length>1) for (var j=0; j<sequence.length; j++) {
//move, if not already first element, the element is not null
if (j<(sequence.length-1) &&
sequence[j].length>0 &&
sequence[j]==key) {
newsequence[j+1]=key;
newsequence[j]=sequence[j+1];
reordered=true;
j++;
}
//if element not found, just copy array
else {
newsequence[j]=sequence[j];
}
}
if (reordered) Sortable.setSequence(list,newsequence);
return reordered;
}
}
For the greatest functionalilty and accessibility, combine Tankut’s handy drop-zone marker with Berny’s wonderful Up/Down arrows as refactored by Nick Folts.
There’s a simple thing to do in order to get Up/Down to work with multiple lists that isn’t explicitly said on Berny’s page. You need to call the up/down functions using the parentNode.id call, instead of the static list name:onclick="moveUp(document.getElementById('[name_][id]').parentNode.id, [id]);"
For example:onclick="moveUp(document.getElementById('featured_6').parentNode.id, 6);"
Let’s put that all together, and include the necessary PHP to save to your database.
In this example, each item is a seperate row on a table.
Which list it is in (featured or archive) is determined by the database binary field `featured` (1 = featured, 0 = archive)
The order is determined by the int field `priority` (0 = first, >0 = later)
We also use the `title` field for text identification for the user, and a thumbnail that has been saved to a filname based on the database row `id`
The example also limits the size of a list, and checks for the limit on saving.
The form does not use ajax for saving – this means too many unneccesary http requests and database writes for my purposes, but AJAX functionality can be easily added, and the PHP for saving the order can be extracted to a seperate file to be called if you so desire.
First of all, apply Tankut’s patched version of dragdrop.js to your scriptaculous installation.
Create the following database table:
CREATE TABLE `clips` (
`id` int(11) NOT NULL auto_increment,
`title` varchar(255) NOT NULL default '',
`featured` binary(1) NOT NULL default '',
`priority` tinyint(4) NOT NULL default '0',
UNIQUE KEY `id` (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
And then populate it with some data:
INSERT INTO `clips` VALUES(1,'Clip One','1', 0);
INSERT INTO `clips` VALUES(2,'Clip Two','1', 1);
INSERT INTO `clips` VALUES(3,'Clip Three','1', 2);
INSERT INTO `clips` VALUES(4,'Clip Four','0', 0);
INSERT INTO `clips` VALUES(5,'Clip Five','0', 2);
INSERT INTO `clips` VALUES(6,'Clip Six','0', 3);
INSERT INTO `clips` VALUES(7,'Clip Seven','0', 4);
Wherever you are testing this on your webserver, create an `images` subfolder
Add some thumbnail images `1.jpg` → `7.jpg` to `images` as well as your `arrow_up.png` and `arrow_down.png`
Now, we have a database structure, some data, and some associated files.
All we need is a page that can utilise all this tasty digital goodness.
Voila!
I have simplified this as much as I can to make it readable – it should function out of the box, once you have entered the correct locations for your prototype/scriptaculous installation.
I hope you find this helpful and timesaving!
One feature I would love to see for accesibility is left-right buttons to swap between lists.
DisasterMan at 01:33 PM (02/19)
<? //database connection variables
$hostname_mysql = "localhost";
$database_mysql = "db_name";
$username_mysql = "db_user";
$password_mysql = "********";
$mysql_connect = mysql_pconnect($hostname_mysql, $username_mysql, $password_mysql) or trigger_error(mysql_error(),E_USER_ERROR);
?>
<? // Pared down function - we are only sending integers, so I removed all other types
if (!function_exists("GetSQLValueString")){
function GetSQLValueString($theValue, $theType, $theDefinedValue = "", $theNotDefinedValue = ""){
$theValue = get_magic_quotes_gpc() ? stripslashes($theValue) : $theValue;
$theValue = function_exists("mysql_real_escape_string") ? mysql_real_escape_string($theValue) : mysql_escape_string($theValue);
switch ($theType) {
case "int":
$theValue = ($theValue != "") ? intval($theValue) : "NULL";
break;
}
return $theValue;
}
}
?>
<? //Process and save the list data
if(isset($_POST['form_action'])&& $_POST['form_action'] == "clip_order"){//basic security check
//Break the post var into an array `$featured_list` of database id's.
//Variable name `$featured_list`is determined from the post data
parse_str($_POST['ft_data']);
for ($i = 0; $i < count($featured_list); $i++){//loop through featured items and save each one
$updateSQL = sprintf("UPDATE clips SET priority=%s, featured=%s WHERE id=%s",
GetSQLValueString($i, "int"),
GetSQLValueString(1, "int"),
GetSQLValueString($featured_list[$i], "int"));
mysql_select_db($database_mysql, $mysql_connect);
$Result1 = mysql_query($updateSQL, $mysql_connect) or die(mysql_error());
}
//Break the post var into an array `$archive_list` of database id's
parse_str($_POST['ar_data']);
for ($i = 0; $i < count($archive_list); $i++) {//loop through archive items and save each one
$updateSQL = sprintf("UPDATE clips SET priority=%s, featured=%s WHERE id=%s",
GetSQLValueString($i, "int"),
GetSQLValueString(0, "int"),
GetSQLValueString($archive_list[$i], "int"));
mysql_select_db($database_mysql, $mysql_connect);
$Result1 = mysql_query($updateSQL, $mysql_connect) or die(mysql_error());
}
$updateGoTo = $_SERVER['PHP_SELF']."?ft=".$_GET['ft']."&action=update&t=".date('d/m/y-h:m:s-T');
header(sprintf("Location: %s", $updateGoTo));
}
//End processing and saving data
?>
<?
$editFormAction = $_SERVER['PHP_SELF'];
if (isset($_SERVER['QUERY_STRING'])) {
$editFormAction .= "?" . htmlentities($_SERVER['QUERY_STRING']);
}
?>
<?
//Get featured items order from database
mysql_select_db($database_mysql, $mysql_connect);
$query_featured = "SELECT clips.id, clips.title, clips.priority FROM clips WHERE clips.featured= 1 ORDER BY clips.priority ASC";
$featured = mysql_query($query_featured, $mysql_connect) or die(mysql_error());
$row_featured = mysql_fetch_assoc($featured);
//Get archive items order from database
mysql_select_db($database_mysql, $mysql_connect);
$query_archive = "SELECT clips.id, clips.title, clips.priority FROM clips WHERE clips.featured = 0 ORDER BY clips.priority ASC";
$archive = mysql_query($query_archive, $mysql_connect) or die(mysql_error());
$row_archive = mysql_fetch_assoc($archive);
?>
<?
//You could get this value from a database, or a seperate common.php variables file
$max_featured_clips = 3;
?>
<!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>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Item ordering</title>
<script src="../Scripts/prototype.js" type="text/javascript"></script>
<script src="../Scripts/scriptaculous.js" type="text/javascript"></script>
<script language="javascript">
// Checks the number of items in featured list on submission
function checkNumFeatured(){
if(featuredList.childNodes.length > <?=$max_featured_clips;?>){
var diff = featuredList.childNodes.length - <?=$max_featured_clips;?>;
var warn = "The maximum number of featured clips is <?=$max_featured_clips;?>\n Please move " + diff + " clip";
if (diff > 1){
warn = warn + "s";
}
warn = warn + " to the archive";
alert(warn);
}else{
document.clipform.submit()
}
}
///////////////////////////////////////////////
//Berny's refactored up/down button functions//
///////////////////////////////////////////////
function moveUp(list, row) {
moveRow(list, row, 1);
}
function moveDown(list, row) {
moveRow(list, row, -1);
}
function moveRow(list, row, dir) {
var sequence=Sortable.sequence(list);
for (var j=0; j<sequence.length; j++) {
var i = j - dir;
if (sequence[j]==row && i >= 0 && i <= sequence.length) {
var temp=sequence[i];
sequence[i]=row;
sequence[j]=temp;
break;
}
}
Sortable.setSequence(list, sequence);
fData.value = Sortable.serialize('featured_list');
aData.value = Sortable.serialize('archive_list');
}
</script>
<style>
<!-- CSS styled for the list items, dropzone marker and up/down arrows-->
#content #featured_list {
list-style-type:none;
margin:0;
padding:0;
}
#content #featured_list li {
width:220px;
background-color:#6699FF;
border: 2px #000000 solid;
padding:4px;
cursor:move;
}
#content #featured_list li:hover {
background-color:#990000;
}
#content #archive_list {
list-style-type:none;
margin:0;
padding:0;
}
#content #archive_list li {
width:220px;
background-color:#6699FF;
border: 2px #000000 solid;
padding:4px;
cursor:move;
}
#content #archive_list li:hover {
background-color:#990000;
}
div.dropmarker {
height:6px;
width:220px;
background: url(/images/dropmarker.png) left top;
margin-top:-3px;
margin-left:-5px;
z-index:1000;
overflow: hidden;
}
.emptyPlaceMarker{
background-color:#5083e0;
border: 2px #FF0000 dotted;
}
.arrow{
padding:3px;
margin-right:2px;
}
.arrow:hover{
background-color:#cc99ff;
}
</style>
</head>
<body>
<table border="0" align="center" cellpadding="0" cellspacing="0">
<tr>
<td colspan="2" align="center">
<? if(isset($_GET['action']) && $_GET['action'] == "update"){?>
Order updated <?= $_GET['t'];?>
<? }?>
</td>
</tr>
<tr>
<td align="center" style="width:350px;">Featured Clips <span style="color:#FF0000;">MAX <?=$max_featured_clips;?></span></td>
<td align="center" style="width:350px;">Archive Clips</td>
</tr>
<tr>
<td colspan="2" align="center" class="admin_menu_head">First Clip</td>
</tr>
<tr>
<td>
<div id="content">
<ul id="featured_list">
<? $i =1;
do { // loop through featured items and display each ?>
<li id="featured_<?= $row_featured['id'];?>">
<table style="width:280px;" border="0" cellpadding="0" cellspacing="0">
<tr>
<td>
<!-- Berny's Up/Down Buttons-->
<img class="arrow" src="images/arrow_up.png" alt="move clip up" width="16" height="16" onclick="moveUp(document.getElementById('featured_<?= $row_featured['id'];?>').parentNode.id, <?= $row_featured['id'];?>);" style="cursor:pointer;" />
<img class="arrow" src="images/arrow_down.png" alt="move clip down" width="16" height="16" onclick="moveDown(document.getElementById('featured_<?= $row_featured['id'];?>').parentNode.id, <?= $row_featured['id'];?>);" style="cursor:pointer;" />
<!-- End Berny's Up/Down Buttons-->
<?= $row_featured['title']; ?>
</td>
<td width="100">
<? if(file_exists("images/".$row_featured['id'].".jpg")){?><img src="images/<?= $row_featured['id'];?>.jpg" /><? }?>
</td>
</tr>
</table>
</li>
<? $featured_array[$i] = $row_featured['id'];
$i++;
} while ($row_featured = mysql_fetch_assoc($featured)); ?>
</ul>
</div>
</td>
<td>
<div id="content">
<ul id="archive_list">
<? $i =1;
do {// loop through archive items and display each ?>
<li id="archive_<?= $row_archive['id'];?>">
<table style="width:280px;" border="0" cellpadding="0" cellspacing="0">
<tr>
<td>
<!-- Berny's Up/Down Buttons-->
<img class="arrow" src="images/arrow_up.png" alt="move clip up" width="16" height="16" onclick="moveUp(document.getElementById('archive_<?= $row_archive['id'];?>').parentNode.id, <?= $row_archive['id'];?>);" style="cursor:pointer;" />
<img class="arrow" src="images/arrow_down.png" alt="move clip down" width="16" height="16" nclick="moveDown(document.getElementById('archive_<?= $row_archive['id'];?>').parentNode.id, <?= $row_archive['id'];?>);" style="cursor:pointer;" />
<!-- Emd Berny's Up/Down Buttons-->
<?= $row_archive['title']; ?>
</td>
<td width="100">
<? if(file_exists("images/".$row_archive['id'].".jpg")){?><img src="images<?= $row_archive['id'];?>.jpg" /><? }?>
</td>
</tr>
</table>
</li>
<? $archive_array[$i] = $row_archive['id'];
$i++;
} while ($row_archive = mysql_fetch_assoc($archive)); ?>
</ul>
</div>
</td>
</tr>
<tr>
<td colspan="2" align="center">Last Clip</td>
</tr>
<tr>
<td colspan="2" align="center">
<!-- Form to send sortable.serialize data to server-->
<form action="<?= $editFormAction;?>" method="post" id="clipform" name="clipform">
<input name="form_action" type="hidden" id="form_action" value="clip_order" />
<input name="ft_data" type="hidden" id="ft_data" value="<? $i = 1; foreach($featured_array as $featured){?>featured_list[]=<?= $featured;?>&<? $i++;}?>" />
<input name="ar_data" type="hidden" id="ar_data" value="<? $i = 1; foreach($archive_array as $archive){?>archive_list[]=<?= $archive;?>&<? $i++;}?>" />
<input name="submit_button" type="button" id="submit_button" onclick="checkNumFeatured();" value="Update order" />
<input name="reset_button" type="button" id="reset_button" onclick="window.location.href = '<?=$_SERVER['PHP_SELF'].$_SERVER['QUERY_STRING'];?>'" value="Reset" />
</form>
<!-- End of form-->
</td>
</tr>
</table>
<script type="text/javascript" language="javascript">
// Define form elements to receive serialized data
var fData = document.getElementById("ft_data");
var aData = document.getElementById("ar_data");
// Define feature_list as a JS object for the checkNumFeatured() function
var featuredList = document.getElementById("featured_list");
//Creat the sortables
Sortable.create("featured_list", {
dropOnEmpty:true,
containment:["featured_list","archive_list"],
constraint:false,
onUpdate: function() {
fData.value = Sortable.serialize('featured_list');
aData.value = Sortable.serialize('archive_list');
}
});
Sortable.create("archive_list", {
dropOnEmpty:true,
containment:["featured_list","archive_list"],
constraint:false,
onUpdate: function() {
fData.value = Sortable.serialize('featured_list');
aData.value = Sortable.serialize('archive_list');
}
});
</script>
</body>
</html>