2 * jQuery dragscrollable Plugin
3 * version: 1.0 (25-Jun-2009)
4 * Copyright (c) 2009 Miquel Herrera
6 * Portions Copyright (c) 2010 Reg Braithwaite
7 * Copyright (c) 2010 Internet Archive / Michael Ang
9 * Dual licensed under the MIT and GPL licenses:
10 * http://www.opensource.org/licenses/mit-license.php
11 * http://www.gnu.org/licenses/gpl.html
14 ;(function($){ // secure $ jQuery alias
17 * Adds the ability to manage elements scroll by dragging
18 * one or more of its descendant elements. Options parameter
19 * allow to specifically select which inner elements will
20 * respond to the drag events.
23 * ------------------------------------------------------------------------
24 * dragSelector | jquery selector to apply to each wrapped element
25 * | to find which will be the dragging elements.
26 * | Defaults to '>:first' which is the first child of
27 * | scrollable element
28 * ------------------------------------------------------------------------
29 * acceptPropagatedEvent| Will the dragging element accept propagated
30 * | events? default is yes, a propagated mouse event
31 * | on a inner element will be accepted and processed.
32 * | If set to false, only events originated on the
33 * | draggable elements will be processed.
34 * ------------------------------------------------------------------------
35 * preventDefault | Prevents the event to propagate further effectivey
36 * | dissabling other default actions. Defaults to true
37 * ------------------------------------------------------------------------
41 * To add the scroll by drag to the element id=viewport when dragging its
42 * first child accepting any propagated events
43 * $('#viewport').dragscrollable();
45 * To add the scroll by drag ability to any element div of class viewport
46 * when dragging its first descendant of class dragMe responding only to
47 * evcents originated on the '.dragMe' elements.
48 * $('div.viewport').dragscrollable({dragSelector:'.dragMe:first',
49 * acceptPropagatedEvent: false});
51 * Notice that some 'viewports' could be nested within others but events
52 * would not interfere as acceptPropagatedEvent is set to false.
56 var append_namespace = function (string_of_events, ns) {
57 return string_of_events
59 .map(function (name) { return name + ns; })
63 var left_top = function(event) {
67 if (typeof(event.clientX) != 'undefined') {
71 else if (typeof(event.screenX) != 'undefined') {
75 else if (typeof(event.targetTouches) != 'undefined') {
76 x = event.targetTouches[0].pageX;
77 y = event.targetTouches[0].pageY;
79 else if (typeof(event.originalEvent) == 'undefined') {
82 str += ', ' + i + ': ' + event[i];
84 console.error("don't understand x and y for " + event.type + ' event: ' + str);
86 else if (typeof(event.originalEvent.clientX) != 'undefined') {
87 x = event.originalEvent.clientX;
88 y = event.originalEvent.clientY;
90 else if (typeof(event.originalEvent.screenX) != 'undefined') {
91 x = event.originalEvent.screenX;
92 y = event.originalEvent.screenY;
94 else if (typeof(event.originalEvent.targetTouches) != 'undefined') {
95 x = event.originalEvent.targetTouches[0].pageX;
96 y = event.originalEvent.targetTouches[0].pageY;
99 return {left: x, top:y};
102 $.fn.dragscrollable = function( options ) {
104 var handling_element = $(this);
106 var settings = $.extend(
108 dragSelector:'>:first',
109 acceptPropagatedEvent: true,
110 preventDefault: true,
111 dragstart: 'mousedown touchstart',
112 dragcontinue: 'mousemove touchmove',
113 dragend: 'mouseup mouseleave touchend',
118 settings.dragstart = append_namespace(settings.dragstart, settings.namespace);
119 settings.dragcontinue = append_namespace(settings.dragcontinue, settings.namespace);
120 settings.dragend = append_namespace(settings.dragend, settings.namespace);
123 dragStartHandler : function(event) {
125 // mousedown, left click, check propagation
126 if (event.which > 1 ||
127 (!event.data.acceptPropagatedEvent && event.target != this)){
131 event.data.firstCoord = left_top(event);
132 // Initial coordinates will be the last when dragging
133 event.data.lastCoord = event.data.firstCoord;
136 .bind(settings.dragcontinue, event.data, dragscroll.dragContinueHandler)
137 .bind(settings.dragend, event.data, dragscroll.dragEndHandler);
139 if (event.data.preventDefault) {
140 event.preventDefault();
144 dragContinueHandler : function(event) { // User is dragging
146 var lt = left_top(event);
148 // How much did the mouse move?
149 var delta = {left: (lt.left - event.data.lastCoord.left),
150 top: (lt.top - event.data.lastCoord.top)};
152 // Set the scroll position relative to what ever the scroll is now
153 event.data.scrollable.scrollLeft(
154 event.data.scrollable.scrollLeft() - delta.left);
155 event.data.scrollable.scrollTop(
156 event.data.scrollable.scrollTop() - delta.top);
158 // Save where the cursor is
159 event.data.lastCoord = lt;
161 if (event.data.preventDefault) {
162 event.preventDefault();
167 dragEndHandler : function(event) { // Stop scrolling
169 .unbind(settings.dragcontinue)
170 .unbind(settings.dragend);
172 // How much did the mouse move total?
173 var delta = {left: Math.abs(event.data.lastCoord.left - event.data.firstCoord.left),
174 top: Math.abs(event.data.lastCoord.top - event.data.firstCoord.top)};
175 var distance = Math.max(delta.left, delta.top);
177 // Trigger 'tap' if did not meet drag distance
178 // $$$ does not differentiate single vs multi-touch
179 if (distance < settings.dragMinDistance) {
180 $(event.originalEvent.target).trigger('tap');
183 // Allow event to propage if min distance was not achieved
184 if (event.data.preventDefault && distance > settings.dragMinDistance) {
185 event.preventDefault();
191 // set up the initial events
192 return this.each(function() {
193 // closure object data for each scrollable element
194 var data = {scrollable : $(this),
195 acceptPropagatedEvent : settings.acceptPropagatedEvent,
196 preventDefault : settings.preventDefault }
197 // Set mouse initiating event on the desired descendant
198 $(this).find(settings.dragSelector).
199 bind(settings.dragstart, data, dragscroll.dragStartHandler);
201 }; //end plugin dragscrollable
203 $.fn.removedragscrollable = function (namespace) {
204 if (typeof(namespace) == 'undefined')
206 return this.each(function() {
207 var x = $(document).find('*').andSelf().unbind(namespace);
211 })( jQuery ); // confine scope