jQuery UI Sortable containment unable to detect placeholder - javascript

So I was working on this application where I want people to be able to drag items from one table data to the other table data, which must be contained within the parent table row.
But whenever I drag it around it seems to stick to the containment excluding the height of any placeholders.
Try it yourself: http://jsfiddle.net/2wy8R/
Any idea of how I can make it select the parent of the parent? Of not, then, how can I make the placeholders count?
Greetings
.
Update: YouTube video of the problem http://youtu.be/PMXcQvJmRGw

OK, here you go. Overridden the default containment as it seems buggy with your scenario. Let me know if this is not a good idea but it seems to work:
http://jsfiddle.net/2wy8R/6/
$('#first, #second').sortable({
connectWith: '.sortable',
placeholder: 'placeholder',
start: function(event, ui) {
ui.placeholder.height(ui.item.height() - 4);
var p = $(ui.helper);
var tr = p.closest("tr");
$(document).mousemove(function(e) {
var pOffset = p.offset();
var trOffset = tr.offset();
if (pOffset.left < trOffset.left) {
p.css({left: trOffset.left});
}
if (pOffset.left + p.width() > trOffset.left + tr.width()) {
p.css({left: trOffset.left + tr.width() - p.width()});
}
if (pOffset.top < trOffset.top) {
p.css({top: trOffset.top});
}
if (pOffset.top + p.height() > trOffset.top + tr.height()) {
p.css({top: trOffset.top + tr.height() - p.height()});
}
});
}
}).disableSelection();
just be careful with it though, as this keeps adding mousemove events to the document. you may want to unbind mousemove before binding it...

Related

Fullcalendar (v2.x) Getting day-cell from an eventClick?

I'm struggling to retrieve the day clicked when clicking on a multi-days (period) event (eventClick).
Whe clicking on the background cell, it's easy :
function dayClick(date, jsEvent, view) {
//todo: store related day
$scope.selectedDay = date;
}
But when clicking on an event (which is on a period of days), I can't retrieve which day exactly the user was on. (I need to perform a different action depending on the background day) :
function alertEventOnClick(event, jsEvent, view) {
// "event" stores only start and end date without any reference to the current day
// "jsEvent" retrieve the "td" element but fullcalendar HTML structure is complex and it's impossible to go-up to find the day-cell
// $scope.selectedDay = ??
};
I tried playing with "selectable" but "eventClick" JS Event doesn't propagate, and doesn't "select" the day
Thanks
If you don't need to catch event clicks at all, you can just add a class to all events with pointer-events: none;
JSFiddle Demo
eventRender: function (event, element) {
$(element).addClass('clickThrough');
},
IE8
Bleh. There is a hacky method to simulate pointer-events: none; on IE8 but while trying to implement it I found a simpler way to achieve the goal. (The simulated pointer-events hack is described here.)
To get the date at the cursor:
Hide the event table
Get element under cursor
Show the event table
JSFiddle Demo
eventClick: function (calEvent, jsEvent, view) {
var topLayer = $(jsEvent.currentTarget).closest("tbody");
topLayer.hide(); //hide the entire event layer (makes it work with multiple events)
var dayElement = document.elementFromPoint(jsEvent.pageX, jsEvent.pageY); //get the element under the cursor (should be a day cell)
topLayer.show();
alert($(dayElement).data("date"));
}
Better IE8 workaround
(this doesn't seem to work)
So it turns out that anchor tags have a better pointer-events: none; workaround. https://stackoverflow.com/a/18118092/728393
You simply add the attribute disabled="disabled" to the <a> and it can be clicked through.
JSFiddle Demo
eventRender: function (event, element) {
$(element).addClass('clickThrough').attr("disabled","disabled"); //disabled attribute is a IE workaround for pointer-events
},
So I finally found a quick(&dirty ?) solution relying on parsing close DOM Table elements.
From eventClick js event, I can retrieve the currentTarget clicked and go back in the table headers : the first row contains thead headers definition containing cell date mapping.
$scope.alertEventOnClick = function(event, jsEvent, view) {
var columnIndex = $(jsEvent.currentTarget).parent('td').get(0).cellIndex + 1;
var parentTable = $(jsEvent.currentTarget).closest('table');
var dateString = $('thead td:nth-child('+ columnIndex +')', parentTable).attr('data-date');
$scope.selectedDay = moment(dateString);
};
edit: solution OK for 1 event by day. but KO for more than one event by day ...
Another solution can be to determine x/y position of the clicked cell (by counting div/td childs) and map it with view.coordMap.grid.cellDates[position]
Here is my solution:
eventClick: function(event, jsEvent, view) {
var day=$(this).closest('.'+(view.type=='month'?'fc-row':'fc-time-grid')).find('.fc-bg td:eq('+$(this).closest('td').index()+')').data('date');
console.log(day);
}
Here is slicedtoad's solution modified to work with v4 of fullcalendar and also taking into account the window's current scroll position:
eventClick: function(ev) {
var topLayer = $(ev.el).closest(".fc-row");
var eventLayer = topLayer.find(".fc-content-skeleton");
eventLayer.hide();
var dayElement = $(document.elementFromPoint(ev.jsEvent.pageX - window.pageXOffset, ev.jsEvent.pageY - window.pageYOffset));
eventLayer.show();
var current_day = dayElement.attr("data-date");
console.log(current_day);
}
I updated Dane Iracleous's solution slightly to make it work with 1. click on multi-day events when there is more than one on the same day. and 2. this works in month/week/day views.
Using visibility/z-index solved my issue of the 'all day' container shrinking temporarily when the event layer was hidden.
getClickedDate(event) {
//used to get specific date clicked on multiple day event
let topLayer = $(event.el).closest(".fc-row");
let eventLayer = topLayer.find(".fc-content-skeleton");
let initialZIndex = $(eventLayer).css('z-index')
$(eventLayer).css({ 'visibility': 'hidden', 'z-index': '-1'});
let dayElement = $(document.elementFromPoint(event.jsEvent.pageX - window.pageXOffset, event.jsEvent.pageY - window.pageYOffset));
$(eventLayer).css({ 'visibility': 'visible', 'z-index': initialZIndex });
let clickedDate = dayElement.attr("data-date");
console.log('clicked date', clickedDate)
return clickedDate;
}

Tricky CSS bordering on grid/dashboard layout

I'm trying to accomplish some tricky borders around certain elements and am on the fence about the best way to accomplish.
Essentially, I want to create a border to the right of each dashboard element and if there is an element under it add a border below it too. I'm using the gridster jquery plugin for the layout engine. It uses <ul><li></li></ul> structure for each grid item.
So given the following I want to the apply the | and _ to each item.
[item] | [item] | [item]
______ _______
[item] | [item]
_____ ______
[ item ]
the issue is these items are 100% customizable by end-users from a column and row spanning aspect. Additionally, the screen resize can come into account to break them down.
I've got a basic implementation for the right with the last item not having a border via css :last.
Any ideas on what would be the best way to accomplish this?
As the grid uses absolute positioning, we can't really use the sequence of the elements in the DOM for much, which makes a pure CSS solution impossible (as far as I can tell...). I would go with some dynamic CSS based on the dimensions of the grid after tthe user has finished dragging a widget. Something like this:
function getGridDimens(grid){
var dimens = {rows: 0, cols: 0};
$.each(grid, function(idx, value){
if(value.row > dimens.rows) dimens.rows = value.row;
if(value.col > dimens.cols) dimens.cols = value.row;
});
return dimens;
}
var $hasRightBorder;
var $hasBottomBorder;
var gridster = $('#gridster ul').gridster({
/* Your options here */
draggable: {
stop: function(e, ui, $widget){
if($hasRightBorder)
$hasRightBorder.css('border-right', '');
if($hasBottomBorder)
$hasBottomBorder.css('border-bottom', '');
var dimens = getGridDimens(gridster);
$hasRightBorder = $('#gridster ul li').not('[data-row=' + dimens.rows + ']');
$hasBottomBorder = $('#gridster ul li').not('[data-col=' + dimens.cols + ']');
$hasRightBorder.css('border-right', '1px solid #eee');
$hasBottomBorder.css('border-bottom', '1px solid #eee');
}
}
});
I don't have a handy gridster example to test this on, but hopefully you get the general idea, and can adapt it to your needs.
Edit re. comments regarding classes instead of raw css:
function getGridDimens(grid){
var dimens = {rows: 0, cols: 0};
$.each(grid, function(idx, value){
if(value.row > dimens.rows) dimens.rows = value.row;
if(value.col > dimens.cols) dimens.cols = value.row;
});
return dimens;
}
var gridster = $('#gridster ul').gridster({
/* Your options here */
draggable: {
stop: function(e, ui, $widget){
$('.hasRightBorder').removeClass('hasRightBorder');
$('.hasBottomBorder').removeClass('hasBottomBorder');
var dimens = getGridDimens(gridster);
$('#gridster ul li').not('[data-row=' + dimens.rows + ']').addClass('hasRightBorder');
$('#gridster ul li').not('[data-col=' + dimens.cols + ']').addClass('hasBottomBorder');
}
}
});

How to change the containment of an object on load in jQuery

I am currently trying to change the containment of jquery page items as a page loads. The current set up is that each of the items is associated with a class which then sets up the draggable properties of all the items.
Given particular actions, I need to be able to change the containment of particular bars to allow them to move inside different bounds than those initially set.
I've attached a jsFiddle (http://jsfiddle.net/cyVYq/28/) demonstrating what I am currently doing which for some reason will not break the original containment that has been set. Any help would be appreciated.
//find the elements that have changed on pback
var newlyConstrainedItems = $("[id*=container]");
$.each(newlyConstrainedItems, function (key, value) {
var barID = this.id.split("_");
$(barID[1]).draggable({
containment: $("#" + this.id)
});
});
Thanks
You forgot the hash symbol in your id selector:
var newlyConstrainedItems = $("[id*=container]");
$.each(newlyConstrainedItems, function (key, value) {
var barID = this.id.split("_");
$('#'+barID[1]).draggable({
containment: $("#" + this.id)
});
});

Creating multiple droppable siblings that at positioned on top of eachother

I am trying to create multiple jquery droppables next to eachother where some parts might overlap, in these cases I want the one that is on top (z-index wise) to be greedy.
I have tried setting the greedy: true option in the droppable, but this does not seem to help. I have also tried to return false on the drop event and used event.stopPropagation();.
Here is a jsfiddle based on the demo page of jquery.
Is there any way to stop the drop event from propagating if there is another droppable triggering it, preferably the one that has the highest z-index?
Use document.elementFromPoint to check the element directly under the cursor. If it’s not your droppable, don’t do anything.
Add the following to the top of your drop: handler:
var droppable = $(this);
ui.helper.css({pointerEvents: 'none'});
var onto = document.elementFromPoint(event.clientX, event.clientY);
ui.helper.css({pointerEvents: ''});
if(!droppable.is(onto) && droppable.has(onto).length === 0) {
return;
}
Disabling pointer events on the helper is necessary for document.elementFromPoint to ignore the thing your dragging and only checking underneath. I’ve updated your jsFiddle for a quick demonstration. Note that the highlight still affects both elements. You should be able to change that by not letting jQuery UI do the highlighting but instead write it yourself on the draggables drag: event. I have, however, found this to be unusable in practice as this check (and disabling pointer events) is quite slow and may also cause flickering.
You need a function to check if the element is the highest visible element. Something is the highest visible when
The item is later in the dom list
It has a higher z-index set
The below function can be used to see if the current element (either in over method or drop method in the droppable setting) is in fact the highest visible element.
function isHighestVisible(cEl) {
cEl = $(this); //use this if you want to use it in the over method for draggable, else just pass it to the method
//get all active droppable elements, based on the classes you provided
var $list = $('.ui-widget-header.ui-state-active');
var listCount = $list.length;
var HighestEl = null;
var HighestZI = -1;
//one element is always highest.
if (listCount<=1) {
console.log(cEl.attr('id'));
return true;
}
//check each element
$list.each(function(i) {
var $el = $(this);
var id = $el.attr('id');
//try to parse the z-index. If its 'auto', return 0.
var zi = isNaN(parseInt($el.css('z-index'))) ? 0 : parseInt($el.css('z-index'));
if (zi>0) {
//z-index is set, use it.
//I add the listCount to prevent errors when a low z-index is used (eg z-index=1) and there are multiple elements.
//Adding the listCount assures that elements with a z-index are always later in the list then elements without it.
zi = zi + listCount;
} else {
//z-index is not set, check for z-index on parents
$el.parents().each(function() {
$par = $(this);
var ziParent = isNaN(parseInt($par.css('z-index'))) ? 0 : parseInt($par.css('z-index'));
if (ziParent>0) {
zi = ziParent;
return false;
}
});
}
//add the current index of the element to the zi
zi += i;
//if the calculated z-index is highest, change the highest element.
if (HighestEl==null || HighestZI<zi) {
HighestEl=$el;
HighestZI = zi;
}
});
//if the highest found active element, return true;
if (HighestEl.attr('id')==cEl.attr('id')) {
console.log(cEl.attr('id'));
return true;
}
//if all else fails, return false
return false;
}
If i had this issue i would use jQuery's .addclass
.droppablewidgetforce {
z-index: 1000 !important;
}
make a class as such so the layer stays on top no matter what.. this should fix the issue.
The greedy options is just for nested elements that have a droppable parent.
In your code the 2 droppable elements are siblings so the greedy option will not work:
<div id="droppable3" class="ui-widget-header">
<p>Outer droppable</p>
</div>
<div id="droppable3-inner" class="ui-widget-header">
<p>Inner droppable (greedy)</p>
</div>
Here is a messy workaround if you still want to use siblings instead of parents and children.
Live code example.
function normalDraggedOver() {
$( this ).addClass( "ui-state-highlight" )
.find( "> p" )
.html( "Droppeeeed!" );
}
var options = {
activeClass: "ui-state-hover",
hoverClass: "ui-state-active",
greedy: true,
drop: normalDraggedOver
};
var options2 = {
activeClass: "ui-state-hover",
hoverClass: "ui-state-active",
disabledClass: "ui-state-disabled",
hoverStateClass: "ui-state-hover",
greedy: true,
greedyWithSibligs: true,
drop: normalDraggedOver,
over: function () {
/* if we have previous siblings of the inner element we handle them as parents */
if(options2.greedyWithSibligs && $(this).prev().length > 0) {
$(this).siblings().slice(0, $(this).index())
.removeClass(options2.hoverClass)
.droppable('disable')
.removeClass(options2.disabledClass)
.removeClass(options2.hoverStateClass);
}
},
out: function () {
/* all the siblings of the inner element that were handled as parents act as one*/
if(options2.greedyWithSibligs && $(this).prev().length > 0) {
$(this).siblings().slice(0, $(this).index())
.droppable('enable')
.addClass(options2.hoverClass);
console.log($(this).attr('id'));
}
}
};
If just want it to look the same you can set the #droppable3 to position: relative and set the child #droppable3-inner to position: absolute.
HTML:
<div id="droppable3" class="ui-widget-header">
<p>Outer droppable</p>
</div>
<div id="droppable3-inner" class="ui-widget-header">
<p>Inner droppable (greedy)</p>
</div>
Here is the live example

JQuery UI.sortable, how to change helper offset dynamically?

I have a list of items each with heavy content. When I apply jQuery sortable, what I wanted to achieve is this, when an item is dragged away and starts sorting, all the item contents will be hidden automatically.
From all my testtings, jQuery doesn't accommodate such changes very well, for example, if I do the following,
$("#sortable").sortable({
start: function(event, ui) {
$(".hidden").addClass("hide");
}
});
jQuery can't automatically figure out the sizes of hidden items and the sorting will be a mess.
So I solved it using an indirection, I firstly check mousedown, mouseup and mousemove event, and once dragging is detected (say when mousedown/mousemove for more than 50ms), I'll hide the items immediately.
And in jQuery, I used some delay to start sorting.
var mouse_down = false;
var mouse_down_time;
var content_hidden;
$(".drag-handle").bind("mousedown", function(e) {
mouse_down = true;
mouse_down_time = e.timeStamp;
$(".drag-handle").bind("mousemove", function(e) {
if (mouse_down && !content_hidden) {
if (e.timeStamp - mouse_down_time > 10) {
$(".hidden").addClass("hide");
content_hidden = true;
}
}
});
}).bind("mouseup", function(e){
$(".drag-handle").unbind("mousemove");
mouse_down = false;
if (content_hidden) {
$(".hidden").removeClass("hide");
content_hidden = false;
}
});
$("#sortable").sortable({
delay:100,
stop: function(event, ui) {
if (content_hidden) {
$(".hidden").removeClass("hide");
$(".drag-handle").unbind("mousemove");
content_hidden = false;
}
}
});
So jQuery will start sorting after item contents are hidden.
Everything works fine except the helper offset. The helper offset seems to bump up for exactly the amount of spaces occupied by the all the item contents above the dragged item (which are hidden now during dragging).
So my question is,
is there a way to dynamically calculate all the item heights before and after hidden, and set the helper offset accordingly?
Thanks.
Well, problem solved.
What I did is dynamically calculate all the items height above the dragged item (before and after hiding contents), and then set the helper offset to the corresponding value by setting css:({'top-margin':offset_value+'px'})

Categories