I am using tampermonkey to add some custom buttons to the Unity Documentation.
I noticed a strange problem If I am using a html <button>
. Everytime when I click on the button, then the scroll list is scrolling to the top and the code is only executed on the second click.
However, If I replace <button>
with <div>
then everything is working perfectly fine.
Why is <button>
behaving so weird?
Below is the tampermonkey
script for reproduction.
// ==UserScript==
// @name Unity Documentation (bugtest)
// @namespace https://docs.unity3d.com
// @version 1.0
// @description test
// @author Edward Black
// @match *://docs.unity3d.com/*
// @grant GM_addStyle
// ==/UserScript==
GM_addStyle('.confirmBox { z-index: 100; width: 275px; background-color: greenyellow; border: 1px solid black; }');
GM_addStyle('.confirmBoxButtons { margin-top: 5px; }');
GM_addStyle('.confirmBoxClose { position: absolute; height: 20px; width: 20px; background-color: white; color:black; border: 0.5px solid black; text-align: center; right: 0px; }');
GM_addStyle('.confirmBoxClose:hover { background-color: black; color:white; cursor: pointer; }');
GM_addStyle('.confirmBoxBtn { background-color: white; color:black; width: 100px; border: 1px solid black; }');
GM_addStyle('.confirmBoxBtn:hover { background-color: black; color:white; cursor: pointer; }');
$(document).ready(function() {
setTimeout(function() {
prepareCustomContextMenue();
$("div.mCSB_container").delegate("#theButton", "click", function() {
alert("Hello from Button");
});
$("div.mCSB_container").delegate("#theDiv", "click", function() {
alert("Hello from Div");
});
$("div.mCSB_container").delegate(".confirmBoxClose", "click", function() {
$(".confirmBox").remove();
});
}, 4000);
});
function prepareCustomContextMenue()
{
$("div.mCSB_container").find("a").each(function(j, obj) {
$(obj).on("contextmenu", function(){
return false;
});
$(obj).on("mousedown", function(e){
if( e.button == 2 ) {
console.log('Right mouse button!');
showConfirmBox(this);
return false;
}
return true;
});
});
}
function showConfirmBox(container)
{
$(".confirmBox").remove();
var elm = '<li><div class="confirmBox">'+
'<div class="confirmBoxClose">x</div>' +
'<div class="confirmBoxButtons">' +
'<button id="theButton" class="confirmBoxBtn"> This is a button </button>' +
'<div id="theDiv" class="confirmBoxBtn"> This is a div </div>' +
'</div>' +
'</div></li>';
$parent = $(container).parent();
$(elm).appendTo($parent);
}
Wait until the site loaded completly (about 4 seconds until the site load indicator disapeared) then right click on a link in the scroll list at left, which should be as far as possible down (so you can see that the list is actually scrolling up after clicking on the button). Now a container should appear:
Now click on the <button>
named This is a button
and notice that the scroll list scrolls to the top and the code from the button is not executed (alert should show). Press again on the button and notice that the code is now executed and the scroll list scrolls to top again after pressing Ok on the alert.
Next click on the <div>
named This is a div
and note that everything works as expected.
There are several issues with that script and, as Jim-miraidev pointed out, you need to use jQuery's .on()
, .stopPropagation()
, and .preventDefault()
.
But the priority problem here is that the page has several other events at play (especially click
and mouseup
). The userscript code is causing the page's flexbox scroll state to get confused.
(Exhibit A: the scroll only happens on first click immediately after the mousedown
event that fires showConfirmBox()
.)
So, an easy way to patch this is to capture all 3 of the (potentially) conflicting events:
$("div.mCSB_container").on ("click mousedown mouseup", "#theButton", function (zEvent) {
zEvent.preventDefault ();
zEvent.stopPropagation ();
if (zEvent.type == "mousedown") {
console.log ("Hello from Button");
}
} );
Note that browsers fire the events in the order: mousedown, mouseup, click.