Realize the One-way, One-to-multiple, Drag&Drop Lists
Set up Basic Program
This time, no sortable involved; but the program got more complex, since we need more functionalities. We will use Draggable and Droppable instead. The behaviors would be:
1. when dragging an item, one copy would be dragged, not the original instance;
2. where will be more than one containers receive the draggables, namely multiple droppables;
3. after the item be dropped into the target containers, it would have one remove button;
4. one droppable only receive one sole copy of item, if another one is dropped on that already has one, the preceding one would be removed automatically;
Okay, let's do it:
<!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" xml:lang="en" lang="en">
<head>
<meta charset="utf-8">
<title>Editing Scheduler - Day List</title>
<link rel="stylesheet" href="script/css/custom-theme/jquery-ui-1.8.18.custom.css">
<script src="script/js/jquery-1.6.2.min.js"></script>
<script src="script/js/jquery-ui-1.8.21.custom.min.js"></script>
<style type="text/css">
#day-list-container {
width: 360px;
height: 780px;
border: 1px solid crimson;
float: right;
}
#day-list {
padding-left: 4px;
}
.day-list-item {
width: 200px;
height: 26px;
border: 1px solid #DDD;
background-color: #FBFBFB;
background-image: url('images/day-icon--background.png');
background-repeat: no-repeat;
background-position: top left;
margin-top: 5px;
padding: 4px;
list-style-type: none;
position: relative;
}
.day-list-item label {
margin-left: 26px;
font-size: 0.8em;
}
.day-list-item .remove {
width: 26px;
height: 26px;
background-image: url('images/red-cross.png');
position: absolute;
top: 4px;
right: 10px;
}
#week-scheduler {
width: 840px;
height: 480px;
border: 1px solid mediumvioletred;
float: left;
}
.day-of-week {
width: 228px;
height: 80px;
border: 1px solid greenyellow;
float: left;
position: relative;
}
.day-container {
width: 220px;
height: 60px;
border: 2px solid hotpink;
padding-left: 0px;
margin-top: 0px;
margin-bottom: 0px;
position: absolute;
bottom: 0px;
right: 0px;
}
</style>
</head>
<body>
<div id="day-list-container">
<label id="title">days</label>
<ul id="day-list">
<li id="day_1" class="day-list-item"><label>how could a</label></li>
<li id="day_2" class="day-list-item"><label>day-02</label></li>
<li id="day_3" class="day-list-item"><label>day-03</label></li>
<li id="day_4" class="day-list-item"><label>day-04</label></li>
</ul>
</div>
<form action="" method="post" id="form-schdl-day">
<div id="week-scheduler">
<div id="monday" class="day-of-week">
<ul id="schd-monday" class="day-container"></ul>
</div>
<div id="tuesday" class="day-of-week">
<ul id="schd-tuesday" class="day-container"></ul>
</div>
<div id="wedonsday" class="day-of-week">
<ul id="schd-wedonsday" class="day-container"></ul>
</div>
<div id="thursday" class="day-of-week">
<ul id="schd-thursday" class="day-container"></ul>
</div>
</div>
<input type="submit" value="submit"/>
</form>
<script type="text/javascript">
$(function() {
$( "#day-list li" ).draggable({
connectToSortable: ".day-container",
helper: "clone",
revert: "invalid"
});
$( ".day-container" ).droppable({
drop: function( event, ui ) {
var $cur_id = ui.draggable.attr("id");
var $li = $( "<li class=\"day-list-item\"></li>" ).attr("id", $cur_id).appendTo( this );
$( "<label></label>" ).text( ui.draggable.text() ).appendTo( $li );
$( "<div class=\"remove\"></div>" ).appendTo( $li );
var $num = $(this).find("li").size();
if($num > 1)
{
var $first_li = $(this).find("li").first();
//$first_li.hide( 500, function(){ $(this).remove();} ); // no idea why it not work
$first_li.hide(
250,
function()
{
$(this).remove();
}
);
}
}
});
$(".day-list-item .remove").live(
'click',
function()
{
$(this).closest('.day-list-item').remove();
}
);
$( "ul, li" ).disableSelection();
});
</script>
</body>
</html>
Some Necessary Explanation
Because we want the UL tag of the weekday stay at the right bottom of their containers, we set their CSS position to 'absolute', and their parent tag position to 'relative'.
By default, if you indicate that jQuery handle dragging by duplicating one copy, then there is nothing happen when you drop it, so you need to code that on your own, and what we do is to create the same HTML piece and insert it into the target container, but we do more, we insert a remove button:
var $li = $( "<li class=\"day-list-item\"></li>" ).attr("id", $cur_id).appendTo( this );
$( "<label></label>" ).text( ui.draggable.text() ).appendTo( $li );
$( "<div class=\"remove\"></div>" ).appendTo( $li );
And we also need to bind the click event handler to the remove button, but we have to use live().
Pass the Data to Server
And this is our final purpose. But this time it is different, since we have multiple receiving containers. Adding one single hidden input won't work well. We add one hidden input to each container!!!
<div id="week-scheduler">
<div id="monday" class="day-of-week">
<ul id="schd-monday" class="day-container"></ul>
<input type="hidden" name="schdl-day[]" id="monday-schdl-day" value="">
</div>
<div id="tuesday" class="day-of-week">
<ul id="schd-tuesday" class="day-container"></ul>
<input type="hidden" name="schdl-day[]" id="tuesday-schdl-day" value="">
</div>
<div id="wedonsday" class="day-of-week">
<ul id="schd-wedonsday" class="day-container"></ul>
<input type="hidden" name="schdl-day[]" id="wedonsday-schdl-day" value="">
</div>
<div id="thursday" class="day-of-week">
<ul id="schd-thursday" class="day-container"></ul>
<input type="hidden" name="schdl-day[]" id="thursday-schdl-day" value="">
</div>
</div>
Because each container only have one element, which is day item, then we can use an array to ship them, so we name the hidden input with the same name 'schdl-day'.
And then, we use javascript to operate this hidden input when adding or removing items. Add this block to drop handler of the Droppable:
var $ip = $(this).next("input").first();
$ip.val($cur_id);
Add the lines to bind the click event of remove button:
$(".day-list-item .remove").live(
'click',
function()
{
var $cont = $(this).closest('.day-container');
$cont.next("input").val("");
$(this).closest('.day-list-item').remove();
}
);
And please note, we don't really need extra javascript logics to handle the submission. It is ready to submit now already. But before trying that, we need a PHP file:
<?php
$scheduler_day = (isset($_REQUEST['schdl-day']) ? $_REQUEST['schdl-day'] : '');
print_r($scheduler_day);
exit;
?>
OK, try it:
What server will print is:
Array
(
[0] => day_2
[1] => day_2
[2] => day_1
[3] => day_4
)
Improve User Experience
Not like Sortable, Droppable has one option for that, it is 'activeClass'. So, it comes simple, we create a CSS rule:
.hightlight-target-background{
background-color: rgb(255, 228, 122);
}
And use it when creating Droppable:
$( ".day-container" ).droppable({
activeClass : "hightlight-target-background",
drop: function( event, ui ) {
var $cur_id = ui.draggable.attr("id");
var $li = $( "<li class=\"day-list-item\"></li>" ).attr("id", $cur_id).appendTo( this );
$( "<label></label>" ).text( ui.draggable.text() ).appendTo( $li );
$( "<div class=\"remove\"></div>" ).appendTo( $li );
var $ip = $(this).next("input").first();
$ip.val($cur_id);
var $num = $(this).find("li").size();
if($num > 1)
{
var $first_li = $(this).find("li").first();
$first_li.hide(
250,
function()
{
$(this).remove();
}
);
}
}
});
But, we got some problems here!
The dragged item is underneath the container. To fix this problem, I found we just needed to add one more property to CSS rule .day-list-item:
z-index: 10;
Further Discovery About the Data Passed to Server
Let's check on the nature of the raw data server received from client in this case, add more lines to PHP file:
foreach($scheduler_day as $day)
{
var_dump($day);
if($day === "")
{
echo "this elem is empty string.<br />\n";
}
if(is_null($day))
{
echo "this elem is null.<br />\n"
}
}
Array
(
[0] => day_3
[1] =>
[2] => day_2
[3] => day_2
)
<br />
string(5) "day_3"
the elem's type is: string.<br />
string(0) ""
this elem is empty string.<br />
the elem's type is: string.<br />
string(5) "day_2"
the elem's type is: string.<br />
string(5) "day_2"
the elem's type is: string.<br />
If, more than one input elements are grouped with one name, their values are becoming an array, and the empty-value input its value would be an empty String-type value in PHP.