I'm using html5's drag and drop functionalities to rearrange dom elements on screen - I attach css behavior to the various states of dragging and dropping when I do this, but the problem I'm experiencing is that the hover state remains even after I've dragged, dropped, and moused out of a DOM element. Here's my code:
JAVASCRIPT:
function addDragListeners(){
$('.segmentListItem').each(function(index){
$(this)[0].addEventListener('dragstart',handleDragStart,false); //rollover for current
$(this)[0].addEventListener('drop',handleDrop,false); //drops dragged element
$(this)[0].addEventListener('dragover',handleDragOver,false); //allows us to drop
$(this)[0].addEventListener('dragenter',handleDragEnter,false); //rollover for target
$(this)[0].addEventListener('dragleave',handleDragLeave,false); //sets dragged item back to normal
$(this)[0].addEventListener('dragend',handleDragEnd,false); //sets all back to normal
});
}
function handleDragEnter(e) {
// this / e.target is the current hover target.
this.classList.add('over');
}
function handleDragLeave(e) {
this.classList.remove('over'); // this / e.target is previous target element.
}
function handleDragEnd(e){
$('.segmentListItem').removeClass('over'); //removes the over class on all elements
}
function handleDragStart(e){
draggedItem = this;
e.dataTransfer.effectAllowed = 'move';
}
function handleDragOver(e) {
if (e.preventDefault) {
e.preventDefault(); // Necessary. Allows us to drop.
}
e.dataTransfer.dropEffect = 'move'; // See the section on the DataTransfer object.
return false;
}
function handleDrop(e){
if (e.stopPropagation) {
e.stopPropagation();
}
if (draggedItem != this) { //MH - swap if we're not dragging the item onto itself
var draggedIndex = $('.segmentListItem').index($(draggedItem));
var targetIndex = $('.segmentListItem').index($(this));
if (draggedIndex > targetIndex){
$(draggedItem).insertBefore($(this));
} else {
$(draggedItem).insertAfter($(this));
}
}
return false;
}
CSS:
a { border-radius: 10px; }
a:hover { background: #ccc; }
.segmentListItem { text-align:center; width: 50px; margin-right: 5px; font-size: 16px; display:inline-block; cursor:move; padding:10px; background: #fff; user-select: none; }
.segmentListItem.over { background: #000; color: #fff; }
Status (six years later)
According to https://bugs.webkit.org/show_bug.cgi?id=134555 this used to be a bug. However, it must have been fixed in the meantime, as it cannot be reproduce anymore in modern browsers. The only browser where I could still replicate it was IE11.
Working fix
You can replace the CSS :hover with a .hover class toggled from JS, in order to better control the hover state:
document.querySelectorAll('.segmentListItem a').forEach(function (item) {
item.addEventListener('mouseenter', function () {
this.classList.add('hover');
});
item.addEventListener('mouseleave', function () {
this.classList.remove('hover');
});
});
Code snippet below:
function addDragListeners() {
$(".segmentListItem").each(function(index) {
$(this)[0].addEventListener("dragstart", handleDragStart, false); //rollover for current
$(this)[0].addEventListener("drop", handleDrop, false); //drops dragged element
$(this)[0].addEventListener("dragover", handleDragOver, false); //allows us to drop
$(this)[0].addEventListener("dragenter", handleDragEnter, false); //rollover for target
$(this)[0].addEventListener("dragleave", handleDragLeave, false); //sets dragged item back to normal
$(this)[0].addEventListener("dragend", handleDragEnd, false); //sets all back to normal
});
}
function handleDragEnter(e) {
// this / e.target is the current hover target.
this.classList.add("over");
}
function handleDragLeave(e) {
this.classList.remove("over"); // this / e.target is previous target element.
}
function handleDragEnd(e) {
e.preventDefault();
$(".segmentListItem").removeClass("over"); //removes the over class on all elements
}
function handleDragStart(e) {
draggedItem = this;
e.dataTransfer.effectAllowed = "move";
}
function handleDragOver(e) {
if (e.preventDefault) {
e.preventDefault(); // Necessary. Allows us to drop.
}
e.dataTransfer.dropEffect = "move"; // See the section on the DataTransfer object.
return false;
}
function handleDrop(e) {
if (e.stopPropagation) e.stopPropagation();
if (draggedItem != this) {
//MH - swap if we're not dragging the item onto itself
var draggedIndex = $(".segmentListItem").index($(draggedItem));
var targetIndex = $(".segmentListItem").index($(this));
if (draggedIndex > targetIndex) {
$(draggedItem).insertBefore($(this));
} else {
$(draggedItem).insertAfter($(this));
}
}
return false;
}
// JS fix starts here:
document.querySelectorAll('.segmentListItem a').forEach(function(item, idx) {
item.addEventListener('mouseenter', function() {
this.classList.add('hover');
});
item.addEventListener('mouseleave', function() {
this.classList.remove('hover');
});
});
// and ends here. Comment these lines, and uncomment the `a:hover` rule in CSS in order to see the initial code
addDragListeners();
a {
border-radius: 10px;
}
/* a:hover {
background: #ccc;
} */
.segmentListItem {
text-align: center;
width: 50px;
margin-right: 5px;
font-size: 16px;
display: inline-block;
cursor: move;
padding: 10px;
background: #fff;
user-select: none;
}
.segmentListItem.over {
background: #000;
color: #fff;
}
.hover {
background: #ccc;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<ul>
<li class="segmentListItem">
test1
</li>
<li class="segmentListItem">
test2
</li>
<li class="segmentListItem">
test3
</li>
</ul>
Related
I have to show and hide a div on triple click on body of my website in mobile devices
the below code that I wrote in JavaScript is work fine in android devices but it doesn't work in IOS.
so would you please help me to resolve it?
the code is :
window.onload = function() {
document.querySelector('body').addEventListener('click', function(evt) {
evt.preventDefault();
if (evt.detail === 3) {
document.getElementById('mmu').style.height = '100px';
}
if (evt.detail === 1) {
document.getElementById('mmu').style.height = '0px';
}
});
}
#mmu {
width: 100px;
height: 0px;
position: fixed;
left: 0;
cursor: pointer;
top: 0;
background: red;
}
body {
height: 1000px;
background: #eee;
width: 100%;
cursor: pointer;
}
<div id="mmu"></div>
On iOS, the click event doesn't fire normally. Instead you will need monitor touch events such as touchend to check how many taps are made.
For example you might try to check if the taps are made within a sufficient timeout window like so
TOUCH_TIMEOUT_MILLISECONDS = 500
touch_count = 0
window.onload = function () {
document.querySelector('body').addEventListener('touchend', function (evt) {
touch_count += 1
setTimeout(function () {
touch_count = 0
}, TOUCH_TIMEOUT_MILLISECONDS);
if (touch_count === 3) {
document.getElementById('mmu').style.height = '100px';
}
if (touch_count === 1) {
document.getElementById('mmu').style.height = '0px';
}
evt.preventDefault();
});
});
Depending on what your requirements are you may also need to account for touchend and click events firing from the same action.
I need your help, again
I can drag and drop a file or click on a basic input file on HTML5 and it'll work.
What I want is to be able to make that with a label, I can click on the label to add a file with a label for but I can't drag and drop a file on the label. I've tried to make it possible with that JavaScript over here but it didn't work, I've read some tutorials and I'm not sure of the code below.
$(document).on('dragenter', '#image-event-label', function() {
$(this).css('border', '1px solid #B8A1F5');
return false;
});
$(document).on('dragover', '#image-event-label', function(e){
e.preventDefault();
e.stopPropagation();
$(this).css('border', '1px solid #B8A1F5');
return false;
});
$(document).on('dragleave', '#image-event-label', function(e) {
e.preventDefault();
e.stopPropagation();
$(this).css('border', '1px solid #422B7E');
return false;
});
$(document).on('drop', '#image-event-label', function(e) {
if(e.originalEvent.dataTransfer){
if(e.originalEvent.dataTransfer.files.length) {
e.preventDefault();
e.stopPropagation();
$(this).css('border', '1px solid #0F0');
upload(e.originalEvent.dataTransfer.files);
}
}
else {
$(this).css('border', '1px solid #422B7E');
}
return false;
});
function upload(files) {
var f = files[0] ;
var reader = new FileReader();
reader.onload = function (event) {
$('#image-event').val(event.target.result);
}
reader.readAsDataURL(f);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<label for="image-event" id="image-event-label">Import an image</label>
<input type="file" name="image-event" style="display:none" id="image-event">
Thanks everybody in advance !
The easiest way, is to append your input[type=file] directly in your <label>, and to style this input in a way it covers all the label.
Doing so, you'll be able to drop files directly on the label, and the input's default behavior will take care of it, ultimately, no js is needed in this part:
// only to show it did change
$('#image-event').on('change', function upload(evt) {
console.log(this.files[0]);
});
// only to show where is the drop-zone:
$('#image-event-label').on('dragenter', function() {
this.classList.add('dragged-over');
})
.on('dragend drop dragexit dragleave', function() {
this.classList.remove('dragged-over');
});
#image-event {
position: absolute;
top: 0;
left: 0;
opacity: 0;
width: 100%;
height: 100%;
display: block;
}
#image-event-label {
position: relative;
}
#image-event-label.dragged-over {
border: 1px solid;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<label for="image-event" id="image-event-label">
Import an image
<input type="file" name="image-event" id="image-event">
</label>
Now, note that it is actually now* possible to set the .files FileList of an input[type=file], but you need to set it to an other FileList object, and fortunately, there is one available in the DataTransfer object that comes along the DropEvent:
function handleDroppedFile(evt) {
evt.preventDefault();
// since we use jQuery we need to grab the originalEvent
var dT = evt.originalEvent.dataTransfer;
var files = dT.files;
if (files && files.length) {
// we set our input's 'files' property
$('#image-event')[0].files = files;
}
}
$('#image-event-label').on({
'drop': handleDroppedFile,
'dragenter': function(e) { e.preventDefault(); },
'dragover': function(e) {
e.preventDefault();
this.classList.add('dragged-over');
}
})
.on('dragleave dragexit', function() {
this.classList.remove('dragged-over')
});
.dragged-over {
border: 1px solid;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<label for="image-event" id="image-event-label">
Drop a file here
</label> <br><br>
<input type="file" name="image-event" id="image-event">
*IIRC, this is now supported in Chrome, Safari, latests Edge and latests Firefox. I don't think IE does support it though, so beware when using this.
I have a javascript that opens up a hidden div:
<script>
function dropdown()
{ document.getElementById("login_dropdown").style.display="block"; }
</script>
The html is:
<div onclick="dropdown()">
<div id="login_dropdown">STUFF</div>
</div>
The CSS is:
<style>
#login_dropdown {
width: 150px;
display:none;
}</style>
Using javascript alone, how can I hide this div when I click anywhere else on the page, excluding the opened DIV itself.
Something like this with vanilljs
document.addEventListener('click', function(event){
const yourContainer = document.querySelector('....');
if(!yourContainer.contains(event.target)) {
//hide things classes.. yourContainer.classList.add('hidden');
}
});
You could do this
var elem = document.getElementById("login_dropdown");
(document.body || document.documentElement).addEventListener('click', function (event) {
// If the element on which the click event occurs is not the dropdown, then hide it
if (event.target !== elem)
elem.style.display="none";
}, false);
function closest(e, t){
return !e? false : e === t ? true : closest(e.parentNode, t);
}
container = document.getElementById("popup");
open = document.getElementById("open");
open.addEventListener("click", function(e) {
container.style.display = "block";
open.disabled = true;
e.stopPropagation();
});
document.body.addEventListener("click", function(e) {
if (!closest(e.target, container)) {
container.style.display = "none";
open.disabled = false;
}
});
#popup {
border: 1px solid #ccc;
padding: 5px;
display: none;
width: 200px;
}
<div id="container">
<button id="open">open</button>
<div id="popup">PopUp</div>
</div>
Something like this:
$("document").mouseup(function(e)
{
var subject = $("#login_dropdown");
if(e.target.id != subject.attr('id'))
{
subject.css('display', 'none');
}
});
works like this. When you click anywhere on the page, the handler fires and compares the ID of the open tab vs the id of the document (which there is none) - so it closes the tab. However, if you click the tab, the handler fires, checks the ID, sees the target is the same and fails the test (thus not closing the tab).
I'm trying to catch a click event on an element which changes its z-pos using appendchild on the mousedown event. The problem is that when you click an element when its not the front element then the click event doesn't fire. I know this is because it is removed from the DOM and then re-added but I'm not sure how I could fix it so that the click also fire when the element is moved to the front.
MyObject = {
init(parent, text) {
this.parent = parent;
this.text= text;
this.visual = document.createElement("div");
this.visual.setAttribute("class", "object");
this.visual.appendChild(document.createTextNode(text))
parent.appendChild(this.visual);;
this.visual.addEventListener("click", (e) => { this.onClicked(e); });
this.visual.addEventListener("mousedown", (e) => { this.onMouseDown (e); });
},
toTop() {
this.parent.appendChild(this.visual);
},
onClicked(e) {
alert(this.text + " was clicked");
e.stopPropagation();
},
onMouseDown(e) {
this.toTop();
// I'm also doing other things here
}
};
var parent = document.querySelector(".parent");
parent.addEventListener("click", (e) => { alert("parent clicked"); });
var createObj = function(text) {
var obj = Object.create(MyObject);
obj.init (parent, text);
};
createObj ("object 1");
createObj ("object 2");
createObj ("object 3");
createObj ("object 4");
.parent {
border: 1px solid red;
}
.object {
background: #0F0;
border: 1px solid black;
margin: 8px;
}
<div class="parent">
</div>
So in this example you always have to click the bottom element to get the alert while I would also like to get the alert when an other items is pressed.
edit: I'm testing in chrome (55.0.2883.87) and the code might not work in other browsers.
I have some code in jQuery in which I want to make a switch animate on or off by clicking on a div. Here is the code. When I test it, it doesn't work. However if I remove toggle = true on line 7, it just works one way and I can't turn it back off.
$(document).ready(function () {
$("#switch").click(function () {
var toggle = false;
if (toggle == false) {
$("#circle").css("left", "27px");
$("#white_rect").attr("src", "green_rect.png");
toggle = true;
}
if (toggle == true) {
$("#circle").css("left", "1px");
$("#white_rect").attr("src", "white_rect.png");
toggle = false;
}
});
});
You need to declare the toggle variable outside of the click handler... else in every click call the variable will get reinitialized so the value of the variable will always be false.
$(document).ready(function () {
//declare it here
var toggle = false;
$("#switch").click(function () {
if (toggle == false) {
$("#circle").css("left", "27px");
$("#white_rect").attr("src", "green_rect.png");
toggle = true;
//also don't use a separate if block here as it will be affected by the execution of the above if block
} else {
$("#circle").css("left", "1px");
$("#white_rect").attr("src", "white_rect.png");
toggle = false;
}
});
});
$(document).ready(function () {
//declare it here
var toggle = false;
$("#switch").click(function () {
if (toggle) {
$("#circle").css("left", "1px");
$("#white_rect").attr("src", "white_rect.png");
} else {
$("#circle").css("left", "27px");
$("#white_rect").attr("src", "green_rect.png");
}
toggle = !toggle;
});
});
It is better to strictly divide appearance and logic. So use .classes and backgounded <div>s instead of <img>. Then you won't need any state variables and code shall be more simple.
HTML:
<div class="container">
<div class="switch off"></div>
</div>
CSS:
.container {
width:100%; height:100px; padding:40px; text-align:center;
}
.container .switch {
width:94px; height: 27px; display:inline-block; background-color:pink; cursor:pointer;
}
.container .switch.on {
background: url('http://squad.pw/tmp/img/01-on.png') no-repeat 0 0;
}
.container .switch.off {
background: url('http://squad.pw/tmp/img/01-off.png') no-repeat 0 0;
}
JS:
$('.switch').click(function() {
// Do visual logic
$(this).toggleClass('on');
$(this).toggleClass('off');
// Do business logic
window.toggle = !window.toggle;
});
Here is FIDDLE