Multiple drop events in HTML5 - javascript

I'm trying to create a drag and drop feature in HTML5 where I can drag from one list to another. I have one list with draggable items and another list with items that have drop events added. The problem is, regardless of what element I drop onto, the last drop event that was added is the one that gets called.
Thanks for any help or suggestions.
I've included my code below:
<!DOCTYPE html>
<head>
<title>List Conversion Test</title>
<style type="text/css">
#list, #cart {
display: inline;
float: left;
border: 1px solid #444;
margin: 25px;
padding: 10px;
}
#list p {
background-color: #036;
color: #fff;
}
#cart p {
background-color: #363;
color: #fff;
}
.listitem {
}
.listitem_done {
text-decoration: line-through;
}
.product {
background-color: #CCC;
}
.product_over {
background-color: #363;
}
</style>
<script type="text/javascript" src="http://html5demos.com/js/h5utils.js"></script>
</head>
<body>
<article>
<div id="list">
<p>On My List</p>
<ul>
<li class="listitem" id="L001">Shopping List Item #1</li>
<li class="listitem" id="L002">Shopping List Item #2</li>
</ul>
<div id="done">
<p>In My Cart</p>
<ul></ul>
</div>
</div>
<div id="cart">
<p>Cart</p>
<ul>
<li class="product" id="P001">Product #1</li>
<li class="product" id="P002">Product #2</li>
</ul>
</div>
</article>
<script>
// make list items draggable
var list = document.querySelectorAll('li.listitem'), thisItem = null;
for (var i = 0; i < list.length; i++) {
thisItem = list[i];
thisItem.setAttribute('draggable', 'true');
addEvent(thisItem, 'dragstart', function (e) {
e.dataTransfer.effectAllowed = 'copy';
e.dataTransfer.setData('Text', this.id);
});
}
// give products drop events
var products = document.querySelectorAll('li.product'), thisProduct = null;
for (var i = 0; i < products.length; i++) {
thisProduct = products[i];
addEvent(thisProduct, 'dragover', function (e) {
if (e.preventDefault) e.preventDefault();
this.className = 'product_over';
e.dataTransfer.dropEffect = 'copy';
return false;
});
addEvent(thisProduct, 'dragleave', function () {
this.className = 'product';
});
addEvent(thisProduct, 'drop', function (e) {
//alert(thisProduct.id);
if (e.stopPropagation) e.stopPropagation();
var thisItem = document.getElementById(e.dataTransfer.getData('Text'));
thisItem.parentNode.removeChild(thisItem);
thisProduct.className = 'product';
handleDrop(thisItem, thisProduct);
return false;
});
}
// handle the drop
function handleDrop(i, p) {
alert(i.id + ' to ' + p.id);
var done = document.querySelector('#done > ul');
done.appendChild(i);
i.className = 'listitem_done';
}
</script>
</body>
</html>

This is why it's often a bad idea to define functions (such as callback functions) within a loop. You're assigning thisProduct within the loop, but it will be reassigned for the next iteration of the loop. The way your closures are set up, each callback is bound to the same variable thisProduct, and will use the latest value.
One possible fix is to create a new closure where thisProduct is needed such as
(function(thisProduct) {
addEvent(thisProduct, 'drop', function (e) {
//alert(thisProduct.id);
if (e.stopPropagation) e.stopPropagation();
var thisItem = document.getElementById(e.dataTransfer.getData('Text'));
thisItem.parentNode.removeChild(thisItem);
thisProduct.className = 'product';
handleDrop(thisItem, thisProduct);
return false;
});
}(thisProduct));
This jsFiddle seems to work for me now. See here for more explanation.

Related

Copy selected <li> to another <ul> with js

function addSelected(clicked_id) {
// alert(clicked_id);
const ul = document.getElementById("sortable2");
const listItems = ul.getElementsByTagName("li");
// const ul2 = document.getElementById('slottable1');
// Loop through the NodeList object.
for (let i = 0; i <= listItems.length - 1; i++) {
if (listItems[i].className == "selectedli") console.log(listItems[i]);
//need to copy these <li> tags in another <ul> list
}
}
<ul id="slottable">
//need to copy selected <li> here and also remove class from those selected <li> before adding here
</ul>
output of the console:
<li id="pc_103" class="selectedli">B73</li>
<li id="pc_104" class="selectedli">B74</li>
I have successfully printed li which I want to copy to another ul in console logs but not able to find the right code to copy them to my another ul. I also need to remove the class 'selectedli' from the li before adding it to the ul 'slottable'.
It's done by creating dynamic tag inside slottable.
See below example:
const getChild = document.getElementById("sortable2").children;
function addSelected() {
let createUl = document.createElement("ul");
createUl.id = "slottable";
document.getElementById("tagBox").appendChild(createUl);
for (let i = 0; i < getChild.length; i++) {
if (getChild[i].className == "selectedli")
{
var createLi = document.createElement("li");
createLi.id = getChild[i].id
createLi.classList.add(getChild[i].classList);
createLi.innerHTML = getChild[i].textContent;
createUl.appendChild(createLi);
console.log(getChild[i]);
}
}
document.getElementById("sortable2").innerHTML = "";
}
ul
{
list-style: none;
}
#sortable2
{
padding: 10px;
background: red;
width: 30px;
}
#slottable
{
padding: 10px;
background: green;
width: 30px;
}
<body>
<div id="tagBox">
</div>
<ul id="sortable2">
<li id="pc_103" class="selectedli">B73</li>
<li id="pc_104" class="selectedli">B74</li>
</ul>
<input type="button" onclick="addSelected()" value="submit">
</body>
The appendChild() method should work.
Like this:
sortable2.appendChild(selectedli)
To remove classes, use selectedli.classList.remove(selectedli)
You looking for something like that?
It copied from one ul to the new ul and removes the class.
classList.remove and appendChild:
lis.map((el) => {
el.classList.remove('selectedli');
el.innerText += ' (copied and without classs slectedli)'
ul2.appendChild(el)
})
const btn = document.getElementById('transfer');
btn.addEventListener('click', () => {
// copy from slottable to sortable2
const ul = document.getElementById("slottable").children;
const ul2 = document.getElementById("sortable2");
let lis = Object.values(ul);
lis.map((el) => {
el.classList.remove('selectedli');
el.innerText += ' (copied and without classs slectedli)'
ul2.appendChild(el)
})
ul.innerHTML = '';
});
.green {
background: green;
}
.gray {
background: gray;
}
<ul id="slottable" class="gray">
<li id="pc_103" class="selectedli">B73</li>
<li id="pc_104" class="selectedli">B74</li>
</ul>
<ul id="sortable2" class="green"></ul>
<button id="transfer">click </button>

How to fix "toggle" a "classList" using JavaScript

I have a list to add a class, but just items after add by input works with toggle. The items in the code don't work.
I wonder if is something related to "this" property too.
Link to CodePen.
https://codepen.io/kennedyrmenezes/pen/BaQRXMq
li.addEventListener("click", function() {
var finished = this.classList.toggle("done");
var removeButton = document.createElement("button");
removeButton.classList.add("deleteButton");
if (finished) {
removeButton.appendChild(document.createTextNode("remove"));
removeButton.classList = "deleteButton";
li.appendChild(removeButton);
removeButton.addEventListener("click", function() {
this.parentElement.remove();
});
} else {
this.getElementsByClassName("deleteButton")[0].remove();
}
})
If you look at the code that you have written, you are only attaching the event handlers to the newly created li nodes.
To get around it, you can attach the event handers to all existing li elements on page load or you can bind the event handlers once using the concept of event delegation. I find the 2nd approach to be cleaner as you don't have to worry about adding handlers when after a new li element is added to the DOM.
I see the following issues in your code.
Not attaching the click handler to existing li elements.
Not removing the click handler for the li or the button when they are being removed ( this can cause memory leaks in the app ).
var button = document.getElementById("enter");
var input = document.getElementById("userinput");
var ul = document.querySelector("ul");
var $body = document.querySelector('body');
// attach event handlers using event delegation.
function removeButtonHandler() {
this.parentElement.remove();
}
$body.addEventListener('click', function(e) {
const $target = e.target;
// if target is not li, do nothing
if ($target.tagName !== 'LI') {
return;
}
var finished = $target.classList.toggle("done");
var removeButton = document.createElement("button");
removeButton.classList.add("deleteButton");
if (finished) {
removeButton.appendChild(document.createTextNode("remove"));
removeButton.classList = "deleteButton";
$target.appendChild(removeButton);
removeButton.addEventListener("click", removeButtonHandler);
} else {
var $liRemoveButton = $target.querySelector('button');
if($liRemoveButton) {
// Also remove the handler for the delete button
$liRemoveButton.removeEventListener("click", removeButtonHandler);
$target.removeChild($liRemoveButton);
}
}
});
function inputLength() {
return input.value.length;
}
function creatListElement() {
var li = document.createElement("li");
li.appendChild(document.createTextNode(input.value));
ul.appendChild(li);
input.value = "";
}
function addListAfterClick() {
if (inputLength() > 0) {
creatListElement();
}
}
function addListAfterKeypress(event) {
if (inputLength() > 0 && event.keyCode === 13) {
creatListElement();
}
}
button.addEventListener("click", addListAfterClick);
input.addEventListener("keypress", addListAfterKeypress);
li {
color: black;
}
h1,
p {
color: black;
}
button {
color: white;
background: #1C3144;
padding: 10px;
border-radius: 3px;
border-style: none;
}
input {
border-radius: 3px;
padding: 10px;
}
.testingIt {
text-decoration-line: line-through;
}
.deleteButton {
background-color: #A31420;
color: #fff;
border-radius: 3px;
margin: 20px;
border-style: none;
}
.done {
text-decoration: line-through #A31420;
}
<body>
<h1>Shopping List</h1>
<p id="first">Get it done today</p>
<input id="userinput" type="text" placeholder="enter items">
<button id="enter">Enter</button>
<ul>
<li>Notebook</li>
<li>Jello</li>
<li>Spinach</li>
<li>Rice</li>
<li>Birthday cake</li>
<li>Candles</li>
</ul>
</body>
That because you only listen click event for only dymanic added li element.
You should add event listenner for hard-code elements also. In example below I show a alert when click to li item
document.querySelectorAll('li').forEach(liItem => {
liItem.addEventListener("click", function() {
alert('click');
});
})
https://codepen.io/1412108/pen/OJbgJEW?editors=1010

I'm trying to get the parent of the parent (div) of the li-element which I click

I'm trying to get the parent of the parent (div) of the li-element which I click and change/remove it's class but I don't know how to tell JS that it should get the specific li class that I click. Sorry for this simple question I'm fairly new to JS.
<!DOCTYPE html>
<html>
<body>
<p>List:</p>
<div class="div">
<ul>
<li class="lis">Coffee</li>
<li class="lis">Tea</li>
<li class="lis">Water</li>
</ul>
</div>
<script>
let li = document.getElementsByClassName("lis")
li.click() = function() {
var x = li.parentElement.parentElement.classList.value
if(x.classList.contains("div")) {
x.remove.classList("div")
}
}
</script>
</body>
</html>
<script>
var elements = document.getElementsByClassName("lis");
var myFunction = function(e) {
x = e.target.innerHTML;
e.target.parentElement.parentElement.innerHTML=x
};
for (var i = 0; i < elements.length; i++) {
elements[i].addEventListener('click', myFunction, false);
}
</script>
You can use .closest() to select the closest div and after that, you can remove the class from that element.
Try below working code -
var elements = document.getElementsByClassName("lis");
var myFunction = function() {
var x = this.closest('.div')
if (x) {
this.closest('.div').classList.remove("div")
console.log('Div Element Class Removed!');
}
};
for (var i = 0; i < elements.length; i++) {
elements[i].addEventListener('click', myFunction, false);
}
<!DOCTYPE html>
<html>
<body>
<p>List:</p>
<div class="div">
<ul>
<li class="lis">Coffee</li>
<li class="lis">Tea</li>
<li class="lis">Water</li>
</ul>
</div>
</body>
</html>
I recommend to delegate to the nearest static container
Also you had remove.classList - that should be classList.remove
Here I give an ID to the UL and test if the LI clicked has the lis class
document.getElementById("ul").addEventListener("click",function(e) {
const tgt = e.target.closest("li")
if (tgt.classList.contains("lis")) {
tgt.closest("div").classList.remove("div")
}
})
.div { background-color:red }
<p>List:</p>
<div class="div">
<ul id="ul">
<li class="lis">Coffee</li>
<li class="lis">Tea</li>
<li class="lis">Water</li>
</ul>
</div>
I came up with another good solution that let's me add and remove the div class, the parent of specified li elements. I added an extra div to make sure it always gets the certain parent that I've specified with .closest(body > div) and used a for loop to make the array that it creates select one certain li element, the one I currently click on.
<!DOCTYPE html>
<html>
<body>
<p>List:</p>
<div class="div">
<ul>
<li class="lis">Coffee</li>
<li class="lis">Tea</li>
<li class="lis">Water</li>
</ul>
</div>
<div>
<ul>
<li class="l">Coffee</li>
<li class="l">Tea</li>
<li class="l">Water</li>
</ul>
</div>
<script>
let lists = document.getElementsByClassName("lis");
let divs = document.getElementsByTagName("div");
for (let list of lists) {
list.addEventListener("click", () => {
list.closest("body > div")
for (let div of divs) {
div.addEventListener("click", () => {
if (div.classList.contains("div")) {
div.classList.remove("div")
} else {
div.classList.add("div")
}
})
}
})
}
</script>
<style>
.div {
color: brown;
font-size: larger;
}
</style>
</body>
</html>
var li = document.getElementsByClassName("lis");
for (var i=0; i<li.length; i++) {
li[i].onclick = function() {
var el = this.parentElement.parentElement;
if (el.className == "div") {
el.className = "";
}
}
}

Tooltipster content doubling up each time it is opened

I'm using Tooltipster to show a list of items that the user can click so as to enter the item into a textarea. When a tooltip is created, I get its list of items with selectors = $("ul.alternates > li");
However, each time a tooltip is opened the item clicked will be inserted a corresponding number of times; for example if I've opened a tooltip 5 times then the item clicked will be inserted 5 times. I've tried deleting the variable's value after a tooltip is closed with functionAfter: function() {selectors = null;} but that had no effect.
I have a Codepen of the error here that should make it clearer.
// set list to be tooltipstered
$(".commands > li").tooltipster({
interactive: true,
theme: "tooltipster-light",
functionInit: function(instance, helper) {
var content = $(helper.origin).find(".tooltip_content").detach();
instance.content(content);
},
functionReady: function() {
selectors = $("ul.alternates > li");
$(selectors).click(function() {
var sampleData = $(this).text();
insertText(sampleData);
});
},
// this doesn't work
functionAfter: function() {
selectors = null;
}
});
// Begin inputting of clicked text into editor
function insertText(data) {
var cm = $(".CodeMirror")[0].CodeMirror;
var doc = cm.getDoc();
var cursor = doc.getCursor(); // gets the line number in the cursor position
var line = doc.getLine(cursor.line); // get the line contents
var pos = {
line: cursor.line
};
if (line.length === 0) {
// check if the line is empty
// add the data
doc.replaceRange(data, pos);
} else {
// add a new line and the data
doc.replaceRange("\n" + data, pos);
}
}
var code = $(".codemirror-area")[0];
var editor = CodeMirror.fromTextArea(code, {
mode: "simplemode",
lineNumbers: true,
theme: "material",
scrollbarStyle: "simple",
extraKeys: { "Ctrl-Space": "autocomplete" }
});
body {
margin: 1em auto;
font-size: 16px;
}
.commands {
display: inline-block;
}
.tooltip {
position: relative;
opacity: 1;
color: inherit;
}
.alternates {
display: inline;
margin: 5px 10px;
padding-left: 0;
}
.tooltipster-content .alternates {
li {
list-style: none;
pointer-events: all;
padding: 15px 0;
cursor: pointer;
color: #333;
border-bottom: 1px solid #d3d3d3;
span {
font-weight: 600;
}
&:last-of-type {
border-bottom: none;
}
}
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.25.2/theme/material.min.css" rel="stylesheet"/>
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/235651/jquery-3.2.1.js"></script>
<script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/235651/tooltipster.bundle.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.25.2/codemirror.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.25.2/addon/mode/simple.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.25.2/addon/hint/show-hint.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.25.2/addon/scroll/simplescrollbars.js"></script>
<div class="container">
<div class="row">
<div class="col-md-6">
<ul class="commands">
<li><span class="command">Hover for my list</span><div class="tooltip_content">
<ul class="alternates">
<li>Lorep item</li>
<li>Ipsum item</li>
<li>Dollar item</li>
</ul>
</li>
</div>
</ul>
</div>
<div class="col-md-6">
<textarea class="codemirror-area"></textarea>
</div>
</div>
</div>
Tooltipster's functionReady fires every time the tooltip is added to the DOM, which means every time a user hovers over the list, you are binding the event again.
Here are two ways to prevent this from happening:
Attach a click handler to anything that exists in the DOM before the tooltip is displayed. (Put it outside of tooltipspter(). No need to use functionReady.)
Example:
$(document).on('click','ul.alternates li', function(){
var sampleText = $(this).text();
insertText(sampleText);
})
Here's a Codepen.
Unbind and bind the event each time functionReady is triggered.
Example:
functionReady: function() {
selectors = $("ul.alternates > li");
$(selectors).off('click').on('click', function() {
var sampleData = $(this).text();
insertText(sampleData);
});
}
Here's a Codpen.
You are binding new clicks every time.
I would suggest different code style but in that format you can just add before the click event
$(selectors).unbind('click');
Then do the click again..

dynamically Adding and removing elements based on checkbox values with DOM

I'm just trying to dynamically add to a div within a form depending on which checkboxes are checked. So, I am creating the li tag and then they are added as li elements within an ol parent element so its just a list of values. I do not know what is wrong with my code, I'm not sure how to remove the appropriate value if the relevant checkbox is unchecked, and when I uncheck and then recheck a checkbox, it keeps adding the value over and over again.
<!DOCTYPE html>
<html>
<head>
<title></title>
<style>
input {
margin: 18px;
}
#o {
list-style-type: none;
}
.u {
list-style: none;
}
</style>
</head>
<body style="width: 700px">
<div style="float: left; width: 340px; height: 250px; border: 1px solid black; padding: 20px 0 10px 20px;">
<form id="myForm">
<ul class="u">
<li><input id="showAlert1" type="checkbox" name="thing" value="laptop">laptop</li>
<li><input id="showAlert2" type="checkbox" name="thing" value="iphone">iphone</li>
</ul>
</form>
</div>
<div id="myDiv" style="float: right; width: 317px; height: 250px; border: solid black; border-width: 1px 1px 1px 0; padding: 20px 0 10px 20px;">
<ol id="o">
</ol>
</div>
<script>
document.getElementById('myForm').addEventListener('change', function () {
var a = document.getElementsByName('thing');
for (var i = 0; i < a.length; i++) {
if (a[i].checked){
createDynamicElement();
} else if (!a[i].checked){
removeDynamicElement();
}
}
function createDynamicElement(){
var node = document.createElement("LI");
node.setAttribute("id1", "Hey");
var textnode = document.createTextNode(event.target.nextSibling.data);
node.appendChild(textnode);
document.getElementById("o").appendChild(node);
}
function removeDynamicElement() {
document.querySelector("#o li").innerHTML = "";
}
});
</script>
</body>
</html>
It looks like that you are adding an event listener to the form instead of the input elements themselves. I dont think the change event will be fired when an input element in a form changes. (see: https://developer.mozilla.org/en-US/docs/Web/Events/change)
On your event listener, try targeting the input elements themselves.
} else if (!a[i].checked){
removeDynamicElement();
}
...
function removeDynamicElement() {
document.querySelector("#o li").innerHTML = "";
}
Will empty the first or all matches(not sure) but wont remove them. Instead you should give li tags a unique ID and remove them completely via something like:
for (var i = 0; i < a.length; i++) {
if (a[i].checked){
console.log(a[i])
createDynamicElement(a[i].value);
} else if (!a[i].checked){
removeDynamicElement(a[i].value);
}
}
function createDynamicElement(id){
var node = document.createElement("LI");
node.setAttribute("id", id);
var textnode = document.createTextNode(id);
node.appendChild(textnode);
console.log(node)
document.getElementById("o").appendChild(node);
}
function removeDynamicElement(id) {
var target = document.getElementById(id)
target.parentElement.removeChild(target);
}
Or you could clear the ol completely on every change and repopulate it again like:
var a = document.getElementsByName('thing');
document.getElementById("o").innerHTML = null;
for (var i = 0; i < a.length; i++) {
if (a[i].checked){
console.log(a[i])
createDynamicElement(a[i].value);
}
}
function createDynamicElement(id){
var node = document.createElement("LI");
var textnode = document.createTextNode(id);
node.appendChild(textnode);
console.log(node)
document.getElementById("o").appendChild(node);
}
Edit:
A proper FIFO solution:
var a = document.getElementsByName('thing');
for (var i = 0; i < 2; i++) {
var target = document.getElementById(a[i].value);
if (a[i].checked && !target){
createDynamicElement(a[i].value);
} else if ((!a[i].checked) && target){
removeDynamicElement(a[i].value);
}
}
function createDynamicElement(id){
var node = document.createElement("li");
node.setAttribute("id", id);
var textnode = document.createTextNode(id);
node.appendChild(textnode);
document.getElementById("o").appendChild(node);
console.log("a")
}
function removeDynamicElement(id) {
target.parentElement.removeChild(target);
}
});

Categories