This seems like a really annoying bug I have been trying to solve for days. I am making a sortable list. When I click and drag grey item and I hover over the red I need a spacer to show up below it so I can drop it in the newly shown spacer. I try to bind a draggable over the spacer (so I know if the user drops it onto it) but I just can't get the spacer to recognise when it has something 'over it' unles >>>> and here is the gotcha while I drag and hold and move my element to the side of the screen.
To recreate :
Click and hold the grey tab
Drag it over the red (at this point the yellow spacer shows)
Drag it onto the yellow but don't let go of the mouse (notice nothing writes to the console).
Now repeat the experiement but before going over the yellow spacer wiggle the draggable grey item at the side of the screen now go over the yellow spacer and hey presto it detects it and writes a message to the console which means the code has worked. Does anybody know what the hell is going on? I have been stuck on it for days!
The code:
$('.draggable').draggable({
revert: true,
revertDuration: 30,
stop: function () {
$('.spacer').removeClass('open');
var dragged = $(this);
dragged.css('z-index', '1000');
},
start: function (event, ui) {
var dragged = $(this);
dragged.css('z-index','1009');
}
});
$('.hoverable').droppable({
over: function (event, ui) {
var target = $(event.target);
var newElem = target.next().append('<div id="spacer" style="display:block; height:30px;width:100px;background-color:yellow;"></div>');
newElem.on().droppable({
over: function() {
console.log('sdfsd');
},
tolerance: "touch"
});
}
});
<body>
<div class="draggable"></div>
<div class="hoverable"></div>
<div class="d"></div>
</body>
.draggable{
width:100px;
height:30px;
background-color:gray;
}
.hoverable{
width:100px;
height:30px;
background-color:red;
}
.spacer{
width:100px;
height:30px;
background-color:yellow;
}
The JsBin is here...
http://jsbin.com/golel/1/
The problem with your code is that your calls to $('.draggable').draggable() and $('.hoverable').droppable() are happening on the current DOM, so those functions are not executed on new DOM elements. You somehow need to trigger these functions on new elements as well. (If you're only worried about dynamic drop regions, you can ignore this aspect for the draggable() call.)
Here's an example of what I mean:
function makeDroppable(selector) {
$(selector).droppable({
over: function (event, ui) {
var target = $(event.target);
var newElem = target.next().append('<div id="spacer" style="display:block; height:30px;width:100px;background-color:yellow;"></div>');
makeDroppable(newElem);
}
});
}
makeDroppable('.hoverable');
Related
I want to stop the fading out of a JQueryUI menu.
Same context: FireFox 43, Linux/Debian/Sid, Jquery2.2, JqueryUI1.11.4, as for this question; the alpha-stage MELT monitor, GPL free software on Linux/Debian with recent Firefox 38 or 43 on Linux; this is commit b505eccc1... on github
(JsFiddle MVCE example at end of question)
In my file webroot/nanoedit.js I hve a global variable mom_menucmdel which hold a JqueryUI menu (a dropdown menu). The mom_removecmdmenu function is clearing that global and removing that menu from the DOM.
I want this menu to fade out and be removed in a bit more than 9 seconds, if the user don't do any interaction. But if the user is moving the mouse inside the menu, I want the fading to abort. So I coded:
var curmenu = mom_menucmdel;
curmenu.mousemove
(function(ev)
{ console.log("momdelayrepl movefinishing ev=", ev, " curmenu=", curmenu);
curmenu.finish();
});
setTimeout(function()
{
console.log("mom_cmdkeypress-delayedreplmenudestroy curmenu=",
curmenu);
curmenu.delay(100).fadeOut(800+75*dollvalseq.length,
function () {
console.log ("momdelayrepl finalfaderemove curmenu=", curmenu);
mom_removecmdmenu();
});
}, 9500);
near line 427 of that nanoedit.js; my understanding is that finish would abort animations. But it does not work. The fading remains, and the menu disappears, even after mouse movements.
If you are brave enough to compile the MELT monitor, browse http://localhost.localdomain:8086/nanoedit.html, type $ e in the textearea, then the esc key.
JsFiddle example (MVCE)
See this JsFiddle which is a simplified variant of above; run it twice. First, click on the button and wait 10 seconds at least. The menu is fading out and disappears. Then, run it again, click on the button, and move the mouse inside the menu (perhaps even selecting some item), the menu still disappears in about 10 seconds but I want it to stay, perhaps indefinitely (in my nanoedit.js code the select function would remove it, in this JsFiddle I don't care)!
var mymenu;
var mybutton;
var count = 0;
var menuTO;
function remove_menu() {
if (!mymenu) return;
console.log("removing mymenu=", mymenu);
mymenu.remove();
}
function fadeOutMenu() {
console.log("fading mymenu=", mymenu);
mymenu.delay(100).fadeOut(900, remove_menu);
}
$(document).ready(function() {
mybutton = $("#mybutton_id");
mybutton.on("click", function() {
count++;
var menuid = "menuid_" + count;
$("#mymenudiv_id").append("<ul class='menucl' id='" + menuid + "'</ul>");
mymenu = $("#" + menuid);
mymenu.append("<li>first</li><li>counting " + count + "</li><li>last</li>")
mymenu.menu({
select: function(ev, ui) {
console.log("selected ui=", ui);
$("#message_id").html("<b>selected</b> <i>" + ui.item.text() + "</i> menu#" + count);
}
});
mymenu.mousemove(function(ev) {
console.log("mousemove ev=", ev);
clearTimeout(menuTO);
menuTO = setTimeout(fadeOutMenu,
9000);
//mymenu.finish();
})
menuTO = setTimeout(fadeOutMenu,
9000);
})
})
ul.menucl {
background-color: lightpink;
color: navy;
font-size: 80%;
display: inline-block;
}
p.explaincl {
font-size: 75%;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link rel="stylesheet" href="//code.jquery.com/ui/1.11.4/themes/smoothness/jquery-ui.css">
<script src="//code.jquery.com/ui/1.11.4/jquery-ui.js"></script>
<p>
See <a href='http://stackoverflow.com/q/34818540/841108'>this SO question</a>
</p>
<h2> my menu </h2>
<p class='explaincl'>
First, try clicking the button, and do nothing more: the menu disappears in 10 sec. Then, try again, click the button, move the mouse inside the menu, it is still disappearing but I want it to stay!</p>
<button id='mybutton_id'>click me</button>
<div id='mymenudiv_id'>
Here
</div>
<p id='message_id'>
</p>
I have stored Timeout id in a variable menuTO. Then on every mousemove I reset Timeout so that the menu won't fade out if mouse is moving inside the menu.
Also keep in mind if your cursor is inside menu but is not moving, then it will obviously fade out in next 9 to 10 seconds.
The accepted answer will unnecessarily create and clear huge lot of timeouts when the mouse moves around.
You can use the built in menu event focus and blur to better handle this as shown below:
mymenu.menu({
focus: function(e, ui) {
clearTimeout($(this).data('timeout'));
}
});
mymenu.on('menublur', function(e, ui) {
var timeout = setTimeout(function() {
console.log("fading mymenu");
}, 5000);
$(this).data('timeout', timeout);
});
mymenu.trigger('menublur'); // start the timeout for the first time
menublur is an internal (documented) jquery ui event which is triggered when a menu item lose focus.
Note that we should bind the event you want to trigger manually using on() method, outside the options object.
Updated Fiddle
So I have a Javascript function that is part of web game I'm creating. It is supposed to act as the speed selector for an object in the game. Selecting the div that says "slow" is supposed to change the object's speed to slow, and so forth. When the page is loaded, the "med" div is selected. For now, I just want to figure out why my attempt at putting a border around the currently selected speed div is not working. The code below is, hopefully, self-explanatory. (Here's a link to the game, if that helps: http://jaminweb.com/snake_TEST_PHP.php)
I'm not getting any errors in my Javascript console and I've tested (using an alert(...)) to make sure the onlick functions are being called for each of the 3 divs when I click on them.
Any help greatly appreciated.
Relevant pieces of code:
function speedController(slowButtonID, medButtonID, fastButtonID, sg)
{
/*
slowButtonID, medButtonID, fastButtonID: IDs of the buttons for slow, medium and fast, respectively
sg: Snake game object
*/
this.spdMap = {"slow" : [slowButtonID, 200], "med" : [medButtonID, 100], "fast" : [fastButtonID , 50]};
this.curSpd = "med";
this.changeSpeed = function(newSpd)
{
/*
newSpd: one of "slow", "med" or "fast"
*/
/* Do nothing if selecting same speed as current */
if (newSpd == this.curSpd)
return;
/* Else, remove border from currently selected speed button
and add border to newly selected speed button, then
change the speed of the snake's mover.
*/
$("#"+this.spdMap[this.curSpd[0]]).css("border: 0px;");
$("#"+this.spdMap[newSpd[0]]).css("border: 4px solid #3D4048;");
sg.mover = setInterval(sg.move(), this.spdMap[newSpd[1]]);
}
}
<div id="snakeSpdDiv">
<p><b>Speed:</b></p>
<div class="snakeSpdBtn" id="snakeSpdSlow">Slow</div>
<div class="snakeSpdBtn" id="snakeSpdMed" style="border: 4px solid #3D4048;">Med</div>
<div class="snakeSpdBtn" id="snakeSpdFast" onlick=>Fast</div>
<script type="text/javascript">
var sc = new speedController("snakeSpdSlow", "snakeSpdMed", "snakeSpdFast", s);
$("#snakeSpdSlow").click(function()
{
sc.changeSpeed("slow");
});
$("#snakeSpdMed").click(function()
{
sc.changeSpeed("med");
});
$("#snakeSpdFast").click(function()
{
sc.changeSpeed("fast");
});
</script>
</div>
The problem is with this part:
$("#"+this.spdMap[this.curSpd[0]]).css("border: 0px;");
$("#"+this.spdMap[newSpd[0]]).css("border: 4px solid #3D4048;");
It should be:
$("#"+this.spdMap[this.curSpd][0]).css("border", "0px");
$("#"+this.spdMap[newSpd][0]).css("border", "4px solid #3D4048");
Also, you need to change this.curSpd with newSpd afterwards.
You are not using your curSpd/newSpd variables correctly. They should be:
$("#"+this.spdMap[this.curSpd]).css("border", "0px");
$("#"+this.spdMap[newSpd]).css("border", "4px solid #3D4048");
Also, as imtheman pointed out, your css call needs to be fixed.
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 want the mouseover on the coverImg then show the coverInfo
the coverInfo show the title and the description of the image
then the coverInfo do show
but I want the coverInfo stay and clickable when mouserover on itself
but it disappear immediately.
So what's the point I have missed?
The HTML
<div class="workshop_img">
<div class="coverInfo"></div>
<a href="#">
<span class="coverImg" style="background-image:url('images/work/show1.jpg')" title="Chictopia "></span>
</a>
The CSS:
.coverInfo {
position:absolute;
width: 200px;
height:200px;
background:rgba(0,0,0,0.5);
top:30%;
left:30%;
display:none;
}
see the jQuery code
$(function() {
$(".coverImg").each(function() {
//make the background image move a little pixels
$(this).css({
'backgroundPosition' : "-40px 0"
}).mouseover(function() {
$(this).stop().animate({
'backgroundPosition' : " -20px -60px "
}, {
duration : 90
});
//shwo the info box
var content = $(this).attr("title");
$("<div class='coverInfo'></div>").text(content).prependTo($(this).parent()).fadeIn("fast");
}).mouseout(function() {
$(this).stop().animate({
'backgroundPosition' : "-40px 0"
}, {
duration : 200,
});
$(this).parent().find(".coverInfo").stop().fadeOut("fast");
})
})
});
</div>
EDIT:
I have searched a lot and find something similar, I took them and the answer given below together to solve my problem, here is the code:
$(function() {
$(".coverImg").css({
'backgroundPosition' : "-40px 0"
}).mouseenter(function() {
var box = $(this).parents(".workshop_img").find(".coverInfo");
var content = $(this).attr("title");
var info = box.text(content);
$(this).stop().animate({
'backgroundPosition' : " -20px -60px "
},90);
info.show();
}).mouseleave(function() {
var box = $(this).parents(".workshop_img").find(".coverInfo");
var content = $(this).attr("title");
var info = box.text(content);
$(this).stop().animate({
'backgroundPosition' : "-40px 0"
},200);
info.stop().hide();
});
});
It has just been clean, but do not work fine.
What's the problem?
The new box shows immediately because it is not initially marked as hidden. .fadeIn() only fades in something that is initially not showing.
You can make it initially not visible like this:
$("<div class='coverInfo'></div>").text(content).hide().prependTo($(this).parent()).fadeIn("fast");
You also can get rid of the .each() iterator you're using. You don't need it. You can just use:
$(".coverImg").css(...).mouseover(...).mouseout(...);
You don't need the .each() at all.
I'd also suggest you use .hover(fn1, fn2) instead of .mouseover(fn1) and .mouseout(fn2).
And, it looks like you are creating a new object and inserting it on every mouseover event such that multiple such objects will pile up in the page. You should either .remove() the object in the mouseout function or you should reuse a previously existing element if it exists in the element rather than creating more and more of them.
Sometimes when you are using the events for mouse hovering and you are also changing the page, the change to the page can cause the element to lose the mouse hover which then hides the change to the page and then it all starts over again. I can't tell for sure if that is happening in your case (I'd need a working example to play with to see), but it seems possible.
I am using jQuery droppable (in conjunction with jQuery draggable) to allow the user to add rows to an HTML table by dragging items from a list and dropping them on the table.
This works well, however at present the logic is that when the user drag-drops on a table row the new row gets added below the row they dropped on.
It would be better if the new row's add position was based on whether the user dropped in the upper or lower half of an existing row.
This is easy enough to calculate in the drop event, but I need to give UI feedback as the user drags (which I would do by means of two CSS classes droppable-above and droppable-below for example).
This doesn't seem to be possible, as the over event only fires once; when the user initially drags over the droppable element.
Is it possible to get the over event to fire for every mouse move while the user is over a droppable element?
If so, then I'd be able to do this:
$("tr.droppable").droppable({
over: function(event, ui) {
if (/* mouse is in top half of row */) {
$(this).addClass("droppable-above").removeClass("droppable-below");
}
else {
$(this).removeClass("droppable-above").addClass("droppable-below");
}
},
out: function(event, ui) {
$(this).removeClass("droppable-above").removeClass("droppable-below");
},
drop: function(event, ui) {
$(this).removeClass("droppable-above").removeClass("droppable-below");
if (/* mouse is in top half of row */) {
// Add new row above the dropped row
}
else {
// Add new row below the dropped row
}
}
});
The CSS styles would be something like...
droppable-above { border-top: solid 3px Blue; }
droppable-below { border-bottom: solid 3px Blue; }
As you said, over (like its counterpart out) is only raised once on the droppable. On the other hand, the drag event of the draggable is raised every time the mouse moves, and seems appropriate for the task. There are, however, two problems with this strategy:
drag is raised whether or not the draggable actually lies over a droppable,
even in that case, the droppable is not passed to the event handler.
One way to solve both problems is to associate the droppable and the draggable in the over handler, using jQuery's data() facility, and disassociate them in the out and drop handlers:
$("tr.droppable").droppable({
over: function(event, ui) {
if (/* mouse is in top half of row */) {
$(this).removeClass("droppable-below")
.addClass("droppable-above");
}
else {
$(this).removeClass("droppable-above")
.addClass("droppable-below");
}
ui.draggable.data("current-droppable", $(this)); // Associate.
},
out: function(event, ui) {
ui.draggable.removeData("current-droppable"); // Break association.
$(this).removeClass("droppable-above droppable-below");
},
drop: function(event, ui) {
ui.draggable.removeData("current-droppable"); // Break association.
$(this).removeClass("droppable-above droppable-below");
if (/* mouse is in top half of row */) {
// Add new row above the dropped row.
}
else {
// Add new row below the dropped row.
}
}
});
Now that the draggable knows the droppable it's lying over, we can update the element's appearance in a drag event handler:
$(".draggable").draggable({
drag: function(event, ui) {
var $droppable = $(this).data("current-droppable");
if ($droppable) {
if (/* mouse is in top half of row */) {
$droppable.removeClass("droppable-below")
.addClass("droppable-above");
} else {
$droppable.removeClass("droppable-above")
.addClass("droppable-below");
}
}
}
});
The code that follows is a simple test case that demonstrates this solution (it basically fills the commented gaps above and refactors common patterns into helper functions). The droppable setup is a little more intricate than in the previous example, mainly because the newly created table rows have to be made droppable like their siblings.
You can see the results in this fiddle.
HTML:
<div class="draggable">New item 1</div>
<div class="draggable">New item 2</div>
<div class="draggable">New item 3</div>
<div class="draggable">New item 4</div>
<div class="draggable">New item 5</div>
<p>Drag the items above into the table below.</p>
<table>
<tr class="droppable"><td>Item 1</td></tr>
<tr class="droppable"><td>Item 2</td></tr>
<tr class="droppable"><td>Item 3</td></tr>
<tr class="droppable"><td>Item 4</td></tr>
<tr class="droppable"><td>Item 5</td></tr>
</table>
CSS:
p {
line-height: 32px;
}
table {
width: 100%;
}
.draggable {
background-color: #d0ffff;
border: 1px solid black;
cursor: pointer;
padding: 6px;
}
.droppable {
background-color: #ffffd0;
border: 1px solid black;
}
.droppable td {
padding: 10px;
}
.droppable-above {
border-top: 3px solid blue;
}
.droppable-below {
border-bottom: 3px solid blue;
}
Javascript:
$(document).ready(function() {
$(".draggable").draggable({
helper: "clone",
drag: function(event, ui) {
var $droppable = $(this).data("current-droppable");
if ($droppable) {
updateHighlight(ui, $droppable);
}
}
});
initDroppable($(".droppable"));
function initDroppable($elements)
{
$elements.droppable({
over: function(event, ui) {
var $this = $(this);
updateHighlight(ui, $this);
ui.draggable.data("current-droppable", $this);
},
out: function(event, ui) {
cleanupHighlight(ui, $(this));
},
drop: function(event, ui) {
var $this = $(this);
cleanupHighlight(ui, $this);
var $new = $this.clone().children("td:first")
.html(ui.draggable.html()).end();
if (isInUpperHalf(ui, $this)) {
$new.insertBefore(this);
} else {
$new.insertAfter(this);
}
initDroppable($new);
}
});
}
function isInUpperHalf(ui, $droppable)
{
var $draggable = ui.draggable || ui.helper;
return (ui.offset.top + $draggable.outerHeight() / 2
<= $droppable.offset().top + $droppable.outerHeight() / 2);
}
function updateHighlight(ui, $droppable)
{
if (isInUpperHalf(ui, $droppable)) {
$droppable.removeClass("droppable-below")
.addClass("droppable-above");
} else {
$droppable.removeClass("droppable-above")
.addClass("droppable-below");
}
}
function cleanupHighlight(ui, $droppable)
{
ui.draggable.removeData("current-droppable");
$droppable.removeClass("droppable-above droppable-below");
}
});
I am hitting the same issue and have been thinking about two solutions which I will share in case they give direction to others who find this relatively rare need.
Two div solution: Add two divs into each cell of the row, positioned to be stacked and each 50% high and full width with z-index set to -1 to protect from UI interference. Now make these droppables and use their 'over' and 'out' events to toggle the classes of the parent cell or row.
Abandon droppable and roll your own collision detection: Write your own collision detection to mimic the droppable effect. This which would give more freedom but would lead to some serious coding and so is not for the casual requirement. Would also be susceptible to performance issues. That said, there should be some obvious case-based shortcuts that would work in your favour.
I would be interested to hear of any other approaches to low-code solution.
I just hit this problem. Not much is required if you just implement hit testing in the 'drag' event. Here I've just tagged all my drop targets with .myDropTarget, so it's easy to find them all, loop through them and check whether the mouse is over them.
Something like this:
thingToBeDragged.draggable({
drag: function(evt) {
$('.myDropTarget').removeClass('topHalf bottomHalf').each(function() {
var target = $(this), o = target.offset(),
x = evt.pageX - o.left, y = evt.pageY - o.top;
if (x > 0 && y > 0 && x < target.width() && y < target.height()) {
// mouse is over this drop target, but now you can get more
// particular: is it in the top half, bottom half, etc.
if (y > target.height() * 0.5) {
target.addClass('topHalf');
} else {
target.addClass('bottomHalf');
}
}
});
},
stop: function() {
var droppedOn = $('.topHalf, .bottomHalf');
}
});
Another method is to add a class or other elector to the hint element. Then in the draggable definition, on the drag handler, update the hint position:
$('#dropArea').droppable({
over: function(event, ui)
// Create a clone 50 pixels above and 100 to the left of drop area
$('#someHint').clone()
.css({
position: 'absolute',
top: ui.offset.top+50,
left: ui.offset.left-50
})
.addClass("clone") // Mark this as a clone, for hiding on drop or out
.addClass("dragHelper") // Mark this as a hint, for moving with drag
.appendTo(document.body)
},
out: function(event, ui) {
$('.clone').remove(); // Remove any hints showing
},
drop: function(event, ui) {
$('.clone').remove(); // Remove any hints showing
}
});
$("#mydiv").draggable({
drag: function(event, ui) {
$('.dragHelper').css('left',ui.offset.left);
$('.dragHelper').css('top',ui.offset.top);
}
});
This is a rather crude (and codeless) solution, but you could try using the hoverClass option with your Droppable and creating a new class called "hovering" to set Droppable behavior that only happens when the Draggable is hovering over the Droppable. This "hovering" class could then (this is the crude bit) run some sort of endless loop or some other sort of checker; I haven't used these classes before, so I can't think of any more specifics past this point. =/
Edit: Even cruder, you could alternate disabling and enabling the Droppable using the "hovering" class; I would definitely do this synchronously though, and with a generous time delineation as well. The alternating disable and enable calls should trigger one of the events, though which one you'll have to experiment with to find out.