Integrate jquery ui draggable with jquery.gantt (works but breaks scrolling) - jquery-ui

I am using the jquery-ui draggable component with jquery.gantt here. I could do enable drag on the items easily by $('.ganttRed').draggable() but the problem with this is that once we start scrolling the graph left to right using the slider below, the elements that are moved remain where they are instead of scrolling with the graph.
I looked through the source and from my understanding the margin-left is being changed during the scrolling; but jquery-ui uses the left attribute and in the presence of left the element keeps its position. My CSS knowledge ends just about there so if any of you are willing to provide any suggestions on how this can be fixed; I will greatly appreciate it.
I have a created a fiddle demonstrating the problem at: http://jsfiddle.net/Y2cxa/. In order to see the behavior I am speaking about:
Scroll the graph (either with your mouse wheel or the slider at the bottom); things should look and behave as expected.
Move any of the magenta(-ish) bars around and then scroll.
Again, thank you for your time and any assistance will be greatly appreciated.
Best regards

You have probably solved this or done something else by now but since I needed this aswell i solved it.
Got a solution for you here:
http://jsfiddle.net/Y2cxa/18/
First I simply copied the left value to margin-left and then removed the left value completely, however this led to some strange numbers.
To solve this I compared the start value of left with the final value of left and applied the same difference in pixels to margin-left!
Simply replace:
$('.ganttRed').draggable({axis:'x'});
with:
$('.ganttRed').draggable({
axis:'x',
start: function(event, ui) {
$(this).data("startx",$(this).offset().left);
},
stop: function(event, ui) {
var change = $(this).offset().left - $(this).data("startx");
var value = $(this).css('margin-left');
value = value.split("px");
value = parseInt(value[0]) + change;
$(this).css('margin-left', value);
$(this).css('left', '');
}
});

I believe below is a better solution and I am using it in my application
For vertical and horizonal dragging
$('.ganttRed').draggable(
{
start: function (event, ui) {
$(this).data("startx", $(this).css('left').split("px")[0]);
$(this).data("starty", $(this).css('top').split("px")[0]);
},
stop: function (event, ui) {
var left = parseInt($(this).css('left').split("px")[0]);
var changex = left - parseInt($(this).data("startx"));
var top = parseInt($(this).css('top').split("px")[0]);
top -= top % 24;
$(this).css('top', top);
var changey = top - parseInt($(this).data("starty"));
}
});
changex, changey will be used in calculation while updating in database
For horizontal resizing
$(".ganttRed").resizable({ handles: 'e, w' });

Related

JQueryUI - droppable.touch change

I am currently trying to make an extended droppable area but I got some problem. Here is the code (explanation below) :
$('div#1').find('div[cid="draggable"]').each(function(i, e) {
$(e).after("<div id='drp' style='width:100%;'></div>");
$('div#drp').droppable({
drop:function(event, ui) {
ajaxCall = false;
ui.draggable.css("left", "0");
ui.draggable.css("top", "0");
$(event.target).after(ui.draggable);
$(this).css("height", "0px");
$(this).removeClass("isDroppable");
$(this).removeClass("mb-1");
var t = ui.draggable.attr("i");
// callPage(81758758, 'POST', {t:t,s:0}, '');
},
over:function(event, ui) {
$(this).css("height", (parseInt(ui.helper.css("height")) + 50)+"px");
$(this).addClass("isDroppable");
$(this).addClass("mb-1");
if($(this).prev().attr("o") < ui.helper.attr("o")) {
// Move to top : Un padding s'ajoute, étrangement.
}
},
out:function(event, ui) {
$(this).css("height", "0px");
$(this).removeClass("isDroppable");
$(this).removeClass("mb-1");
},
accept:function(d) {
if(d.attr("cid") == "draggable" && d.attr("i") != $(this).prev().attr("i")) {
return true;
}
},
tolerance:'touch'
});
});
So actually, I am creating, for each child of div#0, a div#drp. It means that if I have, in div#0, 5 div#card, I will have, for each div#card, a div#drp (width:100%, height:0), for a total amount of 5 div#drp. As you should have understand, each div#drp is a DROPPABLE AREA (and each div#card can be dropped on each div#drp).
But here is the problem : The only way to get this working is to set the tolerance, as it is on my code, to "touch", as long as "pointer", "intersect" & "fit" will never work because of div#drp is set to height=0px.
Now, imagine the following structure :
<div id="card" i="1" style="width:100%;height:300px;">CARDTEST</div>
<div id="grp" i="1" style="width:100%;height:0;"></div>
<div id="card" i="2" style="width:100%;height:100px;">CARDTEST</div>
<div id="grp" i="2" style="width:100%;height:0;"></div>
<div id="card" i="3" style="width:100%;height:100px;">CARDTEST</div>
<div id="grp" i="3" style="width:100%;height:0;"></div>
Here, if I move the 2nd or 3rd card, everything will be alright. But if I move the first card (with height:300px) :
First, the id#grp height will be set to "(parseInt(ui.helper.css("height")) + 50)+"px"", it means 350px.
Second, even with that, my first card will trigger the first div#drp and the second div#drp as long as it height is too big. It's like if changing the height of my div#drp doesn't extend my DROPPABLE AREA.
So, my question is : Is there a way to "actualise" the DROPPABLE AREA within the "over" event ? I tried to change the droppable event within the "over" event, I also tried to change the tolerance within the "over" event, but nothing seems to work.
Any idea ?
Solution :
There isn't anything in Droppable() making this possible. However, it is possible to update DROPPABLE AREA positions within Draggable options. See below :
$( ".selector" ).draggable({
refreshPositions: true
});
I didn't expect a DROPPABLE property to be editable through DRAGGABLE elements. Note that, according to the documentation :
"If set to true, all droppable positions are calculated on every mousemove. Caution: This solves issues on highly dynamic pages, but dramatically decreases performance."
Tested on multiples devices, it doesn't cause lags or freez, so it should be fine.
Note that this config value will also fix margins caused by any Element added on Droppable.over if the Draggable Element is under the Droppable element (In example, if you want to do the same as what Sortable() do, but with Draggable() & Droppable()).
Problem solved !

Is there a way of dynamically toggling the Highstock navigator to regain vertical space for the chart?

I'd like to be able to be able to dynamically toggle the presence of the Highstock navigator and allow the chart to expand into the vertical space it occupied.
I've tried simply toggling chart.userOptions.navigator.enabled but it has no effect.
This thread explains how to use .hide() and .show() methods to conceal the individual components of the navigator and scrollbar, but these use visibility:hidden so the space does not become available for the chart. However, using .css({display: 'none'}) works, but the series itself has no .css() method, and I've been unable to find a way of removing the series from just the navigator.
Does anyone know a method to achieve what I want?
Thanks.
In short: it's not supported to hide navigator in real time. The best way would be to destroy chart and create new one with disabled navigator.
Other solution is to use workaround provided by Sebastian Bochan. Then you will need to update manually yAxis.height, for example: http://jsfiddle.net/dJbZT/91/
$('#btn').toggle(function () {
chart.yAxis[0].defaultHeight = chart.yAxis[0].height;
chart.xAxis[0].defaultHeight = chart.xAxis[0].height;
chart.yAxis[0].update({
height: 500 - chart.plotTop - 35
}, false);
chart.xAxis[0].update({
height: 500 - chart.plotTop - 35
});
chart.scroller.xAxis.labelGroup.hide();
chart.scroller.xAxis.gridGroup.hide();
chart.scroller.series.hide();
chart.scroller.scrollbar.hide();
chart.scroller.scrollbarGroup.hide();
chart.scroller.navigatorGroup.hide();
$.each(chart.scroller.elementsToDestroy, function (i, elem) {
elem.hide();
})
}, function () {
chart.yAxis[0].update({
height: chart.yAxis[0].defaultHeight
}, false);
chart.xAxis[0].update({
height: chart.xAxis[0].defaultHeight
});
chart.scroller.xAxis.labelGroup.show();
chart.scroller.xAxis.gridGroup.show();
chart.scroller.series.show();
chart.scroller.navigatorGroup.show();
chart.scroller.scrollbar.show();
chart.scroller.scrollbarGroup.show();
$.each(chart.scroller.elementsToDestroy, function (i, elem) {
elem.show();
})
});
There's another way to do this: reduce the chart height by the navigator's height, and set chart.reflow to false to prevent the Y-axis from adapting to the new chart height (try setting it to true in the fiddle -- notice the flicker when you show / hide the navigator?).
I've added this answer to the other thread, and the demo is here: http://jsfiddle.net/dJbZT/148/ (credits to Sebastian Bochan for the original answer).
I'm not sure when highcharts added this ability via options, but this worked for me:
var chart = $('#graphContainer').highcharts();
chart.options.navigator.enabled = !chart.options.navigator.enabled;
$('#graphContainer').highcharts(chart.options);

jQuery toggle slide element but not completely hide element

The following example is probably the easiest way to try and explain the effect I'm trying to achieve:
http://jsfiddle.net/qSscJ/2/
Code:
$(function() {
$('#handle').click(function() {
$('#box').toggle('slide', { direction: 'right' });
});
});
Click on the blue handle to make the entire red box collapse. How do I keep the blue handle visible after the box is collapsed (while keeping the handle anchored to the edge of the box)? I'm open to other jQuery UI APIs to achieve this effect.
You could do just animate the width directly so the element doesn't get marked as hidden at the end of the animation:
$(function() {
$('#handle').click(function() {
$('#box').animate({width: "0px"}, 1000);
});
});
But, it would be much better to change the design so that the blue tab was not contained within the box you're closing like here: http://jsfiddle.net/jfriend00/gKQrv/.
$(function() {
$('#handle').click(function() {
var box = $('#box');
var targetWidth = box.width() > 0 ? 0 : 150;
box.animate({width: targetWidth + "px"}, 1000);
});
});

Preventing flexcroll on event

What I have currently is a very simple div that has a flexcroll scroll bar. This simple div contains some draggable itmes inside of it. My goal is to be able to drag one of the items and and move it about without the flexcroll scroll bar moving.
As it stands right now if I were to drag one of the items below the viewable area the simple div will scroll down. I would like to prevent this.
I'm using jQuery UI for the draggable items. I've already tried using the option "scroll:false" but this does not work for flexcroll.
I'm sorry I don't have any example code, I'm currently away from my work computer.
flexcroll: http://www.hesido.com/web.php?page=customscrollbar
I don't know if you have already resolved this problem. This morning, I have the same problem and I found your post. After that, I have googled a lot to find a solution without any lucky. So finally, I decided to do someting myself, I hope my idea will help you.
After read the Programming Guid, I found that in this version (2.0) of flexcroll, we could register a function for onfleXcroll whose description could be found by searching the keyword "Pseudo-event: onfleXcroll". This is to say that the method will be executed after a scroll is done. So here, what I restore the "top" style with the value before you drag an element.
Here are the code
var $assetswrapper; // This variable indicates the contentwrapper of you div.
var $assetsscrollbar; // This variable indicates the vscroller of you div.
window.onfleXcrollRun = function () { // This method will be executed as soon as the div has been rendered with the help of flexcroll
// You could find these two divs by using firebug, because the top value of these two divs will be changed when we scroll the div which use the class .flexcroll.
$assetswrapper = $('#contentwrapper');
$assetsscrollbar = $('#vscrollerbar');
}
var wrapperTopPosition = 0; // This is used to stock the top value of the wrapperContent before dragging.
var scrollbarTopPosition = 0; // This is used to stock the top value of the scrollbar before dragging.
var dragged; // This is a boolean variable which is used for indicating whether the draggable element has been dragged.
var dropped = false; // This is a boolean variable which used to say whether the draggable element has been dropped.
$('.draggable').draggable({ // you could change .draggable with any element.
start: function (event, ui) {
// Your code here.
wrapperTopPosition = $assetswrapper.position().top;
scrollbarTopPosition = $assetsscrollbar.position().top
dragged = true;
},
stop: function (event, ui) {
// Your code here.
dragged = false;
dropped = true;
}
});
$('your drag div')[0].onfleXcroll = function () { // This method will be called each time when a scroll has been done.
if (dragged) {
$assetswrapper.css('top', wrapperTopPosition);
$assetsscrollbar.css('top', scrollbarTopPosition);
} else {
// Code here is used for keeping the top position as before even though you have dragged an element out of this div for a long time.
// You could test the scrollbar without this piece of code, if you drag an element out of the div for a long time, the scrollbar will keep its position,
// but after you dropped this element and try to scroll the div, then the scrollbar will reach the end of the div. To solve this problem,
// I have introduced the method setScrollPos with the old top position plus 72. 72 here is to set the scroll increment for this scroll, I know
// this value is not fit for any size of windows, but I don't know how to get the scroll-increment automatically.
if (dropped) {
dropped = false;
$('your drag div')[0].fleXcroll.setScrollPos(false, Math.abs(wrapperTopPosition) + 72);
$('your drag div')[0].fleXcroll.setScrollPos(false, Math.abs(wrapperTopPosition) + 72);
}
}
};
I hope this could give you a help if you haven't found any solution yet.

fixed position div freezes on page (iPad)

I have an asp.net web site I am building to be supported on ipad. When I focus on an input element and the keyboard pops up, the position fixed header div(which normally scrolls along with the page) will pop up the page a distance equivalent to the amount the keyboard takes up and freeze there for the duration of the input process. Once the keyboard is dropped back down, the div snaps back into place and behaves normally again. I am testing on iOS5 so position: fixed should be supported.
Is this a known issue? Has someone come across this and dealt with it before? I can't seem to find anything on this.
Fixed positioning is broken on iOS5/iOS6/iOS7.
Edit 3: See link to a working fix near end of this answer for iOS8.
Position:fixed is broken when either:
a) the page is zoomed
or
b) the keyboard shows on the iPad/iPhone (due to an input getting focus).
You can view the bugs yourself in jsbin.com/icibaz/3 by opening the link and zooming, or giving the input focus. You can edit the edit the html yourself.
Notes about bugs (a) and (b):
A fixed div with top: 0px; left: 0px; will show in the wrong position (above or below the top of the screen) when an input gets focus and the keyboard shows.
The problem seems to have something to do with the auto-centering of the input on the screen (changing window.pageYOffset).
It appears to be a calculation fault, and not a redraw fault: if you force the top: to change (e.g. switching between 0px and 1px) on the onScroll event, you can see the fixed div move by a pixel, but it remains in the wrong place.
One solution I used previously is to hide the fixed div when an input gets focus - see the other Answer I wrote.
The fixed div seems to becomes stuck at the same absolute position on the page it was at at the time when the keyboard opened.
So perhaps change the div to absolute positioning when an input has focus? Edit 3: see comment at bottom using this solution. Or perhaps save the pageXOffset/pageYOffset values before the keyboard is opened, and in an onScroll event calculate the difference between those values and the current pageXOffset/pageYOffset values (current once the keyboard is opened), and offset the fixed div by that difference.
There appears to be a different problem with fixed positioning if the page is zoomed - try it here (Also good information here about Android support for fixed in comments).
Edit 1: To reproduce use jsbin (not jsfiddle) and use the fullscreen view of jsbin (not the edit page). Avoid jsfiddle (and edit view of jsbin) because they put the code inside an iframe which causes interference with fixed positioning and pageYOffset.
Edit 2: iOS 6 and iOS 7 Mobile Safari position:fixed; still has the same issues - presumably they are by design!.
Edit 3: A working solution for (b) is when the input get focus, change the header to absolute positioning and then set the header top on the page scroll event for example. This solution:
Uses fixed positioning when input not focused (using window.onscroll has terrible jitter).
Don't allow pinch-zoom (avoid bug (a) above).
Uses absolute positioning and window.pageYOffset once an input gets focus (so header is correctly positioned).
If scrolled while input has focus, set style.top to equal pageYOffset (header will jitter somewhat due to onscroll event delay even on iOS8).
If using UIWebView within an App on iOS8, or using <=iOS7, if scrolling when input has focus, header will be super jittery because onscroll is not fired till scroll finishes.
Go back to fixed position header once input loses focus (Example uses input.onblur, but probably tider to use
document.body.onfocus).
Beware usability fail that if header too large, the input can be occluded/covered.
I couldn't get to work for a footer due to bugs in iOS page/viewport height when the keyboard is showing.
Edit example using http://jsbin.com/xujofoze/4/edit and view using http://output.jsbin.com/xujofoze/4/quiet
For my needs, I found it easier to use an absolute positioned header, hide it before scroll and show it when finish scroll (I need the same code to support iOS4 and Android).
For my purposes, I hide the header on a touchstart event, and show it again on touchend or scroll event (plus some timers to improve responsiveness/reduce flickering). It flashes, but is the best compromise I could find. One can detect the start of scrolling using the touchmove event (jQuery does this), but I found touchmove didn't work as well for me because:
regularly the iPad fails to do a repaint before scrolling (i.e. the absolute header remains stuck - even though the top was changed before scrolling started).
when an input element gets focus, the iPad auto-centres the element, but the scrollstart event doesn't get fired (because no touchmove if just clicking an input).
Implementing a fixed header on iOS5 could be improved by using a hybrid approach of fixed and absolute positioning:
used fixed positioning for iOS5 until an input gets focus.
when an input gets focus (keyboard showing), change to the iOS4 absolute positioning code.
when the keyboard is closed, change back to fixed positioning.
Code to detect when keyboard is closed (e.g. using keyboard hide key) is to register the DOMFocusOut event on the document element and do something like the following code. The timeout is needed because the DOMFocusOut event can fire between when one element gets the focus and another loses it.
function document_DOMFocusOut() {
clearTimeout(touchBlurTimer);
touchBlurTimer = setTimeout(function() {
if (document.activeElement == document.body) {
handleKeyboardHide();
}
}.bind(this), 400);
}
My fixed header code is something like:
{
setup: function() {
observe(window, 'scroll', this, 'onWinScroll');
observe(document, 'touchstart', this, 'onTouchStart');
observe(document, 'touchend', this, 'onTouchEnd');
if (isMobile) {
observe(document, 'DOMFocusOut', this, 'docBlurTouch');
} else if (isIE) {
// see http://ajaxian.com/archives/fixing-loss-of-focus-on-ie for code to go into this.docBlurIe()
observe(document, 'focusout', this, 'docBlurIe');
} else {
observe(isFirefox ? document : window, 'blur', this, 'docBlur');
}
},
onWinScroll: function() {
clearTimeout(this.scrollTimer);
this.scrolling = false;
this.rehomeAll();
},
rehomeAll: function() {
if ((isIOS5 && this.scrolling) || isIOS4 || isAndroid) {
this.useAbsolutePositioning();
} else {
this.useFixedPositioning();
}
},
// Important side effect that this event registered on document on iOs. Without it event.touches.length is incorrect for any elements in the document using the touchstart event!!!
onTouchStart: function(event) {
clearTimeout(this.scrollTimer);
if (!this.scrolling && event.touches.length == 1) {
this.scrolling = true;
this.touchStartTime = inputOrOtherKeyboardShowingElement(event.target) ? 0 : (new Date).getTime();
// Needs to be in touchStart so happens before iPad automatic scrolling to input, also not reliable using touchMove (although jQuery touch uses touchMove to unreliably detect scrolling).
this.rehomeAll();
}
},
onTouchEnd: function(event) {
clearTimeout(this.scrollTimer);
if (this.scrolling && !event.touches.length) {
var touchedDuration = (new Date).getTime() - this.touchStartTime;
// Need delay so iPad can scroll to the input before we reshow the header.
var showQuick = this.touchStartTime && touchedDuration < 400;
this.scrollTimer = setTimeout(function() {
if (this.scrolling) {
this.scrolling = false;
this.rehomeAll();
}
}.bind(this), showQuick ? 0 : 400);
}
},
// ... more code
}
jQuery mobile supports scrollstart and scrollstop events:
var supportTouch = $.support.touch,
scrollEvent = "touchmove scroll",
touchStartEvent = supportTouch ? "touchstart" : "mousedown",
touchStopEvent = supportTouch ? "touchend" : "mouseup",
touchMoveEvent = supportTouch ? "touchmove" : "mousemove";
function triggerCustomEvent( obj, eventType, event ) {
var originalType = event.type;
event.type = eventType;
$.event.handle.call( obj, event );
event.type = originalType;
}
// also handles scrollstop
$.event.special.scrollstart = {
enabled: true,
setup: function() {
var thisObject = this,
$this = $( thisObject ),
scrolling,
timer;
function trigger( event, state ) {
scrolling = state;
triggerCustomEvent( thisObject, scrolling ? "scrollstart" : "scrollstop", event );
}
// iPhone triggers scroll after a small delay; use touchmove instead
$this.bind( scrollEvent, function( event ) {
if ( !$.event.special.scrollstart.enabled ) {
return;
}
if ( !scrolling ) {
trigger( event, true );
}
clearTimeout( timer );
timer = setTimeout(function() {
trigger( event, false );
}, 50 );
});
}
};
This is somewhat still a problem in iOS13 (when a long text gets deleted in the 'textarea' field, fixed header jumps to the start of that 'textarea' field, obstructing the view), therefore, I thought I share my quick fix:
Since my footer is rather large, I went about without any JS and just adding a greater z-index to the footer than what the fixed header has. Out of sight, out of mind.

Resources