Reputation: 10081
I am using a ul
as a contextual menu in a php-file-listing application.
I recently added this piece of code to move the menu when the cursor is too close from the borders of the window, in order to prevent the menu to be drawn partially outside of the window.
if ((PosY + elm.outerHeight()) > $(window).height()) {
PosY = PosY - elm.outerHeight();
}
if ((PosX + elm.outerWidth()) > $(window).width()) {
PosX = PosX - elm.outerWidth();
}
But…
Sometimes the code works, and the menu is moved,
And sometimes it doesn't work, and the menu is displayed as below:
(Let's say it doesn't work like 1 out of 5 times)
I tried consoling the variables but they seem to be correct and I really don't understand why it is happening.
Here is a working snippet, where I managed to reproduce the problem.
(As it is an extract of my code, much of the CSS is missing but that's not important.)
// Global vars
var PosX = 0;
var PosY = 0;
// Mouse mouve
$(document).mousemove(function(e) {
PosX = e.pageX;
PosY = e.pageY;
return;
});
/*
CONTEXT MENU
*/
// Trigger action when the contexmenu is about to be shown
$(document).on("contextmenu", function(event) {
// Avoid the regular one
event.preventDefault();
});
// If the document is clicked somewhere
$(document).on("mousedown", function(e) {
// If the clicked element is not the menu
if ($(e.target).parents(".ContextMenu").length == 0) {
// Hide it
$(".ContextMenu").fadeOut(100);
}
});
// On context menu…
function ContextMenu(id) {
elm = $("#" + id);
/*
console.log("PosY:", PosY);
console.log("elm H:", elm.outerHeight());
console.log("win H:", $(window).height());
console.log("PosX:", PosX);
console.log("elm W:", elm.outerWidth());
console.log("win W:", $(window).width());
*/
// Moves menu if out of screen
if ((PosY + elm.outerHeight()) > $(window).height()) {
PosY = PosY - elm.outerHeight();
}
if ((PosX + elm.outerWidth()) > $(window).width()) {
PosX = PosX - elm.outerWidth();
}
// Hide and reopen the menu required
elm.fadeOut(100, function() {
elm.css({
"top": PosY,
"left": PosX
}).fadeIn(200);
});
//console.log("Context menu on: " + elm);
return false;
}
@charset "UTF-8";
* {
margin: 0;
padding: 0;
}
body {
font-size: 16px;
position: fixed;
color: #000;
}
p,
input,
label,
button,
a {
font-family: "Source Sans Pro", "Trebuchet MS", Helvetica, sans-serif;
display: inline-block;
cursor: inherit;
}
#InTable {
table-layout: fixed;
width: 100%;
border: 0;
border-collapse: collapse;
margin-bottom: 800px;
}
/* Scrollbar */
#Table::-webkit-scrollbar-track-piece {
background-color: #DDD;
}
#Table::-webkit-scrollbar-thumb {
background-color: #000;
}
thead th {
padding: 4px 0;
}
thead th i {
margin-left: 4px;
font-size: 14px !important;
color: #CCC;
}
thead th,
tbody tr {
cursor: pointer;
}
tbody td {
vertical-align: middle;
}
tbody td:first-of-type {
margin: 0 auto;
/*overflow: hidden; */
}
tbody td img {
vertical-align: middle;
height: 32px;
}
tbody tr:nth-of-type(odd) td {
background-color: #EEEEEE;
}
tbody tr:nth-of-type(even) td {
background-color: #DDDDDD;
}
#Table th,
#Table td {
position: relative;
}
#Table p {
padding-left: 4px;
}
.ContextMenu {
position: fixed;
display: none;
background: #f8f8f8;
border: 2px solid #888;
z-index: 2;
color: #333;
/* Smoothy color */
}
.ContextMenu div {
background: #e8e8e8;
height: 16px;
padding: 4px;
border-bottom: 1px solid #CCC;
}
.ContextMenu div:not(:first-child) {
border-top: 2px solid #AAA;
}
.ContextMenu li {
display: block;
height: 20px;
padding: 6px 24px 6px 8px;
cursor: pointer;
list-style-type: none;
transition: all .3s ease;
white-space: nowrap;
}
.ContextMenu img,
.ContextMenu i,
.ContextMenu span,
.ContextMenu p {
display: inline-block;
height: 20px;
vertical-align: middle;
}
.ContextMenu img,
.ContextMenu i,
.ContextMenu span {
width: 20px;
text-align: center;
margin-right: 8px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<style media="" data-href="https://use.fontawesome.com/releases/v5.0.6/css/all.css"></style>
<body class="tblr0 bg" id="DropBox" contenttype="text/html; charset=UTF-8" onload="" onresize="">
<div class="abs t40blr0" id="Content" style="top: 100px;">
<div class="abs tblr0 overflow-x-hidden overflow-y-auto" id="Table" style="opacity: 1;">
<table class="tblr0" id="InTable">
<thead class="bg-silver align-l">
<tr style="width: 100%;">
<th data-sort="int" id="Icos" style="width: 68px; min-width: 68px; max-width: 68px;"><p>Icon</p><i class="fa fa-sort"></i></th>
<th data-sort="string" id="Name" style="width: 291.333px;">
<p>Filename</p><i class="fa fa-sort"></i></th>
<th data-sort="string" id="Type" style="width: 218.5px;">
<p>Type</p><i class="fa fa-sort"></i></th>
</tr>
</thead>
<tbody>
<tr onclick="" oncontextmenu="ContextMenu('ContextMenu0');" style="width: 100%;">
<td data-sort-value="0" style="height: 64px;">
<div class="Icons" style="line-height: 64px; font-size: 60px;"></div>
</td>
<td style="height: 64px;">
<div class="Name">
<p>. <i>[Current]</i></p>
</div>
</td>
<td style="height: 64px;">
<p>< Current Directory ></p>
</td>
<td style="height: 64px;">
<ul class="ContextMenu" id="ContextMenu0">
<div onclick="context_menu_close(event);">
<p>Tools</p>
</div>
<li onclick=""><i class="fa fa-folder"></i>
<p>Create new Folder</p>
</li>
<li onclick=""><i class="fa fa-file"></i>
<p>Create new File</p>
</li>
<li onclick=""><i class="fa fa-upload"></i>
<p>Upload files in current folder</p>
</li>
<li onclick=""><i class="fa fa-download"></i>
<p>Download current folder as Zip</p>
</li>
</ul>
</td>
</tr>
<tr onclick="" oncontextmenu="ContextMenu('ContextMenu1');" style="width: 100%;">
<td data-sort-value="1" style="height: 64px;">
<div class="Icons" style="line-height: 64px; font-size: 60px;"></div>
</td>
<td style="height: 64px;">
<div class="Name pr4">
<p>Acces<span class="opac06"> </span></p>
</div>
</td>
<td style="height: 64px;">
<p>< Directory ></p>
</td>
<td style="height: 64px;">
<ul class="ContextMenu" id="ContextMenu1" style="top: 218px; left: 854.625px; display: none;">
<div onclick="context_menu_close(event);">
<p>Tools</p>
</div>
<li onclick=""><i class="fa fa-i-cursor"></i>
<p>Rename</p>
</li>
<li onclick=""><i class="far fa-arrow-alt-circle-right"></i>
<p>Move</p>
</li>
<li onclick=""><i class="far fa-copy"></i>
<p>Copy</p>
</li>
<li onclick=""><i class="far fa-trash-alt"></i>
<p>Delete</p>
</li>
<li onclick=""><i class="fa fa-download"></i>
<p>Download folder as Zip</p>
</li>
</ul>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</body>
Can someone enlighten me?
What is wrong with my code?
Thanks in advance.
Upvotes: 0
Views: 2001
Reputation: 3267
I believe the problem lay around .fadeOut(100)
and .fadeIn(200)
. Those are animations and as such, they run asynchronously regarding the rest of the code.
To be honest, I am not qualified to provide you with a thorough explanation as to why and HOW this was happening in your code. Basically: the process of updating posX
and posY
and the process of hiding and showing the menu were overlapping. Note that with your code as is, if you take your time (~1sec) between each click, the problem never occurs, whereas if you start clicking away repeatedly it breaks more and more often, just another hint that the animation's time is the source of the problem.
The solution: put updating posX
and posY
inside hiding and showing the menu. Specifically inside the callback after hiding the menu, before showing it again.
LET ME KNOW IF THE LEPRECHAUN RESURFACES!
// Global vars
var PosX = 0;
var PosY = 0;
// Mouse mouve
$(document).mousemove(function(e) {
PosX = e.pageX;
PosY = e.pageY;
return;
});
/*
CONTEXT MENU
*/
// Trigger action when the contexmenu is about to be shown
$(document).bind("contextmenu", function(event) {
// Avoid the regular one
event.preventDefault();
});
// If the document is clicked somewhere
$(document).bind("mousedown", function(e) {
// If the clicked element is not the menu
if ($(e.target).parents(".ContextMenu").length == 0) {
// Hide it
$(".ContextMenu").fadeOut(100);
}
});
// On context menu…
function ContextMenu(id) {
elm = $("#" + id);
/*
console.log("PosY:", PosY);
console.log("elm H:", elm.outerHeight());
console.log("win H:", $(window).height());
console.log("PosX:", PosX);
console.log("elm W:", elm.outerWidth());
console.log("win W:", $(window).width());
*/
// Hide and reopen the menu required
elm.fadeOut(100, function() {
// Moves menu if out of screen
if ((PosY + elm.outerHeight()) > $(window).height()) {
PosY = PosY - elm.outerHeight();
}
if ((PosX + elm.outerWidth()) > $(window).width()) {
PosX = PosX - elm.outerWidth();
}
elm.css({
"top": PosY,
"left": PosX
}).fadeIn(200);
});
return false;
}
@charset "UTF-8";
* {
margin: 0;
padding: 0;
}
body {
font-size: 16px;
position: fixed;
color: #000;
}
p,
input,
label,
button,
a {
font-family: "Source Sans Pro", "Trebuchet MS", Helvetica, sans-serif;
display: inline-block;
cursor: inherit;
}
#InTable {
table-layout: fixed;
width: 100%;
border: 0;
border-collapse: collapse;
margin-bottom: 800px;
}
/* Scrollbar */
#Table::-webkit-scrollbar-track-piece {
background-color: #DDD;
}
#Table::-webkit-scrollbar-thumb {
background-color: #000;
}
thead th {
padding: 4px 0;
}
thead th i {
margin-left: 4px;
font-size: 14px !important;
color: #CCC;
}
thead th,
tbody tr {
cursor: pointer;
}
tbody td {
vertical-align: middle;
}
tbody td:first-of-type {
margin: 0 auto;
/*overflow: hidden; */
}
tbody td img {
vertical-align: middle;
height: 32px;
}
tbody tr:nth-of-type(odd) td {
background-color: #EEEEEE;
}
tbody tr:nth-of-type(even) td {
background-color: #DDDDDD;
}
#Table th,
#Table td {
position: relative;
}
#Table p {
padding-left: 4px;
}
.ContextMenu {
position: fixed;
display: none;
background: #f8f8f8;
border: 2px solid #888;
z-index: 2;
color: #333;
/* Smoothy color */
}
.ContextMenu div {
background: #e8e8e8;
height: 16px;
padding: 4px;
border-bottom: 1px solid #CCC;
}
.ContextMenu div:not(:first-child) {
border-top: 2px solid #AAA;
}
.ContextMenu li {
display: block;
height: 20px;
padding: 6px 24px 6px 8px;
cursor: pointer;
list-style-type: none;
transition: all .3s ease;
white-space: nowrap;
}
.ContextMenu img,
.ContextMenu i,
.ContextMenu span,
.ContextMenu p {
display: inline-block;
height: 20px;
vertical-align: middle;
}
.ContextMenu img,
.ContextMenu i,
.ContextMenu span {
width: 20px;
text-align: center;
margin-right: 8px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<style media="" data-href="https://use.fontawesome.com/releases/v5.0.6/css/all.css"></style>
<body class="tblr0 bg" id="DropBox" contenttype="text/html; charset=UTF-8" onload="" onresize="">
<div class="abs t40blr0" id="Content" style="top: 100px;">
<div class="abs tblr0 overflow-x-hidden overflow-y-auto" id="Table" style="opacity: 1;">
<table class="tblr0" id="InTable">
<thead class="bg-silver align-l">
<tr style="width: 100%;">
<th data-sort="int" id="Icos" style="width: 68px; min-width: 68px; max-width: 68px;"><p>Icon</p><i class="fa fa-sort"></i></th>
<th data-sort="string" id="Name" style="width: 291.333px;">
<p>Filename</p><i class="fa fa-sort"></i></th>
<th data-sort="string" id="Type" style="width: 218.5px;">
<p>Type</p><i class="fa fa-sort"></i></th>
</tr>
</thead>
<tbody>
<tr onclick="" oncontextmenu="ContextMenu('ContextMenu0');" style="width: 100%;">
<td data-sort-value="0" style="height: 64px;">
<div class="Icons" style="line-height: 64px; font-size: 60px;"></div>
</td>
<td style="height: 64px;">
<div class="Name">
<p>. <i>[Current]</i></p>
</div>
</td>
<td style="height: 64px;">
<p>< Current Directory ></p>
</td>
<td style="height: 64px;">
<ul class="ContextMenu" id="ContextMenu0">
<div onclick="context_menu_close(event);">
<p>Tools</p>
</div>
<li onclick=""><i class="fa fa-folder"></i>
<p>Create new Folder</p>
</li>
<li onclick=""><i class="fa fa-file"></i>
<p>Create new File</p>
</li>
<li onclick=""><i class="fa fa-upload"></i>
<p>Upload files in current folder</p>
</li>
<li onclick=""><i class="fa fa-download"></i>
<p>Download current folder as Zip</p>
</li>
</ul>
</td>
</tr>
<tr onclick="" oncontextmenu="ContextMenu('ContextMenu1');" style="width: 100%;">
<td data-sort-value="1" style="height: 64px;">
<div class="Icons" style="line-height: 64px; font-size: 60px;"></div>
</td>
<td style="height: 64px;">
<div class="Name pr4">
<p>Acces<span class="opac06"> </span></p>
</div>
</td>
<td style="height: 64px;">
<p>< Directory ></p>
</td>
<td style="height: 64px;">
<ul class="ContextMenu" id="ContextMenu1" style="top: 218px; left: 854.625px; display: none;">
<div onclick="context_menu_close(event);">
<p>Tools</p>
</div>
<li onclick=""><i class="fa fa-i-cursor"></i>
<p>Rename</p>
</li>
<li onclick=""><i class="far fa-arrow-alt-circle-right"></i>
<p>Move</p>
</li>
<li onclick=""><i class="far fa-copy"></i>
<p>Copy</p>
</li>
<li onclick=""><i class="far fa-trash-alt"></i>
<p>Delete</p>
</li>
<li onclick=""><i class="fa fa-download"></i>
<p>Download folder as Zip</p>
</li>
</ul>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</body>
Upvotes: 2