I am working with SVG and Raphael JS. I have a situation where if you hover over an element a second element appears on top of the first element. When the second element appears the mouse is now over the second element and therefore the mouseout event fires on the first element and hides the second element. This continues in a loop. How can I prevent mouseout from occurring on the first element or prevent the hover on the second element?
In other examples I have tried I get a flickering effect. Here is a simplified version of what I'm doing in Raphael...
window.onload = function () {
var paper = Raphael("container", 1000, 900);
var rect_one = paper.rect(30, 30, 150, 150).attr({fill:"#fff"});
var rect_two = paper.rect(50, 50, 60, 60).attr({fill:"#fff"});
rect_two.hide();
rect_one.mouseover(function () {
rect_two.show();
});
rect_one.mouseout(function () {
rect_two.hide();
});
};
If you place your Raphael elements in a set then you can fix the problem:
window.onload = function () {
var paper = Raphael("container", 1000, 900);
var group = paper.set()
var rect_one = paper.rect(30, 30, 150, 150).attr({fill:"#fff"});
var rect_two = paper.rect(50, 50, 60, 60).attr({fill:"#fff"});
group.push(rect_one);
group.push(rect_two);
rect_two.hide();
group.mouseover(function () {
rect_two.show();
});
group.mouseout(function () {
rect_two.hide();
});
};
http://jsfiddle.net/agdMG/
I rethought your solution, and came up with the following approach...
Rather than trying to trigger on a onmouseout event (which is triggered by the mouse going over the text box that contains the state name, thus the flickering that occurs as you move the mouse across the state/name area), I thought it might be more efficient to simply cache the currently-active state in a variable, and on mouse enter, test to see if the cached state matches the state that you're mousing over.
See the attached jsFiddle (a fork of your own), and the comments I've included in that script.
http://jsfiddle.net/XKt9U/8/
Related
TLDR: moving the cursor from tooltip back to triggering element closes, shows and closes the tooltip (flickers).
I need to make the tooltips open on hover and make their content clickable. I have found a working example here on SO.
As you hover over the element it shows you a tooltip which can be interacted with, once you move the cursor away from the tooltip it closes.
There is a problem though.
If you leave the tooltip and move the cursor back on the element which triggered the tooltip, the tooltip pops back up, but dissapears after a moment ("flickering"). You need to move the cursor away from the element and back on the element for the tooltip to show again.
What I am trying to do, is check if the cursor is back on the triggering element and if that is the case not run the closing function (tooltip.hide()).
I have tried to do this by imitating the existing process from the example found on SO. That is, check if the tooltip has lost :hover, setTimout (300ms) and check if cursor is now positioned on the triggering element or back on the tooltip.
Here is a jsFiddle example.
This is the code. The problematic code is between the two looong comment lines.
Note: Moving the cursor away from the triggering element and back on the triggering element also triggers the flickering.
//https://stackoverflow.com/questions/67993080/bootstrap-5-make-tooltip-hoverable-and-link-clickable
var tooltipTriggerList = [].slice.call(document.querySelectorAll('button'))
for (let tt of tooltipTriggerList){
tt.setAttribute("data-bs-placement","top")
}
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
const tooltip = new bootstrap.Tooltip(tooltipTriggerEl, {
trigger: "manual",
'customClass': 'custom-tooltip'
})
let tooltipElTimeout;
let currentToolTip;
let currentTooltipTimeout;
tooltipTriggerEl.addEventListener("mouseenter", function () {
let toolTipID;
// Clear Set Timeout
clearTimeout(currentTooltipTimeout);
// Show Tooltip
tooltip.show();
// Assign current tooltip ID to toolTipID variable
toolTipID = tooltipTriggerEl.getAttribute("aria-describedby");
// Assign current tooltip to currentToolTip variable
currentToolTip = document.querySelector(`#${toolTipID}`);
/*******************************************************************/
// Hide tooltip on tooltip mouse leave
currentToolTip.addEventListener("mouseleave", function () {
currentTooltipTimeout = setTimeout(()=>{
console.log("!currentToolTip.matches(':hover')");
console.log(!currentToolTip.matches(":hover"));
if(!tooltipTriggerEl.matches(":hover")){
console.log("!tooltipTriggerEl.matches(':hover')");
console.log(!tooltipTriggerEl.matches(":hover"));
if (!currentToolTip.matches(":hover")) {
tooltip.hide();
}
}
}, 300)
});
/***********************************************************************/
});
tooltipTriggerEl.addEventListener("mouseleave", function () {
// SetTimeout before tooltip disappears
tooltipTimeout = setTimeout(function () {
// Hide tooltip if not hovered.
if (!currentToolTip.matches(":hover")) {
tooltip.hide();
}
}, 100);
});
return tooltip;
})
Thank you
Edit:
Amine Ramouls answer is correct. isHidden also needs to bet set to false on the 2cnd eventListener, otherwise the tooltips no longer work (problem with aria-describedby).
in your code you have an event listener wich add an event listner and that's a big mistake because it add an infinit number of eveneent listner to your element.
so you juste have to organize your code like this :
//https://stackoverflow.com/questions/67993080/bootstrap-5-make-tooltip-hoverable-and-link-clickable
var tooltipTriggerList = [].slice.call(document.querySelectorAll('button'))
for (let tt of tooltipTriggerList){
tt.setAttribute("data-bs-placement","top")
}
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
const tooltip = new bootstrap.Tooltip(tooltipTriggerEl, {
trigger: "manual",
'customClass': 'custom-tooltip'
})
let isHidden = true;
let currentTooltipTimeout;
tooltipTriggerEl.addEventListener("mouseenter", function () {
let toolTipID;
// Clear Set Timeout
clearTimeout(tooltipElTimeout);
clearTimeout(currentTooltipTimeout);
if (isHidden)
{
tooltip.show();
isHidden=false;
}
});
// Hide tooltip on tooltip mouse leave
tooltipTriggerEl.addEventListener("mouseleave", function () {
console.log("!currentToolTip.matches(':hover')");
if(!tooltipTriggerEl.matches(":hover")){
currentTooltipTimeout=setTimeout(()=>{
if (!isHidden && !tooltipTriggerEl.matches(":hover")){
tooltip.hide();
isHidden=true;
}
console.log("!tooltipTriggerEl.matches(':hover')");
console.log(!tooltipTriggerEl.matches(":hover"));
}, 3000)
}
});
return tooltip;
})
now as you can see i juste added the isHidden var to check if the popup info is hidden or not, you can do that with the element if you can get it by a query selector request. that's it. enjoy your life.
Edit: i forget to tell you that i have put 3 seconde before checking the if the popup is hidden or not.
I noticed that JointJS links can be removed by hovering over them and clicking the big red X that appears. But I was wondering if it is possible remove an element once it has been created, without knowing the variable name.
onCreateButtonClick(function(){
var rect = new joint.shapes.basic.Rect({
position: { x: 100, y: 30 },
size: { width: 100, height: 30 }
});
graph.addCell([rect]);
});
onRemoveButtonClick(function(){
//removeRectangle here?
});
My question is: can I remove this rectangle in the second function?
Removing elements by ID can simply be done as: graph.getCell(cellID).remove(). In your onRemoveButonClick(), you have to somehow know which element you want to remove. This depends on you application UI but you can, for example, do something like:
var selected;
paper.on('cell:pointerdown', function(cellView) {
selected = cellView.model;
});
onRemoveButtonClick(function() {
if (selected) selected.remove();
});
I implemented the removal of an element by single clicking the element , using the cellView arguement directly.
paper.on('cell:pointerclick', function(cellView, evt, x, y) {
cellView.remove();
});
I am experiencing an issue with the jQuery-UI draggable and droppable. The issue that I'm facing is the fact that:
When I move an element from one div to another (during dragging using
.append() ) it shifts the element away from the mouse cursor
jarringly.
I know what causes it the left / top css positions are no longer correct since I'm moving from one relative div to another. But a fix for it I can't find.
I have tried quite a few "solutions" :
Changing the cursorAt position http://api.jqueryui.com/draggable/#option-cursorAt while dragging but this only goes in affect after mouseup and on the next mousedown.
Changing the css during dragging: http://api.jqueryui.com/draggable/#event-drag which while it works is not ideal since it has hiccups that make it flicker and move in random directions which is highly annoying.
Making the draggable div absolute instead of relative (Which locally in my backbone application so far has the 'best' results but is still far from desirable since i require the elements in the sidebar to be relative so they append nicely one below the other )
Here is my JSBin example of my issue.
JavaScript
var positionStack = [];
var fieldview = $('#field');
var sidebarView = $('#sidebar');
$('.draggable').draggable({
containment: ".container",
zIndex: 100,
cursorAt: {
top: 20,
left: 25
},
snap: '.sidebar',
snapMode: 'inner'
});
$('#field').droppable({
over: function(event, ui) {
dragOverElement({event: event, ui:ui});
}
});
$('#sidebar').droppable({
over: function(event, ui) {
dragOverElement({event: event, ui:ui});
}
});
function dragOverElement(data){
var me = this;
var lastItem = positionStack[positionStack -1];
if(lastItem !== data.event.target.id)
{
positionStack.push(data.event.target.id);
var player = $(data.ui.draggable);
var target = data.event.target.id;
switch(target)
{
case ('field'):
fieldview.append(player);
player.css('position', 'absolute');
break;
case ('sidebar'):
sidebarview.append(player);
player.css('position', 'absolute');
break;
}
}
}
I have the following code on my page.
http://jsfiddle.net/SO_AMK/r7ZDm/
As you see it's a list of links, and every time a link is clicked, the popup box opens up right underneath the link in question.
Now, what I need to do is basically the same, except I need to use the .hover event and delay the execution by 2 seconds. So instead of clicking, the user should keep the cursor over a link for 2 seconds.
Sounds simple enough but I can't get the positioning to work properly. here's what I tried:
$('a.showreranks').hover(function()
{
$(this).data('timeout', window.setTimeout(function()
{
position = $(this).position();
$('#rerank_details').css('top', position.top + 17);
$('#rerank_details').slideToggle(300);
}, 2000));
},
function()
{
clearTimeout($(this).data('timeout'));
});
Can someone modify this to make it work?
Try like this:
$('a.showreranks').hover(function()
{
var self = this;
$(this).data('timeout', window.setTimeout(function() {
var position = $(self).offset();
$('#rerank_details').css('top', position.top + 17);
$('#rerank_details').slideToggle(300);
}, 2000));
},
function()
{
clearTimeout($(this).data('timeout'));
});
DEMO
jsFiddle demo
$('ul').on('mousemove','li',function(e){
var m = {x: e.pageX, y: e.pageY};
$('#rerank_details').css({left: m.x+20, top: m.y-10});
}).on('mouseenter','li',function(){
var t = setTimeout(function() {
$('#rerank_details').stop().slideDown(300);
},2000);
$(this).data('timeout', t);
}).on('mouseleave','li',function(){
$('#rerank_details').stop().slideUp(300);
clearTimeout($(this).data('timeout'));
});
The setTimeout will act like a hover intent that actually delays the execution for 2 seconds and counts the time hovered inside a data attribute of the hovered element - that gets 'nulled' on mouseleave.
I added also a few lines of code that will make your tooltip follow the mousemove.
The current plugin, shown below, scrolls the top-most div in a series of divs with the same class upwards, then removes it from the container, and appends it to the bottom of the series (within the container). This gives the illusion of a vertical slideshow.
$.fn.rotateEach = function ( opts ) {
var $this = this,
defaults = {
delay: 5000
},
settings = $.extend(defaults, opts),
rotator = function ($elems) {
$elems.eq(0).slideUp(500, function(){
var $eq0 = $elems.eq(0).detach();
$elems.parent().append($eq0);
$eq0.fadeIn();
setTimeout(function(){ rotator( $($elems.selector) ); },
settings.delay);
});
};
setTimeout(function(){ rotator( $this ); }, settings.delay);
};
$('.dynPanelContent').rotateEach();
However, if there are a large number of elements to scroll through, this would make for a VERY long page. As such, I am attempting to re-write this script so that it accepts a parameter which will determine how many elements to display. Any elements exceeding this number will be hidden until they are in the top 'x' number of elements. Here is an example of what I have attempted to implement.
$.fn.rotateEach = function (opts) {
var $this = this,
defaults = {
delay: 5000,
//Add a parameter named elementsShown, pass in a default value of 3
elementsShown: 3
},
settings = $.extend(defaults, opts),
rotator = function ($elems) {
//Hide the elements that are past the number to be shown
for (i = settings.elementsShown; i <= $elems.eq; i++) {
$elems.eq(i).hide();
}
$elems.eq(0).slideUp(500, function () {
var $eq0 = $elems.eq(0).detach();
var $eqN = $elems.eq(settings.elementsShown) - 1;
//Check & Show the element that is now within the show range
if ($elems.eq() == $eqN) {
$elems.eq($eqN).show('slow');
}
$elems.parent().append($eq0);
$eq0.fadeIn();
setTimeout(function () { rotator($($elems.selector)); },
settings.delay);
});
};
You can use simple CSS for this, mate.
If your elements are all of the same height (which your problem has to assume: if you are rotating a whole bunch of things dynamically, you won't want your page to change height), then you don't really need to use JavaScript for this at all. Just set the height of the container to what you want and hide the overflow. Then when you remove and append, everything appears to work. This won't take care of your dynamic configuration, though.
Improved plug-in: http://jsfiddle.net/morrison/tTJaM/
Notes:
Added support for showing X elements.
Added support for rotating only certain elements.
Added support for stopping the rotations:
Stop after X milliseconds.
Stop after X rotations.
overflow-y:hidden is added to container dynamically.
Simplified your detaching/attaching.
Known Issues:
Displaying X elements doesn't check for a maximum.