Skip to content

support mobile in jq version out of the box #1589

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ module.exports = function(grunt) {
files: {
'dist/jq/jquery.js': 'src/jq/jquery.js',
'dist/jq/jquery-ui.js': 'src/jq/jquery-ui.js',
'dist/jq/jquery.ui.touch-punch.js': 'src/jq/jquery.ui.touch-punch.js',
}
}
},
Expand Down
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -260,8 +260,12 @@ Note: It's not recommended to enable `nw`, `n`, `ne` resizing handles. Their beh

## Touch devices support

Please use [jQuery UI Touch Punch](https://github.com/furf/jquery-ui-touch-punch) to make jQuery UI Draggable/Resizable
working on touch-based devices.
NOTE: gridstack v3.2+ jq version now compile this in, so it works out of the box, so need for anything.

NOTE2: HTML5 v3+ does not currently support `touchmove` events. This will be added in a future release.

Use latest RWAP branch of [jQuery UI Touch Punch](https://github.com/RWAP/jquery-ui-touch-punch) to make jQuery UI Draggable/Resizable
working on touch-based devices (which we now also include in v3.2 as `dist/jq/jquery.ui.touch-punch.js`).

```html
<script src="gridstack-jq.js"></script>
Expand All @@ -277,7 +281,7 @@ let options = {
GridStack.init(options);
```

If you're still experiencing issues on touch devices please check [#444](https://github.com/gridstack/gridstack.js/issues/444)
See [example](http://gridstack.github.io/gridstack.js/demo/mobile.html). If you're still experiencing issues on touch devices please check [#444](https://github.com/gridstack/gridstack.js/issues/444)

# gridstack.js for specific frameworks

Expand Down
6 changes: 0 additions & 6 deletions demo/advance.html
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,6 @@ <h1>Advanced Demo</h1>
<script type="text/javascript">

let grid = GridStack.init({
alwaysShowResizeHandle: /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
navigator.userAgent
),
resizable: {
handles: 'e, se, s, sw, w'
},
acceptWidgets: true,
dragIn: '.newWidget', // class that can be dragged from outside
dragInOptions: { revert: 'invalid', scroll: false, appendTo: 'body', helper: 'clone' },
Expand Down
1 change: 1 addition & 0 deletions demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ <h1>Demos</h1>
<li><a href="serialization.html">Serialization</a></li>
<li><a href="static.html">Static</a></li>
<li><a href="two.html">Two grids</a></li>
<li><a href="mobile.html">Mobile touch (JQ)</a></li>
<li><a href="vue3js.html">Vue3.js</a></li>
<li><a href="vue2js.html">Vue2.js</a></li>
<li><a href="web-comp.html">Web Component</a></li>
Expand Down
26 changes: 26 additions & 0 deletions demo/mobile.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Simple mobile demo</title>

<link rel="stylesheet" href="demo.css"/>
<script src="../dist/gridstack-jq.js"></script>

</head>
<body>
<h1>Simple mobile demo</h1>
<p>uses v3.2+ jquery version which now includes jquery.ui.touch-punch by default (small 2k)</p>
<div class="grid-stack"></div>
<script type="text/javascript">
let grid = GridStack.init({
// column: 1, // will auto switch on smaller screens
cellHeight: 150, // make sure we have a decent height and not width/12 for 1 column
alwaysShowResizeHandle: /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
});
grid.load([{x:0, y:0, content: '1'}, {x:0, y:1, h:2, content: '2'}, {x:0, y:3, content: '3'}])
</script>
</body>
</html>
4 changes: 3 additions & 1 deletion doc/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ Change log
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## 3.1.5-dev

- TBD
- fix [1413](https://github.com/gridstack/gridstack.js/issues/1413) website & lib works on mobile. We now compile the latest v1.0.8 `jquery.ui.touch-punch`
into the JQ version (only 2k) so mobile devices (android, iphone, ipad, touchpad) are supported out of the box.
HTML5 version will require re-write to plain `mousemove` & mobile `touchmove` instead of drag events in a future release.

## 3.1.5 (2021-1-23)

Expand Down
2 changes: 1 addition & 1 deletion doc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ gridstack.js API
* `true` the resizing handles are always shown even if the user is not hovering over the widget
* advance condition such as this mobile browser agent check:
`alwaysShowResizeHandle: /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( navigator.userAgent )`
See [example](http://gridstack.github.io/gridstack.js/demo/advance.html)
See [example](http://gridstack.github.io/gridstack.js/demo/mobile.html)
- `animate` - turns animation on to smooth transitions (default: `true`)
- `auto` - if `false` gridstack will not initialize existing items (default: `true`)
- `cellHeight` - one cell height (default: `auto`). Can be:
Expand Down
1 change: 1 addition & 0 deletions src/jq/gridstack-dd-jqueryui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { GridItemHTMLElement, DDDragInOpt } from '../types';
import * as $ from './jquery'; // compile this in... having issues TS/ES6 app would include instead
export { $ };
import './jquery-ui';
import './jquery.ui.touch-punch'; // include for touch mobile devices

// export our base class (what user should use) and all associated types
export * from '../gridstack-dd';
Expand Down
244 changes: 244 additions & 0 deletions src/jq/jquery.ui.touch-punch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
/*!
* jQuery UI Touch Punch 1.0.8 as modified by RWAP Software
* based on original touchpunch v0.2.3 which has not been updated since 2014
*
* Updates by RWAP Software to take account of various suggested changes on the original code issues
*
* Original: https://github.com/furf/jquery-ui-touch-punch
* Copyright 2011–2014, Dave Furfero
* Dual licensed under the MIT or GPL Version 2 licenses.
*
* Fork: https://github.com/RWAP/jquery-ui-touch-punch
*
* Depends:
* jquery.ui.widget.js
* jquery.ui.mouse.js
*/

(function( factory ) {
if ( typeof define === "function" && define.amd ) {

// AMD. Register as an anonymous module.
define([ "jquery", "jquery.ui" ], factory );
} else {

// Browser globals
factory( jQuery );
}
}(function ($) {

// Detect touch support - Windows Surface devices and other touch devices
$.support.mspointer = window.navigator.msPointerEnabled;
$.support.touch = ( 'ontouchstart' in document
|| 'ontouchstart' in window
|| window.TouchEvent
|| (window.DocumentTouch && document instanceof DocumentTouch)
|| navigator.maxTouchPoints > 0
|| navigator.msMaxTouchPoints > 0
);

// Ignore browsers without touch or mouse support
if ((!$.support.touch && !$.support.mspointer) || !$.ui.mouse) {
return;
}

var mouseProto = $.ui.mouse.prototype,
_mouseInit = mouseProto._mouseInit,
_mouseDestroy = mouseProto._mouseDestroy,
touchHandled;

/**
* Get the x,y position of a touch event
* @param {Object} event A touch event
*/
function getTouchCoords (event) {
return {
x: event.originalEvent.changedTouches[0].pageX,
y: event.originalEvent.changedTouches[0].pageY
};
}

/**
* Simulate a mouse event based on a corresponding touch event
* @param {Object} event A touch event
* @param {String} simulatedType The corresponding mouse event
*/
function simulateMouseEvent (event, simulatedType) {

// Ignore multi-touch events
if (event.originalEvent.touches.length > 1) {
return;
}

// Prevent "Ignored attempt to cancel a touchmove event with cancelable=false" errors
if (event.cancelable) {
event.preventDefault();
}

var touch = event.originalEvent.changedTouches[0],
simulatedEvent = document.createEvent('MouseEvents');

// Initialize the simulated mouse event using the touch event's coordinates
simulatedEvent.initMouseEvent(
simulatedType, // type
true, // bubbles
true, // cancelable
window, // view
1, // detail
touch.screenX, // screenX
touch.screenY, // screenY
touch.clientX, // clientX
touch.clientY, // clientY
false, // ctrlKey
false, // altKey
false, // shiftKey
false, // metaKey
0, // button
null // relatedTarget
);

// Dispatch the simulated event to the target element
event.target.dispatchEvent(simulatedEvent);
}

/**
* Handle the jQuery UI widget's touchstart events
* @param {Object} event The widget element's touchstart event
*/
mouseProto._touchStart = function (event) {

var self = this;

// Interaction time
this._startedMove = event.timeStamp;

// Track movement to determine if interaction was a click
self._startPos = getTouchCoords(event);

// Ignore the event if another widget is already being handled
if (touchHandled || !self._mouseCapture(event.originalEvent.changedTouches[0])) {
return;
}

// Set the flag to prevent other widgets from inheriting the touch event
touchHandled = true;

// Track movement to determine if interaction was a click
self._touchMoved = false;

// Simulate the mouseover event
simulateMouseEvent(event, 'mouseover');

// Simulate the mousemove event
simulateMouseEvent(event, 'mousemove');

// Simulate the mousedown event
simulateMouseEvent(event, 'mousedown');
};

/**
* Handle the jQuery UI widget's touchmove events
* @param {Object} event The document's touchmove event
*/
mouseProto._touchMove = function (event) {

// Ignore event if not handled
if (!touchHandled) {
return;
}

// Interaction was moved
this._touchMoved = true;

// Simulate the mousemove event
simulateMouseEvent(event, 'mousemove');
};

/**
* Handle the jQuery UI widget's touchend events
* @param {Object} event The document's touchend event
*/
mouseProto._touchEnd = function (event) {

// Ignore event if not handled
if (!touchHandled) {
return;
}

// Simulate the mouseup event
simulateMouseEvent(event, 'mouseup');

// Simulate the mouseout event
simulateMouseEvent(event, 'mouseout');

// If the touch interaction did not move, it should trigger a click
// Check for this in two ways - length of time of simulation and distance moved
// Allow for Apple Stylus to be used also
var timeMoving = event.timeStamp - this._startedMove;
if (!this._touchMoved || timeMoving < 500) {
// Simulate the click event
simulateMouseEvent(event, 'click');
} else {
var endPos = getTouchCoords(event);
if ((Math.abs(endPos.x - this._startPos.x) < 10) && (Math.abs(endPos.y - this._startPos.y) < 10)) {

// If the touch interaction did not move, it should trigger a click
if (!this._touchMoved || event.originalEvent.changedTouches[0].touchType === 'stylus') {
// Simulate the click event
simulateMouseEvent(event, 'click');
}
}
}

// Unset the flag to determine the touch movement stopped
this._touchMoved = false;

// Unset the flag to allow other widgets to inherit the touch event
touchHandled = false;
};

/**
* A duck punch of the $.ui.mouse _mouseInit method to support touch events.
* This method extends the widget with bound touch event handlers that
* translate touch events to mouse events and pass them to the widget's
* original mouse event handling methods.
*/
mouseProto._mouseInit = function () {

var self = this;

// Microsoft Surface Support = remove original touch Action
if ($.support.mspointer) {
self.element[0].style.msTouchAction = 'none';
}

// Delegate the touch handlers to the widget's element
self.element.on({
touchstart: $.proxy(self, '_touchStart'),
touchmove: $.proxy(self, '_touchMove'),
touchend: $.proxy(self, '_touchEnd')
});

// Call the original $.ui.mouse init method
_mouseInit.call(self);
};

/**
* Remove the touch event handlers
*/
mouseProto._mouseDestroy = function () {

var self = this;

// Delegate the touch handlers to the widget's element
self.element.off({
touchstart: $.proxy(self, '_touchStart'),
touchmove: $.proxy(self, '_touchMove'),
touchend: $.proxy(self, '_touchEnd')
});

// Call the original $.ui.mouse destroy method
_mouseDestroy.call(self);
};

}));
6 changes: 5 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@ export interface GridStackOptions {
*/
acceptWidgets?: boolean | string | ((element: Element) => boolean);

/** if true the resizing handles are shown even if the user is not hovering over the widget (default?: false) */
/** possible values (default: `false` only show on hover)
* `true` the resizing handles are always shown even if the user is not hovering over the widget
* advance condition such as this mobile browser agent check:
`alwaysShowResizeHandle: /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( navigator.userAgent )`
See [example](http://gridstack.github.io/gridstack.js/demo/mobile.html) */
alwaysShowResizeHandle?: boolean;

/** turns animation on (default?: true) */
Expand Down
1 change: 1 addition & 0 deletions webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ module.exports = {
alias: {
'jquery': '/src/jq/jquery.js',
'jquery-ui': '/src/jq/jquery-ui.js',
'jquery.ui': '/src/jq/jquery-ui.js',
}
},
output: {
Expand Down