html color to alternative rows of dynamic table - html

I have a Dynamic table that I want to give color to on alternative rows. How can I achieve this with css? I need the code to work in IE7+

Look into using even/odd rules in CSS3.
Reference: https://developer.mozilla.org/en/CSS/:nth-child
For instance,
tr:nth-child(odd) will represent the CSS for every 2n + 1 child, whereas tr:nth-child(even) will represent the CSS for every 2n child.

i came across this same problem Friday, i used the jquery solution of
$("tr:even").css("background-color", "#CCC");
$("tr:odd").css("background-color", "#FFF");
a stack overflow solution .js posted here
Detect changes in the DOM
so essentially you add the .js script in the head and fire the jquery rules on dom change.
My finished .js looked like this
<script type="text/javascript">
(function (window) {
var last = +new Date();
var delay = 100; // default delay
// Manage event queue
var stack = [];
function callback() {
var now = +new Date();
if (now - last > delay) {
for (var i = 0; i < stack.length; i++) {
stack[i]();
}
last = now;
}
}
// Public interface
var onDomChange = function (fn, newdelay) {
if (newdelay)
delay = newdelay;
stack.push(fn);
};
// Naive approach for compatibility
function naive() {
var last = document.getElementsByTagName('*');
var lastlen = last.length;
var timer = setTimeout(function check() {
// get current state of the document
var current = document.getElementsByTagName('*');
var len = current.length;
// if the length is different
// it's fairly obvious
if (len != lastlen) {
// just make sure the loop finishes early
last = [];
}
// go check every element in order
for (var i = 0; i < len; i++) {
if (current[i] !== last[i]) {
callback();
last = current;
lastlen = len;
break;
}
}
// over, and over, and over again
setTimeout(check, delay);
}, delay);
}
//
// Check for mutation events support
//
var support = {};
var el = document.documentElement;
var remain = 3;
// callback for the tests
function decide() {
if (support.DOMNodeInserted) {
window.addEventListener("DOMContentLoaded", function () {
if (support.DOMSubtreeModified) { // for FF 3+, Chrome
el.addEventListener('DOMSubtreeModified', callback, false);
} else { // for FF 2, Safari, Opera 9.6+
el.addEventListener('DOMNodeInserted', callback, false);
el.addEventListener('DOMNodeRemoved', callback, false);
}
}, false);
} else if (document.onpropertychange) { // for IE 5.5+
document.onpropertychange = callback;
} else { // fallback
naive();
}
}
// checks a particular event
function test(event) {
el.addEventListener(event, function fn() {
support[event] = true;
el.removeEventListener(event, fn, false);
if (--remain === 0) decide();
}, false);
}
// attach test events
if (window.addEventListener) {
test('DOMSubtreeModified');
test('DOMNodeInserted');
test('DOMNodeRemoved');
} else {
decide();
}
// do the dummy test
var dummy = document.createElement("div");
el.appendChild(dummy);
el.removeChild(dummy);
// expose
window.onDomChange = onDomChange;
})(window);
$(document).ready(function () {
$("tr:even").css("background-color", "#CCC");
$("tr:odd").css("background-color", "#FFF");
onDomChange(function () {
$("tr:even").css("background-color", "#CCC");
$("tr:odd").css("background-color", "#FFF");
});
});
</script>
I would like to caveat this answer that this probably is not the greatest solution but worked for what i needed it to do. :-)

CSS3 nth-child selector:
tr:nth-child(odd) {
background: red /* or whatever */;
}

You can use a CSS3 selector:
tr:nth-child(even) {background: #CCC}
tr:nth-child(odd) {background: #FFF}
or jQuery:
$("tr:even").css("background-color", "#CCC");
$("tr:odd").css("background-color", "#FFF");
or do it on the server side.

Related

ES6 Classes with GSAP says can't Tween null object

My code is as below. Any ideas how can I call this.rule ? I am trying to use GSAP's TweenMax with a plugin called CSSRulePlugin to animate the pseudo elements.
class animate {
constructor() {
this.rule = CSSRulePlugin.getRule(".menu a:before");
this.target = document.querySelectorAll(".menu a");
}
init() {
for (let i = 0; i < this.target.length; i++) {
this.handleClick(i);
}
}
handleClick(index) {
this.target[index].addEventListener('mouseenter', (event) => {
event.preventDefault();
TweenMax.to(this.rule, 0.2, {cssrule:{x: '+10px'}});
});
}
}
let Animate = new animate();
Animate.init();
Keeps saying can not Tween null object. What am I doing wrong?
CSSRulePlugin.getRule doesn't seem to return an Array
var rule = CSSRulePlugin.getRule(".box:after");
TweenLite.to(rule, 1, {cssRule:{backgroundColor:"#600", color:"white"}});
So, you probably need to change your code:
handleClick(index) {
this
.target[index]
.addEventListener('mouseenter', (event) => {
event.preventDefault();
let self = this;
TweenMax.to(self.rule, 0.2, {
cssrule:{x: '+10px'}
});
});
}
ps: you don't need var self = this because arrow function inherit the this.
Why don't you use add remove classes? you can build your animation in css and just add or remove a class at the right moment...

prototype - Trigger event when an element is removed from the DOM

I'm trying to figure out how to execute some js code when an element is removed from the page:
Something in prototype like:
$('custom-div').observe('remove', function(event) {
// Handle the event
});
Does anything like this exist?
In modern browsers, you can use a MutationObserver. Here's code that will call a callback when a DOM element is removed from it's current location:
function watchRemove(el, fn) {
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
var item;
if (mutation.type === "childList" && mutation.removedNodes) {
for (var i = 0; i < mutation.removedNodes.length; i++) {
item = mutation.removedNodes[i];
if (item === el) {
// clear the observer
observer.disconnect();
fn.call(item, item);
break;
}
}
}
});
});
observer.observe(el.parentNode, {childList: true});
return observer;
}
And, a working demo: http://jsfiddle.net/jfriend00/naft3qeb/
This watches the parent for changes to its direct children and calls the callback if the specific DOM element passed in is removed.
The observer will remove itself when the DOM element is removed or watchRemove() returns the observer instance which you can call .disconnect() on at any time to stop the watching.
Here's a jQuery plug-in that uses this functionality:
jQuery.fn.watchRemove = function(fn, observers) {
return this.each(function() {
var o = watchRemove(this, fn);
if (observers) {
observers.push(o);
}
});
}
In this case, it accepts an optional array object as an argument that will be filled with all the observer objects (only necessary to pass this if you want to be able to stop the watching yourself on any given item).

Converting an external init function into a polymer one

I want to create a polymer out of this codrops tutorial
http://tympanus.net/codrops/2013/07/03/interactive-particles-slideshow/
I can create a basic component but the script particlesSlideshow.js does not fire.
I think this is because I need to move its code into the polymer init script but im not sure how to do this.
Here is the script in the file. Any ideas how to move this across to polymer. I suppose I want to know what goes in the init and what can stay in an external file.
var self = window;
;(function(self) {
var canvas,
context,
particles = [],
text = [],
nextText = [],
shape = {},
mouse = { x: -99999, y: -99999 },
currentTransition = 'circle',
left, right,
layout = 0,
type = ['circle', 'ovals', 'drop', 'ribbon'],
FPS = 60,
/*
* List words.
*/
words = [ 'circle', 'ovals', 'drop', 'ribbon' ],
/*
* List colors.
*/
colors = {
circle: [ '#e67e22', '#2c3e50' ],
ovals: [ '#c0392b', '#ff7e15' ],
drop: [ '#1d75cf', '#3a5945' ],
ribbon: [ '#702744', '#f98d00' ]
};
/*
* Init.
*/
function init() {
console.log(document.querySelector('.ip-slideshow').length);
console.log(shadow.querySelector('.ip-slideshow').length);
var slideshowContainer = document.querySelector('.ip-slideshow');
canvas = document.createElement('canvas');
canvas.width = innerWidth;
canvas.height = 500;
slideshowContainer.appendChild(canvas);
// Browser supports canvas?
if(!!(capable)) {
context = canvas.getContext('2d');
// Events
if('ontouchmove' in window) {
canvas.addEventListener('touchup', onTouchUp, false);
canvas.addEventListener('touchmove', onTouchMove, false);
}
else {
canvas.addEventListener('mousemove', onMouseMove, false);
}
// Arrows
handleClick('bind', 'left');
handleClick('bind', 'right');
window.onresize = onResize;
createParticles();
// Arrows elements
left = document.querySelector('.ip-nav-left');
right = document.querySelector('.ip-nav-right');
// Show right arrow
right.classList.add('ip-nav-show');
}
else {
console.error('Sorry, switch to a better browser to see this experiment.');
}
}
/*
* Checks if browser supports canvas element.
*/
function capable() {
return canvas.getContext && canvas.getContext('2d');
}
/*
* On resize window event.
*/
function onResize() {
canvas.width = window.innerWidth;
canvas.height = 500;
// Reset the text particles, and align again on the center of screen
nextText = [];
updateText();
}
function scrollX() {
return window.pageXOffset || window.document.documentElement.scrollLeft;
}
......
/*
* Request new frame by Paul Irish.
* 60 FPS.
*/
window.requestAnimFrame = (function() {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / FPS);
};
})();
window.addEventListener ? window.addEventListener('load', init, false) : window.onload = init;
})(self);
If you change the script such that instead of
window.addEventListener ? window.addEventListener('load', init, false) : window.onload = init;
you did something like
self.initParticlesSlideshow = init;
Then you could invoke it in the domReady of an element like this:
Polymer('my-element', {
domReady: function() {
initParticlesSlideshow();
}
});
There are many other ways to do this, and a lot of other details; I'm just trying to get you started. =P

drag and drop cross frames use html5

I know how to drag and drop in one window with html5. But how to drag and drop across frames?
Here is my script which can work in one window. Can someone help me?
<script>
var drag = document.getElementById("drag");
var drop = document.getElementById("drop");
drag.onselectstart = function () {
return false;
}
drag.ondragstart = function (ev) {
ev.dataTransfer.effectAllowed = "move";
ev.dataTransfer.setData("text", ev.target.innerHTML);
}
drag.ondragend = function (ev) {
var text = ev.dataTransfer.getData("text");
alert(text);
ev.dataTransfer.clearData("text");
return false;
}
drop.ondragover = function (ev) {
ev.preventDefault();
return true;
}
drop.ondragenter = function (ev) {
this.background = "#ffffff";
return true;
}
drop.ondrop = function (ev) {
}
</script>
#Nickolay: oh, ok.
There's an example at http://www.useragentman.com/blog/2010/01/10/cross-browser-html5-drag-and-drop/ .
Added:
I'm not sure why the OP's code didn't work - maybe it wasn't loaded in both frames? I modified their Javascript a little to give more indications:
window.onload = function () {
var drag = document.getElementById('drag');
var drop = document.getElementById("drop");
if (drag) {
drag.style.backgroundColor = '#00ff00';
drag.onselectstart = function () {
return false;
}
drag.ondragstart = function (ev) {
ev.dataTransfer.effectAllowed = "move";
ev.dataTransfer.setData("text", ev.target.innerHTML);
}
drag.ondragend = function (ev) {
var text = ev.dataTransfer.getData("text");
alert(text);
//ev.dataTransfer.clearData("text");
return false;
}
}
if (drop != null) {
drop.style.backgroundColor = '#0000ff';
drop.ondragover = function (ev) {
ev.preventDefault();
return false;
}
drop.ondragenter = function (ev) {
this.style.backgroundColor = "#ff0000";
return false;
}
drop.ondrop = function (ev) {
return false;
}
}
}
It works between iframes and between browser windows (only tested in Firefox 11 and IE9 on Windows 7 x64).
I modified your script to work in the case that the iframe name is "frame1". Please check it now.
<script type="text/javascript">
window.onload = function ()
{
var drag = document.getElementById("drag");
var drop = frame1.document.getElementById("drop");
drag.draggable = true;
drag.onselectstart = function () {
return false;
}
drag.ondragstart = function (ev) {
ev.dataTransfer.effectAllowed = "move";
ev.dataTransfer.setData("text", ev.target.innerHTML);
}
drop.ondragover = function (ev) {
ev.preventDefault();
return true;
}
drop.ondragenter = function (ev) {
this.background = "#ffffff";
return true;
}
drop.ondrop = function (ev) {
var data = ev.dataTransfer.getData("text");
drop.innerHTML += data;
ev.preventDefault();
}
}
Check out the tutorial for Cross-Frame Drag and Drop. It explains the events required and the basic flow when working with multiple frames.
http://blog.dockphp.com/post/78640660324/cross-browser-drag-and-drop-interface-development-using
How are the iframes hosted? are you just using html files? as this could potentially be the issue.
I created a couple of html files with the drag and drop code in your question, this didn't work when just referencing each other. However when I added the files to IIS server and referenced the files using localhost it then started to work.

How do I detect a HTML5 drag event entering and leaving the window, like Gmail does?

I'd like to be able to highlight the drop area as soon as the cursor carrying a file enters the browser window, exactly the way Gmail does it. But I can't make it work, and I feel like I'm just missing something really obvious.
I keep trying to do something like this:
this.body = $('body').get(0)
this.body.addEventListener("dragenter", this.dragenter, true)
this.body.addEventListener("dragleave", this.dragleave, true)`
But that fires the events whenever the cursor moves over and out of elements other than BODY, which makes sense, but absolutely doesn't work. I could place an element on top of everything, covering the entire window and detect on that, but that'd be a horrible way to go about it.
What am I missing?
I solved it with a timeout (not squeaky-clean, but works):
var dropTarget = $('.dropTarget'),
html = $('html'),
showDrag = false,
timeout = -1;
html.bind('dragenter', function () {
dropTarget.addClass('dragging');
showDrag = true;
});
html.bind('dragover', function(){
showDrag = true;
});
html.bind('dragleave', function (e) {
showDrag = false;
clearTimeout( timeout );
timeout = setTimeout( function(){
if( !showDrag ){ dropTarget.removeClass('dragging'); }
}, 200 );
});
My example uses jQuery, but it's not necessary. Here's a summary of what's going on:
Set a flag (showDrag) to true on dragenter and dragover of the html (or body) element.
On dragleave set the flag to false. Then set a brief timeout to check if the flag is still false.
Ideally, keep track of the timeout and clear it before setting the next one.
This way, each dragleave event gives the DOM enough time for a new dragover event to reset the flag. The real, final dragleave that we care about will see that the flag is still false.
Modified version from Rehmat (thx)
I liked this idea and instead of writing a new answer, I am updating it here itself. It can be made more precise by checking window dimensions.
var body = document.querySelector("body");
body.ondragleave = (e) => {
if (
e.clientX >= 0 && e.clientX <= body.clientWidth
&& e.clientY >= 0 && e.clientY <= body.clientHeight
) {} else {
// do something here
}
}
Old Version
Don't know it this works for all cases but in my case it worked very well
$('body').bind("dragleave", function(e) {
if (!e.originalEvent.clientX && !e.originalEvent.clientY) {
//outside body / window
}
});
Adding the events to document seemed to work? Tested with Chrome, Firefox, IE 10.
The first element that gets the event is <html>, which should be ok I think.
var dragCount = 0,
dropzone = document.getElementById('dropzone');
function dragenterDragleave(e) {
e.preventDefault();
dragCount += (e.type === "dragenter" ? 1 : -1);
if (dragCount === 1) {
dropzone.classList.add('drag-highlight');
} else if (dragCount === 0) {
dropzone.classList.remove('drag-highlight');
}
};
document.addEventListener("dragenter", dragenterDragleave);
document.addEventListener("dragleave", dragenterDragleave);
Here's another solution. I wrote it in React, but I'll explain it at the end if you want to rebuild it in plain JS. It's similar to other answers here, but perhaps slightly more refined.
import React from 'react';
import styled from '#emotion/styled';
import BodyEnd from "./BodyEnd";
const DropTarget = styled.div`
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
pointer-events: none;
background-color:rgba(0,0,0,.5);
`;
function addEventListener<K extends keyof DocumentEventMap>(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions) {
document.addEventListener(type, listener, options);
return () => document.removeEventListener(type, listener, options);
}
function setImmediate(callback: (...args: any[]) => void, ...args: any[]) {
let cancelled = false;
Promise.resolve().then(() => cancelled || callback(...args));
return () => {
cancelled = true;
};
}
function noop(){}
function handleDragOver(ev: DragEvent) {
ev.preventDefault();
ev.dataTransfer!.dropEffect = 'copy';
}
export default class FileDrop extends React.Component {
private listeners: Array<() => void> = [];
state = {
dragging: false,
}
componentDidMount(): void {
let count = 0;
let cancelImmediate = noop;
this.listeners = [
addEventListener('dragover',handleDragOver),
addEventListener('dragenter',ev => {
ev.preventDefault();
if(count === 0) {
this.setState({dragging: true})
}
++count;
}),
addEventListener('dragleave',ev => {
ev.preventDefault();
cancelImmediate = setImmediate(() => {
--count;
if(count === 0) {
this.setState({dragging: false})
}
})
}),
addEventListener('drop',ev => {
ev.preventDefault();
cancelImmediate();
if(count > 0) {
count = 0;
this.setState({dragging: false})
}
}),
]
}
componentWillUnmount(): void {
this.listeners.forEach(f => f());
}
render() {
return this.state.dragging ? <BodyEnd><DropTarget/></BodyEnd> : null;
}
}
So, as others have observed, the dragleave event fires before the next dragenter fires, which means our counter will momentarily hit 0 as we drag files (or whatever) around the page. To prevent that, I've used setImmediate to push the event to the bottom of JavaScript's event queue.
setImmediate isn't well supported, so I wrote my own version which I like better anyway. I haven't seen anyone else implement it quite like this. I use Promise.resolve().then to move the callback to the next tick. This is faster than setImmediate(..., 0) and simpler than many of the other hacks I've seen.
Then the other "trick" I do is to clear/cancel the leave event callback when you drop a file just in case we had a callback pending -- this will prevent the counter from going into the negatives and messing everything up.
That's it. Seems to work very well in my initial testing. No delays, no flashing of my drop target.
Can get the file count too with ev.dataTransfer.items.length
#tyler's answer is the best! I have upvoted it. After spending so many hours I got that suggestion working exactly as intended.
$(document).on('dragstart dragenter dragover', function(event) {
// Only file drag-n-drops allowed, http://jsfiddle.net/guYWx/16/
if ($.inArray('Files', event.originalEvent.dataTransfer.types) > -1) {
// Needed to allow effectAllowed, dropEffect to take effect
event.stopPropagation();
// Needed to allow effectAllowed, dropEffect to take effect
event.preventDefault();
$('.dropzone').addClass('dropzone-hilight').show(); // Hilight the drop zone
dropZoneVisible= true;
// http://www.html5rocks.com/en/tutorials/dnd/basics/
// http://api.jquery.com/category/events/event-object/
event.originalEvent.dataTransfer.effectAllowed= 'none';
event.originalEvent.dataTransfer.dropEffect= 'none';
// .dropzone .message
if($(event.target).hasClass('dropzone') || $(event.target).hasClass('message')) {
event.originalEvent.dataTransfer.effectAllowed= 'copyMove';
event.originalEvent.dataTransfer.dropEffect= 'move';
}
}
}).on('drop dragleave dragend', function (event) {
dropZoneVisible= false;
clearTimeout(dropZoneTimer);
dropZoneTimer= setTimeout( function(){
if( !dropZoneVisible ) {
$('.dropzone').hide().removeClass('dropzone-hilight');
}
}, dropZoneHideDelay); // dropZoneHideDelay= 70, but anything above 50 is better
});
Your third argument to addEventListener is true, which makes the listener run during capture phase (see http://www.w3.org/TR/DOM-Level-3-Events/#event-flow for a visualization). This means that it will capture the events intended for its descendants - and for the body that means all elements on the page. In your handlers, you'll have to check if the element they're triggered for is the body itself. I'll give you my very dirty way of doing it. If anyone knows a simpler way that actually compares elements, I'd love to see it.
this.dragenter = function() {
if ($('body').not(this).length != 0) return;
... functional code ...
}
This finds the body and removes this from the set of elements found. If the set isn't empty, this wasn't the body, so we don't like this and return. If this is body, the set will be empty and the code executes.
You can try with a simple if (this == $('body').get(0)), but that will probably fail miserably.
I was having trouble with this myself and came up with a usable solution, though I'm not crazy about having to use an overlay.
Add ondragover, ondragleave and ondrop to window
Add ondragenter, ondragleave and ondrop to an overlay and a target element
If drop occurs on the window or overlay, it is ignored, whereas the target handles the drop as desired. The reason we need an overlay is because ondragleave triggers every time an element is hovered, so the overlay prevents that from happening, while the drop zone is given a higher z-index so that the files can be dropped. I am using some code snippets found in other drag and drop related questions, so I cannot take full credit. Here's the full HTML:
<!DOCTYPE html>
<html>
<head>
<title>Drag and Drop Test</title>
<meta http-equiv="X-UA-Compatible" content="chrome=1" />
<style>
#overlay {
display: none;
left: 0;
position: absolute;
top: 0;
z-index: 100;
}
#drop-zone {
background-color: #e0e9f1;
display: none;
font-size: 2em;
padding: 10px 0;
position: relative;
text-align: center;
z-index: 150;
}
#drop-zone.hover {
background-color: #b1c9dd;
}
output {
bottom: 10px;
left: 10px;
position: absolute;
}
</style>
<script>
var windowInitialized = false;
var overlayInitialized = false;
var dropZoneInitialized = false;
function handleFileSelect(e) {
e.preventDefault();
var files = e.dataTransfer.files;
var output = [];
for (var i = 0; i < files.length; i++) {
output.push('<li>',
'<strong>', escape(files[i].name), '</strong> (', files[i].type || 'n/a', ') - ',
files[i].size, ' bytes, last modified: ',
files[i].lastModifiedDate ? files[i].lastModifiedDate.toLocaleDateString() : 'n/a',
'</li>');
}
document.getElementById('list').innerHTML = '<ul>' + output.join('') + '</ul>';
}
window.onload = function () {
var overlay = document.getElementById('overlay');
var dropZone = document.getElementById('drop-zone');
dropZone.ondragenter = function () {
dropZoneInitialized = true;
dropZone.className = 'hover';
};
dropZone.ondragleave = function () {
dropZoneInitialized = false;
dropZone.className = '';
};
dropZone.ondrop = function (e) {
handleFileSelect(e);
dropZoneInitialized = false;
dropZone.className = '';
};
overlay.style.width = (window.innerWidth || document.body.clientWidth) + 'px';
overlay.style.height = (window.innerHeight || document.body.clientHeight) + 'px';
overlay.ondragenter = function () {
if (overlayInitialized) {
return;
}
overlayInitialized = true;
};
overlay.ondragleave = function () {
if (!dropZoneInitialized) {
dropZone.style.display = 'none';
}
overlayInitialized = false;
};
overlay.ondrop = function (e) {
e.preventDefault();
dropZone.style.display = 'none';
};
window.ondragover = function (e) {
e.preventDefault();
if (windowInitialized) {
return;
}
windowInitialized = true;
overlay.style.display = 'block';
dropZone.style.display = 'block';
};
window.ondragleave = function () {
if (!overlayInitialized && !dropZoneInitialized) {
windowInitialized = false;
overlay.style.display = 'none';
dropZone.style.display = 'none';
}
};
window.ondrop = function (e) {
e.preventDefault();
windowInitialized = false;
overlayInitialized = false;
dropZoneInitialized = false;
overlay.style.display = 'none';
dropZone.style.display = 'none';
};
};
</script>
</head>
<body>
<div id="overlay"></div>
<div id="drop-zone">Drop files here</div>
<output id="list"><output>
</body>
</html>
I see a lot of overengineered solutions out there. You should be able to achieve this by simply listening to dragenter and dragleave as your gut seemingly told you.
The tricky part is that when dragleave fires, it seems to have its toElement and fromElement inverted from what makes sense in everyday life (which kind of makes sense in logical terms since it's the inverted action of dragenter).
Bottom-line when you move the cursor from the listening element to outside that element, toElement will have the listening element and fromElement will have the outer non-listening element. In our case, fromElement will be null when we drag outside the browser.
Solution
window.addEventListener("dragleave", function(e){
if (!e.fromElement){
console.log("Dragging back to OS")
}
})
window.addEventListener("dragenter", function(e){
console.log("Dragging to browser")
})
The ondragenter is fired quite often. You can avoid using a helper variable like draggedFile. If you don't care how often your on ondragenter function is being called, you can remove that helper variable.
Solution:
let draggedFile = false;
window.ondragenter = (e) => {
if(!draggedFile) {
draggedFile = true;
console.log("dragenter");
}
}
window.ondragleave = (e) => {
if (!e.fromElement && draggedFile) {
draggedFile = false;
console.log("dragleave");
}
}
Have you noticed that there is a delay before the dropzone disappears in Gmail? My guess is that they have it disappear on a timer (~500ms) that gets reset by dragover or some such event.
The core of the problem you described is that dragleave is triggered even when you drag into a child element. I'm trying to find a way to detect this, but I don't have an elegantly clean solution yet.
really sorry to post something that is angular & underscore specific, however the way i solved the problem (HTML5 spec, works on chrome) should be easy to observe.
.directive('documentDragAndDropTrigger', function(){
return{
controller: function($scope, $document){
$scope.drag_and_drop = {};
function set_document_drag_state(state){
$scope.$apply(function(){
if(state){
$document.context.body.classList.add("drag-over");
$scope.drag_and_drop.external_dragging = true;
}
else{
$document.context.body.classList.remove("drag-over");
$scope.drag_and_drop.external_dragging = false;
}
});
}
var drag_enters = [];
function reset_drag(){
drag_enters = [];
set_document_drag_state(false);
}
function drag_enters_push(event){
var element = event.target;
drag_enters.push(element);
set_document_drag_state(true);
}
function drag_leaves_push(event){
var element = event.target;
var position_in_drag_enter = _.find(drag_enters, _.partial(_.isEqual, element));
if(!_.isUndefined(position_in_drag_enter)){
drag_enters.splice(position_in_drag_enter,1);
}
if(_.isEmpty(drag_enters)){
set_document_drag_state(false);
}
}
$document.bind("dragenter",function(event){
console.log("enter", "doc","drag", event);
drag_enters_push(event);
});
$document.bind("dragleave",function(event){
console.log("leave", "doc", "drag", event);
drag_leaves_push(event);
console.log(drag_enters.length);
});
$document.bind("drop",function(event){
reset_drag();
console.log("drop","doc", "drag",event);
});
}
};
})
I use a list to represent the elements that have triggered a drag enter event. when a drag leave event happens i find the element in the drag enter list that matches, remove it from the list, and if the resulting list is empty i know that i have dragged outside of the document/window.
I need to reset the list containing dragged over elements after a drop event occurs, or the next time I start dragging something the list will be populated with elements from the last drag and drop action.
I have only tested this on chrome so far. I made this because Firefox and chrome have different API implementations of HTML5 DND. (drag and drop).
really hope this helps some people.
When the file enters and leaves child elements it fires additional dragenter and dragleave so you need to count up and down.
var count = 0
document.addEventListener("dragenter", function() {
if (count === 0) {
setActive()
}
count++
})
document.addEventListener("dragleave", function() {
count--
if (count === 0) {
setInactive()
}
})
document.addEventListener("drop", function() {
if (count > 0) {
setInactive()
}
count = 0
})
I found out from looking at the spec that if the evt.dataTransfer.dropEffect on dragEnd match none then it's a cancelation.
I did already use that event to handle copying without affecting the clipboard. so this was good for me.
When I hit Esc then the drop effect was equal to none
window.ondragend = evt => {
if (evt.dataTransfer.dropEffect === 'none') abort
if (evt.dataTransfer.dropEffect === 'copy') copy // user holds alt on mac
if (evt.dataTransfer.dropEffect === 'move') move
}
on "dropend" event you can check the value of the document.focus() was the magic trick in my case.