I have made this
https://jsfiddle.net/a4376mr8/
When I drag and drop the image div to a new div, why is it not there in the previous div?
const boxes = document.querySelectorAll('.box');
const imageBox = document.querySelector('#draggableItem');
for (const box of boxes) {
box.addEventListener('dragover', function (e) {
e.preventDefault();
this.className += ' onhover';
})
box.addEventListener('dragleave', function () {
this.className = 'box';
})
box.addEventListener('drop', function (e) {
this.className = 'box';
this.append(imageBox);
})
}
If I understood your needs, that the image be repeated on drag and drop, you need to clone your div tag dom object. Since js just sees the reference to it, when you simply append it, this causes it to just move from place to place instead of duplicating.
So instead of just appending, clone the node as follows (line 15 of your fiddle's js).
this.append(imageBox.cloneNode(true));
See here: https://developer.mozilla.org/en-US/docs/Web/API/Node/cloneNode
Updated fiddle: https://jsfiddle.net/a4376mr8/1/
Related
I have created a custom datepicker that opens when a date input is focused on. The content inside that datepicker is created dynamically based on the current date. I want to hide it if someone clicks outside the div, but when I try to determine if the click is inside or outside by using .closest(".date-picker") I get null. Upon further inspection my date picker doesn't return any ancestors at all, not even parentNode. Any idea why this would be?
Here is my click event listener to hide the date picker and where I am getting the null for parentNode.
window.addEventListener("DOMContentLoaded", function () {
_initDatePicker(); // <--- dynamically adding elements here
document.addEventListener("click",
function (event) {
var classes = event.target.classList; // <---- this works just fine
var parent = event.target.parentNode; // <---- returns null
// If user clicks outside the datepicker window, then close
if (!event.target.closest(".date-picker")) {
hideDatePicker();
}
},
false
);
}, false);
Here is my code where I dynamically create the content inside the div and add it to the dom
title_element.textContent = "Select Year";
cal_elements.innerHTML = ''; // Clear previous content
if (!cal_elements.classList.contains('year_list'))
cal_elements.classList.add('year_list');
for (let i = 0; i < amount_years; i++) {
let create_year = year - i;
const element = document.createElement('div'); // Create the new div
element.classList.add('cal_element');
if (create_year >= min_year) {
element.classList.add('text-light');
element.classList.add('pointer');
element.textContent = create_year;
element.addEventListener('click', function (e) {
selectedYear = this.innerText;
selected_date_element.textContent = selectedYear;
if (date_picker_element.classList.contains('years')) {
date_picker_element.classList.remove('years');
date_picker_element.classList.add('months');
}
populateDates();
});
}
cal_elements.appendChild(element); // Add div to the DOM
}
When clicking on the datepicker, I can get the ClassList of the element that I click on, but I can't get the ParentNode even though it has been added to the DOM
I'm working on a JavaScript project where a user can click a button to create a text element. However, I also want a feature where I can click a different button and the element that was created most recently will be removed, so In other words, I want to be able to click a button to create an element and click a different button to undo that action.
The problem I was having was that I created the element, then I would remove the element using:
element.parentNode.removeChild(element); , but it would clear all of the elements that were created under the same variable.
var elem = document.createElement("div");
elem.innerText = "Text";
document.body.appendChild(elem);
This code allows an element to be created with a button click. All elemente that would be created are under the "elem" variable. so when I remove the element "elem", all element are cleared.
Is there a simple way to remove on element at a time that were all created procedurally?
Thanks for any help
When you create the elements, give the a class. When you want to remove an element, just get the last element by the className and remove it.
The below snippet demonstrates it -
for(let i = 0; i<5; i++){
var elem = document.createElement("div");
elem.innerText = "Text " + i;
elem.className = "added";
document.body.appendChild(elem);
}
setTimeout(function(){
var allDivs = document.getElementsByClassName("added");
var lastDiv = allDivs.length-1;
document.body.removeChild(allDivs[lastDiv]);
}, 3000);
I would probably use querySelectors to grab the last element:
// optional
// this is not needed it's just a random string added as
// content so we can see that the last one is removed
function uid() {
return Math.random().toString(36).slice(2);
}
document.querySelector('#add')
.addEventListener('click', (e) => {
const elem = document.createElement('div');
elem.textContent = `Text #${uid()}`;
document.querySelector('#container').appendChild(elem);
// optional - if there are elements to remove,
// enable the undo button
document.querySelector('#undo').removeAttribute('disabled');
});
document.querySelector('#undo')
.addEventListener('click', (e) => {
// grab the last child and remove
document.querySelector('#container > div:last-child').remove();
// optional - if there are no more divs we disable the undo button
if (document.querySelectorAll('#container > div').length === 0) {
document.querySelector('#undo').setAttribute('disabled', '');
}
});
<button id="add">Add</button>
<button id="undo" disabled>Undo</button>
<div id="container"></div>
I've got a fixed height div with a list of clickable list items. In the middle of the div, I have an absolute positioned line that is meant to signify a selected item. Right now, it's just a static line.
Is there a way to add an active class to the list item as it is "selected" by the line?
http://dev.chrislamdesign.com/shortwave/
One of the solutions is to use document.elementFromPoint(x, y) method. Something like this:
let lineCoords, lineTop, lineCenter;
// try to remove these two lines, leave just the scroll event listener
// document.getElementById('scrollUp1').addEventListener('click', setActive);
// document.getElementById('scrollDown1').addEventListener('click', setActive);
// 2nd edition: added these event listeners
window.addEventListener('scroll', getLineCoords);
window.addEventListener('load', getLineCoords);
// added this line
document.getElementById('wrap-scroll-1').addEventListener('scroll', setActive);
function setActive() {
const li = document.elementFromPoint(lineCenter, lineTop + lineCoords.height);
clearActive();
li.classList.add('active');
}
function clearActive() {
const ul = document.getElementById('ul-scroll-1');
const activeLi = ul.querySelector('li.active');
if (activeLi) {
activeLi.classList.remove('active');
}
}
// 2nd edition: added this function
function getLineCoords() {
lineCoords = document.querySelector('.orange-line').getBoundingClientRect();
lineTop = lineCoords.top;
lineCenter = lineCoords.left + (lineCoords.width / 2);
}
You can see this in action here: JsBin. These up and down buttons are assumed to scroll the list, but I don't have this functionality, because that's not a point here - just scroll it youself. The point here is that the element under the orange line will get active class each time you click one of these buttons.
So, take this code and edit it as you want.
Edited: I added an scroll event listener to the #wrap-scroll-1 container, because I guess the scroll event occurs right on it. If not - you can change it. Look at this in action: JsBin
2nd edition: Added event listeners to reassign the orange line coordinates every time when the page scrolled, and also when the page is loaded. Take a look at the result here: JsBin
You could compare the rects of the line and each option to find which is selected:
const line = document.querySelector('#emotional .orange-line');
const options = document.querySelector('#emotional .selection-options').children;
const lineY = line.getBoundingClientRect().y;
const optionAfterSelected = [...options].find((option) => {
return option.getBoundingClientRect().y > lineY;
});
const selected = optionAfterSelected.previousSibling;
selected.classList.add('selected');
The selected option is the one with the largest y value without exceeding the y value of the orange line. To make things simple, the loop just returns the first option with a y value greater than the line, then grabs its previous sibling.
Update
To get this code to run whenever the user scrolls, you can wrap it in a function and attach it as an eventListener:
function updateSelection(menuId) {
const line = document.querySelector(menuId + ' .orange-line');
const options = document.querySelector(menuId + ' .selection-options').children;
const lineY = line.getBoundingClientRect().y;
const optionAfterSelected = [...options].find((option) => {
return option.getBoundingClientRect().y > lineY;
});
const selected = optionAfterSelected.previousSibling;
selected.classList.add('selected');
}
document.querySelector('#emotional .wrap-container').addEventListener('wheel', () => {
updateSelection('#emotional');
});
document.querySelector('#genre .wrap-container').addEventListener('wheel', () => {
updateSelection('#genre');
});
document.querySelector('#cinematic .wrap-container').addEventListener('wheel', () => {
updateSelection('#cinematic');
});
I want to be able to select cloneable draggable objects (red circles) by left-clicking and then delete them by clicking on the button Delete selected circle. I want to enable deleting only those objects that are located INSIDE .dropzone.
Here is my JSFIDDLE.
The .dropzone is a grey-colored div, and a draggable object is a red circle.
This is the code for deleting an object:
function removeObject(div) {
div.querySelector(".draggable").parentNode.removeChild(elem);
return false;
}
I pass the class of a cloned object, however, it gets undetected and therefore I cannot delete it. Also I don't know how to limit the deleting operation to only those objects that are inside .dropzone.
Check this out, add a new class to each cloned object so you can tell the difference and pass a class that will be specific to that circle to the remove function. Using this, you could also add a class to the object when dropped, so that way you know if it is in the box and remove when it is dropped outside of the box.
addClass append every time in every Clone
EDIT:
Here, this is the relevant changes I made to your fiddle, I only pasted the portions I changes. I commented out some of my code that was unnecessary, but I left it in because it might be of use to you. Feel free to delete though. Hope this is what you were looking for.
I made it so that the clones get a class of cloned and if they are dropped in the box area, they get a selected class added on. Then if another circle is touched, the selected class is removed and only gets added on if the last circle touched was moved or placed in the box, or in the box. If the circle is removed from the box, it loses the selected class.
.on('move', function(event) {
var interaction = event.interaction;
if (interaction.pointerIsDown && !interaction.interacting() && event.currentTarget.getAttribute('clonable') != 'false') {
var original = event.currentTarget;
var clone = event.currentTarget.cloneNode(true);
var x = clone.offsetLeft;
var y = clone.offsetTop;
/* var length = $(".cloned").length;
if(length == "0"){
clone.className = "draggable cloned cloned-0";
}
else{
clone.className = "draggable cloned cloned-"+length;
} */
clone.className = "draggable cloned";
clone.setAttribute('clonable', 'false');
clone.style.position = "absolute";
clone.style.left = original.offsetLeft + "px";
clone.style.top = original.offsetTop + "px";
original.parentElement.appendChild(clone);
interaction.start({
name: 'drag'
}, event.interactable, clone);
}
})
// enable draggables to be dropped into this
interact('.dropzone').dropzone({
// Require a 50% element overlap for a drop to be possible
overlap: 0.50,
// listen for drop related events:
ondropactivate: function(event) {
// add active dropzone feedback
event.target.classList.add('drop-active');
$('.cloned').removeClass('selected');
},
ondragenter: function(event) {
var draggableElement = event.relatedTarget,
dropzoneElement = event.target;
// feedback the possibility of a drop
dropzoneElement.classList.add('drop-target');
// draggableElement.classList.add('in-zone');
},
ondragleave: function(event) {
// remove the drop feedback style
// event.relatedTarget.classList.remove('in-zone');
event.target.classList.remove('drop-target');
},
ondrop: function(event) {
event.relatedTarget.classList.add('selected');
},
ondropdeactivate: function(event) {
// remove active dropzone feedback
event.target.classList.remove('drop-active');
event.target.classList.remove('drop-target');
}
});
So right now, I can dynamically create elements (2 rows of 12 blocks) and when I click on an individual block, I can change the color of it as well.
However, I am having one problem. When I click on a block to have its color changed, the color picker will pop up beside it, no issues at all. When I add a new set of rows and try to color the same block number, it will replace the color of the block from the previous row.
For example, if I color the 12th block in the first row, then add 2 new sets of rows and click on the same block in the second set, it will act as if I'm clicking on the previous set's block. I am using https://bgrins.github.io/spectrum/ as my colorPicker
Here is a link to what I have done so far:
http://codepen.io/anon/pen/bwBRmw
var id_num = 1;
var picker = null;
$(function () {
$(document).on('click', ".repeat", function (e) {
e.preventDefault();
var $self = $(this);
var $parent = $self.parent();
if($self.hasClass("add-bottom")){
$parent.after($parent.clone(true).attr("id", "repeatable" + id_num));
id_num = id_num + 1;
//picker = null;
} else {
$parent.before($parent.clone(true).attr("id", "repeatable" + id_num));
id_num = id_num + 1;
//picker = null;
}
});
});
$(".container").on("click", "a", function(e) {
var self = this;
console.log(this.id)
console.log(this)
$(self).spectrum({
color: "#f00",
change: function(color) {
$(self).css('background-color',color.toHexString());
}
});
e.stopPropagation();
})
The problem seems to be that you are cloning elements which already have the colorpicker events bound.
EDIT: I think I've managed to work around the problem by changing your use of jQuery's clone(). If you tell it to clone without including events (omitting the first parameter to clone() which defaults to false, the DOM objects will be created without the colorpicker pointing at the old ones.
Here's an example that I think is doing what you are looking for. I've just removed the true params for clone(). No changes to HTML or CSS.