Jquery UI multiple droppable outside of container still droppable - javascript

Let's say I have the parent container (dotted), with overflow:hidden. Inside I have a div that exceeds the bounds of the container (gray, and red is the hidden part outside of container). This div is a Jquery UI droppable object.
The Problem:
The div part that is outside the container is still droppable. How do I stop this behaviour and only allow the part inside the container to be droppable?
Thank you!

See my comment about context, but here's a first pass at a possible solution at jsFiddle. I'm assuming that the parent (dotted) container is not a droppable.
$(".drag").draggable();
$(".child").droppable({
drop: function (event, ui) {
/*
* Check if draggable is inside the parent. This is a bit brute force, if
* the parent itself is also a droppable it can also be checked by
* listening for the 'over' event and setting a flag.
*/
var pPos = $(".parent").offset(); // the parent container's position
var dPos = ui.offset; // the current draggable's position
// Can also add right/top/bottom contraints if required...?
var withinParent = dPos.left > pPos.left;
if (!withinParent) {
// Move the draggable to its original position (or some safe spot)
ui.draggable.animate(ui.draggable.data().draggable.originalPosition, "slow");
}
}
});
Edit - Draggable Helper
If you're using a helper for your draggable, then this logic should go into the revert callback of the draggable. See the new jsFiddle.
There are a few assumptions I've made that may be wrong, so make sure to read the javascript comments! Good luck =)
$(".drag").draggable({
helper: "clone",
revert: function () {
/*
* Check if draggable is inside the parent. This is a bit brute force, if
* the parent itself is also a droppable it can also be checked by
* listening for the 'over' event and setting a flag.
*/
// The parent container's position
var pPos = $(".parent").offset();
// The current draggable helper's position
var dDraggable = this.data().draggable;
var helper = dDraggable.helper;
var dPos = helper.offset();
/*
* Can also add top and right/bottom contraints
* if we only want to allow dragging onto droppable!
* Will have to calculate right = left + width,
* bottom = top + height. Also take into account possible
* borders/padding/margin with jQuery outerWidth!
*/
var withinParent = dPos.left > pPos.left;
if (!withinParent) {
return true;
}
else {
// Move the draggable to the helper's position
// NOTE: We're using offset here, so you may want to test this under various condition, e.g. relative/absolute parents!
this.css(helper.offset());
return false;
}
}
});
$(".child").droppable();

Related

Element Disappears After Changing Width

I am currently working on a custom resize functionality which will allow the user to drag the vertical border between two divs to resize their widths in respect to each other. I am accomplishing this by creating an invisible third div that has a width of 10px with a resize cursor and sits on top of the border between the two elements. When the user clicks on the invisible 3rd div and drags it, an event listener changes the width of the left element, then re positions the 3rd div accordingly.
However, I am getting a strange error where the left element in question seems to disappear / get removed after changing the width. I have verified this by doing a $("#divID").length to the console before and after. Before shows a count of 1, after shows a count of 0. This is causing an error in one of the functions that uses the $("#divID").offset() function.
Here is the associated code. The $("#"+resultsDiv) is the left side which I referred to previously.
/**
* This will create an invisible div between the results pane (left) and map (right) that will allow the user to click and resize
*/
function handleResize() {
let e = document.createElement('div');
setResizeProperties(e);
document.body.appendChild(e);
$(e).mousedown(function() {
$("#"+resultsDiv).mousemove(function(el) {
doResize(el,$(this));
});
}).mouseup(function() {
$("#"+resultsDiv).unbind("mousemove",doResize);
});
$(window).resize(function() {
setResizeProperties(e);
});
}
/**
* Actually change the length
* #param el to get mouse page X from
* #param e of the invisible resize handle object
*/
function doResize(el,e) {
console.log($("#"+resultsDiv).width());
$("#"+resultsDiv).css("width",(el.pageX - 10 - $("#"+resultsDiv).offset().left));
console.log($("#"+resultsDiv).width());
console.log();
setResizeProperties(e);
}
/**
* Set the resize properties for the div element
* #param e of the invisible resize handle obbject
*/
function setResizeProperties(e) {
$(e).attr("id","resizeHandler");
//$(e).css("visibility","hidden");
$(e).css("background-color","red");
$(e).css("width","20px");
$(e).css("height",$("#gMapContainer").css("height"));
$(e).css("cursor","ew-resize");
$(e).css("position","absolute");
console.log($("#"+resultsDiv).length);
$(e).css("left",$("#"+resultsDiv).width() + $("#"+resultsDiv).offset().left);
$(e).css("top",$("#resultsContainer").offset().top);
$(e).css("z-index",10000);
}
Here is a link to the JS Fiddle:
https://jsfiddle.net/BrandonQDixon/pt70yLbs/
If not already set, it uses the latest version of JQuery.

Animate a div element changing its position in a div list

I'm trying to animate a div element as it repositions itself in a sorted div list, so it doesn't make an instant jump, but a smooth transition. The list data comes from a Mongo collection and every document yields a template (a div element). Every document has a 'votes' property, based on which the list is sorted. The logged-in user can vote once for every post, and if a post reaches a higher number of upvotes it gets repositioned.
I'm following the Discover Meteor book, in case anyone is familiar with it. This was handled by setting a CSS transition property: transition: all 300ms 0ms ease-in;, re-rendering the post in the old spot, then changing the relative position of the post once it's re-rendered. The latter was, of course, handled in the onRendered function.
This is where the trouble starts. Apparently, onRendered doesn't fire once the div element receives enough votes to change its position.
So, my question would be: how do I animate the element changing its position, once the list gets re-ordered?
EDIT: Here's the code:
Template.postItem.onRendered(function(){
//animate post from previous position to new position
console.log(/*something so I know the onRendered function is firing*/);
var instance = this;
var rank = instance.data._rank;
var $this = $(this.firstNode);
var postHeight = 80;
var newPosition = rank * postHeight;
//if element has currentPosition (i.e. is not first ever rendered)
if(typeof(instance.currentPosition)!=='undefined'){
var previousPosition = instance.currentPosition;
//calculate difference between old position and new position and send
//element there
var delta = previousPosition - newPosition;
$this.css("top", delta + "px");
}
//let it draw in the old position, then...
Meteor.defer(function(){
instance.currentPosition = newPosition;
//bring element back to its new original position
$this.css("top", "0px");
});
});
I recommend to use Blaze's _uihooks. You could register a hook which will be called when Blaze intends to move the respective DOM element.
For example:
var hook = {
moveElement: function(node, next) {
var $node = $(node);
/* Add animation code. */
$node.animate();
}
}
Template.postItem.onRendered(function() {
/* Set hook to first node. */
this.firstNode._uihooks = hook;
/* Or to any other DOM element. */
this.find('.postItem')._uihooks = hook;
});

jQuery Drag and Drop slow down

Is it possible to slow down the speed of a draggable element?
I have build a simple slider with jQuery drag and drop. When the slider element (the draggable element) moves to certain positions a picture fades in. So if you move the draggable element not too fast it looks like you can handle a "picture animation" with the slider. Now, I want to slow down the draggable element. So the user never can drag the element too fast.
This is an example of my code.
$('.slider').mousemove(function(){
if($(this).position().left >= 0 && $(this).position().left <= 2 ) {
$('.slider_1').fadeIn();
$('.slider_2').fadeOut();
}
...
I hope someone can help me :)
Ah! finally an interesting jQuery question.
This can definitely be achieved. Below I've explained how. If you want to go straight to the demo, click here.
Let's assume your HTML is setup as follows:
<div id="slider">
<div id="bar"></div>
</div>
Where the bar is the actual thing you click and drag.
Now what you need to do is the following:
get the $('#bar').offset().left
explicitly specify the position of #bar when the draggable is dragged, using some extra variable SPEED
For example:
ui.position.left += (ui.offset.left - ui.originalPosition.left - leftOffset)*SPEED;
Then, you can use the $('#bar').offset().left in jQuery's .fadeTo() (or other) function to change the opacity of the image you are talking about.
This all seems rather trivial, but it's not. There are some problems when trying to implement this. For example:
When the slider reaches the maximum sliding distance, it should stop animating or be reset. You can do this in multiple ways but I think the easiest solution is to write a .mousedown / .mouseup listener which updates a variable dragging, that keeps track whether the user is still trying to drag #bar. If it's not, reset #bar. If it is, keep the slider at the maximum distance until .mouseup is fired.
Also, you must be careful with predefined borders.
The code I propose is the following:
// Specify your variables here
var SPEED = -0.6;
var border = 1; // specify border width that is used in CSS
var fadeSpeed = 0; // specify the fading speed when moving the slider
var fadeSpeedBack = 500; // specify the fading speed when the slider reverts back to the left
// Some pre-calculations
var dragging = false;
var slider = $('#slider');
var leftOffset = slider.offset().left + border;
var adjustedSliderWidth = 0.5*slider.width();
// the draggable function
$('#bar').draggable({
axis: "x",
revert : function(event, ui) {
$(this).data("draggable").originalPosition = {
top : 0,
left : 0
};
$('#image').fadeTo(fadeSpeedBack, 0);
return !event;
},
drag: function (event, ui) {
var barOffset = $('#bar').offset().left - leftOffset;
if (barOffset >= 0) {
if (barOffset < adjustedSliderWidth) {
ui.position.left += (ui.offset.left - ui.originalPosition.left - leftOffset)*SPEED;
} else {
if (!dragging) { return false; }
else { ui.position.left = adjustedSliderWidth; }
}
}
// fading while moving:
$('#image').fadeTo(fadeSpeed, (ui.position.left/adjustedSliderWidth));
// remove this if you don't want the information to show up:
$('#image').html(ui.position.left/adjustedSliderWidth
+"<br \><br \>"
+ui.position.left);
}
});
// the mouse listener
$("#bar").mousedown(function(){ dragging = true; });
$("#bar").mouseup(function(){ dragging = false; });
I've also implemented the revert option on draggable so the slider nicely returns to zero when the user releases #bar. Of course you can delete this if you want.
Now the variable that your whole question is about is the variable: SPEED.
You can specify the speed of dragging by specifying a number for this variable.
E.g.:
var SPEED = 0.0; // the normal dragging speed
var SPEED = 0.5; // the dragging speed is 1.5 times faster than normal
var SPEED = -0.5; // the dragging speed is 0.5 times faster than normal
So negative values give a slower dragging speed and positive values give a faster dragging speed.
Unfortunately (or actually: fortunately), it is not possible to change the speed of the mouse pointer. This because only the OS has control over the mouse coordinates and speed. Browsers cannot influence this. Personally I think it doesn't matter: moving the slider slower than normal is what you're trying to achieve, so you can ignore the mouse pointer.
To see all this in action, I've prepared a working jsFiddle for you:
DEMO
I hope this helps you out :)

jQuery Menu how to contain submenus within Div

I am trying to create an 'application' contained in a div on a web page. This can't be any larger than certain dimensions (lets say: 550px by 280px). I have a menu with at least 1-3 sub menus for each item. The problem is, while I know the submenu is no larger than 280px high, the submenus often extend beyond the parent div's bounds (except for the last one which always grows upward not down).
Is there any way to make the menus grow up or down depending on whether it will extend beyond the div's bounds?
Here is a JSFiddle: http://jsfiddle.net/3FqcG/
Notice how the "Salzburg" submenu grows down beyond the bounds of the black DIV? I want that to grow up if it is too long and down if there is enough room.
Currently, I am just using the basic initialization: $( "#menu" ).menu();
Thanks!
I don't believe you can do this in CSS.
This leaves us with javascript. The basic idea is to:
calculate the baseline of the menu
if this lies outside the boundary
move the menu upwards to correct the position
live almost happily ever after
But, we have one major issue:
Though we capture the focus of an element, we don't know when its submenu is displayed & positioned. So although your problem is technically solved, it is by far not a desirable solution.
UPDATE
The best workaround I could come up with was to:
Turn off the animation (to avoid ugly glitches)
Add a watcher that would constantly monitor the element that is about to be opened
If opened, apply the position correction
Anyway, if you consider coming this far, you might as well override the default positioning of the jquery ui component, with the note that you will not be able to easily update the library. Update: or try Rudy Garcia's version if it works
Demo
Code of the demo:
var BASE_OFFSET = $('#menuContainer').offset().top;
var MAX_OFFSET = $('#menuContainer').height(); // get the offset of the container
var whenVisible = function($el, callback){ //executes callback when $el is visible
if($el.is(':visible')){ // if visible
callback(); // execute callback
}else{ // otherwise
setTimeout(function(){whenVisible($el, callback);},10); // do the same check in 10 ms
}
};
var fixPosition = function($menu){ // correct the position of the menu in respect to #menuContainer
var h = $menu.outerHeight(true); // take the height of the menu
var menuBottom = $menu.offset().top + h - BASE_OFFSET; // the baseline of the menu (taking into consideration the BASE_OFFSET)
if(menuBottom > MAX_OFFSET){ // if this is outside the MAX height
var diff = MAX_OFFSET - menuBottom; // calculate the difference
var newTop = $menu.position().top + diff; // modify current positioning with the calculated diff value
$menu.css('top', newTop + 'px'); // apply it to top (which is also used by jquery to position submenus
}
$.fx.off = false; // switch animations back on
};
$( "#menu" ).menu().on('menufocus', function(ev, ui){ // on the event menufocus
var $ui = $(ui.item); //take the focused element
var $menu = $ui.children('ul'); // take its related submenu
if($menu.length === 0){ // if there is none
return; // just terminate
}
$.fx.off = true; // switch off jQuery effects (otherwise you'll have glitches)
whenVisible($menu, function(){fixPosition($menu);}); // execute fixPosition when $menu is visible
});
You could also look at the API for this widget:
http://api.jqueryui.com/menu/
You can use the position option to position the elements how you want.
This will change the position so that they are within the box, however you will want to dynamically access the last to give it the position you want as the code below will change all menu items to move up 50.
$( "#menu" ).menu({ position: { my: "left top", at: "right+5 top-50" } });
A complete list of positioning options are also found here: http://api.jqueryui.com/position/
Apparently jquery UI has accounted for this and has given the option "within" to make sure your element stays within another element of your choice.
Therefore Your solution should be this:
$( "#menu" ).menu({ position: {within: '#menuContainer' } });

Javascript dragging selects text and elements around the drag area

I'm using the following code to allow users to resize a DIV element vertically, but when the click and drag the handle, text and other elements on the page get selected as if the user was just clicking and highlighting everything on the page. Is there a way to prevent this from happening as this looks very bad? This is being used with Prototype.js.
function DragCorner(container, handle) {
var container = $(container);
var handle = $(handle);
// Add property to container to store position variables
container.moveposition = {y:0};
function moveListener(event) {
// Calculate how far the mouse moved
var moved = { y:(container.moveposition.y - event.pointerY()) };
// Reset container's x/y utility property
container.moveposition = {y:event.pointerY()};
// Update container's size
var size = container.getDimensions();
container.setStyle({height: size.height + moved.y + 'px'});
}
// Listen for 'mouse down' on handle to start the move listener
handle.observe('mousedown', function(event) {
// Set starting x/y
container.moveposition = {y:event.pointerY()};
// Start listening for mouse move on body
Event.observe(document.body,'mousemove',moveListener);
});
// Listen for 'mouse up' to cancel 'move' listener
Event.observe(document.body,'mouseup', function(event) {
Event.stopObserving(document.body,'mousemove',moveListener);
console.log('stop listening');
});
}
Following up your earlier question and answer, add a small handle element and make it as the only element that can be selected and draggable. Also I guess you made that div's position as relative and handle position as absolute. Right??
Adding the following to the DIV fixed this:
onselectstart="return false;"

Categories

Resources