I struggle with the following problem
I have a Page where a user can drag and drop files into another DIV. The user now wants to Drag and Drop multiple files with one drag. The problem however is, I found a codesnippet online (cant find it anymore sadly), customised the code to meet the design requests and now I'm stuck with enabling that multifile Drag and Drop.
I'm a complete beginner if it comes to JqueryUI.
The idea is to add checkboxes in the corners of the files/pictures. All selected items should be moved at once with a Drag and Drop.
Idealy the user should be able to mark the necessary file with the mouse Drag and Drop, but that's just nice to have and not a crucial requirement.
I don't want to ask fora complete solution from someone, but rather for possible sources or examples where i can try and create a personal custom solution from.
CODE:
HTML:
<ul class="gallery col-md-8 borderBoxes" id="gallery">
#foreach (var item in Model.PageList)
{
<li class="imageListItem ui-icon-zoomin" id="#item.ID">
<img class="pages small" src="data:image/jpeg;base64,#item.ImageBaase64" onclick="imageLarger(this)" />
</li>
}
</ul>
<div id="trash" class="col-md-3">
</div>
<div id="documentContainer" class="document-container">
<div class="row justify-content-between">
<button id="addNewDocument" class="col-2 btn btn-primary add-doc-button">new document</button>
<form method="post" class="col-md-2 offset-md-10 forward-form">
</form>
</div>
<ul id="newDocuments"></ul>
</div>
<div style="display: none;">
<div id="docDescription" class='col-md-4 form-group documentDescription'>
<label class='control-label input-label'>Dokumenttyp</label>
<select>
<!--stuff that is not important for this post-->
</select>
</div>
</div>
JavaScript:
function imageLarger(image, liID) {
if ($(image).width() === 700) {
$(image).width(100);
setTimeout(function () {
$(image).removeClass("largePages");
}, 300);
} else {
$(image).width(700);
$(image).addClass("largePages");
}
};
$("#addNewDocument").click(function () {
var uid = Math.floor(Math.random() * 26) + Date.now()
var newDocumentBox = $("<li class='row documentContainer justify-content-between' id='" + uid + "'></li>").prependTo("#newDocuments");
var newDocumentDescription = $("#docDescription").clone(true);
newDocumentDescription.appendTo(newDocumentBox);
var newDroppableBox = $("<div class='documents col-md-7 droppableBox' ></div>").appendTo(newDocumentBox);
var newDeleteButton = $("<div class='close-container deleteButton' onclick='deleteDocument(" + uid + ")'><div class='leftright'></div><div class='rightleft'></div></div>").appendTo(newDroppableBox);
newDroppableBox.droppable({
accept: "#gallery > li",
drop: function (event, ui) {
ui.draggable.appendTo(this).fadeIn(function () {
ui.draggable
.animate()
.find("img")
});
}
});
});
function deleteDocument(elementID) {
var element = document.getElementById(elementID);
var listOfPages = element.getElementsByClassName("imageListItem");
var amounOfPages = listOfPages.length;
if (element.style.display !== "none") {
element.style.display = "none";
for (var i = 0; i < amounOfPages; i++) {
$("#gallery").append(listOfPages[0]); // 0 because removing an object decreases $children.length by 1
}
}
};
$(function () {
var $gallery = $("#gallery"),
$trash = $("#trash"),
$document = $("#document"),
trash_icon = "<a href='link/to/trash/script/when/we/have/js/off' title='Delete this image' class='ui-icon ui-icon-trash'>Delete image</a>",
recycle_icon = "<a href='link/to/recycle/script/when/we/have/js/off' title='Recycle this image' class='ui-icon ui-icon-refresh'>Recycle image</a>";
$("li", $gallery)
.draggable({
cancel: "a.ui-icon",
revert: "invalid",
containment: "document",
helper: "clone",
cursor: "move"
});
$trash
.droppable({
accept: "li",
drop: function (event, ui) {
deleteImage(ui.draggable);
}
});
$gallery
.droppable({
accept: "li",
drop: function (event, ui) {
recycleImage(ui.draggable);
}
});
$document
.droppable({
accept: "li",
drop: function (event, ui) {
ui.draggable.appendTo(this).fadeIn(function () {
$item
.animate()
.find("img")
});
deleteImage(ui.draggable);
}
});
function deleteImage($item) {
$item.fadeOut(function () {
var $list = $("ul", $trash).length ?
$("ul", $trash) :
$("<ul class='gallery ui-helper-reset'/>").appendTo($trash);
$item.find("a.ui-icon-trash").remove();
$item.append(recycle_icon).appendTo($list).fadeIn(function () {
$item.addClass('removedPage');
$item
.animate()
.find("img")
});
});
}
function recycleImage($item) {
$item.fadeOut(function () {
$item.removeClass('removedPage');
$item
.find("a.ui-icon-refresh")
.remove()
.end()
.find("img")
.end()
.appendTo($gallery)
.fadeIn();
});
}
$("#recycleAllDocuments")
.on("click", function () {
if ($('#trash').children().length > 0) {
var listOfDeletedItems = document.getElementById("trash").getElementsByClassName("imageListItem");
for (var i = 0; i < listOfDeletedItems.length; i++) {
var $item = $(listOfDeletedItems[i]);
recycleImage($item);
}
}
});
$("deleteButton")
.on("click", function () {
var $item = $(this);
recycleImages($item);
});
$("ul.gallery > li")
.on("click", function (event) {
var $item = $(this),
$target = $(event.target);
if ($target.is("a.ui-icon-trash")) {
deleteImage($item);
} else if ($target.is("a.ui-icon-zoomin")) {
viewLargerImage($target);
} else if ($target.is("a.ui-icon-refresh")) {
recycleImage($item);
}
return false;
});
});
Consider the following example.
$(function() {
var $gallery = $("#gallery"),
$trash = $("#trash"),
$document = $("#document"),
trash_icon = "<a href='link/to/trash/script/when/we/have/js/off' title='Delete this image' class='ui-icon ui-icon-trash'>Delete image</a>",
recycle_icon = "<a href='link/to/recycle/script/when/we/have/js/off' title='Recycle this image' class='ui-icon ui-icon-refresh'>Recycle image</a>";
$("li", $gallery)
.draggable({
cancel: "a.ui-icon",
revert: "invalid",
containment: "document",
helper: function() {
var help = $("<div>", {
class: "ui-draggable-helper"
});
if ($(".ui-selected").length) {
help.data("items", $(".ui-selected"));
help.addClass("multiple");
} else {
help.data("items", $(this));
}
return help;
},
cursor: "move"
});
$gallery.selectable();
$trash
.droppable({
accept: "li",
drop: function(event, ui) {
deleteImage(ui.helper.data("items"));
}
});
$gallery
.droppable({
accept: "li",
drop: function(event, ui) {
recycleImage(ui.draggable);
}
});
$document
.droppable({
accept: "li",
drop: function(event, ui) {
ui.draggable.appendTo(this).fadeIn(function() {
$item
.animate()
.find("img")
});
deleteImage(ui.draggable);
}
});
function deleteImage($item) {
$item.fadeOut(function() {
var $list = $("ul", $trash).length ?
$("ul", $trash) :
$("<ul class='gallery ui-helper-reset'/>").appendTo($trash);
$item.find("a.ui-icon-trash").remove();
$item.append(recycle_icon).appendTo($list).removeClass("ui-selected").fadeIn(function() {
$item.addClass('removedPage');
$item
.animate()
.find("img")
});
});
}
function recycleImage($item) {
$item.fadeOut(function() {
$item.removeClass('removedPage');
$item
.find("a.ui-icon-refresh")
.remove()
.end()
.find("img")
.end()
.appendTo($gallery)
.fadeIn();
});
}
$("#recycleAllDocuments")
.on("click", function() {
if ($('#trash').children().length > 0) {
var listOfDeletedItems = document.getElementById("trash").getElementsByClassName("imageListItem");
for (var i = 0; i < listOfDeletedItems.length; i++) {
var $item = $(listOfDeletedItems[i]);
recycleImage($item);
}
}
});
$("deleteButton")
.on("click", function() {
var $item = $(this);
recycleImages($item);
});
$("ul.gallery > li")
.on("click", function(event) {
var $item = $(this),
$target = $(event.target);
if ($target.is("a.ui-icon-trash")) {
deleteImage($item);
} else if ($target.is("a.ui-icon-zoomin")) {
viewLargerImage($target);
} else if ($target.is("a.ui-icon-refresh")) {
recycleImage($item);
}
return false;
});
});
.gallery {
list-style: none;
}
.gallery .ui-selecting {
background: #ccc;
border-color: #999;
}
.gallery .ui-selected {
background: #eee;
border-color: #222
}
.imageListItem {
width: 100px;
height: 100px;
border: 1px solid #ccc;
border-radius: 3px;
display: inline-block;
margin: 3px;
}
.ui-draggable-helper {
background: #fff;
width: 50px;
height: 65px;
border: 1px solid #999;
border-radius: 3px;
position: relative;
z-index: 100;
}
.ui-draggable-helper:after {
content: "";
width: 0;
height: 0;
border-bottom: 20px solid #999;
border-right: 20px solid #fff;
position: absolute;
left: 29px;
top: -1px;
}
.ui-draggable-helper.multiple:before {
content: "";
width: 50px;
height: 65px;
border-left: 1px solid #999;
border-bottom: 1px solid #999;
border-radius: 3px;
position: absolute;
top: 5px;
left: -5px;
z-index: 0;
}
#trash {
border: 1px solid #ccc;
width: 200px;
height: 200px;
}
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<ul class="gallery col-md-8 borderBoxes" id="gallery">
<li class="imageListItem ui-icon-zoomin" id="item-1">
<img class="pages small" src="data:image/jpeg;base64,#item.ImageBaase64" onclick="imageLarger(this)" />
</li>
<li class="imageListItem ui-icon-zoomin" id="item-2">
<img class="pages small" src="data:image/jpeg;base64,#item.ImageBaase64" onclick="imageLarger(this)" />
</li>
<li class="imageListItem ui-icon-zoomin" id="item-3">
<img class="pages small" src="data:image/jpeg;base64,#item.ImageBaase64" onclick="imageLarger(this)" />
</li>
</ul>
<div id="trash" class="col-md-3">
</div>
<div id="documentContainer" class="document-container">
<div class="row justify-content-between">
<button id="addNewDocument" class="col-2 btn btn-primary add-doc-button">new document</button>
<form method="post" class="col-md-2 offset-md-10 forward-form">
</form>
</div>
<ul id="newDocuments"></ul>
</div>
<div style="display: none;">
<div id="docDescription" class='col-md-4 form-group documentDescription'>
<label class='control-label input-label'>Dokumenttyp</label>
<select>
<!--stuff that is not important for this post-->
</select>
</div>
</div>
With Draggable, you can create your own helper with a Function call.
Allows for a helper element to be used for dragging display. Multiple types supported:
String: If set to "clone", then the element will be cloned and the clone will be dragged.
Function: A function that will return a DOMElement to use while dragging.
So if you want to represent multiple items, you can make a helper looks like this and you can use .data() to carry all the items. When you drop the helper, you can then perform the actions needed on each of the items.
Making use of Selectable, the User can select a number of items in the gallery and then drag them to the Trash, in this example.
Related
In my project I'm using this drag and drop library called gridstack. You can see their documentation on github here. When you hardcode elements inside the dom and initialize the gridstack, those elements are draggable. But when the elements are created dynamically with a forloop, they are not draggable even if they have the proper draggable classes. How can I make this work?
//initialize grid stack
var grid = GridStack.init({
minRow: 5, // don't collapse when empty
cellHeight: 70,
acceptWidgets: true,// acceptWidgets - accept widgets dragged from other grids or from outside (default: false).
dragIn: '.newWidget', // class that can be dragged from outside
dragInOptions: { revert: 'invalid', scroll: false, appendTo: 'body', helper:'clone' }, // clone or can be your function
removable: '#trash', // drag-out delete class
});
//gridstack on change
grid.on('added removed change', function(e, items) {
let str = '';
items.forEach(function(item) { str += ' (x,y)=' + item.x + ',' + item.y; });
console.log(e.type + ' ' + items.length + ' items:' + str );
});
//dynamic elemenets
const arr = ["Bruh cant drag me!", "Nope me neither", "Me too mouhahaha"];
//loop
for (let i = 0; i < arr.length; i++) {
var div = document.createElement('div');
var el = "<div class='newWidget grid-stack-item ui-draggable ui-resizable ui-resizable-autohide'> <div class='grid-stack-item-content' style='padding: 5px;'> "+arr[i]+"</div></div>";
div.innerHTML = el;
$('.dynamic').append(div);
}
.grid-stack-item-removing {
opacity: 0.8;
filter: blur(5px);
}
#trash {
background: rgba(255, 0, 0, 0.4);
padding: 5px;
text-align: center;
}
.grid-stack-item{
background: whitesmoke;
width: 50%;
border: 1px dashed grey;
}
.grid-stack {
background : #F0FFC0;
}
<!-- jquery -->
<script src="https://code.jquery.com/jquery-3.6.0.js"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<!-- gridstack-->
<link rel="stylesheet" href="https://gridstackjs.com/node_modules/gridstack/dist/gridstack-extra.min.css"/>
<script src="https://gridstackjs.com/node_modules/gridstack/dist/gridstack-h5.js"></script>
<!-- body-->
<body>
<div id="trash"><span>drop here to remove</span> </div>
<br>
<div class="dynamic"></div>
<div class="newWidget grid-stack-item ui-draggable ui-resizable ui-resizable-autohide">
<div class="grid-stack-item-content" style="padding: 5px;">
<div>
</div>
<div>
<span>I'm original domster! Drag and drop me!</span>
</div>
</div>
</div>
<br>
<div class="grid-stack"></div>
</body>
This issue was raised here. The grid does not automatically track external changes so the grid needs to be re-initialize for the dragIn option to notice the new dynamic widgets
GridStack.setupDragIn()
Full code
//initialize grid stack
var grid = GridStack.init({
minRow: 5, // don't collapse when empty
cellHeight: 70,
acceptWidgets: true,// acceptWidgets - accept widgets dragged from other grids or from outside (default: false).
dragIn: '.newWidget', // class that can be dragged from outside
dragInOptions: { revert: 'invalid', scroll: false, appendTo: 'body', helper:'clone' }, // clone or can be your function
removable: '#trash', // drag-out delete class
});
//gridstack on change
grid.on('added removed change', function(e, items) {
let str = '';
items.forEach(function(item) { str += ' (x,y)=' + item.x + ',' + item.y; });
console.log(e.type + ' ' + items.length + ' items:' + str );
});
//dynamic elemenets
const arr = ["Bruh cant drag me!", "Nope me neither", "Me too mouhahaha"];
//loop
for (let i = 0; i < arr.length; i++) {
var div = document.createElement('div');
var el = "<div class='newWidget grid-stack-item ui-draggable ui-resizable ui-resizable-autohide'> <div class='grid-stack-item-content' style='padding: 5px;'> "+arr[i]+"</div></div>";
div.innerHTML = el;
$('.dynamic').append(div);
}
GridStack.setupDragIn(
'.newWidget',
);
.grid-stack-item-removing {
opacity: 0.8;
filter: blur(5px);
}
#trash {
background: rgba(255, 0, 0, 0.4);
padding: 5px;
text-align: center;
}
.grid-stack-item{
background: whitesmoke;
width: 50%;
border: 1px dashed grey;
}
.grid-stack {
background : #F0FFC0;
}
<!-- jquery -->
<script src="https://code.jquery.com/jquery-3.6.0.js"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<!-- gridstack-->
<link rel="stylesheet" href="https://gridstackjs.com/node_modules/gridstack/dist/gridstack-extra.min.css"/>
<script src="https://gridstackjs.com/node_modules/gridstack/dist/gridstack-h5.js"></script>
<!-- body-->
<body>
<div id="trash"><span>drop here to remove</span> </div>
<br>
<div class="dynamic"></div>
<div class="newWidget grid-stack-item ui-draggable ui-resizable ui-resizable-autohide">
<div class="grid-stack-item-content" style="padding: 5px;">
<div>
</div>
<div>
<span>I'm original domster! Drag and drop me!</span>
</div>
</div>
</div>
<br>
<div class="grid-stack"></div>
</body>
I have connected lists using jQuery sortable().
The lists are initialized with
$( "#held, #notheld" ).sortable({
connectWith: ".connectedSortable",
}).disableSelection();
When the page loads I also bind dblclick
$('#held li').on('dblclick', function() {
var litem = $(this).clone();
litem.appendTo($('#notheld'));
$(this).remove();
update_holding(litem.attr('id'), 'remove');
$( "#held, #notheld" ).sortable( "refresh" );
});
$('#notheld li').on('dblclick', function() {
var litem = $(this).clone();
litem.appendTo($('#held'));
$(this).remove();
update_holding(litem.attr('id'), 'add');
$( "#held, #notheld" ).sortable( "refresh" );
});
Once the cloned LI is appended to the other list it needs to have the correct .on('dblclick') function bound. If I clone with true boolean as an arg the bindings get copied but I do not want the original function but rather the one associated with the list to which it now belongs.
The elements can still be dragged to new list without error.
I have tried adding the binding function to the activate, change, and update events in the initializing call in the hope that refresh() would see new element and do the .on() assignment but these were ineffective.
I also tried re-writing the initial bindings like so
$('#notheld li').on('dblclick', function() {
var litem = $(this).clone();
litem.appendTo($('#held'));
$(this).remove();
update_holding(litem.attr('id'), 'add');
litem.on('dblclick', function() {
var litem2 = $(this).clone();
litem2.appendTo($('#notheld'));
$(this).remove();
update_holding(litem2.attr('id'), 'remove');
});
});
But this does not call the function correctly? Perhaps the use of $(this) is not correct?
The update_holding() function should not bear on the issue as it is just an ajax post to another script managing the database updates.
Here is a working example: https://jsfiddle.net/qn6v42c9/
Also read
jQuery clone() not cloning event bindings, even with on() and
jquery .on() doesn't work for cloned element
I would use click event delegation on sortable container itself so I would not have to bind the dbclick again and again, here is the code for it
$("#held, #notheld").sortable({
connectWith: ".connectedSortable",
}).disableSelection();
$('#held').on('dblclick', 'li', function() {
var litem = $(this).clone();
litem.appendTo($('#notheld'));
$(this).remove();
update_holding(litem.attr('id'), 'remove');
$("#held, #notheld").sortable("refresh");
});
$('#notheld').on('dblclick', 'li', function() {
var litem = $(this).clone();
litem.appendTo($('#held'));
$(this).remove();
update_holding(litem.attr('id'), 'add');
$("#held, #notheld").sortable("refresh");
});
// dropped
$('#held').on('sortreceive', function(event, ui) {
update_holding(ui.item.attr('id'), 'add');
});
// dropped
$('#notheld').on('sortreceive', function(event, ui) {
update_holding(ui.item.attr('id'), 'remove');
});
function update_holding(EntityNumber, action) {
// ajax here
}
#held, #notheld {
border: 1px solid #eee;
width: 272px;
min-height: 20px;
list-style-type: none;
margin: 0;
padding: 5px 0 0 0;
float: left;
margin-right: 10px;
}
#held li, #notheld li {
margin: 0 5px 5px 5px;
padding: 5px;
font-size: 1.2em;
width: 250px;
cursor:move;
}
#notheld li {
float: left;
clear: none;
display: inline-block;
}
div#notheld-container, div#held-container {
width: 300px;
float:left;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<link href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css" rel="stylesheet"/>
<div id="notheld-container">
<h3>Properties Not Held by <em>Client</em></h3>
<ul id="notheld" class="connectedSortable">
</ul>
</div>
<div id="held-container">
<h3>Current Holdings</h3>
<ul id="held" class="connectedSortable ui-sortable">
<li class="ui-state-highlight ui-sortable-handle" id="12">Farragut (12)</li><li class="ui-state-highlight ui-sortable-handle" id="1010" style="">King Street (1010)</li>
<li class="ui-state-highlight ui-sortable-handle" id="07">Annandale (07)</li>
<li class="ui-state-highlight ui-sortable-handle" id="13">Aquahart (13)</li>
</ul>
</div>
I am trying to make an editor of a sort were you choose your layout for the given section and then drop another paragraph, button, img etc. on to the content area of that layout.
The thing is that when ever i drop the first div "layout" and i try to drop an item to that div, it chooses the wrong droppable area, because it is located "behind" the, as if the z-index is wrong, but the correct area is visible.
i made i jsfiddle as an example of the code, I'am logging the id of the droppable area and the dropped item in the console:
https://jsfiddle.net/g9aragsp/3/
so i would to add one of the "items" to the "insidedrop" instead of the "editor". Ive only set the id "insidedrop" on the first layout for testing purpose.
My html:
<div class="container-fluid">
<div class="row content">
<div class="col-sm-8 col-sm-offset-2">
<div class="row htmlEditor">
<div class="col-sm-3" style="background-color: gray; min-height: 800px;">
<p style="border-bottom: 5px solid black">Layouts</p>
<ul id="layouts" style="list-style-type: none;">
<li id="layout1" class="PickerButton">layout1</li>
<li id="layout2" class="PickerButton">layout2</li>
<li id="layout3" class="PickerButton">layout3</li>
</ul>
<p style="border-bottom: 5px solid black">Items</p>
<ul id="items" style="list-style-type: none;">
<li id="item1" class="PickerButton">button</li>
<li id="item2" class="PickerButton">img</li>
<li id="item3" class="PickerButton">text</li>
</ul>
</div>
<div id="editor" class="col-sm-9" style="background-color: lightgray; min-height: 800px; padding: 20px;">
</div>
</div>
</div>
</div>
</div>
My Javascript:
$(function () {
var $layouts = $("#layouts"),
$items = $("#items"),
$insideDrop = $("#insideDrop"),
$trash = $("#editor");
$("li", $layouts).draggable({
cancel: "button", // these elements won't initiate dragging
revert: "invalid", // when not dropped, the item will revert back to its initial position
containment: "document",
helper: "clone",
cursor: "move"
});
$("li", $items).draggable({
cancel: "button", // these elements won't initiate dragging
revert: "invalid", // when not dropped, the item will revert back to its initial position
containment: "document",
helper: "clone",
cursor: "move"
});
$trash.droppable({
//accept: "#list1 > li",
drop: function (event, ui) {
console.log(ui.draggable.attr("id"));
console.log($(this).attr("id"));
if (ui.draggable.attr("id") == "layout1")
$("#editor").append("<div class='layout1Outer' style='min-height:400px; margin-bottom: 15px;'><div class='layout1imageplaceholder' style='background-color:#8a2be2;height:150px'></div><div class='layout1content' id='insideDrop' style='background-color:bisque;min-height:250px'></div></div>");
if (ui.draggable.attr("id") == "layout2")
$("#editor").append("<div class='layout2Outer' style='min-height:400px; margin-bottom: 15px;'><div class='layout2imageplaceholder' style='background-color:#8a2be2;float:right;height:400px;width:40%'></div><div class='layout2content' style='background-color:bisque;float:left;min-height:400px;width:60%'></div></div>");
if (ui.draggable.attr("id") == "layout3")
$("#editor").append("<div class='layout3Outer' style='min-height:400px; margin-bottom: 15px;'><div class='layout3imageplaceholder' style='background-color:#8a2be2;float:left;height:400px;width:40%'></div><div class='layout3content' style='background-color:bisque;float:right;min-height:400px;width:60%'></div></div>");
}
});
$insideDrop.droppable({
drop: function (event, ui) {
if (ui.draggable.attr("id") == "item1")
$("#insideDrop").append("<span id='button' style='background-color:#dc143c;padding: 10px; width: 50px'>BUTTON</span>");
if (ui.draggable.attr("id") == "item2")
$("#insideDrop").append("<img src='http://via.placeholder.com/50x50'>");
if (ui.draggable.attr("id") == "item3")
$("#insideDrop").append("<p style='background- color: #284B63; padding: 10px; width: 200px; color: white;'>Insert text here</p>");
console.log(ui.draggable.attr("id"));
}
});
});
I figured out a solution to the problem. instead of targeting a nested "droppable" container. i reused the same function and on hover made javascript return the current hovered object, and then append the item to that specific object as so:
var hoveredObj;
function getid(obj) {
hoveredObj = obj;
}
var $layouts = $("#layouts"),
$items = $("#items"),
$insideDrop = $("#insideDrop"),
$editor = $("#editor");
$("li", $layouts).draggable({
cancel: "button", // these elements won't initiate dragging
revert: "invalid", // when not dropped, the item will revert back to its initial position
containment: "document",
helper: "clone",
cursor: "move"
});
$("li", $items).draggable({
cancel: "button", // these elements won't initiate dragging
revert: "invalid", // when not dropped, the item will revert back to its initial position
containment: "document",
helper: "clone",
cursor: "move"
});
$editor.droppable({
//accept: "#list1 > li",
drop: function (event, ui) {
console.log(ui.draggable.attr("id"));
console.log($(this).attr("id"));
if (ui.draggable.attr("id") == "layout1")
$("#editor").append("<div class='layout1Outer' style='min-height:400px; margin-bottom: 15px;'><div class='layout1imageplaceholder' style='background-color:#8a2be2;height:150px'></div><div class='layout1content' id='insideDrop' onmouseover='getid(this)' style='background-color:bisque;min-height:250px'></div></div>");
if (ui.draggable.attr("id") == "layout2")
$("#editor").append("<div class='layout3Outer' style='min-height:400px; margin-bottom: 15px;'><div class='layout3imageplaceholder' style='background-color:#8a2be2;float:left;height:400px;width:40%'></div><div class='layout3content' style='background-color:bisque;float:right;min-height:400px;width:60%'></div></div>");
if (ui.draggable.attr("id") == "layout3")
$("#editor").append("<div class='layout2Outer' style='min-height:400px; margin-bottom: 15px;'><div class='layout2imageplaceholder' style='background-color:#8a2be2;float:right;height:400px;width:40%'></div><div class='layout2content' style='background-color:bisque;float:left;min-height:400px;width:60%'></div></div>");
if (ui.draggable.attr("id") == "item1")
$(hoveredObj).append("<span id='button' style='background-color:#dc143c;padding: 10px; width: 50px'>BUTTON</span>");
if (ui.draggable.attr("id") == "item2")
$(hoveredObj).append("<img src='http://via.placeholder.com/50x50'>");
if (ui.draggable.attr("id") == "item3")
$(hoveredObj).append("<p style='background- color: #284B63; padding: 10px; width: 200px; color: white;'>Insert text here</p>");
}
});
I found this code, and the fiddle that was linked.
This is close to what I am looking for, however I would like to add a counter because I would like list 2 and list 3 to only accept 3 draggable items.
I intend to add more lists to this code for my purposes and they also need to only accept 3 items.
List 1 needs to be able to accept all of the draggable items.
I think I need to create an array with a counter to keep track of all of this, but I don't know how to do that. I don't really understand or know javascript.
Any help with this would be appreciated.
$(document).ready(function(){
$(".droppable").droppable({
drop: function(event, ui) {
var $list = $(this);
$helper = ui.helper;
$($helper).removeClass("selected");
var $selected = $(".selected");
if($selected.length > 1){
moveSelected($list,$selected);
}else{
moveItem(ui.draggable,$list);
}
}, tolerance: "touch"
});
$(".draggable").draggable({
revert: "invalid",
helper: "clone",
cursor: "move",
drag: function(event,ui){
var $helper = ui.helper;
$($helper).removeClass("selected");
var $selected = $(".selected");
if($selected.length > 1){
$($helper).html($selected.length + " items");
}
}
});
function moveSelected($list,$selected){
$($selected).each(function(){
$(this).fadeOut(function(){
$(this).appendTo($list).removeClass("selected").fadeIn();
});
});
}
function moveItem( $item,$list ) {
$item.fadeOut(function() {
$item.find(".item").remove();
$item.appendTo( $list ).fadeIn();
});
}
$(".item").click(function(){
$(this).toggleClass("selected");
});
});
The HTML...
<div id="list1" class="droppable list"><!-- I want this to accept many. -->
<div class="item draggable">1</div>
<div class="item draggable">2</div>
<div class="item draggable">3</div>
<div class="item draggable">4</div>
</div>
<div id="list2" class="droppable list"><!-- I want this to accept only 3. -->
<div class="item draggable">5</div>
<div class="item draggable">6</div>
</div>
<div id="list3" class="droppable list"><!-- I want this to accept only 3. -->
<div class="item draggable">7</div>
</div>
Added a data-max attribute for each droppable list and inside the drop function you can check if the number of elements inside that list reached the max (limit), and if so - just return false.
Here is the change to your code:
$(document).ready(function(){
$(".droppable").droppable({
drop: function(event, ui) {
var $list = $(this);
$helper = ui.helper;
// Check if we reached the maximum number of children.
if ($(this).children().length == $(this).data('max')) {
return false;
}
$($helper).removeClass("selected");
var $selected = $(".selected");
if($selected.length > 1){
moveSelected($list,$selected);
}else{
moveItem(ui.draggable,$list);
}
}, tolerance: "touch"
});
$(".draggable").draggable({
revert: "invalid",
helper: "clone",
cursor: "move",
drag: function(event,ui){
var $helper = ui.helper;
$($helper).removeClass("selected");
var $selected = $(".selected");
if($selected.length > 1){
$($helper).html($selected.length + " items");
}
}
});
function moveSelected($list,$selected){
$($selected).each(function(){
$(this).fadeOut(function(){
$(this).appendTo($list).removeClass("selected").fadeIn();
});
});
}
function moveItem( $item,$list ) {
$item.fadeOut(function() {
$item.find(".item").remove();
$item.appendTo( $list ).fadeIn();
});
}
$(".item").click(function(){
$(this).toggleClass("selected");
});
});
div.list {
border: 1px solid red;
margin: 5px;
min-height: 20px;
}
div.list div {
background: gray;
margin: 4px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<div id="list1" class="droppable list" data-max="-1"><!-- I want this to accept many. -->
<div class="item draggable">1</div>
<div class="item draggable">2</div>
<div class="item draggable">3</div>
<div class="item draggable">4</div>
</div>
<div id="list2" class="droppable list" data-max="3"><!-- I want this to accept only 3. -->
<div class="item draggable">5</div>
<div class="item draggable">6</div>
</div>
<div id="list3" class="droppable list" data-max="3"><!-- I want this to accept only 3. -->
<div class="item draggable">7</div>
</div>
I am having issues with drag and drop functionality and I'm hoping someone can help. The rules in a nutshell are:
the "stage" (.stage) of which there can be more than one can accept cloned .pageControl. It is the only class it can accept.
Once dropped on .stage, .pageControl becomes .pageControlDropped and can accept cloned .wfcControl. It is the only class it can accept.
Once .wfcControl is dropped, it is replaced with new html and becomes .wfcControlDropped.
My problems are:
When I drag cloned .pageControl to .stage, it jumps to a position on .stage that is not the position I'm dropping it. I can drag it back to where I want it but it needs to drop where I drop it. I tried CSS positioning but it seems to work on .pageControl. Once .pageControl -> .pageControlDropped, it jumps to another position. Also, its not a very fluid drag like in the examples
If I drag multiple .pageControls to .stage, any of them should accept .wfcControl. But it seems like only the first .pageControl (now .pageControlDropped) receives it. I can't get the second .pageControlDropped to receive it.
How do I get successive .pageControl to not overlay existing ones on .stage?
CSS:
<style type="text/css">
.stage { margin-left: -.3em; width: 500px; height: 550px; padding: 0.0em;}
.pageControl {height:15px; width:15px; background-color:#EAEEFF; border:1px solid blue;}
.pageControlDropped {height:450px; width:600px;background-color:#F9FAFF;border:1px solid blue;}
.wfcControl { }
.wfcControlDropped { }
</style>
JQuery:
$('.pageControl').draggable({
helper: 'clone',
snap: false,
containment: '.stage',
handle: '.wfcHandle',
stop: function (event, ui) {
var pos = $(ui.helper).offset();
$(this).css({
"left": pos.left,
"top": pos.top
});
}
});
$('.wfcControl').draggable({ helper: 'clone', containment: '.pageControlDropped' });
$('.stage').droppable({
accept: '.pageControl',
greedy: true,
drop: function (event, ui) {
$(this).append($(ui.helper).clone());
$('.stage .pageControl')
.removeClass('pageControl')
.addClass('pageControlDropped')
.resizable()
.draggable({
containment: '.stage',
handle: '.wfcHandle'
})
.droppable({
accept: '.wfcControl',
greedy: true,
drop: function (event, ui) {
switch (ui.helper[0].title) {
case "Play Greeting Control":
wfcControlDropped = wfcPlayGreetingControl
break;
case "Input Control":
wfcControlDropped = wfcInputControl
break;
}
$(this).append($(ui.helper).clone());
$('.pageControlDropped .wfcControl').replaceWith($(wfcControlDropped));
$('.pageControlDropped .wfcControlDropped')
.draggable({
containment: '.pageControlDropped'
})
}
}).clone(false)
return false;
}
});
Finally, the HTML:
<div id = "divPageControl" title = "Page Control" class="pageControl">
<table style = "width:100%" border = "0">
<tr>
<td colspan = "1" width = "100%"></td>
</tr>
</table>
</div>
<div id = "divInputControl" title = "Input Control" class="wfcControl" style="height:15px; width:15px; background-color:light green; border:1px solid green;">
<table style = "width:100%" border = "0">
<tr class = "wfcHandle">
<td colspan = "1" width = "100%"> </td>
</tr>
</table>
</div>
Thanks for any help on this.
This should get you WELL ON YOUR WAY:
HTML:
<div class="pageControl"></div>
<div class="wfcControl"></div>
<div class="stage"> STAGE</div>
<div class="stage"> STAGE</div>
JAVASCRIPT:
$('.pageControl,.wfcControl').draggable({
helper:"clone",
opacity:0.5
});
//=========================================
$('.stage').droppable(
{
tolerance: "fit",
greedy:true,
accept: ".pageControl",
drop: function(e,ui){
$(this).append(
$(ui.draggable).clone()
.css({
position:"absolute",
//IMPORTANT
top: e.clientY-e.offsetY,
left: e.clientX-e.offsetX
})
//note containment:parent => IMPORTANT
.draggable({containment:"parent",
snap:true,
snapMode:"outer",
//MY ATTEMPT TO STOP USERS FROM OVERLAPPING
snapTolerance:15
})
.removeClass("pageControl")
.addClass("pageControlDropped")
.resizable()
.droppable({
accept: ".wfcControl",
drop: function(ev,ui){
$(this).append(
$(ui.draggable).clone()
.css({
position:"absolute",
top:ev.clientY-ev.offsetY-$(this).offset().top,
left: ev.clientX-ev.offsetX - $(this).offset().left
})
//note containment:parent
.draggable({containment:"parent"})
.removeClass("wfcControl")
.addClass("wfcControlDropped")
);
}
})
);
}
}
);
DEMO:
http://jsbin.com/orepew
Let me know if you had any questions