jQuery Mobile swipe gesture for touch screen only and not for mouse - jquery-mobile

I am using jQuery Mobile to allow touch screen users to navigate back and forth in a website with swipe left and swipe right gestures. The problem is that the swipeleft and swiperight events are also triggered with a normal mouse, and that is very annoying because it happens when the user selects some text with the mouse.
You can see the problem on the website itself (http://laetitia-stucki.ch/) and the JavaScript snippet below.
Do you have any idea how to trigger the swipe events only with touch devices and not with a regular mouse?
"use strict";
$( document ).ready( function() {
( function() {
$( "body" ).on( "swiperight", function( e ) { navigate_prev_page(); });
$( "body" ).on( "swipeleft", function( e ) { navigate_next_page(); });
function navigate_next_page() {
var target_page = $( ".button-next" ).first().attr( "href" );
window.location.href = target_page;
}
function navigate_prev_page() {
var target_page = $( ".button-prev" ).first().attr( "href" );
window.location.href = target_page;
}
})();
});

Thank you Gjermund Dahl for your answer. I followed your link and found another interesting link http://www.stucox.com/blog/you-cant-detect-a-touchscreen/ and finally managed to find a solution. The idea is to disable the swipe event when the mousedown event is triggered and to enable it again when a touchstart event is triggered. I post my solution below. As you can see, jQuery Mobile and Mousetrap libraries can work together.
"use strict";
$( document ).ready( function() {
( function() {
var navigate_to_page = function( e, button_class ) {
var target_page = $( button_class ).first().attr( 'href' );
window.location.href = target_page;
}
Mousetrap.bind( 'left', function( e ) { navigate_to_page( e, '.bouton-prec' ); });
Mousetrap.bind( 'esc', function( e ) { navigate_to_page( e, '.bouton-accueil' ); });
Mousetrap.bind( 'right', function( e ) { navigate_to_page( e, '.bouton-suiv' ); });
$( 'body' ).on( 'mousedown', function( e ) { disable_swipe( e ); });
$( 'body' ).on( 'touchstart', function( e ) { enable_swipe( e ); });
function disable_swipe( e ) {
$( 'body' ).off( 'swiperight swipeleft' );
}
function enable_swipe( e ) {
$( 'body' ).on( 'swiperight', function( e ) { navigate_to_page( e, '.bouton-prec' ); });
$( 'body' ).on( 'swipeleft', function( e ) { navigate_to_page( e, '.bouton-suiv' ); });
}
})();
});

Related

Remove initial selection of Select2

I am working with Select2 and jQuery UI Tooltip. I made a jsfiddle in this regard. I am describing issue below.
$( function() {
$(document).ready(function () {
$('.js-example-basic-single').select2();
$( document ).tooltip({
position: {
my: "center bottom-20",
at: "center top",
using: function( position, feedback ) {
$( this ).css( position );
$( "<div>" )
.addClass( "arrow" )
.addClass( feedback.vertical )
.addClass( feedback.horizontal )
.appendTo( this );
}
}
});
});
});
If I click on dropdwon of Select2, the drop down opens with first value selected. I would like to remove this initial selection.

Maintain Scroll Position When Panel Swipes Open

I have a page with a side panel that swipes open. I would like the scroll position to remain in the same place, when this panel is swiped open. Currently, it snaps to the top. My below code is not working. Any advice?
var storePosition = {
topCoordinate : null
}
$(document).ready(function(){
/////////////////////// JQUERY MOBILE SWIPING (Scroll position) //////////////////////
$( "#B" ).panel({
beforeopen: function( event ) {
storePosition.topCoordinate = $(this).offset().top;
$( "body [data-role=page]" ).css("position","fixed");
}
});
$( "#B" ).panel({
beforeclose: function( event ) {
$( "body [data-role=page]" ).css("position","");
if($.mobile.activePage.attr("id") == "page" && storePosition.topCoordinate !== 0){
$('html, body').animate({
scrollTop: $("#A").position().top += storePosition.topCoordinate - 60}, 10);
}
}
});
////////////////////// SIDE PANEL //////////////////////
$('#open').click(function(){
if($('#B').width() > 0){
$('#B').animate({width: '0px'}),
$( ".container" ).removeClass( "no-scroll" ).animate({right: '200px'});
}
else{
$('#B').animate({width: '200px'}),
$( ".container" ).addClass( "no-scroll" ).animate({right: '200px'});
}
});
$('#close').click(function(){
$('#B').animate({width:"0px"}),
$( ".container" ).removeClass( "no-scroll" ).animate({right: '0px'});
});
$("body").on("swipeleft",function(){
$('#B').animate({width:"200px"}),
$( ".container" ).addClass( "no-scroll" ).animate({right: '200px'});
});
$("#B").on("swiperight",function(){
$(this).animate({width:"0px"}),
$( ".container" ).removeClass( "no-scroll" ).animate({right: '0px'});
});
Here's the fiddle.
Note: The function of the panel is to push the content of the page to the left, when opened. It should be scrollable, but the content of the page should not be. This panel can also be opened/closed with a toggle button on the page.
It turns out, the answer was pretty simple.
Adding height:100vh to the wrapper is what made the page jump, upon opening the panel. I added that to prevent to content from being scrollable. But, I discovered that if I put overflow:hidden on the body instead of the wrapper, it would prevent scrolling. So, I could eliminate height:100vh and all that "scroll position" jargon all together.
Here is the fix.
/////////////////////// SEARCH TOGGLE //////////////////////
$('#open').click(function(){
if($('#B').width() > 0){
$('#B').animate({width: '0px'}),
$( ".container" ).animate({right: '200px'});
$( "body" ).removeClass( "no-scroll" );
}
else{
$('#B').animate({width: '200px'}),
$( ".container" ).animate({right: '200px'});
$( "body" ).addClass( "no-scroll" );
}
});
$('#close').click(function(){
$('#B').animate({width:"0px"}),
$( ".container" ).animate({right: '0px'});
$( "body" ).removeClass( "no-scroll" );
});
$("body").on("swipeleft",function(){
$('#B').animate({width:"200px"}),
$( ".container" ).animate({right: '200px'});
$( "body" ).addClass( "no-scroll" );
});
$("#B").on("swiperight",function(){
$(this).animate({width:"0px"}),
$( ".container" ).animate({right: '0px'});
$( "body" ).removeClass( "no-scroll" );
});

Pagecontainer change callback on change

I can't for the life of me figure out how to fire a callback to do something when the page has finished changing.
I have this:
$( ":mobile-pagecontainer" ).pagecontainer( "change", "#page2", {"transition":"slide"});
});
I basically want a function to fire when the page has finished sliding.
This is from the documentation, but it will fire on every page change, rather than on a specific page to page:
$( ":mobile-pagecontainer" ).pagecontainer({
change: function( event, ui ) {}
});
Please help.
Try the transition event
$(document).on( "pagecontainertransition", function( event, ui ) {
if (ui.toPage.prop("id") == "page2") {
//page2 finished transition, so do something
}
});
To sum it up : rewrite a pagecontainer( "change" ) function with an added callback
$.fn.pagecontainerChangeWithCallback = function ( to , option , callback ) {
var toClean = to.split('#').join('');
// listen to transition
$(document).off( "pagecontainertransition" ).on( "pagecontainertransition", function( event, ui ) {
// end of "change"
if ( ui.toPage.prop("id") == toClean && typeof callback == 'function' )
callback();
});
// effective change
$( "body" ).pagecontainer( "change" , to , option );
return ( this );
}
Usage :
$( ":mobile-pagecontainer" ).pagecontainerChangeWithCallback( "#page2" , {"transition":"slide"} , function() {
// my job after transition
});

How to correctly destroy spinner once it reaches certain value

In my application once user has entered certain value in the spinner I should change content of the view. As part of this process I need to destroy and remove spinner.
The problem is that spinner gets into the loop and increments its' value to no end.
Sample code:
spinner = $( "#spinner" ).spinner({
change: function( event, ui ) {
console.log('change');
},
spin: function( event, ui ) {
console.log( 'spin event, value = ', ui.value );
if ( ui.value == 3 ) {
spinner.spinner( "destroy" );
}
}
});
Please check the following sample: http://jsfiddle.net/XseWc/312/
Increase value 3 times: click, click, click
How can this be achieved?
Update:
Two options available here.
To use stop event instead of spin.
To trigger mouse up event before destroying spinner. With mouse up spinner will remove it handlers and destroy will work correctly.
Just prevent the default event!
spinner = $( "#spinner" ).spinner({
change: function( event, ui ) {
console.log('change');
},
spin: function( event, ui ) {
if ( ui.value == 3 ) {
//NEW
event.preventDefault();
spinner.spinner( "destroy" );
}
}
});
old fiddle
See this documentation entry. It prevents the spinner from doing the normal (default) behaviour.
EDIT
Perhaps this can be seen as a workaround but this solution works and doesn't trigger tons of errors.
$(function () {
var value;
spinner = $("#spinner").spinner({
change: function (event, ui) {
console.log('change');
},
spin: function (event, ui) {
value = ui.value;
},
stop: function (event, ui) {
if (value == 3) {
$(this).spinner("destroy");
}
}
});
});
updated fiddle
Try this instead (both hide() and remove() worked for me)
spinner = $( "#spinner" ).spinner({
change: function( event, ui ) {
console.log('change');
},
spin: function( event, ui ) {
if ( ui.value == 3 ) {
spinner.hide();
}
}
});
Will this accomplish what you're looking for?
spinner = $( "#spinner" ).spinner({
change: function( event, ui ) {
console.log('change');
},
spin: function( event, ui ) {
if ( ui.value == 3 ) {
spinner.remove();
}
}
});

jQueryUI droppable, stop propagation to overlapped sibling

As you can see here: http://jsfiddle.net/rA4CB/6/
When I make the drop in the overlapped area it is received in both droppables, greedy doesn't work when the items are siblings. Is there any way to block the reception on droppables lower in the zIndex?
BTW, mouseOver won't fire for the droppable element as the mouse is actually over the draggable element.
relevant JS:
$(function() {
$( "#draggable" ).draggable();
$( "#droppable" ).droppable({
tolerance:'pointer',
drop: function( event, ui ) {
$( this )
.addClass( "ui-state-highlight" )
.find( "p" )
.html( "Dropped!" );
}
});
$( "#droppable2" ).droppable({
tolerance:'pointer',
greedy:true,
drop: function( event, ui ) {
$( this )
.addClass( "ui-state-highlight" )
.find( "p" )
.html( "Dropped!" );
}
});
});
Okay, so I spend an hour trying to figure it out, then as soon as I ask I then find my answer
http://jsfiddle.net/rA4CB/7/
Modified the JS to the following:
$(function() {
$( "#draggable" ).draggable();
$( "#droppable" ).droppable({
tolerance:'pointer',
drop: function( event, ui ) {
$( this )
.addClass( "ui-state-highlight" )
.find( "p" )
.html( "Dropped!" );
}
});
$( "#droppable2" ).droppable({
tolerance:'pointer',
greedy:true,
drop: function( event, ui ) {
$( this )
.addClass( "ui-state-highlight" )
.find( "p" )
.html( "Dropped!" );
},
over: function(event, ui){
$( "#droppable" ).droppable( "disable" )
},
out: function(event, ui){
$( "#droppable" ).droppable( "enable" )
}
});
});
$( "#droppable" ).droppable({
greedy: true,
drop: function( event, ui ) {
if(ui.helper.is(".dropped")) {
return false;
}
ui.helper.addClass(".dropped");
}
});
Just set a css class on ui.helper of draggable. If the draggable has been accepted once, it will be refused by all other droppables (Could be done with the droppable "accept" parameter,too , like accept : ":not(.dropped)" and the class added to ui.draggable).
If you have a number of droppable in a container area then what????
You must re think for that problem. Greedy works for only parent to child relationship not in siblings. So you must edit droppable js or put your own logic for it.
So if you have a numbers of droppable then you must write some extra code to handle dropping on perfect droppable as it is in this example A number of siblings droppables
For making perfect siblings droppable modify your droppbale as below
var topElement = undefined;
var topElementArray = Array();
$( "#draggable" ).draggable();
$( ".droppableBox" ).droppable({
activeClass: "active-droppable",
tolerance:'pointer',
over: function( event, ui ) {
// This is for only show a message that z-index must be required for proppable
// you can remove it after testing or after development
if ($(this).css("z-index") == 'auto') {
alert("Please provide z-index for every droppable.");
return;
}
//
topElement = undefined;
topElementArray = Array();
// Change it as you want to write in your code
// For Container id or for your specific class for droppable or for both
$('#demo > .droppableBox').each(function(){
_left = $(this).offset().left, _right = $(this).offset().left + $(this).width();
_top = $(this).offset().top, _bottom = $(this).offset().top + $(this).height();
if (event.pageX >= _left && event.pageX <= _right && event.pageY >= _top && event.pageY <= _bottom) {
topElementArray.push($(this).attr('id'));
}
});
},
drop: function( event, ui ) {
if (!topElement) {
topElement = determineTopElement(topElementArray);
}
if ($(this).attr('id') == $(topElement).attr('id')) {
$( this ).addClass( "ui-state-highlight" ).find( "p" ).html( "Dropped!" );
// Your code after successful dropped element on specific siblings
// Start writing code for dropped element & perfect droppable
}
}
});
determineTopElement = function(_array) {
var topElement;
var zIndexHighest = 0;
for (i = 0; i < _array.length; i++){
var element = $("#"+ _array[i]);
var z_index = $(element).css("z-index");
if( z_index > zIndexHighest){
zIndexHighest = z_index;
topElement = element;
}
}
return topElement;
}
In certain circonstances :
myjQuery.droppable({
over:function(evt,ui){
ui.draggable.attr('drg_time', this.drg_time = evt.timeStamp)
},
accept:function(draggeds){
if(draggeds.attr('drg_time'))
{
return draggeds.attr('drg_time') == this.drg_time
}
return draggeds.hasClass('acceptable_classes_here')
},
out:function(evt,ui){
// useless but cleaner
ui.draggable.removeAttr('drg_time')
}
drop:...,
})
I had the same problem and made a small plugin :)
check it out:
https://stackoverflow.com/a/16720944/1308461
I find that using #invertedSpear's method will cause UI state change which is not desirable in some cases. Here is the code.
var lastOpertion = new Date().getTime();
drop: function (event, ui) {
if (new Date().getTime() - lastOpertion < 100) return;
lastOpertion = new Date().getTime();
....
}
So, when trying to find simple solution I came up with this (and it works for me):
window.overNowOnThis = "";
window.olderOnes = [];
$obj.droppable({
accept: ".draggable_imgs",
drop: function(e, ui) {
if(window.overNowOnThis == this){
// Do whatever
}
},
over: function(event, ui){
window.olderOnes.push(window.overNowOnThis);
window.overNowOnThis = this;
},
out: function(){
var lastElem = window.olderOnes.pop();
window.overNowOnThis = lastElem;
}
});
Top element would be selected because on it "over" fires the last one. Also - if there is more than two siblings - then when moving out of element we set the last one as current. Using global "window" was for example. Better option would be using classes as it can safely keep data.
It's very similar to invertedSpear answer. I had to change it a little bit because enabling/disabling inside "over"/"out" didn't work for me .I had to do below instead
$("#droppable2").droppable({
greedy: true,
drop: function (event, ui) {
$("droppable").droppable("disable");
}
}
I had to explicitly enable"droppable" div when I needed it. For instance, enable it when starting the drag event. Eg
$("#drageMe").draggable({
start:function(event,ui){
$("droppable").droppable("enable");
}
})
try greedy option in ui dropable. see the fiddle link below for demo:
http://jsfiddle.net/creators_guru/3vT5C/embedded/result/
$( ".circles" ).droppable({
greedy: true,
drop: function( event, ui ) {
$_data = ('drgged class = ' + ui.draggable.attr('class'));
$_data1 = ('droped id = #' + $(this).attr('id'));
$('#data').html($_data + '<br/>'+$_data1);
return false;
}
});

Resources