I am working on a simple drag and drop operation in JS. I have to generate the containers, since I do not know in advance how many I will need, and that seems to be leading to a couple of problems.
The first is that if I drag an item over the last div, the div disappears. I have no idea what is causing it, but it is odd.
The second is that in the drop section
box.addEventListener('drop', function(e) {
e.preventDefault();
var data = e.dataTransfer.getData('id');
e.target.appendChild(document.getElementById(data));
});
I am getting the error message: "Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'," and the 'data' is not being passed. I only get this message on JSFiddle: in both Firefox and Chrome it works fine, but I suspect that it is part of a larger issue.
I am very new at this, so any help would be appreciated.
JSFiddle here.
I think this will work for you.
I've made some changes to your javascript.
Please have a look here:
var productList = [];
for (var w = 0; w < 2; w++) {
productList.push('Apples', 'Plums', 'Rice', 'Potatoes', 'Chicken', 'Pork');
}
productList.sort();
console.log(productList.length);
var boxContainer = document.createDocumentFragment();
for (var i = 0; i < 3; i++) {
var box = boxContainer.appendChild(document.createElement("div"));
var boxID = "box" + i;
box.setAttribute('id', boxID);
box.setAttribute('class', 'dropTarget');
box.addEventListener('dragend', function(e) {
elementDragged = null;
});
box.addEventListener('dragover', function(e) {
if (e.preventDefault) {
e.preventDefault();
};
//This close was right below your Remove Class. Preventing the over class from being added
});
box.addEventListener('dragenter', function(e) {
if(this.className.indexOf('over') < 0)
//Append the className, don't remove it.
this.className += " over";
});
box.addEventListener('dragleave', function(e) {
//Now we remove it.
this.className = this.className.replace(' over','');
e.dataTransfer.dropEffect = 'move'
return false;
});
box.addEventListener('drop', function(e) {
e.preventDefault();
var data = e.dataTransfer.getData('id');
//Just preventing the HierarchyRequestError.
var parent= e.dataTransfer.getData('parent');
if(parent == e.target.id) return;
target=e.target;
//Prevent it from dragging into another div box and force it to go into the box.
if(e.target.id.indexOf('box') < 0 ) target=e.target.parentNode;
target.appendChild(document.getElementById(data));
});
document.drag = function(target, e) {
e.dataTransfer.setData("Text", 'id');
//This is the drag function that's being called. It needed the reference to the ID.
e.dataTransfer.setData('id', target.id);
//Add parentID, so we can check it later.
e.dataTransfer.setData('parent',target.parentNode.id)
}
document.getElementById("placeholder").appendChild(box);
};
for (var a = 0; a < productList.length; a++){
renderProductList(productList[a], a);
};
function renderProductList(element, index) {
console.log(element);
var nameDiv = document.createElement('div');
var itemName = element;
nameDiv.setAttribute('class','dragger');
nameDiv.setAttribute('id', itemName + index);
nameDiv.setAttribute('name', itemName);
nameDiv.setAttribute('draggable', "true");
nameDiv.setAttribute('ondragstart', 'drag(this, event)');
nameDiv.style.backgroundColor = pastelColors();
var aBox = document.getElementById('box0');
aBox.appendChild(nameDiv);
var t = document.createTextNode(element);
console.log("T: " + t);
nameDiv.innerHTML = nameDiv.innerHTML + element;
};
function pastelColors(){
var r = (Math.round(Math.random()* 127) + 127).toString(16);
var g = (Math.round(Math.random()* 127) + 127).toString(16);
var b = (Math.round(Math.random()* 127) + 127).toString(16);
pColor = '#' + r + g + b;
console.log(pColor);
return pColor;
};
function drag(target, e) {
e.dataTransfer.setData('id', target.id);
};
https://jsfiddle.net/3yLk11eb/5/
I've made comments everywhere I made changes. And there were a lot of changes to make this work smoothly.
Related
I want to dynamically create, populate and clear a list with html and javascript. The creation and population of the list work just fine, but when I want to add the delete-button to the list item I can't attach the onclick event to the newly created element. Here is my complete function, it is called every time some changes happen to the printlist array:
var printlist = [];
var awesome = document.createElement("i");
awesome.className = "fa fa-minus";
function addToList(stationid, stationname)
{
var object = {id: stationid, name: stationname};
printlist.push(object);
drawList();
}
function removeFromList(id)
{
printlist.splice(id, 1);
drawList();
}
function drawList()
{
if (printlist.length > 0)
{
document.getElementById("printListDialog").style.visibility = 'visible';
var dlg = document.getElementById("DlgContent");
dlg.innerHTML = "";
for (var i = 0; i < printlist.length; i++)
{
var item = document.createElement("li");
item.className = "list-group-item";
var link = document.createElement("a");
link.href = "#";
link.dataset.listnumber = i;
link.style.color = "red";
link.style.float = "right";
link.appendChild(awesome);
link.onclick = function(){onRemove();};
item.innerHTML = printlist[i].name + " " + link.outerHTML;
dlg.appendChild(item);
}
}
else
{
document.getElementById("printListDialog").style.visibility = 'hidden';
}
}
function onRemove(e)
{
if (!e)
e = window.event;
var sender = e.srcElement || e.target;
removeFromList(sender.dataset.listnumber);
}
I tried:
link.onclick = function(){onRemove();};
as well as
link.addEventListener("click", onRemove);
Neither of those lines successfully adds the event from the script. However when I call any of the 2 lines above from the console it works and the event is attached.
Why does it work from the console but not from the script?
link.onclick = function(){onRemove();};
doesn't work because you're not passing through the event argument. link.onclick = onRemove should work just as your addEventListener call.
However, both of them don't work because of the line
item.innerHTML = printlist[i].name + " " + link.outerHTML;
which destroys the link element with all its dynamic data like .dataset or .onclick, and forms a raw html string that doesn't contain them. They're lost.
Do not use HTML strings!
Replace the line with
item.appendChild(document.createTextNode(printlist[i].name + " "));
item.appendChild(link); // keeps the element with the installed listener
This is a Sudoko generator I'm programming in vanilla javascript:
Fiddle with code
Nicer looking full screen fiddle
If you click on one of the fields, a popup will be shown with 3x3 fields from 1 to 9. The problem is this popup can't be closed anymore, although I'm applying the close dialog.
The code how I'm generating the Sudoku board:
// create sudoku
function tableCreate() {
var body = document.getElementsByClassName("frame")[0];
var containerDiv = body.appendChild(document.createElement('div'))
containerDiv.className = 'container';
// create single cells with numbers
function createInnnerCells(parent, xx, yy) {
for (var x = 1; x <= 3; x++) {
for (var y = 1; y <= 3; y++) {
var abc = function () {
var div = parent.appendChild(document.createElement('div'))
var X = y+yy;
var Y = x+xx;
var id = 'x' + [X] + 'y' + [Y];
var cellValue = sudoku[X][Y]['value'] || '';
div.style.background = sudoku[X][Y]['background'] || 'white'
div.className = 'cell';
div.id = id;
var popover = createDialog(id);
popover.onclick = function() {
popover.close();
};
div.onclick = function() {
popover.show();
};
div.appendChild(popover);
div.appendChild(document.createTextNode(cellValue));
};
abc();
}
}
}
// create big cells for 3x3 single cells
for (var i = 0; i <= 6; i+=3) {
for (var j = 0; j <= 6; j+=3) {
var div = containerDiv.appendChild(document.createElement('div'))
div.className = 'block';
createInnnerCells(div, i, j);
}
}
}
Note that I apply the close() function to each cell:
popover.onclick = function() {
popover.close();
};
The code how I create the popup:
// create dialog
function createDialog(position){
var dialog = document.createElement('dialog');
dialog.id ='window_'+ position;
var dialogblock = dialog.appendChild(document.createElement('div'));
dialogblock.className = 'dialogblock';
for (var z = 1; z <= 9; z++) {
var div = dialogblock.appendChild(document.createElement('div'));
div.className = 'dialogcell';
div.id = position + 'z'+ z;
div.appendChild(document.createTextNode(position));
}
dialog.onclick = function() {
dialog.close();
};
return dialog;
}
I applied the close() dialog here as well
dialog.onclick = function() {
dialog.close();
};
I don't know why show() is working, but close() not?
DOM events bubble up the DOM through its parents. In your code, the dialog is a child of div. Therefore, a click event happens on dialog and then again on div which means you're closing and then opening the dialog.
You can stop the propagation of the event by using event.stopPropagation.
You can change your code like this:
popover.onclick = function(e) {
e.stopPropagation();
popover.close();
};
and
dialog.onclick = function(e) {
e.stopPropagation();
dialog.close();
};
modified your fiddle: http://jsfiddle.net/p40oahkd/9/
There's no method close() on the element you are trying to hide. You should either do element.style.display = "none" if you need to hide. Or do the following:
dialog.addEventListener('click', function() {
this.remove();
});
Check out this edit to your fiddle.
I'm creating a tool which generates a bunch of divs based on data I input into an array, however they all have the same class. The idea is that when one link is clicked it shows one of the ".catbox" divs and hides the rest.
All of these divs have the same class so I need to iterate through them, but I'm not quite sure how this is done with jQuery. Currently clicking on the last ".list" class triggers the on click event instead of all of them, and currently it shows all of the divs with the class ".catbox" instead of the corresponding one.
Here is the code:
var HTMLcatName = '<h1>%data%</h1>';
var HTMLcatImage = '<img id="cat" src="%data%">';
var HTMLcatCounter = '<p class="counter">Number of clicks: %data%</p>';
var HTMLcatList = '<p>%data%</p>'
var noCats = 'No cats selected m8';
var getCounterClass = document.getElementsByClassName("counter");
$(document).ready(function() {
cats.display();
$('.catbox').hide();
for (u = 0; u < cats.name.length; u++) {
formattedCatList = HTMLcatList.replace("%data%", cats.name[u]);
var listDiv = document.createElement('div');
listDiv.innerHTML = formattedCatList;
listDiv.className = "list";
$(".list").click(function() {
$(".catbox").toggle("slow");
});
$("body").prepend(listDiv);
}
});
var update = function() {
for (j = 0; j < getCounterClass.length; j++) {
getCounterClass[j].innerHTML = 'Number of clicks: ' + cats.clicks[j];
}
}
var cats = {
"name": ["Monte", "Jib"],
"image": ["images/monte.jpg", "images/jib.jpg"],
"clicks": [0, 0],
display: function () {
for (i = 0; i < cats.image.length; i++) {
formattedCatNames = HTMLcatName.replace("%data%", cats.name[i]);
formattedCatImages = HTMLcatImage.replace("%data%", cats.image[i]);
formattedCatCounter = HTMLcatCounter.replace("%data%", cats.clicks[i]);
var catDiv = document.createElement('div');
catDiv.className = "catbox";
catDiv.innerHTML = formattedCatNames + formattedCatImages + formattedCatCounter;
catDiv.querySelector('img').addEventListener('click', (function(catCountUp) {
return function() {
cats.clicks[catCountUp]++;
update();
};
})(i));
document.body.appendChild(catDiv);
}
},
}
The function I need help with is found within $(document).ready(function() {
Any help would be greatly appreciated.
The following can do it:
$(".list").on("click", function(){
$(this).find(".catbox").toggle("slow");
});
With $('.list') you get a group of elements of class list, so if you use $('.list').click(); you will bind the click event to just one element. You should use:
$(".list").each(function(){
$(this).click(function() {
$(".catbox").toggle("slow");
});
});
I'm activating a javascript function with a Jquery onclick button:
$('#on').click(function() {
var a = document.getElementsByTagName('a');
for (i = 0; i < a.length; i++) {
a[i].addEventListener('click', function() {
var span = document.createElement('span');
var text = document.createTextNode(this.innerHTML + " ");
span.appendChild(text);
document.getElementsByClassName('output')[0].appendChild(span);
})
}
});
The problem is if the button is clicked more than once the function will repeat more than once. In this case it will print the output multiple times. How can I modify the javascript function to only print one character per click?
Example:
http://jsfiddle.net/874Ljaq1/
Use the jQuery event binding method one
$('#on').one("click", function() {
var a = document.getElementsByTagName('a');
for (i = 0; i < a.length; i++) {
a[i].addEventListener('click', function() {
var span = document.createElement('span');
var text = document.createTextNode(this.innerHTML + " ");
span.appendChild(text);
document.getElementsByClassName('output')[0].appendChild(span);
})
}
});
You can use the jQuery .data() function to set a flag when the button has been clicked once, and only proceed if the flag is not set.
The code:
$('#on').click(function () {
// if we have a flag that indicates this button has been clicked before,
// don't do anything.
if ($(this).data('clicked'))
return;
$(this).data('clicked', true); // set the flag
var a = document.getElementsByTagName('a');
for (i = 0; i < a.length; i++) {
a[i].addEventListener('click', function () {
var span = document.createElement('span');
var text = document.createTextNode(this.innerHTML + " ");
span.appendChild(text);
document.getElementsByClassName('output')[0].appendChild(span);
})
}
});
i have a div with multiple images inside and i need to click on a random image then again click on a random picture and when i clicked the second image to change images with each other. All images are interchangeable.Heres what i've done so far:
EDIT FIDDLE: http://jsfiddle.net/w53Ls/5/
$("#image1").click(function(){
imagePath = $("#image2").attr("src");
if(imagePath == "http://s15.postimg.org/oznwrj0az/image.jpg"){
$("#image3").attr("src", "http://s21.postimg.org/ojn1m2eev/image.jpg");
}else{
$("#image4").attr("src", "http://s23.postimg.org/epckxn8uz/image.jpg");
}
});
EDIT: The code i have tryed for check function is in EDIT FIDDLE and with the alert i check src of pictures.Now i simply need to make a condition to alert something after i change all the pieces in order and found the whole picture.Any hint?
DEMO
var clickCount = 0;
var imgSrc;
var lastImgId;
$("img.element").click(function(){
if (clickCount == 0)
{
imgSrc = $(this).attr("src");
lastImgId = $(this).attr("id");
clickCount++;
}
else {
$("#"+lastImgId).attr("src",$(this).attr("src"));
$(this).attr("src",imgSrc)
clickCount = 0;
}
});
Updated
This let's you know when you're done with the puzzle
DEMO
var clickCount = 0;
var imgSrc;
var lastImgId;
// Function for Comparing Arrays
// source: http://stackoverflow.com/questions/7837456/
Array.prototype.compare = function (array) {
if (!array) return false;
if (this.length != array.length) return false;
for (var i = 0, l = this.length; i < l; i++) {
if (this[i] instanceof Array && array[i] instanceof Array) {
if (!this[i].compare(array[i])) return false;
} else if (this[i] != array[i]) {
return false;
}
}
return true;
}
$(document).ready(function () {
// Store the correct order first in an array.
var correctOrder = $("#puzzle > img").map(function () {
return $(this).attr("src");
}).get();
// Randomize your images
var a = $("#puzzle > img").remove().toArray();
for (var i = a.length - 1; i >= 1; i--) {
var j = Math.floor(Math.random() * (i + 1));
var bi = a[i];
var bj = a[j];
a[i] = bj;
a[j] = bi;
}
$("#puzzle").append(a);
$("img.element").click(function () {
if (clickCount == 0) {
imgSrc = $(this).attr("src");
lastImgId = $(this).attr("id");
clickCount++;
} else {
$("#" + lastImgId).attr("src", $(this).attr("src"));
$(this).attr("src", imgSrc);
clickCount = 0;
// Get the current order of the images
var currentOrder = $("#puzzle > img").map(function () {
return $(this).attr("src");
}).get();
// Compare the current order with the correct order
if (currentOrder.compare(correctOrder)) alert("Puzzle completed");
}
});
});
http://jsfiddle.net/w53Ls/2/
var counter = 0;
The code was improvised but works XD
you try improve it
Here is a new version of your jsfiddle that I think will do what you want.
It applies the same click handler to every object with the class swapable. Each time a swapable element is clicked, the handler checks whether another element was previously clicked first. If so, it swaps them. If not, it just remembers that this element is the first one.
var firstId = ''; // Initially, no element has been clicked first
var firstSrc = '';
// Apply the following function to every element with 'class="swapable"
$('.swapable').click(function(){
if (firstId !== '') { // There is already a first element clicked
// Remember the information of the currently clicked element
var secondId = $(this).attr('id');
var secondSrc = $(this).attr('src');
// Replace the currently clicked element with the first one
$('#' + secondId).attr('src', firstSrc);
// Replace the first one with the current one
$('#' + firstId).attr('src', secondSrc);
// Forget the first one, so that the next click will produce a new first
firstId = '';
firstSrc = '';
}
else // This is the first element clicked (this sequence)
{
// Remember this information for when a second is clicked
firstId = $(this).attr('id');
firstSrc = $(this).attr('src');
}
});