Drag and drop returns 'null' on item created by DOM - javascript

I'm currently working on a trello-like app, and have run into some issues with drag and drop events, and DOM. I basically have four lists, each containing a div with id "card-container". I'm able to move cards created in the source code as expected, however, when I add a card through DOM, and try to move it to another container, the container appends a child "null". Not sure what I am doing wrong.
When adding a new card, the function makeCard() is run by submitting a button.
draganddorp.js
const card = document.querySelector('.task-card');
const cards = document.querySelectorAll('.task-card')
const cardContainers = document.querySelectorAll('.card-container');
var draggingCard = null;
// card listeners
cards.forEach(addCardListeners);
// Loop through taskContainer boxes and add listeners
cardContainers.forEach(addContainerListeners);
// Drag Functions
function dragStart(event) {
this.className += ' card-hold';
setTimeout(() => (this.className = 'invisible'), 0); //set timeout so card wont dissapear
draggingCard = event.target;
}
function dragEnd() {
this.className = 'task-card';
draggingCard = null;
}
function dragOver(e) {
e.preventDefault();
}
function dragEnter(e) {
e.preventDefault();
this.className += ' card-container-hover';
}
function dragLeave() {
this.className = 'card-container';
}
function dragDrop() {
this.className = 'card-container';
this.append(draggingCard);
}
function addCardListeners(card) {
card.addEventListener('dragstart', dragStart);
card.addEventListener('dragend', dragEnd);
}
function addContainerListeners(cardContainer) {
cardContainer.addEventListener('dragover', dragOver);
cardContainer.addEventListener('dragenter', dragEnter);
cardContainer.addEventListener('dragleave', dragLeave);
cardContainer.addEventListener('drop', dragDrop);
}
makecard.js
function makeCard(destination) {
//defining all variables needed for creating a card
let getCardContainer = document.getElementById(destination);
let createTaskCard = document.createElement("div");
//varibles needed for task header
let createTaskHeader = document.createElement("div");
let createTags = document.createElement("div");
let createTag = document.createElement("span");
let createActionsBtn = document.createElement("div");
//varibles needed for task body
let createTaskBody = document.createElement("div");
let createTaskTitle = document.createElement("p");
//varibles needed for task footer
let createTaskFooter = document.createElement("div");
let createAsignee = document.createElement("div");
let createAsigneeIcon = document.createElement("span");
let createAsigneeMember = document.createElement("span");
let createDueDate = document.createElement("div");
let createDueDateDate = document.createElement("span");
let createDueDateIcon = document.createElement("span");
//creating card
createTaskCard.className = "task-card";
createTaskCard.setAttribute("draggable", true);
//addding class/id and HTML to task header
createTaskHeader.className = "task-card-header";
createTags.className = "tags";
createTag.className = "tag";
createTag.id = "tag-";
createTag.innerHTML = "someTags"
createActionsBtn.className = "actions";
//add action itself (svg)
//addding class/id and HTML to task body
createTaskBody.className = "task-card-body";
createTaskTitle.innerHTML = "someTitle"
//addding class/id and HTML to task footer
createTaskFooter.className = "task-card-footer";
createAsignee.className = "asignee";
createAsigneeIcon.className = "icon";
createAsigneeIcon.innerHTML = "I";
createAsigneeMember.innerHTML = "Assignee name";
createDueDate.className = "dueDate";
createDueDateDate.innerHTML = "someDate"
createDueDateIcon.className = "icon";
createDueDateIcon.innerHTML = "I";
//setting up structure
createTaskHeader.appendChild(createTags);
createTaskCard.appendChild(createTaskHeader);
createTags.appendChild(createTag);
createTaskHeader.appendChild(createActionsBtn);
createTaskCard.appendChild(createTaskBody);
createTaskBody.appendChild(createTaskTitle);
createTaskCard.appendChild(createTaskFooter);
createTaskFooter.appendChild(createAsignee);
createAsignee.appendChild(createAsigneeIcon);
createTaskFooter.appendChild(createDueDate);
createAsignee.appendChild(createAsigneeMember)
createDueDate.appendChild(createDueDateDate);
createDueDate.appendChild(createDueDateIcon);
//appending card to card container
getCardContainer.appendChild(createTaskCard);
}
html
<div class="task-card" draggable="true">
<div class="task-card-header">
<div class="tags">
<span class="tag">Priority</span>
<span class="tag">Design</span>
</div>
<div class="actions">
<a href="#">
<!--icon-->
</a>
</div>
</div>
<div class="task-card-body">
<p>Test</p>
</div>
<div class="task-card-footer">
<div class="asignee">
<span class="icon">
<!--icon-->
</span>
<span>Daniel Kjellid</span>
</div>
<div class="dueDate">
<span>23.05</span>
<span class="icon">
<!--icon-->
</span>
</div>
</div>
</div>
css
.card-container {
background: white;
height: auto;
margin: 2px;
min-height: 150px;
width: 115%;
}
.card-container-hover {
border: dashed 3px #F364A2 !important;
}
.card-dragging {
display: absolute;
}
.card-hold {
border: solid 5px #ccc;
}
.task-card {
border-radius: 8px;
box-shadow: 0 0 4px 0 rgba(0,0,0,0.50);
color: #3E4C59;
height: auto;
margin-bottom: 13px;
width: 100%;
}

It seems to me when you make a new card in the makeCard(destination) function, you do not add the dragstart snd dragend listeners to it. As such draggingCard is still null because it has not been set (which happens in the dragStart event listener).
Try adding this to the end of your makeCard function.
addCardListeners(createTaskCard);
Ofcourse, it is a bit hard to help without a working example.

Related

one element to change color while hovering another one

This is what I tried so far. As they are in different divs and in no parent, sibling or child relation with each other, by my understanding CSS is no valid option.
<script>
var circleOne = document.querySelector('.first_circle');
var adressOne = document.querySelector('.first_adress');
if (circleOne.matches(':hover')) {
adressOne.style.color='green';
} else {
adressOne.style.color='black';
}
</script>
You can use a combination of onmouseenter and onmouseleave
var circleOne = document.querySelector('.first_circle');
var adressOne = document.querySelector('.first_adress');
circleOne.onmouseenter = () => adressOne.style.color='green';
circleOne.onmouseleave = () => adressOne.style.color='black';
<div class="first_circle">First Circle</div>
<div class="first_adress">First Address</div>
You should use mouseenter, mouseleave event listeners for this task
const firstEl = document.getElementById('first');
const secondEl = document.getElementById('second');
firstEl.addEventListener('mouseenter', () => secondEl.style.background = 'black');
firstEl.addEventListener('mouseleave', () => secondEl.style.background = 'transparent');
secondEl.addEventListener('mouseenter', () => firstEl.style.background = 'blue');
secondEl.addEventListener('mouseleave', () => firstEl.style.background = 'transparent');
#first, #second {
display: inline-block;
width: 100px;
height: 100px;
border: 1px solid #e0e0e0;
}
<div id="first"></div>
<div id="second"></div>

How to create element onclick which removes the element's parent node?

I'm trying to make a box copier that creates boxes which each contain a button to delete itself. Each box is a duplicate of a hidden template box, and each has an id starting at box1:
This is what I have so far:
let boxcount = 0;
function removebox() {
this.parentNode.remove();
}
function addbox() {
var container = document.getElementById("container"),
box = document.getElementById("boxoriginal");
var boxcopy = box.cloneNode(true);
boxcount += 1;
boxcopy.id = "box" + boxcount;
container.appendChild(boxcopy);
var remover = document.createElement("DIV");
remover.innerHTML = "x";
remover.onclick = removebox;
document.getElementById(boxid).appendChild(remover);
}
The problem is that if I click on the X in box1 for instance, it removes the last box just added, rather than box1. I've tried something similar using EventListener but with the same result.
I'm brand new to JS, so I can only guess that I'm misunderstanding how this works.
Maybe not a perfect solution for what you are trying to achieve, but you might want to create a structure more like:
let doc, bod, M, I, SimpleBoxMaker; // for use on other loads
addEventListener('load', ()=>{
doc = document; bod = doc.body; M = tag=>doc.createElement(tag); I = id=>doc.getElementById(id);
SimpleBoxMaker = function(appendTo = bod){
this.container = M('div');
this.addBox = contentNode=>{
const container = this.container, box = M('div'), x_div = M('div');
box.className = 'box_div'; x_div.className = 'x_div'; x_div.innerHTML = '×';
box.appendChild(x_div);
if(contentNode)box.appendChild(contentNode);
x_div.onclick = ()=>{
box.remove();
}
container.appendChild(box);
return this;
}
appendTo.appendChild(this.container);
}
// below code can be put on a separate page using a `load` Event (besides // end load line)
const bigBox = new SimpleBoxMaker, addBox = I('add_box');
addBox.onclick = ()=>{
const div = M('div');
div.textContent = 'Before adding this node there were '+bigBox.container.children.length+' children in the container';
bigBox.addBox(div);
}
}); // end load
*{
box-sizing:border-box:
}
.box_div{
min-height:30px; border:1px solid #000; margin-top:2px;
}
.x_div{
cursor:pointer; display:flex; justify-content:center; align-items:center; width:30px; height:30px; background:#900; color:#fff; font:bold 24px san-serif; text-align:center; float:right;
}
<button id='add_box'>Add Box</div>

JavaScript - Background switch between green and white with button

I wanted to ask how I do it, when you click the button, the background turns green, it goes back again with the same button, so that it then becomes white again.
spanTwo.addEventListener('click', () =>{
liItem.style.background = 'rgb(24, 189,24';
liItem.style.color = 'white';
Solution 1:
Add onlick event to the button, and check, if the target.backgroundColor = white? Then change to green. Else? change to white. This will toggle between the two values.
var btn = document.getElementById('myBtn');
var target = document.body;
btn.addEventListener('click', () => {
target.style.backgroundColor == 'green' ? target.style.backgroundColor = 'white' : target.style.backgroundColor = 'green';
})
<button id="myBtn">Change</button>
Solution 2:
Toggle between a class and a manually added property: value to override it.
var btn = document.getElementById('myBtn');
var target = document.body;
btn.addEventListener('click', () => {
target.classList.toggle('bg_green')
})
body {
background-color: white;
}
.bg_green {
background-color: green;
}
<button id="myBtn">Change</button>
Solution 3:
Toggle two classes, one for each value (color).
var btn = document.getElementById('myBtn');
var target = document.body;
btn.addEventListener('click', () => {
target.className == 'bg_green' ? target.className = 'bg_white' : target.className = 'bg_green';
})
.bg_green {
background-color: green;
}
.bg_white {
background-color: white;
}
<button id="myBtn">Change</button>
Extra (Longer) Solution:
Check if target contains one of these class names (green in the example), if yes remove it and add the other (white). And it'll repeat it self (toggle).
var btn = document.getElementById('myBtn');
var target = document.body;
btn.addEventListener('click', () => {
if(target.classList.contains('bg_green')){
target.classList.add('bg_white');
target.classList.remove('bg_green');
} else {
target.classList.add('bg_green');
target.classList.remove('bg_white');
}
})
.bg_green {
background-color: green;
}
.bg_white {
background-color: white;
}
<button id="myBtn">Change</button>
Note:
The JavaScript classList property is recommended over className, since it allows us to add, delete and toggle a Class List, in case the target contains more than 1 class name.
MDN - Element.classList:
The Element.classList is a read-only property that
returns a live DOMTokenList collection of the class attributes of the
element. This can then be used to manipulate the class list.
More details:
MDN - Element.classList
MDN - Element.className
Try this:
function myFunction() {
var element = document.getElementById("myDIV");
element.classList.toggle("mystyle");
}
.mystyle {
width: 100%;
padding: 25px;
background-color: rgb(24, 189,24);
color: white;
font-size: 25px;
box-sizing: border-box;
}
<button onclick="myFunction()">Click</button>
<div id="myDIV">
This is a DIV element.
</div>
There's too many ways to do that you can try one of those
Check the element background and apply your style
spanTwo.addEventListener('click', () =>{
if(getComputedStyle(liItem, null).backgroundColor !== "rgb(24, 189, 24)") {
liItem.style.backgroundColor = 'rgb(24, 189, 24)';
liItem.style.color = 'white';
} else {
liItem.style.backgroundColor = 'rgb(0, 0, 0)';
liItem.style.color = 'white';
}
})
Toggle class
.liItem {
background: #000;
color: #fff;
}
.green {
background: rgb(24, 189, 24);
color: #fff;
}
spanTwo.addEventListener('click', () =>{
liItem.classList.toggle("green")
})

Why do I have to click twice to get links in search results menu to load page?

QUESTION:
Why do I have to click twice to get links in search results menu to load page?
See here:
Type in Staff, or Blog in the filter field. You have to click on each link twice to get the page to load?
https://startech-enterprises.github.io/docs/data-integration-and-etl/branches-and-loops-local.html
I'm trying to get to this behaviour (i/e just one click):
https://learn.microsoft.com/en-us/dotnet/csharp/linq/
NOTE
The code in the link above has now been updated, based on the answers given below
CODE
JS I'm using
/**
* Search Filter
*/
"use strict";
(function searchFilter() {
eventListeners();
// Add Event Listerns
function eventListeners(){
document.getElementById('searchFilter').addEventListener('keyup', searchQuery);
document.getElementById('searchFilter').addEventListener('focusout', searchQuery);
document.getElementById('searchFilter').addEventListener('focusin', searchQuery);
};
function searchQuery(){
// Declare variables
let input, filter, ul_toc, li_toc, ul_suggestions, li_suggestion, a1, a2, a3, i, j, k, txtValue, txtValue2, txtValue3, link;
input = document.getElementById('searchFilter');
filter = input.value.toUpperCase();
ul_toc = document.getElementsByClassName("toc")[0];
li_toc = ul_toc.getElementsByClassName("none");
ul_suggestions = document.getElementsByClassName("searchFilter-suggestions")[0];
// Check whether input is empty. If so hide UL Element
if (filter === "") {
ul_suggestions.classList.add("is-hidden")
};
// Check whether input is not empty. If so show UL Element
if (filter !== "") {
ul_suggestions.classList.remove("is-hidden")
};
// Check whether input is not active. If so hide UL Element
if (input !== document.activeElement) {
setTimeout(function(){
ul_suggestions.classList.add("is-hidden");
}, 2000);
};
// Check whether input is active. If so show UL Element
if (input === document.activeElement) {
ul_suggestions.classList.remove("is-hidden")
};
// Keep emptying UL on each keyup event, or when input element is not active
ul_suggestions.innerHTML = "";
let df = new DocumentFragment();
// Run search query so long as filter is not an empty string
if(filter !== ""){
// Loop through all list items, and update document fragment for those that match the search query
for (i = 0; i < li_toc.length; i++) {
a1 = li_toc[i].getElementsByTagName("a")[0];
txtValue = a1.textContent || a1.innerText;
if (txtValue.toUpperCase().indexOf(filter) > -1) {
// Start creating internal HTML
li_suggestion = document.createElement('li');
li_suggestion.classList.add("searchFilter-suggestion");
// Parent span element
let span = document.createElement("SPAN");
span.className = ("is-block is-size-7 has-padding-left-small has-padding-right-small");
link = document.createElement('a');
link.href = a1.href;
span.appendChild(link);
// Child 1 span element
let span2 = document.createElement("SPAN");
span2.className = ("is-block has-overflow-ellipsis-tablet");
span2.textContent = txtValue;
// Child 2 span element
let span3 = document.createElement("SPAN");
span3.className = ("is-block has-text-subtle has-overflow-ellipsis is-size-8 has-line-height-reset has-padding-bottom-extra-small");
j = 0;
let immediateParent = li_toc[i].parentElement;
let correctParent = li_toc[i].parentElement;
// Get top most level of branch --> Set as Node 1
while(true){
if (immediateParent.parentElement.classList.contains('toc')) break;
immediateParent = immediateParent.parentElement;
j++;
};
if (j == 0){
a2 = li_toc[i].getElementsByTagName("a")[0];
}
else {
k = 0;
for ( k = 0; k < j - 1; k++) {
correctParent = correctParent.parentElement;
};
a2 = previousByClass(correctParent, "treeitem");
a2 = child_by_selector(a2, "tree-expander")
}
txtValue2 = a2.textContent || a2.innerText;
txtValue2 = document.createTextNode(txtValue2);
// Insert Chevron Right --> Set as Node 2
let span4 = document.createElement("SPAN");
span4.className = ("has-padding-right-extra-small has-padding-left-extra-small");
span4.innerHTML = '&nbsp&#9002&nbsp';
span4.setAttribute("style", "font-size: 0.70rem; font-weight: bold");
// Get second-top most level of branch --> Set as Node 3
correctParent = li_toc[i].parentElement;
switch (j) {
case 0:
a3 = "";
break;
case 1:
a3 = li_toc[i].getElementsByTagName("a")[0];
default: {
k = 0;
for ( k = 0; k < j - 2; k++) {
correctParent = correctParent.parentElement;
};
a3 = previousByClass(correctParent, "treeitem");
a3 = child_by_selector(a3, "tree-expander")
}
}
if (a3 != ""){
txtValue3 = a3.textContent || a3.innerText;
txtValue3 = document.createTextNode(txtValue3);
span3.appendChild(txtValue2);
span3.appendChild(span4);
span3.appendChild(txtValue3);
} else {
span3.appendChild(txtValue2);
}
span.firstChild.appendChild(span2);
span.firstChild.appendChild(span3);
li_suggestion.appendChild(span);
df.appendChild(li_suggestion)
}
}
// Output HTML, and remove is-hidden class
ul_suggestions.appendChild(df);
}
}
})();
// WAIT TILL DOCUMENT HAS LOADED BEFORE INITIATING FUNCTIONS
document.addEventListener('DOMContentLoaded', searchFilter);
CSS I'm using:
/* Search Filter */
.filter-icon{
display: inline-block;
height:0.9rem;
width: 1.0rem;
text-transform: none;
text-align: center;
}
.searchFilter {
display: inline-block;
position: relative;
}
.searchFilter-input {
padding-right: 26px;
}
.searchFilter-suggestions {
list-style-type: none;
z-index: 1;
position: absolute;
max-height: 18rem;
min-width: 100%;
max-width: 100%;
padding: 0;
margin: 2px 0 0 !important;
cursor: default;
box-shadow: 0 1.6px 3.6px 0 rgba(0,0,0,0.132),0 .3px .9px 0 rgba(0,0,0,0.108);
border: 1px solid #e3e3e3;
background-color: white;
}
#media screen and (min-width: 768px), print {
.searchFilter-suggestions {
max-width: 500px;
}
}
.searchFilter-suggestion {
display: block;
border: 1px solid transparent;
}
.searchFilter-suggestion a {
color: rgb(23, 23, 22);
text-decoration: none;
}
.searchFilter-suggestion:hover{
background-color: #f2f2f2;;
border: 1px solid rgba(0,0,0,0);
}
.is-hidden {
display: none !important;
}
Relevant portion of HTML with UL that loads the search results:
(The search results document fragment generated by the JS gets loaded in the ul, with the class, searchFilter-suggestions)
form class = "has-margin-bottom-small" action="javascript:" role="search">
<label class="visually-hidden">Search</label>
<div class="searchFilter is-block">
<div class="control has-icons-left">
<input id="searchFilter" class="searchFilter-input input control has-icons-left is-full-width is-small" role="combobox" maxlength="100" autocomplete="off" autocapitalize="off" autocorrect="off" spellcheck="false" placeholder="Filter by title" type="text">
<span class="icon is-small is-left">
<img src="/../docs/assets/images/filter.png" class="filter-icon">
</span>
</div>
<ul class="searchFilter-suggestions is-vertically-scrollable is-hidden"></ul>
</div>
</form>
I think the best solution is to remove the focus listeners temporarily.
It should work using this:
(function searchFilter() {
let input = document.getElementById('searchFilter');
let suggestions = document.getElementsByClassName("searchFilter-suggestions")[0];
eventListeners();
// Add Event Listerns
function eventListeners() {
input.addEventListener('keyup', searchQuery);
suggestions.addEventListener("mouseenter", () => removeInputFocusListeners());
suggestions.addEventListener("mouseleave", () => addInputFocusListeners());
};
function addInputFocusListeners() {
input.addEventListener('focusout', searchQuery);
input.addEventListener('focusin', searchQuery);
}
function removeInputFocusListeners() {
input.removeEventListener('focusout', searchQuery);
input.removeEventListener('focusin', searchQuery);
}
...

Getting a position of an element in a 2D array

So I'm building a a turn based board game which needs to contain 2 player that can move across the map. I'm stuck at getting the position of the player(which is just a simple div element) inside of that 2D array. I've tried using indexOf, but even tho it's placed inside an onclick function, always returns 0.
The html code contains just of few div's with col classes:
And here is the JavaScript code (btw it contains some unnecessary stuff that I've just added for test purposes) :
let row = document.querySelector('.row');
let fields = document.getElementsByClassName('col-md-2')
let fieldsArr = Array.from(fields);
let header = document.getElementById("clicked");
let cols = header.getElementsByClassName("col-md-2");
let player = document.getElementById('player');
let player2 = document.getElementById('player2');
let blockedField = document.getElementsByClassName('blocked');
fieldsArr.sort(function() {
return 0.5 - Math.random();
}).forEach(function(el) {
row.appendChild(el);
});
// ADD AN EVENT LISTENER AND LISTEN FOR COLS ID
function replyClick(e) {
e = e || window.event;
e = e.target || e.srcElement;
if (e.nodeName === 'DIV') {
let changable = e.id;
//console.log(changable);
}
}
// CREATE A 2D ARRAY (MAP)
var map = [];
while (fieldsArr.length) map.push(fieldsArr.splice(0, 6));
// ON CLICK ADD A CLASS OF ACTIVE
for (var i = 0; i < cols.length; i++) {
cols[i].addEventListener("click", function() {
var current = document.getElementsByClassName("active");
current[0].className = current[0].className.replace(" active", "");
this.className += " active";
});
}
// MOVE PLAYER ONE ACROSS THE MAP
function movePlayer(multWidth, multHeight) {
$(".active").append(player);
if ((row).click > multWidth) {
alert(1)
}
}
// MOVE PLAYER 2 ACROSS THE MAP
function movePlayer2() {
$(".active").append(player2);
}
// MAKE GRAYED OUT FIELD UNAVALIABLE AND SHOW AN ALERT
$(blockedField).css("pointer-events", "none");
// APPEND PLAYER1(2) TO THE FIRST(LAST) FIELD ON THE MAP
map[0][0].appendChild(player);
map[5][5].appendChild(player2);
// GET PLAYERS CURRENT POSITION
$(row).click(function() {
let current = player.offsetTop;
});
const widthAllowed = 3 * 156;
const heightAllowed = 3 * 146;
// LIMIT PLAYER MOVES
let player1Moves = 3;
player2Moves = 3;
$(row).click(function() {
movePlayer();
let remainingMoves = player1Moves -= 1;
if (remainingMoves === 0) {
alert("You don't have any more moves. Player's 2 turn.");
$(player).css("pointer-events", "none");
$(row).click(movePlayer2);
}
})
for (var x = 0; x < map.length; x++) {
for (var y = 0; y < map[x].length; y++) {
console.log(x, y);
}
}
console.log(map);
console.log(map[2][5]);
console.log(map[5][0]);
You should take some beginner jquery/javascript course, since your question is very simple and you will find the whole programming thing way easier with a few basic concepts (like selectors, events and callbacks)
That said, here is a basic example of how to return the div element that contains the player element and how to use event attachment instead of inline events.
let row = $('.row');
row.on('click', replyClick);
function replyClick(e) {
var targetRow = $(e.target);
$('.row > div.active').removeClass('active');
targetRow.addClass('active');
var player = $('.row div.player');
alert(player.parent().attr('id'));
};
.player {
width: 20px;
height: 20px;
background: red;
}
.row > div {
padding: 10px;
width: 20px;
height: 20px;
border: 1px solid red;
}
.row > div.active {
background: blue;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container">
<div class="row">
<div id="col1" class="col-md-2">
<div class="player"></div>
</div>
<div id="col2" class="col-md-2 blocked"></div>
<div id="col3" class="col-md-2 active"></div>
<div id="col4" class="col-md-2"></div>
</div>
</div>

Categories