A more efficient way to enable right clicking? - javascript

I wrote this script a while back that's worked better for me than any other right-click-enabling extension.
function rightClickEvent(event) {
event.stopPropagation();
return true;
}
function enableRightClick(elements) {
if (elements.length === 0) return;
Array.prototype.slice.call(elements, 0).forEach(element => {
element.addEventListener('contextmenu', rightClickEvent, true);
enableRightClick(element.children);
});
}
enableRightClick(document.children);
It seems like overkill because it adds an action listener to every single element on the whole page. Is there any way to achieve similar results, like a way to intercept every event for oncontextmenu, without having to traverse the DOM of the entire page?
To clarify, this is for ENABLING right click in environments where a simple change of contextmenu on the button doesn't work.

I'd suggest that you just use document.getElementsByTagName("*") to fetch a nodeList of all the elements in the document rather than recursively walking the hierarchy yourself.
And, for completeness, you may have to monitor for DOM elements being inserted into the page dynamically after you hook up.
How to work around a page that disables right clicking depends entirely upon how the page implements the disable. If you want a method that handles the most possible ways it might disable it, then you HAVE to handle it at the leaf nodes and on every node like you are doing - there is no other way. Any simpler option that uses event propagation to handle the event at a higher level may be blocked by the JS in the page so if you're trying to avoid being blocked by that, then you have to handle it at the leaf node.

Related

Can you destroy the document element and event listeners without using document.write()?

In the example below:
If you right click the document, it will tell you it's listening.
If you click m1 it will replace the document element, but right clicking the document will still inform you that it's listening. You must right click near the top because the document has no contents.
If you click m2 it will overwrite the document contents and right clicking near the top no-longer does anything. Examining the document in the development tools verifies that the event handlers are gone.
After pressing one button, you must "run code snippet" again to try the next because this demonstration is destructive.
With this information. Is there a different way to destroy the document and replace it with a new one, in such a way that the event handlers are destroyed, without using the document.write() function?
document.write() is prone to errors and its usage is "strongly discouraged", but I would still like to be able to destroy the document and it's event listeners.
document.addEventListener('contextmenu', function (e) {
alert('still listening');
e.preventDefault();
})
function m1() {
var doc = document.implementation.createHTMLDocument();
document.replaceChild(
document.importNode(doc.documentElement, true),
document.documentElement
);
}
function m2() {
document.close();
document.write('<html></html>');
}
<button onclick="m1()">m1</button>
<button onclick="m2()">m2</button>
To be clear, button/function "m1" fails my goals, because although the document element was replaced, the event handlers from the previous document element were preserved for some reason. I would like to achieve what m2 achieves, but without using document.write and document.close which are recommended against.
Addendum:
This question is strictly for the sake of better understanding the limits of the language and the engines that implement them. Please do not try to read between the lines or solve some alternate goal. It's just a question of whether something is possible or not.
I don't need to know how to remove event listeners or manage event listeners, or remove all child elements of a document. I would like to know if it's possible to destroy the document element itself, leaving no <html> tag whatsoever and leaving none of its event listeners behind. This is possible with document.write() but I simply want to know if there are alternate means of achieving this exact goal, not any other assumed goals.
I just realised that actually you can, and that the first example, m1, actually does replace the documentElement and removes its event listeners. The interesting thing is that there were no event listeners on it in the first place, they were on the document itself not the document element. The developer tools (in FireFox) tricks you and shows them as being attached to the <html> element, even though they aren't technically attached to an element. If you modify the example to actually attach the events to the document.documentElement instead of the document itself the event will be disabled when you click m1:
document.documentElement.addEventListener('contextmenu', function (e) {
alert('still listening');
e.preventDefault();
})
function m1() {
var doc = document.implementation.createHTMLDocument();
document.replaceChild(
document.importNode(doc.documentElement, true),
document.documentElement
);
}
function m2() {
document.close();
document.write('<html></html>');
}
<button onclick="m1()">m1</button>
<button onclick="m2()">m2</button>
Originally I was going to say no, and that you can't do it, as user Livingston Samuel states in this similar but not identical question: "You cannot replace the current document object or any document object with the Document object created with createHTMLDocument method."
Sticking to my original question though, it is not the document I was asking about, it was the documentElement. So given the modified code above, I will say that Yes, it is possible.
It is interesting to see that the event listeners are displayed on the html element in FireFox whether they are attached to the document object or the documentElement object, as of version 81. I might expect that they would instead not display them at all when attached to document, or put them on some abstract object. An iframe does have an abstract object available labeled #document, but it is not used for this and has no event label:
It is also interesting that document.write() actually destroys the events on the document object, not just the document.documentElement. It might be documented somewhere in here but I can't seem to find it.
After testing Chrome I noticed that the developer tools distinguishes between events being attached to the documentElement:
and the document itself:
I'm not sure what's behind this question but I may guess, that you are having issues when the objects and events are re-created in the page.
If this is the case, as can be seen in MDN (https://developer.mozilla.org/en-US/docs/Web/API/Node/removeChild), despite you remove the elements on the HTML, they still could remain in the memory for a while, waiting for the garbage collector to clean them up.
So in case you wish to remove safely a piece of code that contains events, the safest way is to remove the events first using your favorite library (like jQuery) or using the DOM through native JavaScript:
https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener
Once you have removed all events, the you could remove all elements, and the safest way to do this (from a memory point of view) is through a loop, removing the inner elements first and the parent elements then, and so on.
You can use document.getElementById('id-here').innerHTML = "", like so:
<html id="the-website">
<body>
<input type="button" onclick="document.getElementById('the-website').innerHTML = '';" value="Don't click me plz!">
</body>
</html>

Using jQuery delegate (on) with a check box causes huge delay in response - why?

Hi I have a dynamically create table which acts as a pick list using check boxes. I Want these check boxes to be mutually exclusive. So upon checking a box I need to clear any other checked boxes.
$(document).on("keydown", "#list_Instructors input.allocate",function(event){
alert("hit");
$("#list_Instructors input.allocate").removeAttr('checked');
$(event.target).attr('checked', 'checked');
});
This sort of works but there is a huge delay between clicking and anything happening which is no good. I have tried all sorts of combinations with no success.
Is there is simple explanation as to why this is creating a delay.
Your problem is you bind on method for whole DOM which is really BAD.
So always try to bind that to the closest div (closest parent element) which your controls are exist.
And second thing is always cache your selectors for better performance.Like below
var dataTable=$('#dataTable');
dataTable.on("click", function(event){
alert($(this).text());
});
About Event performance from Jquery API says like below.
Attaching many delegated event handlers near the top of the document
tree can degrade performance. Each time the event occurs, jQuery must
compare all selectors of all attached events of that type to every
element in the path from the event target up to the top of the
document. For best performance, attach delegated events at a document
location as close as possible to the target elements. Avoid excessive
use of document or document.body for delegated events on large
documents.
What you might be seeing is that until the alert box is dismissed, the code afterwards is not executed. The alert command is a blocking one.
Perhaps you can use console.log() for debugging purposes of this feature. This will not block your code and it will be executed on the keydown event.
You need to use $(this) instead of going through another lookup. Also as stated above try to bind to the closest parent element if possible, for example a container div. With that said this should speed you up a bit:
$(document).on('keydown', '#list_Instructors input.allocate', function (event) {
//alert("hit");
console.log('hit');
$(this).removeAttr('checked');
$(event.target).attr('checked', 'checked');
});
But you should try to replace document with a container div or another parent element

How can I add onLoad events to elements with unknown names at document load using jQuery?

I'm using a third-party commenting plugin, and I would like to change the content of some of the buttons. This is straightforward for buttons with id's known ahead of time, but it also has buttons that don't appear until a 'Reply' button is clicked. To be clear, these elements are not present when the page is loaded. They are inserted into the DOM following some event. For those elements, I only know a prefix of the id.
My first thought was to use .on, and to delegate to the children of the reply container, but the load event does not bubble, so this doesn't work:
<script>
$("#container").on("load", 'a[id|="reply-button"]', function(event) { $(this).html("different text"); } );
</script>
<div id="container">
<a id="reply-button-42das56ds6d78a">some text</a>
</div>
What's the next best thing?
"I know they will appear when the 'Reply' button is clicked. When that happens, new elements are inserted into the DOM, and I know what the prefix of the id of those elements will be."
You could use something like the DOMSubtreeModified event to tell when elements are added, but that isn't supported by all browsers. (In fact it has been deprecated.)
Or you could attach a click handler to the 'Reply' button:
$(document).ready(function() {
// initialise plugin here, then:
$("some selector for the reply button(s)").click(function(e) {
// setTimeout(function() {
$('a[id|="reply-button"]').html("different text");
// }, 10);
});
});
jQuery ensures that multiple event handlers will run in the order they are bound, but of course this only applies to handlers added with jQuery. So if the third-party commenting plugin you are using also uses jQuery then just be sure it is initialised first and your own reply click handler should run afterwards and at that time it will be able to access the elements added by the plugin.
If the plugin doesn't use jQuery you can't be sure your click handler will run last so instead uncomment the setTimeout code I've shown above - it will wait a few milliseconds to give the plugin events time to run and then update the text.
Use the selector $('#^=id')
id being the prefix
e.g all ids starting test123
$('#^=test123')
this would work for things like
test1234
test12345
test123fgjfdgj
This might help: http://oscarotero.com/jquery/
And use jquery event listeners for the page load..
e.g. $(document).ready(function(){});
If they are loaded when a button is clicked then do..
$('#buttonid').click(function() {//handle click});
You're looking for DOM Mutation Events. This spec allows you to be notified when DOM nodes are inserted, changed, etc. Browser support is not really there, though... well, IE is behind (IE >= 9 has support). It's also a major performance hog. See this MDN document. For these reasons, I don't think a lot of folks here would suggest using them. Here's some code, though:
document.addEventListener("DOMNodeInserted", function(e) {
if ($(e.target).is('selector matching new elements')) {
//do what you want with e.target, which is the newly-inserted element
}
}, false);
There is a performance-boosting hack involving listening for CSS animation events instead: here. Only problem is that IE9 does not support CSS animations. I think we're all waiting for the day when we can use these events in a cross-browser and performant way, though.

In JavaScript, is it possible save an on-click trigger and re-apply it later?

On a dynamic page I wish to disable and later re-enable sections. Visually they are grayed out, but need to prevent users from playing with the section until later.
For elements using on-click triggers, would like to:
save the current on-click trigger in an attribute
remove the current on-click trigger
add trigger that no-defaults and no-propagates
to re-enable:
get rid of the no-default trigger
re-apply the trigger previously saved
clear attribute where it was saved
From replies so far:
conjecture: using pure JavaScript html5 level, without delegation or some other external mechanism, it is impossible to extract the on-click trigger from an element.
Solution
was very tempted by delegations - and was defeated by not being able to prevent memory leaks. ended up doing it anyway, with a simple gc to do the job.
extends (add|remove)EventListener with (add|push|pop|remove)PopableEventListener, making code change trivial. allows me to push and pop listeners to any level - wonderful for form context changes other than merely enable/disable.
source: http://code.google.com/p/chess-spider/source/browse/http/scripts/popable.js
doc: http://code.google.com/p/chess-spider/wiki/PopableEventListener?ts=1303738300&updated=PopableEventListener
Editorial
Contrary to most I have seen, the ability to access listeners in the dom would be a significant benefit; besides being able to sanely disable re-enable, the ability to coerce them into a different scope would be incredibly useful.
The best way to handle hundreds of triggers is to use event delegation.
You then examine each click for if it matches your trigger. If it is the child of a disabled section you discard the click.
A jQuery implementation would be something like as follows:
$('.trigger', document.body).live('click', function(e) {
var target = $(e.target);
if (target.parents('.disabled').length > 0) {
// handle click
}
});
Obviously you can use other frameworks or plain JavaScript as suits you best.
I presume you are talking about adding and removing listeners. You can do that a number of ways, the simplest if you only have one listener for an event is to add it as a property of the element. To remove it, just assign null:
function sayHi() {
alert('hi');
}
// Add a listener
var someElement = document.getElementById('someElementID');
someElement.onclick = sayHi;
// Remove it
someElement.onclick = null;
If you need more than one listener for an event, you can use other schemes, such as addEventListener and attachEvent
Of course you can just track the state of the elements (say in a class or object), then the listener can respond based on the state.
What about classes? Something like
function act(e) {
var target = e.currentTarget;
if (target.className === 'active') {
//element active, disable it
target.className = 'disabled';
//other stuff
}
else if (target.className === 'disabled') {
//element disabled, enable it
target.className = 'active';
e.preventDefault();
//other stuff
}
}
elem.onclick = act;
You can also be brave and use data-* attributes (link)
Why not just save each onclick's enabled state into an array, then check that array each time it is called, if the state is false, just return without running anything, this could also help keep track of what to grey out.
Here is my idea: http://jsfiddle.net/mazzzzz/MXEjv/1/
It is a bit messy, but the two top functions are the important ones. The first will toggle (based on class), and the second will say if the element's onclick is enabled (again by class). Just make sure the objects have the same class, and one will effect the other, and vise versa. Alternately, you could just pass in the id, instead of using the class (like I did).
Hope that helps a bit.

middle click (new tabs) and javascript links

I am in charge of a website at work and recently I have added ajaxy requests to make it faster and more responsive. But it has raised an issue.
On my pages, there is an index table on the left, like a menu. Once you have clicked on it, it makes a request that fills the rest of the page. At anytime you can click on another item of the index to load a different page.
Before adding javascript, it was possible to middle click (open new tabs) for each item of the index, which allowed to have other pages loading while I was dealing with one of them.
But since I have changed all the links to be ajax requests, they now execute some javascript instead of being real links. So they are only opening empty tabs when I middle click on them.
Is there a way to combine both functionalities: links firing javascript when left clicked or new tabs when middle clicked?
Does it have to be some ugly javascript that catches every clicks and deal with them accordingly?
Thanks.
Yes. Instead of:
...
Do this:
...
And then in your JS, hook the link via it's ID to do the AJAX call. Remember that you need to stop the click event from bubbling up. Most frameworks have an event killer built in that you can call (just look at its Event class).
Here's the event handling and event-killer in jquery:
$("#thisLink").click(function(ev, ob) {
alert("thisLink was clicked");
ev.stopPropagation();
});
Of course you can be a lot more clever, while juggling things like this but I think it's important to stress that this method is so much cleaner than using onclick attributes.
Keep your JS in the JS!
Yes, You need to lookup progressive enhancement and unobtrusive Javascript, and code your site to work with out Javascript enabled first and then add the Javascripts functions after you have the basic site working.
I liked Oli's approach, but it didn't discern from left and middle clicks. checking the "which" field on the eventArgs will let you know.
$(".detailLink").click(function (ev, ob) {
//ev.which == 1 == left
//ev.which == 2 == middle
if (ev.which == 1) {
//do ajaxy stuff
return false; //tells browser to stop processing the event
}
//else just let it go on its merry way and open the new tab.
});
It would require some testing, but I believe that most browsers do not execute the click handler when you click them, meaning that only the link is utilized.
Not however that your handler function needs to return false to ensure these links aren't used when normally clicking.
EDIT:
Felt this could use an example:
<a href="/Whatever/Wherever.htm" onclick="handler(); return false;" />
link text
For more info and detailed explanation view my answer in another post.
Possibly, I could provide two links each time, one firing the javascript and another being a real link that would allow for middle click.
I presume, one of them would have to be an image to avoid overloading the index.
The onclick event won't be fired for that type of click, so you need to add an href attribute which would actually work. One possible way to do this by adding a #bookmark to the URL to indicate to the target page what the required state is.

Categories

Resources