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 * ------------------------------------------------------------------------
38 * scrollWindow | Scroll the window rather than the element
40 * ------------------------------------------------------------------------
44 * To add the scroll by drag to the element id=viewport when dragging its
45 * first child accepting any propagated events
46 * $('#viewport').dragscrollable();
48 * To add the scroll by drag ability to any element div of class viewport
49 * when dragging its first descendant of class dragMe responding only to
50 * evcents originated on the '.dragMe' elements.
51 * $('div.viewport').dragscrollable({dragSelector:'.dragMe:first',
52 * acceptPropagatedEvent: false});
54 * Notice that some 'viewports' could be nested within others but events
55 * would not interfere as acceptPropagatedEvent is set to false.
59 var append_namespace = function (string_of_events, ns) {
61 /* IE doesn't have map
62 return string_of_events
64 .map(function (name) { return name + ns; })
67 var pieces = string_of_events.split(' ');
68 var ret = new Array();
69 for (var i = 0; i < pieces.length; i++) {
70 ret.push(pieces[i] + ns);
75 var left_top = function(event) {
79 if (typeof(event.clientX) != 'undefined') {
83 else if (typeof(event.screenX) != 'undefined') {
87 else if (typeof(event.targetTouches) != 'undefined') {
88 x = event.targetTouches[0].pageX;
89 y = event.targetTouches[0].pageY;
91 else if (typeof(event.originalEvent) == 'undefined') {
94 str += ', ' + i + ': ' + event[i];
96 console.error("don't understand x and y for " + event.type + ' event: ' + str);
98 else if (typeof(event.originalEvent.clientX) != 'undefined') {
99 x = event.originalEvent.clientX;
100 y = event.originalEvent.clientY;
102 else if (typeof(event.originalEvent.screenX) != 'undefined') {
103 x = event.originalEvent.screenX;
104 y = event.originalEvent.screenY;
106 else if (typeof(event.originalEvent.targetTouches) != 'undefined') {
107 x = event.originalEvent.targetTouches[0].pageX;
108 y = event.originalEvent.targetTouches[0].pageY;
111 return {left: x, top:y};
114 $.fn.dragscrollable = function( options ) {
116 var handling_element = $(this);
118 var settings = $.extend(
120 dragSelector:'>:first',
121 acceptPropagatedEvent: true,
122 preventDefault: true,
123 dragstart: 'mousedown touchstart',
124 dragcontinue: 'mousemove touchmove',
125 dragend: 'mouseup mouseleave touchend',
131 settings.dragstart = append_namespace(settings.dragstart, settings.namespace);
132 settings.dragcontinue = append_namespace(settings.dragcontinue, settings.namespace);
133 settings.dragend = append_namespace(settings.dragend, settings.namespace);
136 dragStartHandler : function(event) {
137 // console.log('dragstart');
139 // mousedown, left click, check propagation
140 if (event.which > 1 ||
141 (!event.data.acceptPropagatedEvent && event.target != this)){
145 event.data.firstCoord = left_top(event);
146 // Initial coordinates will be the last when dragging
147 event.data.lastCoord = event.data.firstCoord;
150 .bind(settings.dragcontinue, event.data, dragscroll.dragContinueHandler)
151 .bind(settings.dragend, event.data, dragscroll.dragEndHandler);
153 if (event.data.preventDefault) {
154 event.preventDefault();
158 dragContinueHandler : function(event) { // User is dragging
159 // console.log('drag continue');
161 var lt = left_top(event);
163 // How much did the mouse move?
164 var delta = {left: (lt.left - event.data.lastCoord.left),
165 top: (lt.top - event.data.lastCoord.top)};
168 console.log(event.data.scrollable);
169 console.log('delta.left - ' + delta.left);
170 console.log('delta.top - ' + delta.top);
173 var scrollTarget = event.data.scrollable;
174 if (event.data.scrollWindow) {
175 scrollTarget = $(window);
177 // Set the scroll position relative to what ever the scroll is now
178 scrollTarget.scrollLeft( scrollTarget.scrollLeft() - delta.left );
179 scrollTarget.scrollTop( scrollTarget.scrollTop() - delta.top );
181 // Save where the cursor is
182 event.data.lastCoord = lt;
184 if (event.data.preventDefault) {
185 event.preventDefault();
190 dragEndHandler : function(event) { // Stop scrolling
191 // console.log('drag END');
194 .unbind(settings.dragcontinue)
195 .unbind(settings.dragend);
197 // How much did the mouse move total?
198 var delta = {left: Math.abs(event.data.lastCoord.left - event.data.firstCoord.left),
199 top: Math.abs(event.data.lastCoord.top - event.data.firstCoord.top)};
200 var distance = Math.max(delta.left, delta.top);
202 // Trigger 'tap' if did not meet drag distance
203 // $$$ does not differentiate single vs multi-touch
204 if (distance < settings.dragMinDistance) {
205 //$(event.originalEvent.target).trigger('tap');
206 $(event.target).trigger('tap'); // $$$ always the right target?
209 // Allow event to propage if min distance was not achieved
210 if (event.data.preventDefault && distance > settings.dragMinDistance) {
211 event.preventDefault();
217 // set up the initial events
218 return this.each(function() {
219 // closure object data for each scrollable element
220 var data = {scrollable : $(this),
221 acceptPropagatedEvent : settings.acceptPropagatedEvent,
222 preventDefault : settings.preventDefault,
223 scrollWindow : settings.scrollWindow }
224 // Set mouse initiating event on the desired descendant
225 $(this).find(settings.dragSelector).
226 bind(settings.dragstart, data, dragscroll.dragStartHandler);
228 }; //end plugin dragscrollable
230 $.fn.removedragscrollable = function (namespace) {
231 if (typeof(namespace) == 'undefined')
233 return this.each(function() {
234 var x = $(document).find('*').andSelf().unbind(namespace);
238 })( jQuery ); // confine scope