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) {
58 /* IE doesn't have map
59 return string_of_events
61 .map(function (name) { return name + ns; })
64 var pieces = string_of_events.split(' ');
65 var ret = new Array();
66 for (var i = 0; i < pieces.length; i++) {
67 ret.push(pieces[i] + ns);
72 var left_top = function(event) {
76 if (typeof(event.clientX) != 'undefined') {
80 else if (typeof(event.screenX) != 'undefined') {
84 else if (typeof(event.targetTouches) != 'undefined') {
85 x = event.targetTouches[0].pageX;
86 y = event.targetTouches[0].pageY;
88 else if (typeof(event.originalEvent) == 'undefined') {
91 str += ', ' + i + ': ' + event[i];
93 console.error("don't understand x and y for " + event.type + ' event: ' + str);
95 else if (typeof(event.originalEvent.clientX) != 'undefined') {
96 x = event.originalEvent.clientX;
97 y = event.originalEvent.clientY;
99 else if (typeof(event.originalEvent.screenX) != 'undefined') {
100 x = event.originalEvent.screenX;
101 y = event.originalEvent.screenY;
103 else if (typeof(event.originalEvent.targetTouches) != 'undefined') {
104 x = event.originalEvent.targetTouches[0].pageX;
105 y = event.originalEvent.targetTouches[0].pageY;
108 return {left: x, top:y};
111 $.fn.dragscrollable = function( options ) {
113 var handling_element = $(this);
115 var settings = $.extend(
117 dragSelector:'>:first',
118 acceptPropagatedEvent: true,
119 preventDefault: true,
120 dragstart: 'mousedown touchstart',
121 dragcontinue: 'mousemove touchmove',
122 dragend: 'mouseup mouseleave touchend',
127 settings.dragstart = append_namespace(settings.dragstart, settings.namespace);
128 settings.dragcontinue = append_namespace(settings.dragcontinue, settings.namespace);
129 settings.dragend = append_namespace(settings.dragend, settings.namespace);
132 dragStartHandler : function(event) {
134 // mousedown, left click, check propagation
135 if (event.which > 1 ||
136 (!event.data.acceptPropagatedEvent && event.target != this)){
140 event.data.firstCoord = left_top(event);
141 // Initial coordinates will be the last when dragging
142 event.data.lastCoord = event.data.firstCoord;
145 .bind(settings.dragcontinue, event.data, dragscroll.dragContinueHandler)
146 .bind(settings.dragend, event.data, dragscroll.dragEndHandler);
148 if (event.data.preventDefault) {
149 event.preventDefault();
153 dragContinueHandler : function(event) { // User is dragging
155 var lt = left_top(event);
157 // How much did the mouse move?
158 var delta = {left: (lt.left - event.data.lastCoord.left),
159 top: (lt.top - event.data.lastCoord.top)};
161 // Set the scroll position relative to what ever the scroll is now
162 event.data.scrollable.scrollLeft(
163 event.data.scrollable.scrollLeft() - delta.left);
164 event.data.scrollable.scrollTop(
165 event.data.scrollable.scrollTop() - delta.top);
167 // Save where the cursor is
168 event.data.lastCoord = lt;
170 if (event.data.preventDefault) {
171 event.preventDefault();
176 dragEndHandler : function(event) { // Stop scrolling
179 .unbind(settings.dragcontinue)
180 .unbind(settings.dragend);
182 // How much did the mouse move total?
183 var delta = {left: Math.abs(event.data.lastCoord.left - event.data.firstCoord.left),
184 top: Math.abs(event.data.lastCoord.top - event.data.firstCoord.top)};
185 var distance = Math.max(delta.left, delta.top);
187 // Trigger 'tap' if did not meet drag distance
188 // $$$ does not differentiate single vs multi-touch
189 if (distance < settings.dragMinDistance) {
190 //$(event.originalEvent.target).trigger('tap');
191 $(event.target).trigger('tap'); // $$$ always the right target?
194 // Allow event to propage if min distance was not achieved
195 if (event.data.preventDefault && distance > settings.dragMinDistance) {
196 event.preventDefault();
202 // set up the initial events
203 return this.each(function() {
204 // closure object data for each scrollable element
205 var data = {scrollable : $(this),
206 acceptPropagatedEvent : settings.acceptPropagatedEvent,
207 preventDefault : settings.preventDefault }
208 // Set mouse initiating event on the desired descendant
209 $(this).find(settings.dragSelector).
210 bind(settings.dragstart, data, dragscroll.dragStartHandler);
212 }; //end plugin dragscrollable
214 $.fn.removedragscrollable = function (namespace) {
215 if (typeof(namespace) == 'undefined')
217 return this.each(function() {
218 var x = $(document).find('*').andSelf().unbind(namespace);
222 })( jQuery ); // confine scope