This example shows how you can support a "swipeleft" gesture, built on top of the synthetic "gesturemove" events, which work not only on touch devices, but also on mouse based input devices.
Modules Used
For the example, the two core modules we'll use are:
- The
event-move
module -
Provides the
gesturemovestart
,gesturemove
andgesturemoveend
low-level gesture events. These events are fired whenever the user performs a move gesture (mouse button/finger down, mouse/finger move, mouse button/finger up) with the mouse or their finger. - The
transitions
module - Provides transitions support, leveraging CSS transitions under the hood where supported.
The YUI use statement for the example is shown below:
YUI().use("node-base", "node-event-delegate", "transition", "event-move", function(Y) { ... });
Delegating Gesture Move Events
The basic idea for the example is to listen for a gesturemovestart
on a
list item, and when we get one, store its position, and then listen for a
gesturemoveend. If the gesturemoveend occurs more than X pixels to the left
of the start, then we've identified a "swipeleft" gesture. Future versions
of the library will package such logic into a higher level gesture event
(ala event-flick).
For this example, since we're dealing with a list of items, rather than
attach individual listeners to each <li> in the list, we use
delegate
on the parent <ul> element, which leads to better
performance and avoids having to add/remove listeners each time the
contents of the list change. The gesturemovestart
, gesturemove
and
gesturemoveend
are synthetic events, and can all be used with delegate
,
just like any DOM event.
We set up a delegate listener on the <ul> which listens for the
gesturemovestart
event (gesturemovestart
abstracts
mousedown
/touchstart
events under the hood). The delegate listener is
set up to be notified when the target of the gesturemovestart
is an
<li>
Y.Node.one("#swipe").delegate("gesturemovestart", function(e) { ... }, "li", { // filter for "li" preventDefault:true });
The gesturemovestart
event supports a configuration object passed as an
additional subscription argument, which can be used to set minimum distance
and minimum time thresholds at which to fire the start event. The
configuration also supports the ability to prevent the default behavior
before the minimum time or distance thresholds kick in, which is what we do
above by passing preventDefault:true
.
The advantage of the gesture synthetic events is that the developer can use the same API without having to worry about whether or not the browser platform is touch based or mouse based.
Gesture Move End
As mentioned above, the gesturemovestart
listener is notified whenever a
mousedown
or touchstart
is received on a list item. The listener has
access to:
e.currentTarget
- The list item targeted.
e.target
- The element clicked on (it may be an element inside the targeted list item, the span for example).
e.container
- The element to which the delegate listener is attached (the ul in this case).
The event facade also has the page coordinates for the mousedown
or
touchstart
event. We use the list item's setData
method, to store the
pageX
position for the start event, so we can compare it when we get the
gesturemoveend
event. This way it's stored on the node instance, and we
don't need to pass it along separately to the gesturemoveend
event, or
store it globally.
getData
, setData
and clearData
are useful methods to store ad-hoc
node centric data.
Y.Node.one("#swipe").delegate("gesturemovestart", function(e) { var item = e.currentTarget, target = e.target, container = e.container, ... item.setData("swipeStart", e.pageX); item.once("gesturemoveend", function(e) { var swipeStart = item.getData("swipeStart"), swipeEnd = e.pageX, isSwipeLeft = (swipeStart - swipeEnd) > MIN_SWIPE; if (isSwipeLeft) { item.one(".myapp-delete").removeClass("myapp-hidden"); } }); ... });
When we get the gesturemovestart
event, we set up a listener for the
gesturemoveend
event, so we can determine the end of the gesture, and
figure out if the user swiped left. Since we don't want to set up a new
listener every time we get a gesturemovestart
we use once
to set up the
gesturemoveend
listener. once
will detach the listener after it's been
invoked. Again, since gesturemoveend
is a synthetic event, it works with
once
just like any other DOM event.
In the gesturemoveend
listener we check the page position of the event,
and if it's far enough to the left of the start position, we display the
"Delete" button by removing the hidden class which it contains.
Transitions
To hide and remove the item when the delete button is pressed, we use the
transition
method, to animate its opacity and height down to 0. Under the
hood transition will use CSS transition support where available (WebKit)
and set up timer based animation where not (IE). As with the gesture event
support, the developer gets to use the same API without having to worry
about the browser environment.
item = target.get("parentNode"); item.transition({ duration:0.3, opacity:0, height:0 }, function() { this.remove(); });
The second argument to transition above is a callback function, which is invoked when the transition is complete. We use this support to remove the item from the DOM.
Full Code Listing
YUI().use('node-base','node-event-delegate', 'transition', 'event-move', function (Y) { var MIN_SWIPE = 10; Y.all(".myexample-hidden").removeClass("myexample-hidden"); Y.one("#swipe").delegate("gesturemovestart", function(e) { var item = e.currentTarget, target = e.target, container = e.container, isDeleteButton = target.hasClass("myapp-delete"); // Prevent Text Selection in IE item.once("selectstart", function(e) { e.preventDefault(); }); if (!isDeleteButton) { container.all(".myapp-delete").addClass("myapp-hidden"); item.setData("swipeStart", e.pageX); item.once("gesturemoveend", function(e) { var swipeStart = item.getData("swipeStart"), swipeEnd = e.pageX, isSwipeLeft = (swipeStart - swipeEnd) > MIN_SWIPE; if (isSwipeLeft) { item.one(".myapp-delete").removeClass("myapp-hidden"); } }); } else { item = target.get("parentNode"); if (item.get("id") != "kitkat" || confirm("Seriously? The KitKats?")) { item.transition({ duration:0.3, opacity:0, height:0 }, function() { this.remove(); }); } } }, "li", { preventDefault:true }); });