Javascript Drag and Drop Hover Temporary Div Placeholder similar to Trello? - javascript
I have a basic drag and drop trello-like kanban board. You can drag tasks between different grey boxes. It uses HTML drag and drop API found here https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API.
var dropTarget = document.querySelector(".drop-target");
var draggables = document.querySelectorAll(".drag-task");
// Tells the other side what data is being passed (e.g. the ID is targeted)
draggables.forEach(item => {
item.addEventListener("dragstart", function(ev){
ev.dataTransfer.setData("srcId", ev.target.id);
});
})
// The end destination, prevent browsers default drag and drop (disabling breaks feature)
// because it's disabled by browsers by default
dropTarget.addEventListener('dragover', function(ev) {
ev.preventDefault();
});
// End destination where item is dropped into
dropTarget.addEventListener('drop', function(ev) {
ev.preventDefault();
let target = ev.target;
let droppable = target.classList.contains('drag-box');
let srcId = ev.dataTransfer.getData("srcId");
if (droppable) {
ev.target.appendChild(document.getElementById(srcId));
}
});
/***********DRAGGABLE BACKGROUND ****************/
.drag-box {
background-color: lightgray;
float: right;
width: 120px;
min-height: 50px;
padding-bottom: 30px;
height: auto;
margin: 30px;
}
.drag-task {
background-color: white;
margin: 15px;
}
.drop-active {
border: 1px dashed red;
}
<div class="drop-target">
<div class="drag-box">
<div class="drag-card">
<div draggable="true" id="task1" class="drag-task">Test Card 1</div>
</div>
<div class="drag-card">
<div draggable="true" id="task2" class="drag-task">Test Card 2</div>
</div>
<div class="drag-card">
<div draggable="true" id="task3" class="drag-task">Test Card 3</div>
</div>
</div>
<div class="drag-box">
</div>
<div class="drag-box">
</div>
</div>
What I want to do to achieve is an effect similar to this gif found here. This creates another <div> element on the same level as drag-card class on a draghover effect, and repositions itself accordingly.
I know I have to use dragover and dragleave event listeners but that's as far as I got. I added this code at the end of the file. I have never used drag event listeners so this is new to me.
var makeHoverElement= true;
dropTarget.addEventListener("dragover", function(ev){
if(makeHoverElement){
let newNode =document.createElement('div');
newNode.className ='drop-active'
ev.target.parentElement.prepend(newNode);
makeHoverElement = false;
}
});
dropTarget.addEventListener("dragleave", function(ev){
// really I have no idea how to make this effect
});
Results so far have not turned out as I expected. Dragover is applying to element where the task item originated from
The problem is in ev.target.parentElement.prepend(newNode);
Your ev.target is still a child of the node you are dragging it from. That's why the dotted border div gets added to the 'old' box. I suggest that in your 'dragover' function you explicitly find the element the mouse is over and add your newNode to it. For example, you can select it by document.querySelector(":hover" ) or try to handle 'mouseover' events there.
As for the 'dragleave' effect, I suggest you clone your ev.target with Node.cloneNode() method and append the clone to the ev.target.parentElement using Node.insertBefore().
MDN on .insertBefore()
Using jquery and jquery UI, I did something quite like this a while ago. I didn't create a "make new card" function, I began with a "launchpad" and created two droppable areas that cards could be appended to and switched between - similar to what you have. Using "intersect" as I remember was a tipping point to getting it to work as I wanted - being able to move elements up and down the list (so they don't necessarily move back to where they originated). Perhaps it could be a starting point for you?
Here's the fiddle (the jquery is old.. recommend updating to newer versions)
Hope this helps.
EDIT: I made a couple of small tweaks to your code to add an outline and change the cursor on move. According to a comment on another question, adding a border is the most efficient way to create the visual 'outline' effect. There is a longer way to create the 'sortable' effect which is demoed in this codepen I found, and explained simply, the function is based around calculating hover position and if the dragged element is half-way over an item in the list, the effect displays and the item can be dropped in between list items.
Hope this is clear enough!
// Tells the other side what data is being passed (e.g. the ID is targeted)
var dropTarget = document.querySelector(".drop-target");
var draggables = document.querySelectorAll(".drag-task");
// Tells the other side what data is being passed (e.g. the ID is targeted)
draggables.forEach(item => {
item.addEventListener("dragstart", function(ev) {
ev.dataTransfer.setData("srcId", ev.target.id);
});
})
// The end destination, prevent browsers default drag and drop (disabling breaks feature)
// because it's disabled by browsers by default
dropTarget.addEventListener('dragover', function(ev) {
ev.preventDefault();
});
// End destination where item is dropped into
dropTarget.addEventListener('drop', function(ev) {
ev.preventDefault();
let target = ev.target;
let droppable = target.classList.contains('drag-box');
let srcId = ev.dataTransfer.getData("srcId");
if (droppable) {
ev.target.appendChild(document.getElementById(srcId));
}
});
.drag-box {
background-color: lightgray;
float: left;
width: 120px;
min-height: 80px; /*lengthened the height slightly*/
padding-bottom: 30px;
height: auto;
margin: 30px;
cursor: move; /*added the 'cross' cursor*/
}
.drag-task {
background-color: white;
margin: 10px;
padding: 5px; /*added padding to make tiles bigger*/
border:1px dashed #000000; /*set an outline*/
}
.drop-active {
border: 1px dashed red;
cursor: pointer; /*change the pointer back to the default cursor while moving between lists*/
}
<div class="drop-target">
<div class="drag-box">
<div class="drag-card">
<div draggable="true" id="task1" class="drag-task">Test Card 1</div>
</div>
<div class="drag-card">
<div draggable="true" id="task2" class="drag-task">Test Card 2</div>
</div>
<div class="drag-card">
<div draggable="true" id="task3" class="drag-task">Test Card 3</div>
</div>
</div>
<!-- added tiles to the 2nd list (and deleted 3rd box)-->
<div class="drag-box">
<div class="drag-card">
<div draggable="true" id="orange" class="drag-task">Orange</div>
</div>
<div class="drag-card">
<div draggable="true" id="apple" class="drag-task">Apple</div>
</div>
<div class="drag-card">
<div draggable="true" id="pear" class="drag-task">Pear</div>
</div>
</div>
$("#launchPad").height($(window).height() - 20);
var dropSpace = $(window).width() - $("#launchPad").width();
$("#dropZone").width(dropSpace - 70);
$("#dropZone").height($("#launchPad").height());
$(".card").draggable({
appendTo: "#launchPad",
cursor: "move",
helper: 'clone',
revert: "invalid",
});
$("#launchPad").droppable({
tolerance: "intersect",
accept: ".card",
activeClass: "ui-state-default",
hoverClass: "ui-state-hover",
drop: function(event, ui) {
$("#launchPad").append($(ui.draggable));
}
});
$(".stackDrop").droppable({
tolerance: "intersect",
accept: ".card",
activeClass: "ui-state-default",
hoverClass: "ui-state-hover",
drop: function(event, ui) {
$(this).append($(ui.draggable));
}
});
body {
margin: 0;
background-color: #ffffcc;
}
#launchPad {
width:170px;
float:left;
border: 1px solid #eaeaea;
background-color: #f5f5f5;
}
#dropZone {
float:right;
border: 1px solid #eaeaea;
background-color: #ffffcc;
}
.card {
width: 130px;
padding: 5px 10px;
margin:5px;
border:1px solid #ccc;
background-color: #eaeaea;
}
.stack {
display:inline-block;
vertical-align:top;
width: 180px;
border: 1px solid #ccc;
background-color: #f5f5f5;
margin: 20px;
}
.stackHdr {
background-color: #eaeaea;
border: 1px solid #fff;
padding: 5px
}
.stackDrop {
min-height:100px;
padding: 15px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
<script src="http://code.jquery.com/ui/1.9.2/jquery-ui.js"></script>
<div id="launchPad">
<div class="card draggable" >
apple
</div>
<div class="card draggable">
orange
</div>
<div class="card draggable">
banana
</div>
<div class="card draggable">
car
</div>
<div class="card draggable">
bus
</div>
</div>
<div id="dropZone">
<div class="stack">
<div class="stackHdr">
Drop here
</div>
<div class="stackDrop droppable">
</div>
</div>
<div class="stack">
<div class="stackHdr">
Or here
</div>
<div class="stackDrop droppable">
</div>
</div>
</div>
Related
Gridstack: Dragging widget from one grid into another, nested one
I am trying to create this behavior and not sure whether Gridstack supports it or not. I have 3 Gridstack grids: Grid1, Grid2, and Grid3. Grid1 is a standalone grid and Grid3 is nested inside Grid2. I need to be able to drag widgets from Grid1 both into Grid2 (outer grid) and into Grid3 (nested grid). Following samples I was able to drag widgets between 2 top level grids and create a nested grid, but not combining these 2 together. If this is supported - any pointers are appreciated. NB: Expand the snippet to full screen $(document).ready(function() { $('.grid-stack').gridstack(); }); .grid-stack { background: lightgoldenrodyellow; } .grid-stack-item-content { color: #2c3e50; text-align: center; background-color: #18bc9c; } .grid-stack .grid-stack { /*margin: 0 -10px;*/ background: rgba(255, 255, 255, 0.3); } .grid-stack .grid-stack .grid-stack-item-content { background: lightpink; } <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css"> <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/gridstack.js/0.4.0/gridstack.min.css" /> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.0/jquery-ui.js"></script> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.5.0/lodash.min.js"></script> <script type="text/javascript" src='//cdnjs.cloudflare.com/ajax/libs/gridstack.js/0.4.0/gridstack.min.js'></script> <script type="text/javascript" src='//cdnjs.cloudflare.com/ajax/libs/gridstack.js/0.4.0/gridstack.jQueryUI.min.js'></script> <div class="container-fluid"> <h1> Multilevel Nested grids demo</h1> <div class="grid-stack" id="container-stack"> <div class="grid-stack-item" data-gs-x="0" data-gs-y="0" data-gs-width="4" data-gs-height="4"> <div class="grid-stack-item-content"> <span>Grid One</span> <div class="grid-stack" id="grid-one"> <div class="grid-stack-item widget" data-gs-x="0" data-gs-y="0" data-gs-width="3" data-gs-height="1"> <div class="grid-stack-item-content">1</div> </div> <div class="grid-stack-item widget" data-gs-x="3" data-gs-y="0" data-gs-width="3" data-gs-height="1"> <div class="grid-stack-item-content">2</div> </div> </div> </div> </div> <div class="grid-stack-item" data-gs-x="4" data-gs-y="0" data-gs-width="8" data-gs-height="4"> <div class="grid-stack-item-content"> <span>Grid Two</span> <div class="grid-stack" id="grid-two"> <div class="grid-stack-item widget" data-gs-x="0" data-gs-y="0" data-gs-width="3" data-gs-height="1"> <div class="grid-stack-item-content">3</div> </div> <div class="grid-stack-item widget" data-gs-x="3" data-gs-y="0" data-gs-width="3" data-gs-height="1"> <div class="grid-stack-item-content">4</div> </div> <div class="grid-stack-item" data-gs-x="6" data-gs-y="0" data-gs-width="6" data-gs-height="3"> <div class="grid-stack-item-content"> <span>Grid Three</span> <div class="grid-stack" id="grid-three"> <div class="grid-stack-item widget" data-gs-x="0" data-gs-y="0" data-gs-width="6" data-gs-height="1"> <div class="grid-stack-item-content">5</div> </div> <div class="grid-stack-item widget" data-gs-x="6" data-gs-y="0" data-gs-width="6" data-gs-height="1"> <div class="grid-stack-item-content">6</div> </div> </div> </div> </div> </div> </div> </div> </div> </div>
Following samples I was able to drag widgets between 2 top level grids and create a nested grid, but not combining these 2 together. If this is supported Sad but true, that's not possible. At least, not with gridstack. The key behind this dragging mechanism is accomplished by the acceptWidgets option. But this can't handle multilevel .grid-stack element. Hence, error appears. You can try to modify the script added in the snippet to something like this: $("#container-stack").gridstack({ acceptWidgets: '.grid-stack-item' }) But sadly, this will cause error once you start dragging any widget. But it works with single level nesting, which is not definitely what you are looking for. The documentation also does not indicate anything at all regarding the nested level dragging. But the reason I am assuming this isn't possible is this issue, also this one. I guess this is (almost) exactly what you wanted. but there is no response from any officials. Also, it's three years old. Another flaw that indicates this project is dying is when you try to access some of their files such as the script, you get a security warning, which prevented me to add this snippet for a while. Therefore, if I were you I would go for jqueryUI and custom code this. Update Here is a snippet of something similar to what you expected I guess, let me know if this is right, then I will improve this once again, like adding resizing, snap to sibling widgets and a few more things: Once again, check the snippet in fullscreen mode. $("#gridThree").draggable({ snap: '#gridTwo', snapMode: 'inner', zIndex: 5, containment: 'parent' }); $(".widgetInOne, .widgetInTwo, .widgetInThree").draggable({ snap: '#gridOne, #gridTwo, #gridThree', snapMode: 'inner', zIndex: false, stack: 'div', cursor: 'grab', // grid: [ 100, 100 ] }); $("#gridOne, #gridTwo, #gridThree").droppable({ accept: '.widgetInOne, .widgetInTwo, .widgetInThree', drop: function(event, ui) { if ($(event.target).find($(event.toElement)).length == 0) { $(event.toElement).css({ 'left': '', top: '' }); $(event.target).append($(event.toElement)); } } }); * { margin: 0; padding: 0; box-sizing: border-box; } #gridOne { background: #cecece; width: 40%; height: 400px; display: inline-block; margin-right: 4%; vertical-align: top; } .widgetInOne, .widgetInTwo, .widgetInThree { width: 100px; height: 100px; padding: 0.5em; } #gridTwo { background: #bfe9f3; width: 50%; height: 400px; display: inline-block; margin-left: 4%; vertical-align: top; position: relative; } #gridThree { background: #ffdda9; width: 300px; height: 300px; display: inline-block; vertical-align: top; position: absolute; right: 100px; top: 0; } <link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css"> <link rel="stylesheet" href="https://jqueryui.com/resources/demos/style.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> <div id="gridOne"> <div class="ui-widget-content widgetInOne"> <p>One</p> </div> </div> <div id="gridTwo"> <div class="ui-widget-content widgetInTwo"> <p>Two</p> </div> <div id="gridThree"> <div class="ui-widget-content widgetInThree"> <p>Three</p> </div> </div> </div> A Few Notable things here: When you drag a widget into a grid, the widget element actually moves in that grid (in the DOM structure), so any parent dependent css selector might not work here. only apply css with the widget class. For now the widgets only snaps to the grid inner edges (not to the outer side of other widgets), to find more check: here I couldn't find any other option but to use // grid: [ 100, 100 ] for this one. Resizable option is not added here yet, hope you can tweak it as you need
This is code from an old drag/drop fiddle I composed (an age ago!), with areas that are both draggable and droppable. The items that can be pulled across to either of the droppable areas can be dragged again from one area to another and dragged up and down. While the items are not sized the same as are in your example, it goes to show that the same user experience can be achieved by using simply jquery/jquery-ui without sticking to gridstack. You may save yourself some laborious hours by working outside the grid! ;) Hope this helps fiddle $("#launchPad").height($(window).height() - 20); var dropSpace = $(window).width() - $("#launchPad").width(); $("#dropZone").width(dropSpace - 70); $("#dropZone").height($("#launchPad").height()); $(".card").draggable({ appendTo: "#launchPad", cursor: "move", helper: 'clone', revert: "invalid", }); $("#launchPad").droppable({ tolerance: "intersect", accept: ".card", activeClass: "ui-state-default", hoverClass: "ui-state-hover", drop: function(event, ui) { $("#launchPad").append($(ui.draggable)); } }); $(".stackDrop1").droppable({ tolerance: "intersect", accept: ".card", activeClass: "ui-state-default", hoverClass: "ui-state-hover", drop: function(event, ui) { $(this).append($(ui.draggable)); } }); $(".stackDrop2").droppable({ tolerance: "intersect", accept: ".card", activeClass: "ui-state-default", hoverClass: "ui-state-hover", drop: function(event, ui) { $(this).append($(ui.draggable)); } }); body { margin: 0; } #launchPad { width: 200px; float: left; border: 1px solid #eaeaea; background-color: #f5f5f5; } #dropZone { float: right; border: 1px solid #eaeaea; background-color: #ffffcc; } .card { width: 150px; padding: 5px 10px; margin: 5px; border: 1px solid #ccc; background-color: #eaeaea; } .stack { width: 180px; border: 1px solid #ccc; background-color: #f5f5f5; margin: 20px; } .stackHdr { background-color: #eaeaea; border: 1px solid #fff; padding: 5px } .stackDrop1, .stackDrop2 { min-height: 100px; padding: 15px; } <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.0/jquery.min.js"></script> <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script> <body> <div id="launchPad"> <div class="card draggable"> apple </div> <div class="card draggable"> orange </div> <div class="card draggable"> banana </div> <div class="card draggable"> car </div> <div class="card draggable"> bus </div> </div> <div id="dropZone"> <div class="stack"> <div class="stackHdr"> Drop here </div> <div class="stackDrop1 droppable"> </div> </div> <div class="stack"> <div class="stackHdr"> Or here </div> <div class="stackDrop2 droppable"> </div> </div> </div>
I create fork which solves the problem of nested grids.
jQuery UI sortable & contenteditable=true
I am using jquery sortable and contenteditable the problem is when I am using jquery sortable the contenteditable is not working so I have searched on stackoverflow I have got the solution but when I implemented it now the problem is on mousedown event the sortable is not working. What am I doing worng. Here is the stackoverflow link <div class="sortable"> <div id="" style="background: #eee; width: 80%; margin: 10px auto; padding: 10px;"> <p contenteditable="true" style="padding: 5px;">Add your text here.</p> </div> <div id="" style="background: #eee; width: 80%; margin: 10px auto; padding: 10px;"> <p contenteditable="true" style="padding: 5px;">Add your text here.</p> </div> <div id="" style="background: #eee; width: 80%; margin: 10px auto; padding: 10px;"> <p contenteditable="true" style="padding: 5px;">Add your text here.</p> </div> </div> First I am using this code $('.sortable').sortable(); Second I am using this code $('.sortable').on('onmousedown', function() { $(".sortable").sortable({ cursor: 'move' }); })
change onmousedown to mousedown $('.sortable').on('mousedown', function() { $(".sortable").sortable({ cursor: 'move' }); }) Also on the click event of p destroy the sortable $('p').on('click', function() { $( ".sortable" ).sortable( "destroy" ); }); http://jsfiddle.net/fum9sf2m/
$(window).on('mouseup','.sortable',function(){ $( ".sortable" ).sortable( "destroy" ); }
onmouseover eventlistener onto all children
I have a problem with the onmouseover() event listener. <div class="parent" onmouseover="myfunction()"> <div class="child"></div> </div> I trigger a javascript function whenever the mouse is hovering over the parrent, but whenever I'm hovering over the child, it doesn't register the onmouseover anymore. Is there a workaround so the onmouseover() also gets triggered while hovering over its child elements, using pure Javascript?
Use mouseenter event instead, which doesn't bubble with children elements like mouseoverdoes. In other words with mouseover the event will be attached to all the element children too, so when you hover a child the event will be fired as if we left the parent div. Demo: document.getElementById("test").addEventListener("mouseenter", function(event) { var target = event.target; console.log(target.id); }, false); .child { min-width: 50px; min-height: 50px; padding: 10px; display: inline-block; background-color: yellow; } .parent { padding: 20px; background-color: red; } <div id="test" class="parent"> <div id="child1" class="child">1</div> <div id="child2" class="child">2</div> <div id="child3" class="child">3</div> <div id="child4" class="child">4</div> <div id="child4" class="child">5</div> </div> You can see in the above snippet that using mouseenter the event is always firing even if we hover over children and only the parent id is logged, as if we didn't leave it. Mouseover demo: You can see the difference here using mouseover event: document.querySelector(".parent").addEventListener("mouseover", function(event){ console.log(event.target.id); }); .child { min-width: 50px; min-height: 50px; padding: 10px; display: inline-block; background-color: yellow; } .parent { padding: 20px; background-color: red; } <div class="parent"> <div id="child1" class="child">1</div> <div id="child2" class="child">2</div> <div id="child3" class="child">3</div> <div id="child4" class="child">4</div> <div id="child4" class="child">5</div> </div>
Jquery slide panel moves adjacent picture down
I'm using Jquery slide panels for some images. The problem I'm having is when the slide panel shows it moves the image next to it down. I do not want this to happen. Is there a way to show the slider panel but not move the image next to it? There are questions similar to this but there are no answers to them on the site. Here's the jsfiddle without the image files: https://jsfiddle.net/amyspod/q2obknwt/ Here's my code: <div class="images"> <div class="image1"> <img class="myImg" id="heroimage" src="heros website.jpg" alt="www.heros.com" width="300" height="200"> <div class="panel" id="hero">PSD to responsive website</div> </div> <div class="image2"> <img class="myImg" id="oakimage" src="oak website.jpg" alt="www.oak.com" width="300" height="200"> <div class="panel" id="oak">PSD to responsive website 2</div> </div> </div> .myImg { border-radius: 5px; cursor: pointer; display: inline-block; margin-top: 40px; } /*slider panels*/ .image1, .image2{ display:inline-block; margin-left: 10%; } .panel { padding: 10px; text-align: center; background-color: white; font-size: 20px; display: none; } $(document).ready(function(){ function slidepanel(x,y){ $(x).mouseenter(function(){ $(y).slideToggle("slow"); }); $(x).mouseleave(function(){ $(y).slideToggle("slow"); }); } slidepanel("#oakimage", "#oak"); slidepanel("#heroimage", "#hero"); });
Inline (or inline-block) elements align to the baseline. Try this: .image1, .image2 { ... vertical-align: top; } Demo Also note that CSS classes, by design, are intended to be reusable. I see no reason to have two of them in this case, and they should have semantic values that describe their use or function.
Resize divs related to parent divs with jQuery
So I have 4 divs. I want to change the size of the inner divs compared to parent divs. I want to dynamically change the child div size related to parent's one. Now I've added .top class, but I don't really know if its needed or if it will be useful. Here is the fiddle I'm testing with http://jsfiddle.net/y3597/171/ jQuery below $(".top").each(function () { $('.object').width($(".inner").parent().width()); }); CSS below: .container1 { width: 200px; background: red; padding: 2px; } .container2 { width: 225px; background: purple; padding: 2px; } .container3 { width: 250px; background: blue; padding: 2px; } .container4 { width: 275px; background: black; padding: 2px; } /* top ? */ .inner { width: 150px; background: gray; } .object { width: 100px; background: green; } HTML below: <div class="container1 top"> <div class="inner"> <div class="object">Text 1</div> </div> <div class="container2 top"> <div class="inner"> <div class="object">Text 2</div> </div> <div class="container3 top"> <div class="inner"> <div class="object">Text 3</div> </div> <div class="container4 top"> <div class="inner"> <div class="object">Text 4</div> </div>
I think that you are trying to achieve this: $(".top").each(function () { $(this).find(".object").width($(this).width()); }); In your code jQuery will check for every element with .object class in DOM on each loop. When you use (this) you are refering to element that is currently "selected" in loop. Better way to achive this is to set widths od children to 100%, so they will inherit the witdhs from parents.