Skip to content

Commit

Permalink
Merge pull request #6 from motiz88/master
Browse files Browse the repository at this point in the history
Fix #5 and add tests for touch functionality
  • Loading branch information
mikepb committed Apr 21, 2015
2 parents ed6b7b0 + 1129546 commit af08f76
Show file tree
Hide file tree
Showing 3 changed files with 237 additions and 59 deletions.
148 changes: 95 additions & 53 deletions lib/draggable.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,6 @@
var React = require('react/addons');
var emptyFunction = function () {};

// for accessing browser globals
var root = typeof window !== 'undefined' ? window : this;
var bodyElement;
if (typeof document !== 'undefined' && 'body' in document) {
bodyElement = document.body;
}

function updateBoundState (state, bound) {
if (!bound) return state;
bound = String(bound);
Expand Down Expand Up @@ -72,10 +65,6 @@ function matchesSelector(el, selector) {
return el[method].call(el, selector);
}

// @credits: http://stackoverflow.com/questions/4817029/whats-the-best-way-to-detect-a-touch-screen-device-using-javascript/4819886#4819886
var isTouchDevice = 'ontouchstart' in root // works on most browsers
|| 'onmsgesturechange' in root; // works on ie10 on ms surface

// look ::handleDragStart
//function isMultiTouch(e) {
// return e.touches && Array.isArray(e.touches) && e.touches.length > 1
Expand All @@ -84,21 +73,18 @@ var isTouchDevice = 'ontouchstart' in root // works on most browsers
/**
* simple abstraction for dragging events names
* */
var dragEventFor = (function () {
var eventsFor = {
touch: {
start: 'touchstart',
move: 'touchmove',
end: 'touchend'
},
mouse: {
start: 'mousedown',
move: 'mousemove',
end: 'mouseup'
}
};
return eventsFor[isTouchDevice ? 'touch' : 'mouse'];
})();
var dragEventsFor = {
touch: {
start: 'touchstart',
move: 'touchmove',
end: 'touchend'
},
mouse: {
start: 'mousedown',
move: 'mousemove',
end: 'mouseup'
}
};

/**
* get {clientX, clientY} positions of control
Expand Down Expand Up @@ -380,10 +366,20 @@ module.exports = React.createClass({
onStop: React.PropTypes.func,

/**
* A workaround option which can be passed if onMouseDown needs to be accessed, since it'll always be blocked (due to that there's internal use of onMouseDown)
* A workaround option which can be passed if these event handlers need to be accessed, since they're always handled internally.
*
*/
onMouseDown: React.PropTypes.func
onMouseDown: React.PropTypes.func,
onTouchStart: React.PropTypes.func,
onMouseUp: React.PropTypes.func,
onTouchEnd: React.PropTypes.func,
},

componentWillMount: function() {
this._dragHandlers = {
mouse: {start: this.handleMouseDown, move: this.handleMouseMove, end: this.handleMouseUp},
touch: {start: this.handleTouchStart, move: this.handleTouchMove, end: this.handleTouchEnd}
};
},

getDefaultProps: function () {
Expand All @@ -399,7 +395,10 @@ module.exports = React.createClass({
onStart: emptyFunction,
onDrag: emptyFunction,
onStop: emptyFunction,
onMouseDown: emptyFunction
onMouseDown: emptyFunction,
onMouseUp: emptyFunction,
onTouchStart: emptyFunction,
onTouchEnd: emptyFunction,
};
},

Expand Down Expand Up @@ -435,53 +434,98 @@ module.exports = React.createClass({

componentWillUnmount: function() {
// Remove any leftover event handlers
removeEvent(bodyElement, dragEventFor['move'], this.handleDrag);
removeEvent(bodyElement, dragEventFor['end'], this.handleDragEnd);
var bodyElement = this.getDOMNode().ownerDocument.body;
removeEvent(bodyElement, dragEventsFor.mouse.move, this._dragHandlers.mouse.move);
removeEvent(bodyElement, dragEventsFor.mouse.end, this._dragHandlers.mouse.end);
removeEvent(bodyElement, dragEventsFor.touch.move, this._dragHandlers.touch.move);
removeEvent(bodyElement, dragEventsFor.touch.end, this._dragHandlers.touch.end);
},

handleMouseDown: function(e) {
if (typeof this.props.onMouseDown === 'function')
this.props.onMouseDown(e);
if (!e.defaultPrevented)
this.handleDragStart(e, 'mouse');
},

handleMouseMove: function(e) {
this.handleDrag(e, 'mouse');
},

handleMouseUp: function(e) {
if (typeof this.props.onMouseUp === 'function')
this.props.onMouseUp(e);
if (!e.defaultPrevented)
this.handleDragEnd(e, 'mouse');
},

handleDragStart: function (e) {
handleTouchStart: function(e) {
if (typeof this.props.onTouchStart === 'function')
this.props.onTouchStart(e);
if (!e.defaultPrevented)
this.handleDragStart(e, 'touch');
},

handleTouchMove: function(e) {
this.handleDrag(e, 'touch');
},

handleTouchEnd: function(e) {
if (typeof this.props.onTouchEnd === 'function')
this.props.onTouchEnd(e);
if (!e.defaultPrevented)
this.handleDragEnd(e, 'touch');
},

handleDragStart: function (e, device) {
if (this.state.dragging)
return;

// todo: write right implementation to prevent multitouch drag
// prevent multi-touch events
// if (isMultiTouch(e)) {
// this.handleDragEnd.apply(e, arguments);
// return
// }

// Make it possible to attach event handlers on top of this one
this.props.onMouseDown(e);

// Short circuit if handle or cancel prop was provided and selector doesn't match
if ((this.props.handle && !matchesSelector(e.target, this.props.handle)) ||
(this.props.cancel && matchesSelector(e.target, this.props.cancel))) {
return;
}

e.preventDefault();

var dragPoint = getControlPosition(e);

// Initiate dragging
this.setState({
dragging: true,
dragging: device,
clientX: dragPoint.clientX,
clientY: dragPoint.clientY
});

// Call event handler
this.props.onStart(e, createUIEvent(this));

var bodyElement = this.getDOMNode().ownerDocument.body;

// Add event handlers
addEvent(bodyElement, dragEventFor['move'], this.handleDrag);
addEvent(bodyElement, dragEventFor['end'], this.handleDragEnd);
addEvent(bodyElement, dragEventsFor[device].move, this._dragHandlers[device].move);
addEvent(bodyElement, dragEventsFor[device].end, this._dragHandlers[device].end);

// Add dragging class to body element
if (bodyElement) bodyElement.className += ' react-draggable-dragging';
},

handleDragEnd: function (e) {
handleDragEnd: function (e, device) {
// Short circuit if not currently dragging
if (!this.state.dragging) {
if (!this.state.dragging || (this.state.dragging !== device)) {
return;
}

e.preventDefault();

// Turn off dragging
this.setState({
dragging: false
Expand All @@ -490,9 +534,13 @@ module.exports = React.createClass({
// Call event handler
this.props.onStop(e, createUIEvent(this));


var bodyElement = this.getDOMNode().ownerDocument.body;

// Remove event handlers
removeEvent(root, dragEventFor['move'], this.handleDrag);
removeEvent(root, dragEventFor['end'], this.handleDragEnd);
removeEvent(bodyElement, dragEventsFor[device].move, this._dragHandlers[device].move);
removeEvent(bodyElement, dragEventsFor[device].end, this._dragHandlers[device].end);


// Remove dragging class from body element
if (bodyElement) {
Expand All @@ -502,7 +550,7 @@ module.exports = React.createClass({
}
},

handleDrag: function (e) {
handleDrag: function (e, device) {
var dragPoint = getControlPosition(e);
var offsetLeft = this._toPixels(this.state.offsetLeft);
var offsetTop = this._toPixels(this.state.offsetTop);
Expand Down Expand Up @@ -615,11 +663,6 @@ module.exports = React.createClass({
this.props.onDrag(e, createUIEvent(this));
},

onTouchStart: function (e) {
e.preventDefault(); // prevent for scroll
return this.handleDragStart.apply(this, arguments);
},

render: function () {
var style = {
top: this.state.offsetTop,
Expand All @@ -635,11 +678,10 @@ module.exports = React.createClass({
style: style,
className: 'react-draggable',

onMouseDown: this.handleDragStart,
onTouchStart: this.onTouchStart,

onMouseUp: this.handleDragEnd,
onTouchEnd: this.handleDragEnd
onMouseDown: this._dragHandlers.mouse.start,
onTouchStart: this._dragHandlers.touch.start,
onMouseUp: this._dragHandlers.mouse.end,
onTouchEnd: this._dragHandlers.touch.end,
};

// Reuse the child provided
Expand Down
2 changes: 1 addition & 1 deletion lib/styles.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.react-draggable, .react-draggable-dragging {
.react-draggable strong, .react-draggable-dragging strong {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
Expand Down
Loading

0 comments on commit af08f76

Please sign in to comment.