I am making a chess game, and I need your help to solve the following problem.
event.target.removeChild(event.target.firstChild);
dragged.parentNode.removeChild(dragged);
event.target.appendChild(dragged);
//dragged is ref to the dragged piece
//event is the drop event
when I move a piece :
I remove the existing piece from the square
I add the new Piece
If the square is empty I just add the piece
note: event.target is a square and the pieces are images.
The problem is when I remove The existing piece from the square, I get the following error:
Failed to execute 'removeChild' on 'Node': parameter 1 is not of type 'Node'.
It is possible that the event.target is not what you think it is and that therefore does not have the child element you expect (likely if your pieces are images with transparency that fill a significant part of the parent aquare). The target of an event can be any descendent of the element on which the event listener was set, depending where the event occured.
In other words, if you establish an event listener on the square divs representing squares on the chess board, a click (or drop, or whatever event you listen for) inside the square might easily target the piece and not the square.
Here's an illustration using a click event (but it applies to drops or anything else), a console message reports the target element's id:
const square=document.getElementById("div1");
square.addEventListener('click', event => {
console.log(event.target.id);
}); // end click listener
#div1 {
width: 50px;
aspect-ratio: 1;
background: yellow;
padding: 5px;
}
#div2 {
width: 100%;
aspect-ratio: 1;
border-radius: 50%;
background: red;
}
<div id="div1">
<div id="div2"></div>
</div>
(You can easily check what's going on in your example by putting some console messages inside the event listener to report information about the target)
Personally, I approach tasks like this by making the listener as non-specific as possible (by targetting the document in its entirety). This forces you to test where the event was received and so allows you to cover all bases.
In this working snippet, I've used a click event listener but it will be the same for any event. The listener contains an if-else loop that tests whether the outer square or the inner circle was clicked and forms the code required to remove the circle dependent on what the target was:
document.addEventListener('click', event => {
if (event.target.className == 'square' && event.target.children[0]) {
event.target.removeChild(event.target.children[0]);
} else if (event.target.className == 'piece') {
event.target.parentElement.removeChild(event.target.parentElement.children[0]);
} // end if/else;
}); // end click event listener
.square {
width: 50px;
aspect-ratio: 1;
background: yellow;
padding: 5px;
}
.piece {
width: 100%;
height: 100%;
border-radius: 50%;
background: red;
}
<div class="square">
<div class="piece"></div>
</div>
Note the opening if block contains two conditions:
(event.target.className == 'square' && event.target.children[0])
obviously the first confirms a square was clicked, the second checks whether there's anything inside that element to remove (preventing an error when an empty square is clicked).
Related
I try to make a select box whose entries are opened after clicking into the input box. After selecting one of the items, the dropdown should be closed again.
I want to achieve the open/close part of the dropdown without the use of javascript.
The html looks like this:
<div id="outer">
<input type="text" id="input">
<div id="results">
<div>Test 1 </div>
<div>Test 2 </div>
<div>Test 3 </div>
<div>Test 4 </div>
</div>
</div>
<div id="label">
</div>
After clicking onto an item, the selected value should appear below the #outer div (just for demonstration purposes).
The Javascript for assigning click events to the dropdown values:
document.querySelectorAll("#results div").forEach(setClick);
function setClick(node) {
node.addEventListener("click", setText.bind(null, node.innerHTML))
}
function setText(t) {
document.getElementById("label").innerHTML = t;
}
Now I will show you my first draft of css code:
#outer {
width: 200px;
position: relative;
}
#input {
width: 100%;
}
#results {
position: absolute;
width: 100%;
display: block;
visibility: hidden;
background-color: white;
}
#results > div:hover {
background-color: lightblue;
cursor: pointer;
}
#outer:focus-within #results, #results:hover {
visibility: visible;
}
This works like a charm but fails in one point:
After clicking an item, the dropdown is not closed. This is because of the #results:hover selector which is needed to keep the dropdown open after clicking onto an item. The click takes the focus out of the input field, thus the focus-within selector is not applied anymore. As the focus is removed from the input before the click occurs, the dropdown is hidden when the final click arrives in the document (this is my understanding of the problem).
Thus I use the hover selector which forces the div to keep open as long as the mouse is above the div.
You can test this here:
https://jsfiddle.net/hcetz1og/3/
My solution for this was a transition that hides the dropdown after the focus has been taken away:
#outer:not(:focus-within) #results:hover {
visibility: hidden;
transition-property: visibility;
/*use 10 ms and the clicked value in the drop down won't be shown */
transition-delay: 100ms;
transition-timing-function: step-end;
}
This works on my machine when I use 100ms as a delay. If I use 10ms, I have the same problem again. It seems that the click event is triggered "very" late.
Feel free to test it here:
https://jsfiddle.net/hcetz1og/2
Question:
How long will it take until the click event arrives at the document? Is there a fixed time span I have to wait or can the delay depend on every machine?
If so, I am forced to not use plain CSS but must use javascript for this I think.
Edit:
Feel free to post an alternative solution using plain css. But please be aware that I mainly want to focus on getting an answer to this question, not alternative solutions.
As #Mark Baijens said in the comments, using timeouts is a bad practice, so here is a pretty clean solution.
I used JavaScript to render the dropdown, not the CSS, because the CSS is where Your issue is coming from.
I don't know why would You want to set the innerHTML, but not some other property, like style.visibility for example. It just doesn't make sense to me, so with that in mind, let's get our hands on this :)
Working demo >> HERE <<.
Step 1 - remove the #outer...:hover parts of CSS
So, You are left with this:
#outer {
width: 200px;
position: relative;
}
#input {
width: 100%;
}
#results {
position: absolute;
width: 100%;
display: block;
visibility: hidden;
background-color: white;
}
#results > div:hover {
background-color: lightblue;
cursor: pointer;
}
Step 2 - add the onfocus event to the input field
Just assign a function call to the onfocus attribute of the input. Everything else in the HTML stays the same.
<div id="outer">
<input type="text" id="input" onfocus="showElements()">
<div id="results">
<div>Test 1 </div>
<div>Test 2 </div>
<div>Test 3 </div>
<div>Test 4 </div>
</div>
</div>
<div id="label">
</div>
Step 3 - create the showElements and hideElements function:
function showElements() {
document.getElementById("results").style.visibility = 'visible';
}
function hideElements() {
document.getElementById("results").style.visibility = 'hidden';
}
Step 4 - call the hideElements() when clicked outside the input element
There are two cases for the click outside the input element:
Case 1 - we clicked on one of the divs inside the #results wrapper
Case 2 - clicking outside the input field, but not on one of the divs inside the #results wrapper
In the first case, we will modify the assignment of the onclick handler like this:
document.querySelectorAll("#results div").forEach(setClick);
function setClick(node) {
node.addEventListener("click", setTextAndHideElements.bind(null, node.innerHTML));
}
So, the setText function now becomes setTextAndHideElements and looks like this:
function setTextAndHideElements(t) {
document.getElementById("label").innerHTML = t;
hideElements();
}
For the second case (clicking outside the input field, but not on one of the divs inside the #results wrapper), we must watch for the click on the whole page (document element), and respond to the action like this:
document.onclick = function(e) {
if (e.target.id !== 'input'){
hideElements();
}
}
Note: this will override any previously assigned onclick events assigned to the document element.
As mentioned in the beginning, working demo is >> HERE (codepen.io) <<.
I tried another solution which requires no setting of additional JS events.
See: https://jsfiddle.net/hcetz1og/4/
I gave every result item a tabindex of "0" to ensure, those items can be focusable.
Then i removed the #outer:not() part from the css and replaced the hover selector with this: #results:focus-within. Additional I called node.blur() on the node after clicking onto them.
Summary:
Change in HTML:
<div tabindex="0">Test 1 </div>
Change in JS:
function setText(t, node) {
document.getElementById("label").innerHTML = t;
node.blur();
}
Change in CSS:
#outer:focus-within #results, #results:focus-within {
visibility: visible;
}
What do you think about this one? Should be stable I think because the focus onto the #results div is set before the click event is triggered onto the result item.
Event order should be (based on my observation):
input focus -> input blur -> item focus -> item click
Not sure if the step between blur and focus can lead to a visible problem. Theoretically, the results div must be hidden and shown again in a very small amount of time.
But I investigated this with chrome's performance timeline and did not recognize a new render between both events. One can see, that the result item is focused (outline is set onto it) and then it disappears as expected.
Web development isn't my strongest point, so apologies if this is foolish, but I want to have a div that is tappable on mobile so I can intercept the tap and do things.
I'm attaching a touchdown event listener to the div in JavaScript, and when I tap, I color the div to indicate it's been selected.
However, if there's an anchor tag within the div with some text in it (totally valid in this case), I don't want the div to be highlighted when you're just clicking the link.
I'm not sure the term in web development, but is there a way to make the anchor tag intercept touches so they don't get sent to the div if the anchor tag is selected?
You can use a conditional in the event handler to test if the event target is an anchor. I used the click event in my example, but it should work with touchdown also.
var tapDiv = document.querySelector('#tap');
tapDiv.addEventListener('click', function(e) {
//conditional ensures the clicked element isn't an <a>
if (e.target.tagName.toLowerCase() !== 'a') {
e.currentTarget.style.backgroundColor = 'orange';
}
});
#tap {
padding: 8px;
background-color: black;
}
#tap a {
display: inline-block;
padding: 8px;
background-color: white;
}
<div id="tap">This is the anchor</div>
I have a problem with mouseenter/mouseleave unexpected behavior in Firefox. The thing I need to do is:
Move the node to another place in DOM after mouse enter has been registered on it
Then move it back to original place after mouse leave has been registerd on it
The problem is that after inserting the node into another place, mouseenter fires like crazy and mouseleave doesn't fire at all.
Here's a pen illustrating that. You should inspect console output to see how many times mouseenter is fired. It might seem nonsensical in the pen, but in my app it isn't. I'm adding a zoom to the image on hover. One of it's ancestor's has overflow: hidden (which I cannot fight with CSS) and hides the enlarged portion of the image. So after the image is zoomed I need to move it into some other place in DOM (while making sure that it stays in the same place on the screen), then move it back in place when zoom is over (mouse leaves the image).
Question
Can someone explain to me what happens here? And how to fight it to achieve the regular mouseenter and mouseleave behavior (fired only once).
First: Your variables outer1 and outer2 are the same.
Second, if you are changing the element of node it will trigger the mouseleave because the cursor is no longer on top of the element..
I think this is what you are trying to achieve:
var inner1 = document.getElementById("inner1");
var inner2 = document.getElementById("inner2");
var outer1 = document.getElementById("outer1");
var outer2 = document.getElementById("outer2");
var moved = false;
inner2.addEventListener("mouseenter", function(e) {
if (moved) {
zoomOut();
} else {
zoom();
}
});
function zoom() {
console.log("mouseenter");
outer2.appendChild(inner1);
moved = true;
}
function zoomOut() {
console.log("mouseenter 2");
outer1.appendChild(inner1);
moved = false;
}
#outer1,
#outer2 {
width: 300px;
height: 200px;
}
#outer1 {
background-color: green;
}
#outer2 {
position: absolute;
background-color: blue;
}
#inner2 {
position: absolute;
height: 70px;
width: 100px;
background-color: yellow;
}
<div id="outer1">
<div id="inner1">
<div id="inner2">Hover me</div>
</div>
</div>
<div id="outer2">
</div>
As you can see I only listen to the mouseenter event because the mouseleave will always be triggered after you append the element. I added a flag: moved to figure out where the div is.
I'm having the same problem using D3 and a force-directed graph.
Each node has a mouseenter handler that calls this.parentNode.appendChild() to bring the element to the top. It's the only way I know of to rearrange SVG elements.
This works fine in Chrome and Safari, but in Firefox, it causes the looping mouseenter behavior. No mouseleave event is ever fired, either.
Best fix I have right now is to only call appendChild() when the node is not the topmost (node.nextSibling !== null)
I'm trying to mouseover an image which is behind an another image.
Is there any way to render the front image like it is not there, so I can mouseover the other image behind it?
Example can be seen here:
I can't mouseover the characters which are behind the logo boundingbox.
You can set the CSS property pointer-events: none on the obstructing object... but be aware that pointer events are all or nothing; mouseovers and hovers will pass right through, but so will clicks.
Here is a description of the value, from the Mozilla Developer's Network:
none: The element is never the target of mouse events; however, mouse events may target its descendant elements if those descendants have pointer-events set to some other value. In these circumstances, mouse events will trigger event listeners on this parent element as appropriate on their way to/from the descendant during the event capture/bubble phases.
I've put together a little example. In this example, I'm using onmouseover and onmouseout, since that's what you use on your website, but you could just as easily use the CSS :hover pseudo-selector. Here's a jsfiddle version of the example, and the stack snippet is below.
.hoverable {
width: 100px;
height: 100px;
background-color: blue;
}
.obscuring {
/* this first line is the important part */
pointer-events: none;
position: absolute;
top: 50px;
left: 50px;
width: 100px;
height: 100px;
background-color: red;
opacity: 0.5;
}
<div class="hoverable" onmouseover="this.style.backgroundColor = 'green'" onmouseout="this.style.backgroundColor = 'blue'"> </div>
<div class="obscuring"> </div>
You can create some invisible divs on top of the whole thing and put the hover behaviour on them. Then control the characters position with the position of the invisible divs.
Hope it makes sense.
I'm having a problem where the left two pixels of a Font-Awesome icon I've placed inside of a button element do not trigger the click event of the button.
Here's an example button:
<button class="btn btn-mini">
<i class="icon-edit"></i>
</button>
And here's what it looks like with bootstrap
Any ideas for why those left two pixels don't trigger a click event?
Edit: Here's a test site where I've managed to recreate the issue: http://ace.cwserve.com
I know this post is 4 years old but it might help people understand why a font-awesome "icon" inside a button prevents the click event.
When rendered, the icon class adds a ::before pseudo-element to the icon tag that prevents the button's click event.
Given this situation, we should definitly take a look at the CSS pointer-events Property
The pointer-events property defines whether or not an element reacts
to pointer events.
So we just need to add this css declaration for the "icon" which is inside a button:
button > i {
pointer-events: none;
}
Outline
The outline isn't part of the CSS box, which means it won't fire click events. This is perhaps slightly counter-intuitive, but that's how it works ...
Your page sets an outline on .btn:focus, but this doesn't seem to be the problem, since it has an offset of -2 (meaning it's displayed inside the box, rather than outside of it).
Moving the box on :active
You can move the box on :active, which can cause neat effect, but first the box is moved, and then will the click event be fired, which will use the moved position.
You can see this in action by keeping the mouse button pressed; the box will move, but the event won't be fired until you release the button. So if you move your box to the right by then pixels, then the left 10 pixels won't do anything.
This is according to spec, from the DOM spec:
click
The click event occurs when the pointing device button is clicked over an element. A click is defined as a mousedown and mouseup
over the same screen location. The sequence of these events is:
mousedown
mouseup
click
This seems to be the problem, this following CSS seems to solve it:
button.btn:active {
left: 1px;
top: 1px;
}
Example
Here's a script to demonstrate both issues:
<!DOCTYPE html>
<html><head><style>
body { margin-left: 30px; }
div {
background-color: red;
border: 20px solid green;
outline: 20px solid blue;
width: 100px;
height: 100px;
position: relative;
}
div:active {
left: 20px;
top: 20px;
}
</style></head> <body>
<div></div>
<script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
<script>
$('div').on('click', function(e) {
alert('click!');
});
</script></body></html>