Change dragscrollable.js to add option to scroll window rather than the element....
[bookreader.git] / BookReader / dragscrollable.js
1 /*
2  * jQuery dragscrollable Plugin
3  * version: 1.0 (25-Jun-2009)
4  * Copyright (c) 2009 Miquel Herrera
5  *
6  * Portions Copyright (c) 2010 Reg Braithwaite
7  *          Copyright (c) 2010 Internet Archive / Michael Ang
8  *
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
12  *
13  */
14 ;(function($){ // secure $ jQuery alias
15
16 /**
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.
21  * 
22  * options properties:
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
39  *                       | Defaults to false
40  * ------------------------------------------------------------------------
41  *  
42  *  usage examples:
43  *
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(); 
47  *
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});
53  *
54  *  Notice that some 'viewports' could be nested within others but events
55  *  would not interfere as acceptPropagatedEvent is set to false.
56  *              
57  */
58  
59 var append_namespace = function (string_of_events, ns) {
60     
61     /* IE doesn't have map
62         return string_of_events
63                 .split(' ')
64                         .map(function (name) { return name + ns; })
65                                 .join(' ');
66     */
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);
71     }
72     return ret.join(' ');
73 };
74
75 var left_top = function(event) {
76         
77         var x;
78         var y;
79         if (typeof(event.clientX) != 'undefined') {
80                 x = event.clientX;
81                 y = event.clientY;
82         }
83         else if (typeof(event.screenX) != 'undefined') {
84                 x = event.screenX;
85                 y = event.screenY;
86         }
87         else if (typeof(event.targetTouches) != 'undefined') {
88                 x = event.targetTouches[0].pageX;
89                 y = event.targetTouches[0].pageY;
90         }
91         else if (typeof(event.originalEvent) == 'undefined') {
92                 var str = '';
93                 for (i in event) {
94                         str += ', ' + i + ': ' + event[i];
95                 }
96                 console.error("don't understand x and y for " + event.type + ' event: ' + str);
97         }
98         else if (typeof(event.originalEvent.clientX) != 'undefined') {
99                 x = event.originalEvent.clientX;
100                 y = event.originalEvent.clientY;
101         }
102         else if (typeof(event.originalEvent.screenX) != 'undefined') {
103                 x = event.originalEvent.screenX;
104                 y = event.originalEvent.screenY;
105         }
106         else if (typeof(event.originalEvent.targetTouches) != 'undefined') {
107                 x = event.originalEvent.targetTouches[0].pageX;
108                 y = event.originalEvent.targetTouches[0].pageY;
109         }
110         
111         return {left: x, top:y};
112 };
113
114 $.fn.dragscrollable = function( options ) {
115         
116         var handling_element = $(this);
117    
118         var settings = $.extend(
119                 {   
120                         dragSelector:'>:first',
121                         acceptPropagatedEvent: true,
122             preventDefault: true,
123                         dragstart: 'mousedown touchstart',
124                         dragcontinue: 'mousemove touchmove',
125                         dragend: 'mouseup mouseleave touchend',
126                         dragMinDistance: 5,
127                         namespace: '.ds',
128                         scrollWindow: false
129                 },options || {});
130         
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);
134
135         var dragscroll= {
136                 dragStartHandler : function(event) {
137                     // console.log('dragstart');
138                         
139                         // mousedown, left click, check propagation
140                         if (event.which > 1 ||
141                                 (!event.data.acceptPropagatedEvent && event.target != this)){ 
142                                 return false; 
143                         }
144                         
145                         event.data.firstCoord = left_top(event);
146                         // Initial coordinates will be the last when dragging
147                         event.data.lastCoord = event.data.firstCoord;
148                         
149                         handling_element
150                                 .bind(settings.dragcontinue, event.data, dragscroll.dragContinueHandler)
151                                 .bind(settings.dragend, event.data, dragscroll.dragEndHandler);
152                 
153                         if (event.data.preventDefault) {
154                 event.preventDefault();
155                 return false;
156             }
157                 },
158                 dragContinueHandler : function(event) { // User is dragging
159                     // console.log('drag continue');
160                         
161                         var lt = left_top(event);
162                         
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)};
166                         
167                         /*
168                         console.log(event.data.scrollable);
169                         console.log('delta.left - ' + delta.left);
170                         console.log('delta.top - ' + delta.top);
171                         */
172                         
173                         var scrollTarget = event.data.scrollable;
174                         if (event.data.scrollWindow) {
175               scrollTarget = $(window);
176                         }
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 );
180                         
181                         // Save where the cursor is
182                         event.data.lastCoord = lt;
183                         
184                         if (event.data.preventDefault) {
185                 event.preventDefault();
186                 return false;
187             }
188
189                 },
190                 dragEndHandler : function(event) { // Stop scrolling
191                     // console.log('drag END');
192                 
193                         handling_element
194                                 .unbind(settings.dragcontinue)
195                                 .unbind(settings.dragend);
196                                 
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);
201                                                 
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?
207                         }
208                         
209                         // Allow event to propage if min distance was not achieved
210                         if (event.data.preventDefault && distance > settings.dragMinDistance) {
211                 event.preventDefault();
212                 return false;
213             }
214                 }
215         }
216         
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);
227         });
228 }; //end plugin dragscrollable
229
230 $.fn.removedragscrollable = function (namespace) {
231         if (typeof(namespace) == 'undefined')
232                 namespace = '.ds';
233         return this.each(function() {
234                 var x = $(document).find('*').andSelf().unbind(namespace);
235         });
236 };
237
238 })( jQuery ); // confine scope