HitTestObject between a Jelly and a character - actionscript-3

I have a code for a character and a Jelly.
Character:
up_btn.addEventListener(MouseEvent.CLICK, function(){
character.y-=10;
})
down_btn.addEventListener(MouseEvent.CLICK, function(){
character.y+=10;
})
left_btn.addEventListener(MouseEvent.CLICK, function(){
character.x+=10;
})
right_btn.addEventListener(MouseEvent.CLICK, function(){
character.x-=10;
})
Jelly:
var JellyHitted:Boolean;
function hitJelly(e: Event):void {
if (character.hitTestObject(Jelly1)) {
JellyHitted = true;
character.gotoAndStop(7);
if (character.electrocuted.currentFrame == 30) {
character.gotoAndStop(1);
}
}
else{
JellyHitted = false;
}
Jelly Test
Any ideas how to keep a character until it hits the jelly?

Ok, I cannot test it, but the idea is the following:
function hitJelly(e:Event):void
{
if (character.hitTestObject(Jelly1))
{
character.gotoAndStop(7);
// Now you need to monitor the animation for a while.
addEventListener(Event.ENTER_FRAME, onElectrcuted);
}
}
// Electrocution handler.
function onElectrocuted(e:Event):void
{
// I assume you want animation to end, rather then to reach a certain frame.
if (character.electrocuted.currentFrame >= character.electrocuted.totalFrames)
{
// Stop monitoring.
removeEventListener(Event.ENTER_FRAME, onElectrcuted);
// Return to normal.
character.gotoAndStop(1);
}
}

Related

Smooch chat has elements on top of it, how can I fix this?

I am using this library:
!function(e,n,t,r){
function o(){try{var e;if((e="string"==typeof this.response?JSON.parse(this.response):this.response).url){var t=n.getElementsByTagName("script")[0],r=n.createElement("script");r.async=!0,r.src=e.url,t.parentNode.insertBefore(r,t)}}catch(e){}}var s,p,a,i=[],c=[];e[t]={init:function(){s=arguments;var e={then:function(n){return c.push({type:"t",next:n}),e},catch:function(n){return c.push({type:"c",next:n}),e}};return e},on:function(){i.push(arguments)},render:function(){p=arguments},destroy:function(){a=arguments}},e.__onWebMessengerHostReady__=function(n){if(delete e.__onWebMessengerHostReady__,e[t]=n,s)for(var r=n.init.apply(n,s),o=0;o<c.length;o++){var u=c[o];r="t"===u.type?r.then(u.next):r.catch(u.next)}p&&n.render.apply(n,p),a&&n.destroy.apply(n,a);for(o=0;o<i.length;o++)n.on.apply(n,i[o])};var u=new XMLHttpRequest;u.addEventListener("load",o),u.open("GET","https://"+r+".webloader.smooch.io/",!0),u.responseType="json",u.send()
}(window,document,"Smooch",smooch_key);
And this is how I init:
function initSmooch() {
if (!$rootScope.smooch_inited) {
try {
var window_focus = true;
$(window).focus(function() {
window_focus = true;
}).blur(function() {
window_focus = false;
});
Smooch.on('message:received', function() {
if (!window_focus || !Smooch.isOpened()) {
//Play sound
var audio = new Audio('assets/audio/ding.wav');
audio.play();
}
Smooch.open();
});
var promise = Smooch.init({
appId: smooch_key,
});
promise.then(function() {
$('#sk-holder').addClass('no-print');
$rootScope.smooch_inited = true;
Smooch.updateUser({
givenName: $rootScope.data.user.first_name,
surname: $rootScope.data.user.last_name,
properties: {
email: $rootScope.data.user.email,
uid: $rootScope.data.user.id,
language: $rootScope.data.user.language,
country: $rootScope.data.user.country
}
});
});
} catch(e) {
console.log("error trying to init smooch: " + e);
$timeout(function() {
initSmooch();
}, 1000);
}
}
}
Looked but I have nothing where I add the view, so I don't know how to modify, force it to be on top. Any ideeas?
This looks like there is a conflict between the Z index of the Web Messenger and the elements beneath it. The Web Messenger already has a very high z-index of 9998, but to fix this issue you'll either need to increase that value more, or decrease the z-index of the elements that appear on top.
For the former, you can insert this CSS rule on your page and tweak the value to something suitable:
#web-messenger-container {
z-index: 9998;
}
Or write a similar rule targeting the problematic elements and giving them a lower number

jquery sidebar putting next to each other

I have put together a sidebar with hover-delay animation, but I can't seem to exactly copy the column to place next to the first. This is my first problem.
The second is that I would like to use the jspanel plugin, so that a dragable window will pop up when I click on a sub-item in the sidebar.
I hope this can be brought to a working state.
Thank you very much for responses in advance!
Here's [a link] (http://jsfiddle.net/chrisoutwright/tc4d9t6d/)!
$('#categories').corner("top keep");
$(document).ready(function(){
$("#foo").click(function(){
$().jsPanel().show();
});
});
$( "#navigation ul.sub-level" ).corner("").css( "border", "3px double blue" );
jQuery.fn.hoverWithDelay = function(inCallback,outCallback,delay) {
this.each(function() {
var timer, $this = this;
$(this).hover(function(){
timer = setTimeout(function(){
timer = null;
inCallback.call($this);
}, delay);
},function() {
if (timer) {
clearTimeout(timer);
timer = null;
} else
outCallback.call($this);
});
});
};
var hovering = {mainMenu: false, categories: false};
function closeSubMenus() {
$('ul.sub-level').css('display', 'none');
}
closeSubMenus();
function closeMenuIfOut() {
setTimeout(function(){
if (!hovering.mainMenu && !hovering.categories) {
$('#navigation').fadeOut('fast',closeSubMenus);
}
},100);
}
$('ul.top-level li').hoverWithDelay(function() {
$(this).find('ul').show();
}, function() {
$(this).find('ul').fadeOut('fast', closeMenuIfOut);
}, 500);
$('#categories').hoverWithDelay(function() {
$('#navigation').show();
hovering.categories = true;
},
function(){
hovering.categories = false;
closeMenuIfOut();
},500);
$('#navigation').hover(function() {
hovering.mainMenu = true;
}, function() {
hovering.mainMenu = false;
});
I can see at least one error in line 4 where you try to generate/open the jsPanel.
Which jsPanel version do you use? Version 1.x or Version 2.x? The two versions differ on how to use the jsPanel() command.
version 1.x: $( selector ).jsPanel( config );
version 2.x: $.jsPanel( config );
Do you get any error messages?

jquery toggle function and set a cookie

I have the following function which toggles mouseenter and mouseleave on click:
var flag = true;
$('.aaa').mouseenter(function () {
if(flag) {
$(this).css('background', '#aaaaaa');
}
$(this).css('border', 'solid 1px red');
});
$('.aaa').mouseleave(function () {
if(flag) {
$(this).css('background','blue');
}
$(this).css('border', 'solid transparent 1px');
});
$('#tog').click(function () {
flag = !flag;
});
http://jsfiddle.net/z8KuE/15/
How can chosen preference be "remembered" and loaded on the next page load?
edit: in case that the solution from bellow doesn't work on the site for some reason, just put it here:
(function($){
$(document).ready(function(){
//Scripts go in here!
});
})(jQuery);
I would use the jQuery cookie plugin:
var storedFlag = $.cookie('userSelection'); // read cookie
var flag = 1; //default value
if(storedFlag != undefined){ // some flag was stored
flag = storedFlag;
}
$('.aaa').mouseenter(function () {
if(flag > 0) {
$(this).css('background', '#aaaaaa');
}
$(this).css('border', 'solid 1px red');
});
$('.aaa').mouseleave(function () {
if(flag > 0) {
$(this).css('background','blue');
}
$(this).css('border', 'solid transparent 1px');
});
$('#tog').click(function () {
flag = 1 - flag;
$.cookie('userSelection', flag, { expires: 30 }); // store cookie
});
The problem is that the boolean values are stored as strings, and the string 'false' is a true value, thus i resorted to using numbers and >0 comparison.
See updated fiddle

Flash cs6 AS3 Error #2007: Parameter child must be non-null

I just implemented that last piece of code you sent over - many thanks!!
This is the FULL context of this frame, the complete code with the other buttons as well, in case that is causing the problem. The 19 errors I get with this piece of code is:
1120: Access of undefined property fl_ProLoader_01
stop();
//home button
mythbutt_home.addEventListener(MouseEvent.CLICK, fl_ClickToLoadUnloadSWF_01_1,false,0,true);
function fl_ClickToLoadUnloadSWF_01_1(event:MouseEvent):void
{
removeChild(fl_ProLoader_01);
fl_ProLoader_01.unloadAndStop();
fl_ProLoader_01 = null;
}
mythbutt_home.addEventListener(MouseEvent.CLICK, fl_ClickToGoToAndStopAtFrame_01_1,false,0,true);
function fl_ClickToGoToAndStopAtFrame_01_1(event:MouseEvent):void
{
removeChild(fl_ProLoader_01);
}
mythbutt_home.addEventListener(MouseEvent.CLICK, fl_ClickToStopAllSounds_01_1,false,0,true);
function fl_ClickToStopAllSounds_01_1(event:MouseEvent):void
{
SoundMixer.stopAll();
}
mythbutt_home.addEventListener(MouseEvent.CLICK, fl_ClickToGoToAndStopAtFrame_01_2,false,0,true);
function fl_ClickToGoToAndStopAtFrame_01_2(event:MouseEvent):void
{
gotoAndStop(1);
}
//other buttons at the bottom
mythbutt_aboriginal_culture.addEventListener(MouseEvent.CLICK, fl_ClickToGoToWebPage_01_1);
function fl_ClickToGoToWebPage_01_1(event:MouseEvent):void
{
navigateToURL(new URLRequest("http://www.bigmyth.com/fullversion/password033/download/ABORIGINAL_CULTURE.pdf"), "_blank");
}
mythbutt_aboriginal_pantheon.addEventListener(MouseEvent.CLICK, fl_ClickToGoToWebPage_01_2);
function fl_ClickToGoToWebPage_01_2(event:MouseEvent):void
{
navigateToURL(new URLRequest("http://www.bigmyth.com/fullversion/password033/download/ABORIGINAL_PANTHEON.pdf"), "_blank");
}
mythbutt_aboriginal_exercises.addEventListener( MouseEvent.CLICK, fl_ClickToGoToWebPage_01_3);
function fl_ClickToGoToWebPage_01_3(event:MouseEvent):void
{
navigateToURL(new URLRequest( "http://www.bigmyth.com/fullversion/password033/download/ABORIGINAL_EXERCISES.pdf"), "_blank");
}
//start button
//Change your event handler function.
start_button_aboriginal.addEventListener(MouseEvent.CLICK,fl_ClickToLoadSWF_01_2);
function fl_ClickToLoadSWF_01_2(event:MouseEvent):void {
fl_ProLoader_01=new ProLoader ;
fl_ProLoader_01.load(new URLRequest("myths/myth_aboriginal.swf"));
fl_ProLoader_01.contentLoaderInfo.addEventListener(Event.COMPLETE,
//Using closure callback instead of *onComplete_1* function
function( e : Event ) {
e.currentTarget.content.addEventListener( Event.ENTER_FRAME, OEF_01);
});
addChild(fl_ProLoader_01);
fl_ProLoader_01.x=323;
fl_ProLoader_01.y=41;
//Swap the event handlers,no need for flag,clear code blocks
start_button_aboriginal.removeEventListener(MouseEvent.CLICK,fl_ClickToLoadSWF_01_2);
start_button_aboriginal.addEventListener(MouseEvent.CLICK,fl_ClickToUnLoadSWF_01_2);
}
function fl_ClickToUnLoadSWF_01_2(event:MouseEvent):void {
fl_ProLoader_01.removeEventListener(Event.ENTER_FRAME,OEF_01);
removeChild(fl_ProLoader_01);
fl_ProLoader_01.unloadAndStop();
fl_ProLoader_01=null;
start_button_aboriginal.removeEventListener(MouseEvent.CLICK,fl_ClickToUnLoadSWF_01_2);
}
function OEF_01(e:Event):void {
if (e.currentTarget.currentFrame==e.currentTarget.totalFrames) {
e.currentTarget.stop();
fl_ClickToUnLoadSWF_01_2(null);
}
}
I think #GarryWong is right. If you click the start button after the external swf has been loaded and before his timeline has finished you will get an error.
I've changed your implementation, please try this.
No need to check for null object.
UPDATE
//Change your event handler function.
startbutton.addEventListener(MouseEvent.CLICK,fl_ClickToLoadSWF_1_2);
function fl_ClickToLoadSWF_1_2(event:MouseEvent):void {
fl_ProLoader_1=new ProLoader();
fl_ProLoader_1.load(new URLRequest("myths/myth_aboriginal.swf"));
fl_ProLoader_1.contentLoaderInfo.addEventListener(Event.COMPLETE,
//Using closure callback instead of *onComplete_1* function
function( e : Event ) {
e.currentTarget.content.addEventListener(Event.ENTER_FRAME, OEF_1);
});
addChild(fl_ProLoader_1);
fl_ProLoader_1.x=207;
fl_ProLoader_1.y=41;
//Swap the event handlers,no need for flag,clear code blocks
startbutton.removeEventListener(MouseEvent.CLICK,fl_ClickToLoadSWF_1_2);
startbutton.addEventListener(MouseEvent.CLICK,fl_ClickToUnLoadSWF_1_2);
}
function fl_ClickToUnLoadSWF_1_2(event:MouseEvent):void {
fl_ProLoader_1.removeEventListener(Event.ENTER_FRAME,OEF_1);
removeChild(fl_ProLoader_1);
fl_ProLoader_1.unloadAndStop();
fl_ProLoader_1=null;
startbutton.removeEventListener(MouseEvent.CLICK,fl_ClickToUnLoadSWF_1_2);
//Use the below line only if you want to repeat the procedure of loading the *Proloader*, otherwise omit it.
startbutton.addEventListener(MouseEvent.CLICK,fl_ClickToLoadSWF_1_2);
}
function OEF_1(e:Event):void {
if (e.currentTarget.currentFrame==e.currentTarget.totalFrames) {
e.currentTarget.stop();
fl_ClickToUnLoadSWF_1_2(null);
}
}
Sorry but I haven't Flash installed in my PC right now so maybe there will be some mistakes in my code. You made me boot my Windows partition!

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.