NoModificationAllowedError in Firefox when using DnD events - javascript

Using some basic drag'n'drop features and i keep getting this message in Firefox, works well in Chrome though.
NoModificationAllowedError: Modifications are not allowed for this document
This happens when i try to use the clearData() function on the event when the ondrop event is fired.
HTML:
<!-- draggable element -->
<div draggable="true" ondragstart="onDragStart(event);">
<!-- dropzone element -->
<div ondragover="onDragOver(event);" ondragenter="onDragEnter(event);" ondrop="onDrop(event);"></div>
JS:
function onDragStart(event) {
event
.dataTransfer
.setData('text/plain', 'test');
}
function onDragOver(event) {
event.preventDefault();
}
function onDragEnter(event) {
event.preventDefault(); // polyfill fix
}
function onDrop(event) {
event.preventDefault();
event
.currentTarget
.parentNode
.classList.add("dropped");
event
.dataTransfer
.clearData(); //<---- This is where it fails
}
So yeah no idea how to fix this, and i need to clear the data as i set/reset some variable and internal params using it (code omitted). Works well in chrome (even IE)

Use Cleardata() method in OnDragStart and not use it in OnDrop() Method.
onDragStart(event) {
event.dataTransfer.clearData();
event
.dataTransfer
.setData('text/plain', event.target.dataset.item);
}
Must user text/plain.
Must use preventDefault() while dropping.
onDragOver(event) {
event.preventDefault();
}
Must use preventDefault inside onDrop Method like that.
onDrop(event) {
const id = event
.dataTransfer
.getData('text');
const draggableElement = '';
event.preventDefault();
this.template.querySelectorAll('.example-draggable').forEach(element => {
if (element.innerHTML == id) {
this.draggableElement = element;
}
});;
if ((!event.target.innerText.includes('Drag From Here')) && (!event.target.innerText.includes('Drop Here'))) {
const bar = event.target.parentElement;
bar.appendChild(this.draggableElement);
}
else {
const bar = event.target;
bar.appendChild(this.draggableElement);
}
}
here i use preventdefault in middle of the code that will prevent reloading of the page while appending element.

Related

Is there any way to revert preventDefault method in Vanilla JavaScript?

I'm trying to create a horizontal scrolling container. In a precise case i need to revert e.preventDefault(); from a click.
I tried a lot of options, changing 'window.location.href' in the else statement seems to be a great option.
But i can't figure how to grab the href from the link clicked.
Any idea can help to achieve my goal. :)
slider.addEventListener('mouseup', () => {
isDown = false;
// Disable click event (for ever unfortunately)
if(moved === true) {
this.addEventListener('click', (e) => {
e.preventDefault();
});
} else {
// trying to reset click function
}
You can conditionally prevent a click event from firing on your slider by registering a click event listener that shares the moved variable with your mousedown and mousemove event listeners.
The { passive: true } option indicates that the listener does not call event.preventDefault(), and saves a lot CPU time particularly for the mousemove event which can fire several times per second.
The true parameter indicates that the event listener should be called before the event starts to bubble up from the target element. This allows it to prevent propagation even to listeners that were already added on the same element, as long as they didn't also set useCapture to true.
const slider = document.querySelector('input[type="range"]');
// prevent this if mousemove occurred between mousedown and mouseup
slider.addEventListener('click', () => {
console.log('click event fired on slider');
});
// fires just before click event
slider.addEventListener('mouseup', () => {
console.log('mouseup event fired on slider');
});
let moved = false;
// reset for each potential click
slider.addEventListener('mousedown', () => {
moved = false;
});
// indicate cancellation should occur for click
slider.addEventListener('mousemove', () => {
moved = true;
}, { passive: true });
// prevents click event if mousemove occurred between mousedown and mouseup
slider.addEventListener('click', event => {
if (moved) {
event.preventDefault();
event.stopImmediatePropagation();
}
}, true);
<input type="range" />
You should remove the event listener containing the event.preventDefault();.
In order to do that you have to save your function reference into a variable like so:
const preventClickHandler = (e) => e.preventDefault;
slider.addEventListener('mouseup', () => {
isDown = false;
// Disable click event (for ever unfortunately)
if(moved === true) {
this.addEventListener('click', preventClickHandler);
} else {
this.removeEventListener('click', preventClickHandler);
}
})

How to trigger touch event on mouse click in GeckoFX using C#

I have been trying to trigger a touch event that is restricting me to perform click, as the function is prepared for Smart Phones.
Here is the function that I need to be called on click event using GeckoFX.
$('#next_button,#bottom_next_button,#next_arrow,.image_inner_a').on('touchstart', function(e) {
if (e.touches && e.touches[0].pageX > 5) {
this.href = this.href.replace(/[&?]z=[^&]*/, '');
valid_user = true;
}
});
What I have so far is written below.
GeckoElement clickedElement = e.Target.CastToGeckoElement();
if (xpathvalue != null) {
GeckoHtmlElement element = (GeckoHtmlElement)browser.Document.GetSingleElement(xpathvalue);
if (element != null)
{
element.Click();
}
}
If there is way to use JavaScript for calling the touch event, it would be accepted too.
You can dispatch a custom type of event.
Look at the event documentation here
https://developer.mozilla.org/en-US/docs/Web/API/Touch_events
Then initialize the event:
nsAStringBase eventType = new nsAString(eventName.ToLower());
DomEventArgs ev = browser.Document.CreateEvent(eventTypeName);
ev.DomEvent.InitEvent(eventType, true, false);
And then try dispatching the event on a proper element
element.DispatchEvent(ev);

Remove specific set of event listeners from an HTML element using JavaScript

I want to remove only the mouseup event listners from a selected HTML element.
I used the below code but it will remove all listners.
var old_element = divs[d];
var new_element = old_element.cloneNode(true);
old_element.parentNode.replaceChild(new_element, old_element);
this is how i attach event listners.
var divs = document.getElementsByTagName('body');// to enhance the preformance
for(var d in divs) {
try{
if (divs[d].addEventListener) {
divs[d].addEventListener('mouseup',callHighlight);
} else {
divs[d].attachEvent('mouseup', callHighlight);
}
}catch(err){
//alert(err.message);
}
}
You should use removeEventListener instead of replacechild which will obviously remove all events.
old_element.removeEventListener('mouseup', handler);
When cloning an element, listeners added using addEventListener or by direct property assignment (element.onclick = fn;) are removed, but in–line listeners and those added using IE's attachEvent are not.
In your scenario where listeners are added by reference and also possibly using attachEvent, you are best to remove them using removeEventListener and detachEvent. So you might like to create add and remove functions like:
function addEvent(element, event, fn) {
if (element.addEventListener) {
element.addEventListener(event, fn, false);
} else if (element.attachEvent) {
element.attachEvent('on' + event, fn);
}
}
function removeEvent(element, event, fn) {
if (element.removeEventListener) {
element.removeEventListener(event, fn);
} else if (element.detachEvent) {
element.detachEvent('on' + event, fn);
}
}
Note that there are some significant differences between addEventListener and attachEvent, the most important are that in the latter, this is not set to the element whose handler is calling the function and a reference to the event isn't passed as the first argument to the listener. So the listener function ends up looking like:
function foo(evt) {
evt = evt || window.event;
var target = evt.target || evt.srcElement;
...
}
There are ways around this, but they introduce more issues. Keep it simple if you can.
By default all event listeners are null, so simply just reset it. Problem is that all your mouseup events are registered to the body, so therefore you won't be able to drop the event without first stopping the event from bubbling to the body. You can solve that problem with, stopPropagation()
old_element.onmouseup = function (e) {
// event won't go up to the body tag
e.stopPropagation();
return null;
};
or
function kill (e) {
e.stopPropagation();
return null;
}
old_element.onmouseup = kill;
second_element.onmouseup = kill;
JSFIDDLE

Detect click outside element (vanilla JavaScript)

I have searched for a good solution everywhere, yet I can't find one which does not use jQuery.
Is there a cross-browser, normal way (without weird hacks or easy to break code), to detect a click outside of an element (which may or may not have children)?
Add an event listener to document and use Node.contains() to find whether the target of the event (which is the inner-most clicked element) is inside your specified element. It works even in IE5
const specifiedElement = document.getElementById('a')
// I'm using "click" but it works with any event
document.addEventListener('click', event => {
const isClickInside = specifiedElement.contains(event.target)
if (!isClickInside) {
// The click was OUTSIDE the specifiedElement, do something
}
})
var specifiedElement = document.getElementById('a');
//I'm using "click" but it works with any event
document.addEventListener('click', function(event) {
var isClickInside = specifiedElement.contains(event.target);
if (isClickInside) {
alert('You clicked inside A')
} else {
alert('You clicked outside A')
}
});
div {
margin: auto;
padding: 1em;
max-width: 6em;
background: rgba(0, 0, 0, .2);
text-align: center;
}
Is the click inside A or outside?
<div id="a">A
<div id="b">B
<div id="c">C</div>
</div>
</div>
You need to handle the click event on document level. In the event object, you have a target property, the inner-most DOM element that was clicked. With this you check itself and walk up its parents until the document element, if one of them is your watched element.
See the example on jsFiddle
document.addEventListener("click", function (e) {
var level = 0;
for (var element = e.target; element; element = element.parentNode) {
if (element.id === 'x') {
document.getElementById("out").innerHTML = (level ? "inner " : "") + "x clicked";
return;
}
level++;
}
document.getElementById("out").innerHTML = "not x clicked";
});
As always, this isn't cross-bad-browser compatible because of addEventListener/attachEvent, but it works like this.
A child is clicked, when not event.target, but one of it's parents is the watched element (i'm simply counting level for this). You may also have a boolean var, if the element is found or not, to not return the handler from inside the for clause. My example is limiting to that the handler only finishes, when nothing matches.
Adding cross-browser compatability, I'm usually doing it like this:
var addEvent = function (element, eventName, fn, useCapture) {
if (element.addEventListener) {
element.addEventListener(eventName, fn, useCapture);
}
else if (element.attachEvent) {
element.attachEvent(eventName, function (e) {
fn.apply(element, arguments);
}, useCapture);
}
};
This is cross-browser compatible code for attaching an event listener/handler, inclusive rewriting this in IE, to be the element, as like jQuery does for its event handlers. There are plenty of arguments to have some bits of jQuery in mind ;)
How about this:
jsBin demo
document.onclick = function(event){
var hasParent = false;
for(var node = event.target; node != document.body; node = node.parentNode)
{
if(node.id == 'div1'){
hasParent = true;
break;
}
}
if(hasParent)
alert('inside');
else
alert('outside');
}
you can use composePath() to check if the click happened outside or inside of a target div that may or may not have children:
const targetDiv = document.querySelector('#targetDiv')
document.addEventListener('click', (e) => {
const isClickedInsideDiv = e.composedPath().includes(targetDiv)
if (isClickedInsideDiv) {
console.log('clicked inside of div')
} else {
console.log('clicked outside of div')
}
})
I did a lot of research on it to find a better method. JavaScript method .contains go recursively in DOM to check whether it contains target or not. I used it in one of react project but when react DOM changes on set state, .contains method does not work. SO i came up with this solution
//Basic Html snippet
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="mydiv">
<h2>
click outside this div to test
</h2>
Check click outside
</div>
</body>
</html>
//Implementation in Vanilla javaScript
const node = document.getElementById('mydiv')
//minor css to make div more obvious
node.style.width = '300px'
node.style.height = '100px'
node.style.background = 'red'
let isCursorInside = false
//Attach mouseover event listener and update in variable
node.addEventListener('mouseover', function() {
isCursorInside = true
console.log('cursor inside')
})
/Attach mouseout event listener and update in variable
node.addEventListener('mouseout', function() {
isCursorInside = false
console.log('cursor outside')
})
document.addEventListener('click', function() {
//And if isCursorInside = false it means cursor is outside
if(!isCursorInside) {
alert('Outside div click detected')
}
})
WORKING DEMO jsfiddle
using the js Element.closest() method:
let popup = document.querySelector('.parent-element')
popup.addEventListener('click', (e) => {
if (!e.target.closest('.child-element')) {
// clicked outside
}
});
To hide element by click outside of it I usually apply such simple code:
var bodyTag = document.getElementsByTagName('body');
var element = document.getElementById('element');
function clickedOrNot(e) {
if (e.target !== element) {
// action in the case of click outside
bodyTag[0].removeEventListener('click', clickedOrNot, true);
}
}
bodyTag[0].addEventListener('click', clickedOrNot, true);
Another very simple and quick approach to this problem is to map the array of path into the event object returned by the listener. If the id or class name of your element matches one of those in the array, the click is inside your element.
(This solution can be useful if you don't want to get the element directly (e.g: document.getElementById('...'), for example in a reactjs/nextjs app, in ssr..).
Here is an example:
document.addEventListener('click', e => {
let clickedOutside = true;
e.path.forEach(item => {
if (!clickedOutside)
return;
if (item.className === 'your-element-class')
clickedOutside = false;
});
if (clickedOutside)
// Make an action if it's clicked outside..
});
I hope this answer will help you !
(Let me know if my solution is not a good solution or if you see something to improve.)

How to continue event propagation after cancelling?

When a user clicks a certain link I would like to present them with a confirmation dialog. If they click "Yes" I would like to continue the original navigation. One catch: my confirmation dialog is implemented by returning a jQuery.Deferred object which is resolved only when/if the user clicks the Yes button. So basically the confirmation dialog is asynchronous.
So basically I want something like this:
$('a.my-link').click(function(e) {
e.preventDefault(); e.stopPropogation();
MyApp.confirm("Are you sure you want to navigate away?")
.done(function() {
//continue propogation of e
})
})
Of course I could set a flag and re-trigger click but that is messy as heck. Any natural way of doing this?
Below are the bits from the code that actually worked in Chrome 13, to my surprise.
function handler (evt ) {
var t = evt.target;
...
setTimeout( function() {
t.dispatchEvent( evt )
}, 1000);
return false;
}
This is not very cross-browser, and maybe will be fixed in future, because it feels like security risk, imho.
And i don't know what happens, if you cancel event propagation.
It could be risky but seems to work at the time of writing at least, we're using it in production.
This is ES6 and React, I have tested and found it working for the below browsers. One bonus is if there is an exception (had a couple during the way making this), it goes to the link like a normal <a> link, but it won't be SPA then ofc.
Desktop:
Chrome v.76.0.3809.132
Safari v.12.1.2
Firefox Quantum v.69.0.1
Edge 18
Edge 17
IE11
Mobile/Tablet:
Android v.8 Samsung Internet
Android v.8 Chrome
Android v.9 Chrome
iOs11.4 Safari
iOs12.1 Safari
.
import 'mdn-polyfills/MouseEvent'; // for IE11
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
class ProductListLink extends Component {
constructor(props) {
super(props);
this.realClick = true;
this.onProductClick = this.onProductClick.bind(this);
}
onProductClick = (e) => {
const { target, nativeEvent } = e;
const clonedNativeEvent = new MouseEvent('click', nativeEvent);
if (!this.realClick) {
this.realClick = true;
return;
}
e.preventDefault();
e.stopPropagation();
// #todo what you want before the link is acted on here
this.realClick = false;
target.dispatchEvent(clonedNativeEvent);
};
render() {
<Link
onClick={(e => this.onProductClick(e))}
>
Lorem
</Link>
}
}
I solved problem by this way on one of my projects. This example works with some basic event handling like clicks etc. Handler for confirmation must be first handler bound.
// This example assumes clickFunction is first event handled.
//
// you have to preserve called function handler to ignore it
// when you continue calling.
//
// store it in object to preserve function reference
var ignoredHandler = {
fn: false
};
// function which will continues processing
var go = function(e, el){
// process href
var href = $(el).attr('href');
if (href) {
window.location = href;
}
// process events
var events = $(el).data('events');
for (prop in events) {
if (events.hasOwnProperty(prop)) {
var event = events[prop];
$.each(event, function(idx, handler){
// do not run for clickFunction
if (ignoredHandler.fn != handler.handler) {
handler.handler.call(el, e);
}
});
}
}
}
// click handler
var clickFunction = function(e){
e.preventDefault();
e.stopImmediatePropagation();
MyApp.confirm("Are you sure you want to navigate away?")
.done(go.apply(this, e));
};
// preserve ignored handler
ignoredHandler.fn = clickFunction;
$('.confirmable').click(clickFunction);
// a little bit longer but it works :)
If I am understanding the problem correctly, I think you can just update the event to be the original event in that closure you have there. So just set e = e.originalEvent in the .done function.
https://jsfiddle.net/oyetxu54/
MyApp.confirm("confirmation?")
.done(function(){ e = e.originalEvent;})
here is a fiddle with a different example (keep the console open so you can see the messages):
this worked for me in chrome and firefox
I solved this by:
placing a event listener on a parent element
removing the class from the link ONLY when the user confirms
reclicking the link after I have removed the class.
function async() {
var dfd = $.Deferred();
// simulate async
setTimeout(function () {
if (confirm('Stackoverflow FTW')) {
dfd.resolve();
} else {
dfd.reject();
}
}, 0);
return dfd.promise();
};
$('.container').on('click', '.another-page', function (e) {
e.stopPropagation();
e.preventDefault();
async().done(function () {
$(e.currentTarget).removeClass('another-page').click();
});
});
$('body').on('click', function (e) {
alert('navigating somewhere else woot!')
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container">
Somewhere else
</div>
The reason I added the event listener to the parent and not the link itself is because the jQuery's on event will bind to the element until told otherwise. So even though the element does not have the class another-page it still has the event listener attached thus you have to take advantage of event delegation to solve this problem.
GOTCHAS this is very state based. i.e. if you need to ask the user EVERYTIME they click on a link you'll have to add a 2nd listener to readd the another-page class back on to the link. i.e.:
$('body').on('click', function (e) {
$(e.currentTarget).addClass('another-page');
});
side note you could also remove the event listener on container if the user accepts, if you do this make sure you use namespace events because there might be other listeners on container you might inadvertently remove. see https://api.jquery.com/event.namespace/ for more details.
We have a similar requirement in our project and this works for me. Tested in chrome and IE11.
$('a.my-link').click(function(e) {
e.preventDefault();
if (do_something === true) {
e.stopPropogation();
MyApp.confirm("Are you sure you want to navigate away?")
.done(function() {
do_something = false;
// this allows user to navigate
$(e.target).click();
})
}
})
I edited your code. New features that I added:
Added namespace to event;
After click on element event will be removed by namespace;
Finally, after finish needed actions in "MyApp" section continue propagation by triggering others element "click" events.
Code:
$('a.my-link').on("click.myEvent", function(e) {
var $that = $(this);
$that.off("click.myEvent");
e.preventDefault();
e.stopImmediatePropagation();
MyApp.confirm("Are you sure you want to navigate away?")
.done(function() {
//continue propogation of e
$that.trigger("click");
});
});
This is untested but might serve as a workaround for you
$('a.my-link').click(function(e) {
e.preventDefault(); e.stopPropogation();
MyApp.confirm("Are you sure you want to navigate away?")
.done(function() {
//continue propogation of e
$(this).unbind('click').click()
})
})

Categories