Skip to content

Commit

Permalink
feat(ngTouch): add optional ngSwipeDisableMouse attribute to `ngSwi…
Browse files Browse the repository at this point in the history
…pe` directives to ignore mouse events.

This attribute is useful for text that should still be selectable
by the mouse and not trigger the swipe action.

This also adds an optional third argument to `$swipe.bind` to define
the pointer types that should be listened to.

Closes angular#6627
Fixes angular#6626
  • Loading branch information
robinboehm authored and tbosch committed May 14, 2014
1 parent e9bc51c commit 3deb458
Show file tree
Hide file tree
Showing 5 changed files with 464 additions and 382 deletions.
7 changes: 6 additions & 1 deletion src/ngTouch/directive/ngSwipe.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
* Though ngSwipeLeft is designed for touch-based devices, it will work with a mouse click and drag
* too.
*
* To disable the mouse click and drag functionality, add `ng-swipe-disable-mouse` to
* the `ng-swipe-left` or `ng-swipe-right` DOM Element.
*
* Requires the {@link ngTouch `ngTouch`} module to be installed.
*
* @element ANY
Expand Down Expand Up @@ -101,6 +104,8 @@ function makeSwipeDirective(directiveName, direction, eventName) {
deltaY / deltaX < MAX_VERTICAL_RATIO;
}

var pointerTypes = angular.isDefined(attr['ngSwipeDisableMouse']) ?
['touch'] : ['touch','mouse'];

This comment has been minimized.

Copy link
@matsko

matsko May 14, 2014

Can this be refactored not to have floating arrays?

$swipe.bind(element, {
'start': function(coords, event) {
startCoords = coords;
Expand All @@ -117,7 +122,7 @@ function makeSwipeDirective(directiveName, direction, eventName) {
});
}
}
});
}, pointerTypes);
};
}]);
}
Expand Down
49 changes: 40 additions & 9 deletions src/ngTouch/swipe.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,20 @@ ngTouch.factory('$swipe', [function() {
// The total distance in any direction before we make the call on swipe vs. scroll.
var MOVE_BUFFER_RADIUS = 10;

var POINTER_EVENTS = {
'mouse': {
start: 'mousedown',
move: 'mousemove',
end: 'mouseup'
},
'touch': {
start: 'touchstart',
move: 'touchmove',
end: 'touchend',
cancel: 'touchcancel'
}
};

function getCoordinates(event) {
var touches = event.touches && event.touches.length ? event.touches : [event];
var e = (event.changedTouches && event.changedTouches[0]) ||
Expand All @@ -38,6 +52,17 @@ ngTouch.factory('$swipe', [function() {
};
}

function getEvents(pointerTypes, eventType) {
var res = [];
angular.forEach(pointerTypes, function(pointerType) {
var eventName = POINTER_EVENTS[pointerType][eventType];
if (eventName) {
res.push(eventName);
}
});
return res.join(' ');
}

return {
/**
* @ngdoc method
Expand All @@ -46,6 +71,9 @@ ngTouch.factory('$swipe', [function() {
* @description
* The main method of `$swipe`. It takes an element to be watched for swipe motions, and an
* object containing event handlers.
* The pointer types that should be used can be specified via the optional
* third argument, which is an array of strings `'mouse'` and `'touch'`. By default,
* `$swipe` will listen for `mouse` and `touch` events.
*
* The four events are `start`, `move`, `end`, and `cancel`. `start`, `move`, and `end`
* receive as a parameter a coordinates object of the form `{ x: 150, y: 310 }`.
Expand All @@ -68,7 +96,7 @@ ngTouch.factory('$swipe', [function() {
* as described above.
*
*/
bind: function(element, eventHandlers) {
bind: function(element, eventHandlers, pointerTypes) {
// Absolute total movement, used to control swipe vs. scroll.
var totalX, totalY;
// Coordinates of the start position.
Expand All @@ -78,21 +106,24 @@ ngTouch.factory('$swipe', [function() {
// Whether a swipe is active.
var active = false;

element.on('touchstart mousedown', function(event) {
pointerTypes = pointerTypes || ['mouse', 'touch'];
element.on(getEvents(pointerTypes, 'start'), function(event) {
startCoords = getCoordinates(event);
active = true;
totalX = 0;
totalY = 0;
lastPos = startCoords;
eventHandlers['start'] && eventHandlers['start'](startCoords, event);
});
var events = getEvents(pointerTypes, 'cancel');
if (events) {

This comment has been minimized.

Copy link
@matsko

matsko May 14, 2014

Will this return false for an empty array incase mouse is used (since it doesn't have cancel) ?

This comment has been minimized.

Copy link
@tbosch

tbosch May 14, 2014

Owner

It will return an empty string, which is falsy

element.on(events, function(event) {
active = false;
eventHandlers['cancel'] && eventHandlers['cancel'](event);

This comment has been minimized.

Copy link
@matsko

matsko May 14, 2014

There is no reason to use [] notation since this is an object.

This comment has been minimized.

Copy link
@tbosch

tbosch May 14, 2014

Owner

That was not introduced by me... Would rather let it stand as it is

});
}

element.on('touchcancel', function(event) {
active = false;
eventHandlers['cancel'] && eventHandlers['cancel'](event);
});

element.on('touchmove mousemove', function(event) {
element.on(getEvents(pointerTypes, 'move'), function(event) {
if (!active) return;

// Android will send a touchcancel if it thinks we're starting to scroll.
Expand Down Expand Up @@ -126,7 +157,7 @@ ngTouch.factory('$swipe', [function() {
}
});

element.on('touchend mouseup', function(event) {
element.on(getEvents(pointerTypes, 'end'), function(event) {
if (!active) return;
active = false;
eventHandlers['end'] && eventHandlers['end'](getCoordinates(event), event);
Expand Down
30 changes: 30 additions & 0 deletions test.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<!doctype html>
<html ng-app>
<body>
<script src="build/angular.js"></script>
<script>
function Ctrl($scope) {
$scope.user = { name: 'say', data: '' };

$scope.cancel = function (e) {
if (e.keyCode == 27) {
$scope.userForm.userName.$cancelUpdate();
}
};
}
</script>
<div ng-controller="Ctrl">
<form name="userForm">
Name:
<input type="text" name="userName"
ng-model="user.name"
ng-model-options="{ updateOn: 'blur' }"
ng-keyup="cancel($event)" /><br />

Other data:
<input type="text" ng-model="user.data" /><br />
</form>
<pre>user.name = <span ng-bind="user.name"></span></pre>
</div>
</body>
</html>
23 changes: 23 additions & 0 deletions test/ngTouch/directive/ngSwipeSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,29 @@ var swipeTests = function(description, restrictBrowsers, startEvent, moveEvent,
expect($rootScope.swiped).toBe(true);
}));

it('should only swipe given ng-swipe-disable-mouse attribute for touch events', inject(function($rootScope, $compile) {
element = $compile('<div ng-swipe-left="swiped = true" ng-swipe-disable-mouse></div>')($rootScope);
$rootScope.$digest();
expect($rootScope.swiped).toBeUndefined();

browserTrigger(element, startEvent, {
keys : [],
x : 100,
y : 20
});
browserTrigger(element, endEvent,{
keys: [],
x: 20,
y: 20
});
if(description === 'mouse'){
expect($rootScope.swiped).toBeUndefined();

This comment has been minimized.

Copy link
@matsko

matsko May 14, 2014

Consider collapsing this down to a terinary operation.

Something like expect(!!$rootScope.swiped).toBe(description === 'mouse');

This comment has been minimized.

Copy link
@tbosch

tbosch May 14, 2014

Owner

right, but with a negated value :-)

}
else{
expect($rootScope.swiped).toBe(true);
}
}));

it('should pass event object', inject(function($rootScope, $compile) {
element = $compile('<div ng-swipe-left="event = $event"></div>')($rootScope);
$rootScope.$digest();
Expand Down
Loading

0 comments on commit 3deb458

Please sign in to comment.