).Hoverable(); // which creates a Touchable internally
52 |
53 | then you bind to the events
54 |
55 | div.bind('touchmove', function(e, touch){})
56 | div.bind('newHover2', function in(e, touch){}, function out(e, touch){})
57 |
58 | notice, that each event gets passed a touch object, besides the normal event object. The Touch object has the following properties:
59 |
60 |
61 | * startTouch: this is where the touch or mousedown event originated
62 | * currentTouch: this is where we are right now with our finger or mouse
63 | * previousTouch: for internal calculations of the previous position of the mouse pointer or finger, used for deltas see below
64 | * currentDelta: measured from previous move event
65 | * currentStartDelta, currentPosition: the relative position, measured from start calculated in different ways, but should be the same
66 | **Event targets:**
67 | ----------------
68 | * currentTarget: the event target element (may differ on mobile browsers from the ctarget element)
69 | * target: the event target element
70 |
71 | ## Todos ##
72 |
73 | * make long tap independent from Touchable
74 | * support more gestures like swiping,...
--------------------------------------------------------------------------------
/demo/demo.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
28 |
29 |
30 |
LongTap and Touchmove are the new hover!
31 |
32 |
42 |
Try to long tap in the box on an iPad or iPhone or just hover on a Desktop
43 |
44 |
45 |
55 |
Try to click and move your mouse or just hover on a Desktop
56 |
57 |
For further demonstration prupose I have disabled hover also on the desktop to make you see how this would function on all devices too
58 |
59 |
69 |
Try to long tap in the box on an iPad or iPhone or Desktop
70 |
71 |
72 |
82 |
Try to click and move your mouse on a Desktop or iPad/iPhone
83 |
84 |
85 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
105 |
115 |
116 |
117 |
--------------------------------------------------------------------------------
/demo/demo.html.orig:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
32 |
33 |
34 |
LongTap and Touchmove are the new hover!
35 |
36 |
46 |
Try to long tap in the box on an iPad or iPhone or just hover on a Desktop
47 |
48 |
49 |
59 |
Try to click and move your mouse or just hover on a Desktop
60 |
61 |
For further demonstration prupose I have disabled hover also on the desktop to make you see how this would function on all devices too
62 |
63 |
73 |
Try to long tap in the box on an iPad or iPhone or Desktop
74 |
75 |
76 |
86 |
Try to click and move your mouse on a Desktop or iPad/iPhone
87 |
88 |
89 |
90 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 | <<<<<<< HEAD
103 |
104 |
105 | =======
106 |
107 |
108 | >>>>>>> master
109 |
110 |
111 |
112 |
113 |
115 |
125 |
126 |
127 |
--------------------------------------------------------------------------------
/demo/demo.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function(){
2 | $t=$('#Touchable').Hoverable();
3 | $t2=$('#Touchable2').Hoverable();
4 | /*$link= $('#Touchable3 a').click(function(e){
5 | e.preventDefault()
6 | alert('sdasa')
7 | location.href=$(this).attr('href')
8 | });*/
9 | $t3=$('#Touchable3').Hoverable({disableHover:true, logging:true});
10 | $t4=$('#Touchable4').Hoverable({disableHover:true});
11 |
12 | $t.newHover(function(e, touch){ //hoverIN
13 | $tooltip=$(this).find('.tooltip');
14 | $tooltip.show();
15 | }, function(e, touch){//hoverOut
16 | $tooltip=$(this).find('.tooltip');
17 | $tooltip.hide();
18 | });
19 | $t2.newHover2(function(e, touch){ //hoverIN
20 | $tooltip=$(this).find('.tooltip');
21 | $tooltip.show();
22 | }, function(e, touch){//hoverOut
23 | $tooltip=$(this).find('.tooltip');
24 | $tooltip.hide();
25 | });
26 | $t3.newHover(function(e, touch){ //hoverIN
27 | $tooltip=$(this).find('.tooltip');
28 | $tooltip.show();
29 | }, function(e, touch){//hoverOut
30 | $tooltip=$(this).find('.tooltip');
31 | $tooltip.hide();
32 | });
33 | $t4.newHover2(function(e, touch){ //hoverIN
34 | $tooltip=$(this).find('.tooltip');
35 | $tooltip.show();
36 | }, function(e, touch){//hoverOut
37 | $tooltip=$(this).find('.tooltip');
38 | $tooltip.hide();
39 | });
40 | })
--------------------------------------------------------------------------------
/demo/hoverable.js:
--------------------------------------------------------------------------------
1 | hoverable.js
--------------------------------------------------------------------------------
/demo/touchable.js:
--------------------------------------------------------------------------------
1 | touchable.js
--------------------------------------------------------------------------------
/hoverable and touchable.js:
--------------------------------------------------------------------------------
1 | /*
2 | * jQuery Hoverable
3 | *
4 | * Simplified BSD License (@see License)
5 | * @author Gregor Schwab
6 | * @copyright (c) 2010 Gregor Schwab
7 | * Usage: $(elem).Touchable() (@see Readme.md)
8 | * @version 0.0.4
9 | * @requires jQuery Touchable
10 | */
11 |
12 | (function($) {
13 | var Touchable=$.Touchable;
14 | $.fn.Hoverable = function(conf) {
15 | return this.each(function() {
16 | var t= $(this).data['Hoverable']=new Hoverable(this, conf);
17 | return t;
18 | });
19 | }
20 | $.fn.newHover = function(fn1, fn2, disableHover) {
21 | return this.each(function() {
22 | $(this).bind('newHoverIn', fn1).bind('newHoverOut', fn2);
23 | });
24 | }
25 | $.fn.newHover2 = function(fn1, fn2) {
26 | return this.each(function() {
27 | $(this).bind('newHoverIn2', fn1).bind('newHoverOut2', fn2);
28 | });
29 | }
30 | /**
31 | * @constructor
32 | */
33 | function Hoverable(elem, conf)
34 | {
35 |
36 | var self=this;
37 | this.logging=false; //set to false to disabele logging default false gets overwritten by conf see below
38 | var log=function(a){if(self.logging && (typeof console !== 'undefined')){
39 | console.log(Array.prototype.slice.call(arguments));}
40 | }; //private logging function
41 |
42 | this.elem=elem;
43 | //test for touchable
44 | if(!$(elem).Touchable) throw new Error('Hoverable depends on Touchable! Please be sure to include Touchable in your project.')
45 | this.$elem=$(elem).Touchable(conf);
46 |
47 | this.inHover=false;
48 | this.target=null;
49 |
50 |
51 | if (typeof conf!=='undefined'){
52 | if(typeof conf.disableHover!=='undefined'){this.disableHover=conf.disableHover;}
53 | else{this.disableHover=false;}
54 | if(typeof conf.logging!=='undefined'){this.logging=conf.logging;}
55 | }
56 |
57 |
58 | //longTap is the new Hover ;)
59 | if (!this.disableHover){
60 | this.$elem.mouseenter(genericHover);
61 | this.$elem.bind('mouseleave', genericHover);
62 | }
63 |
64 |
65 | this.$elem.bind('longTap', genericHover);
66 | this.$elem.bind('touchableend', genericHover);
67 |
68 | function genericHover(e, touch){
69 | if(e.type==='touchableend' ||e.type==='mouseleave'){
70 | log('Touchable newHoverOut');
71 | //this.$elem.unbind('touchend', genericHover);//don't have to unbind mouseleave .unbind('mouseleave', genericHover);
72 | return self.$elem.trigger('newHoverOut', self);
73 | }
74 | log('Touchable newHoverIn');
75 | self.$elem.trigger('newHoverIn', self); //trigger a genericHover see Readme
76 | }
77 |
78 | //mousemove is the new Hover ;)
79 | if (!this.disableHover){
80 | this.$elem.bind('mouseenter', genericHover2);
81 | this.$elem.bind('mouseleave', genericHover2);
82 | }
83 | self.$elem.bind('touchablestart', function(e, touch){
84 | self.$elem.bind('touchablemove', genericHover2);
85 | }, false);
86 | self.$elem.bind('touchableend', function(e, touch){
87 | self.$elem.unbind('touchablemove', genericHover2);
88 | genericHover2(e, touch);
89 | }, false);
90 |
91 |
92 | //this.$elem.bind('touchend', genericHover2);
93 | function genericHover2(e, touch){
94 | if(e.type==='touchableend'||e.type==='touchend'){
95 | log('Touchable newHoverOut2');
96 | self.inHover=false;
97 | return self.$elem.trigger('newHoverOut2', self);
98 | }else if(e.type==='mouseenter'){
99 | log('Touchable newHoverIn2');
100 | return self.$elem.trigger('newHoverIn2', self);
101 | }else if(e.type==='mouseleave'){
102 | log('Touchable newHoverOut2');
103 | return self.$elem.trigger('newHoverOut2', self);
104 | }
105 | if (e.type == 'touchablemove'){
106 | if(touch instanceof Touchable){
107 | //var x=touch.currentTouch.x; var y=touch.currentTouch.y;
108 | var hitTarget = self.hitTarget; //document.elementFromPoint(x, y);
109 | /* if(typeof hitTarget==='undefined'||hitTarget===null&&self.target!==null) {var target=self.target;var currentTarget=self.target}//just if the browser looses memory
110 | else{var target=hittarget}//first the hittarget
111 | if(self.logging){console.log('target '+target+' x, y:'+x,+' ' +y);}
112 | }else{
113 | var target = e.target;
114 | }*/
115 | log('Touchable target ID/node'+ ' hitTarget'+ ' ' +
116 | hitTarget+'e.target'+e.target + ' e.currentTarget'+e.currentTarget+
117 | ' self in hover'+self.inHover);
118 | //lets see if we can macth our element...still playing with the right settings here cause browsers seem to have differenes in what they pass as an event.target
119 | var pass=false;
120 | //not good cause it goes on paragraphs but can be used to test for being outside the view element
121 | //if (typeof hitTarget !== 'undefined' && hitTarget === self.$elem.get(0) ){pass=true;}
122 | //if (typeof hitTarget !== 'undefined' && hitTarget !== self.$elem.get(0) ){pass=false;}
123 | //gives info to the inner element too
124 | //else if (typeof self.target !== 'undefined' && self.target === self.$elem.get(0)){pass=true;}//Chrome
125 | //relates to the this pointer the touchmove event was bound to (normally document)
126 | //else if (typeof self.currentTarget !== 'undefined' && self.currentTarget === self.$elem.get(0)){pass=true;}//iPad has it in currentTarget
127 | //e.target might work too
128 | //else if (typeof e.target !== 'undefined' && e.target === self.$elem.get(0)){pass=true;}
129 | //the winner is:
130 | if (typeof e.currentTarget !== 'undefined' && e.currentTarget === self.$elem.get(0)){pass=true;}
131 | if(pass&& !self.inHover){
132 | self.inHover=true;
133 | log('Touchable newHoverIn2');
134 | self.$elem.trigger('newHoverIn2', self);
135 | //e.stopPropagation();//we are talking about touchablemove event here
136 | }
137 | else if (pass===false && self.inHover){
138 | self.inHover=false;
139 | log('Touchable newHoverOut2');
140 | self.$elem.trigger('newHoverOut2', self);
141 | //e.stopPropagation(); //we are talking about touchablemove event here
142 | }
143 | }
144 | }
145 | }
146 | }
147 | })(jQuery);//end closure
148 |
149 | /*
150 | * jQuery Touchable
151 | *
152 | * Simplified BSD License (@see License)
153 | * @author Gregor Schwab
154 | * @copyright (c) 2010 Gregor Schwab
155 | * Usage Command Line: $(elem).Touchable() (@see Readme.md)
156 | * @version 0.0.5
157 | * @requires jQuery
158 | */
159 |
160 |
161 | (function($) {
162 |
163 | $.fn.Touchable = function(conf) {
164 | return this.each(function() {
165 | var t= $(this).data['Touchable']=new Touchable(this, conf);
166 | return t;
167 | });
168 | }
169 | $.fn.newHover = function(fn1, fn2, disableHover) {
170 | return this.each(function() {
171 | $(this).bind('newHoverIn', fn1).bind('newHoverOut', fn2);
172 | });
173 | }
174 | $.fn.newHover2 = function(fn1, fn2) {
175 | return this.each(function() {
176 | $(this).bind('newHoverIn2', fn1).bind('newHoverOut2', fn2);
177 | });
178 | }
179 |
180 | $.Touchable=Touchable;
181 |
182 | /**
183 | * @constructor
184 | */
185 |
186 | function Touchable(elem, conf)
187 | {
188 |
189 | this.logging=false; //set to false to disabele logging gets overwritten by conf see below
190 | var log=function(a){if(self.logging && (typeof console !== 'undefined')){
191 | console.log(Array.prototype.slice.call(arguments));}
192 | }; //private logging function
193 |
194 | this.elem=elem;
195 | this.$elem=$(elem);
196 | this.is_doubleTap=false;
197 | this.is_currentlyTouching=false;
198 | this.isOneFingerGesture = false;
199 |
200 | this.startTouch={x:0,y:0};
201 | this.currentTouch={x:0,y:0};
202 | this.previousTouch={x:0,y:0};
203 | this.currentDelta={x:0,y:0};//measured from previous move event
204 | this.currentStartDelta={x:0,y:0}; //measured from start
205 | this.currentPosition={x:0,y:0};
206 | this.doubleTapTimer=null, this.longTapTimer=null;
207 |
208 | var self=this;
209 |
210 | if (typeof conf!=='undefined'){
211 | if(typeof conf.logging!=='undefined'){this.logging=conf.logging;}
212 | }
213 | //make IE happy
214 | var addEventListener=elem.addEventListener||elem.attachEvent
215 | var removeEventListener = elem.removeEventListener||elem.detachEvent
216 | //add touchstart eventlistener
217 | addEventListener.call(elem, 'touchstart', function(){self.$elem.trigger('touchstart')}, true);
218 | addEventListener.call(elem, 'touchend', function(){self.$elem.trigger('touchend')}, false);
219 | addEventListener.call(elem, 'touchmove', function(){self.$elem.trigger('touchmove')}, false);
220 |
221 | addEventListener.call(elem, 'touchstart', touchstart, false);
222 | this.$elem.mousedown(touchstart);
223 |
224 | function touchstart (e) {
225 | if(typeof e.touches!== "undefined")
226 | {
227 | log('Touchable Touchstart touches length ' + e.touches.length);
228 | //only handle 1 or 2 touches
229 | if (e.touches.length !== 1 && e.touches.length !== 2) {
230 | return false;
231 | }
232 | if (self.isCurrentlyTouching) {
233 | return false;
234 | }
235 |
236 |
237 | self.isCurrentlyTouching = true;
238 | if (e.touches.length == 1) { //1 finger
239 | self.isOneFingerGesture = true;
240 | //init pos
241 | self.startTouch.x = self.previousTouch.x = e.touches[0].clientX;
242 | self.startTouch.y = self.previousTouch.y = e.touches[0].clientY;
243 | } else if (e.touches.length == 2) { //two fingers
244 | self.isOneFingerGesture = false;
245 | if (e.touches[0].clientY > e.touches[1].clientY) {//0 is lower
246 | self.startTouch.x = self.previousTouch.x = e.touches[0].clientX;
247 | self.startTouch.y = self.previousTouch.y = e.touches[0].clientY;
248 | } else {
249 | self.startTouch.x = self.previousTouch.x = self.touches[1].clientX;
250 | self.startTouch.y = self.previousTouch.y = self.touches[1].clientY;
251 | }
252 | }
253 |
254 | addEventListener.call(document, 'touchmove', touchmove, false);
255 | addEventListener.call(document, 'touchend', touchend, false);
256 | }else{
257 | log('Touchable Touchstart touches length ' + e.pageX + ' ' + e.pageY);
258 | self.startTouch.x = self.previousTouch.x = e.pageX;
259 | self.startTouch.y = self.previousTouch.y = e.pageY;
260 | $(document).mousemove(touchmove);
261 | $(document).mouseup(touchend);
262 | }
263 | //don't shallow links, but all the rest
264 | self.target=e.target;//some browser loose the info here
265 | self.currentTarget=e.currentTarget;//some browser loose the info here so save it for later
266 | var x=self.startTouch.x; var y=self.startTouch.y;
267 | self.hitTarget = ( document.elementFromPoint ) ? (document.elementFromPoint(x, y)):'';
268 | //if (self.hitTarget && !(self.hitTarget instanceof HTMLAnchorElement) &&!(e.currentTarget instanceof HTMLAnchorElement))
269 | e.preventDefault()
270 |
271 |
272 | //setup double tapping
273 | if (!self.inDoubleTap) {
274 | self.inDoubleTap = true;
275 | //setup a timer
276 | self.doubleTapTimer = setTimeout(function() {
277 | self.inDoubleTap = false;
278 | }, 500);
279 | } else {//we are double tapping
280 | // call function to run if double-tap
281 | log('Touchable doubleTap');
282 | self.$elem.trigger('doubleTap', self); //trigger a doubleTap
283 | //reset doubleTap state
284 | clearTimeout(self.doubleTapTimer);
285 | self.inDoubleTap = false;
286 | }
287 | //setup long tapping and long mousedown
288 | //setup a timer
289 | self.longTapTimer = setTimeout(function() {
290 | log('Touchable longTap'/*, self.hitTarget, self.target, self.currentTarget, self.elem*/);
291 | $(self.elem).trigger('longTap', self); //trigger a longTap
292 | }, 1000);
293 |
294 | log('Touchable Tap');
295 | $(self.elem).trigger('tap', self); //trigger a tap
296 | $(self.elem).trigger('touchablestart', self); //trigger a tap
297 |
298 |
299 | }
300 |
301 |
302 | //called on iPad/iPhone when touches started and the finger is moved
303 | function touchmove(e) {
304 |
305 | if (typeof e.touches !== 'undefined'){
306 | log('Touchable Touchsmove touches length ' + e.touches.length);
307 | if (e.touches.length !== 1 && e.touches.length !== 2) //use touches to track all fingers on the screen currently (also the ones not in the pane) if there are more than 2 its a gesture
308 | return false;
309 |
310 | //1 finger
311 | if (e.touches.length == 1 || self.isOneFingerGesture) {//we ignore the second finger if we are already in movement
312 | self.currentTouch.x = e.touches[0].clientX;
313 | self.currentTouch.y = e.touches[0].clientY;
314 | //2 finger
315 | } else if (self.touches.length == 2 && !self.isOneFingerGesture) {//two fingers move , take the upper finger as reference
316 | if (e.touches[0].clientY > e.touches[1].clientY) {//0 is lower
317 | self.currentTouch.x = e.touches[0].clientX;
318 | self.currentTouch.y = e.touches[0].clientY;
319 | } else {
320 | self.currentTouch.x = e.touches[1].clientX;
321 | self.currentTouch.y = e.touches[1].clientY;
322 | }
323 | }
324 | }else{
325 | self.currentTouch.x = e.pageX;
326 | self.currentTouch.y = e.pageY;
327 | }
328 | //if we are moving stop any css animations currently running
329 | $(self.elem).removeClass('webkitAnimate');
330 | //e.preventDefault();
331 |
332 | self.currentDelta.x = (self.currentTouch.x - self.previousTouch.x);///s.currentScale;
333 | self.currentDelta.y = (self.currentTouch.y - self.previousTouch.y);///s.currentScale;
334 | self.currentStartDelta.x = (self.currentTouch.x - self.startTouch.x);///s.currentScale;
335 | self.currentStartDelta.y = (self.currentTouch.y - self.startTouch.y);///s.currentScale;
336 | //just for the records (accumulation)
337 | self.currentPosition.x = self.currentPosition.x + self.currentDelta.x;
338 | self.currentPosition.y = self.currentPosition.y + self.currentDelta.y;
339 | //reset the start position for the next delta
340 | self.previousTouch.x = self.currentTouch.x;
341 | self.previousTouch.y = self.currentTouch.y;
342 | log('Touchable Touchablemove self e.target' + e.target + 'e.currentTarget '+ e.currentTarget +' x:'+ self.currentStartDelta.x);
343 | //Target handling
344 | self.target=e.target;//some browser loose the info here
345 | self.currentTarget=e.currentTarget;//some browser loose the info here so save it for later
346 | var x=self.currentTouch.x; var y=self.currentTouch.y;
347 | self.hitTarget = ( document.elementFromPoint ) ? (document.elementFromPoint(x, y)):'';
348 | $(self.elem).trigger('touchablemove', self);
349 |
350 | //clear the long tap timer on mousemove
351 | if (self.longTapTimer) clearTimeout(self.longTapTimer);
352 | }
353 | function touchend(e) {
354 | if (typeof e.touches !== 'undefined'){
355 | if (e.targetTouches.length > 0)
356 | return false;
357 | removeEventListener.call(self.elem, 'touchmove', touchmove, false);
358 | removeEventListener.call(self.elem, 'touchend', touchend, false);
359 | }else{
360 | $(document).unbind('mousemove',touchmove);
361 | $(document).unbind('mouseup',touchend);
362 | }
363 |
364 | //e.preventDefault();
365 | self.isCurrentlyTouching = false;
366 | //clear the long tap timer on mouseup
367 | if (self.longTapTimer) clearTimeout(self.longTapTimer);
368 | log('Touchable Touchend self ' + self.currentStartDelta.x);
369 | $(self.elem).trigger('touchableend', self);
370 | log('Touchable: touchableend');
371 | $(self.hitTarget).trigger('click', self);//trigger a click on the hittarget cause on iPad/Mobile Safari preventdefault seems to shallow click events
372 | log('Touchable: Hittarget click');
373 |
374 | }
375 | }
376 | })(jQuery);//end closure
377 |
378 |
--------------------------------------------------------------------------------
/hoverable.js:
--------------------------------------------------------------------------------
1 | /*
2 | * jQuery Hoverable
3 | *
4 | * Simplified BSD License (@see License)
5 | * @author Gregor Schwab
6 | * @copyright (c) 2010 Gregor Schwab
7 | * Usage: $(elem).Touchable() (@see Readme.md)
8 | * @version 0.0.4
9 | * @requires jQuery Touchable
10 | */
11 |
12 | (function($) {
13 | var Touchable=$.Touchable;
14 | $.fn.Hoverable = function(conf) {
15 | return this.each(function() {
16 | var t= $(this).data['Hoverable']=new Hoverable(this, conf);
17 | return t;
18 | });
19 | }
20 | $.fn.newHover = function(fn1, fn2, disableHover) {
21 | return this.each(function() {
22 | $(this).bind('newHoverIn', fn1).bind('newHoverOut', fn2);
23 | });
24 | }
25 | $.fn.newHover2 = function(fn1, fn2) {
26 | return this.each(function() {
27 | $(this).bind('newHoverIn2', fn1).bind('newHoverOut2', fn2);
28 | });
29 | }
30 | /**
31 | * @constructor
32 | */
33 | function Hoverable(elem, conf)
34 | {
35 |
36 | var self=this;
37 | this.logging=false; //set to false to disabele logging default false gets overwritten by conf see below
38 | var log=function(a){if(self.logging && (typeof console !== 'undefined')){
39 | console.log(Array.prototype.slice.call(arguments));}
40 | }; //private logging function
41 |
42 | this.elem=elem;
43 | //test for touchable
44 | if(!$(elem).Touchable) throw new Error('Hoverable depends on Touchable! Please be sure to include Touchable in your project.')
45 | this.$elem=$(elem).Touchable(conf);
46 |
47 | this.inHover=false;
48 | this.target=null;
49 |
50 |
51 | if (typeof conf!=='undefined'){
52 | if(typeof conf.disableHover!=='undefined'){this.disableHover=conf.disableHover;}
53 | else{this.disableHover=false;}
54 | if(typeof conf.logging!=='undefined'){this.logging=conf.logging;}
55 | }
56 |
57 |
58 | //longTap is the new Hover ;)
59 | if (!this.disableHover){
60 | this.$elem.mouseenter(genericHover);
61 | this.$elem.bind('mouseleave', genericHover);
62 | }
63 |
64 |
65 | this.$elem.bind('longTap', genericHover);
66 | this.$elem.bind('touchableend', genericHover);
67 |
68 | function genericHover(e, touch){
69 | if(e.type==='touchableend' ||e.type==='mouseleave'){
70 | log('Touchable newHoverOut');
71 | //this.$elem.unbind('touchend', genericHover);//don't have to unbind mouseleave .unbind('mouseleave', genericHover);
72 | return self.$elem.trigger('newHoverOut', self);
73 | }
74 | log('Touchable newHoverIn');
75 | self.$elem.trigger('newHoverIn', self); //trigger a genericHover see Readme
76 | }
77 |
78 | //mousemove is the new Hover ;)
79 | if (!this.disableHover){
80 | this.$elem.bind('mouseenter', genericHover2);
81 | this.$elem.bind('mouseleave', genericHover2);
82 | }
83 | self.$elem.bind('touchablestart', function(e, touch){
84 | self.$elem.bind('touchablemove', genericHover2);
85 | }, false);
86 | self.$elem.bind('touchableend', function(e, touch){
87 | self.$elem.unbind('touchablemove', genericHover2);
88 | genericHover2(e, touch);
89 | }, false);
90 |
91 |
92 | //this.$elem.bind('touchend', genericHover2);
93 | function genericHover2(e, touch){
94 | if(e.type==='touchableend'||e.type==='touchend'){
95 | log('Touchable newHoverOut2');
96 | self.inHover=false;
97 | return self.$elem.trigger('newHoverOut2', self);
98 | }else if(e.type==='mouseenter'){
99 | log('Touchable newHoverIn2');
100 | return self.$elem.trigger('newHoverIn2', self);
101 | }else if(e.type==='mouseleave'){
102 | log('Touchable newHoverOut2');
103 | return self.$elem.trigger('newHoverOut2', self);
104 | }
105 | if (e.type == 'touchablemove'){
106 | if(touch instanceof Touchable){
107 | //var x=touch.currentTouch.x; var y=touch.currentTouch.y;
108 | var hitTarget = self.hitTarget; //document.elementFromPoint(x, y);
109 | /* if(typeof hitTarget==='undefined'||hitTarget===null&&self.target!==null) {var target=self.target;var currentTarget=self.target}//just if the browser looses memory
110 | else{var target=hittarget}//first the hittarget
111 | if(self.logging){console.log('target '+target+' x, y:'+x,+' ' +y);}
112 | }else{
113 | var target = e.target;
114 | }*/
115 | log('Touchable target ID/node'+ ' hitTarget'+ ' ' +
116 | hitTarget+'e.target'+e.target + ' e.currentTarget'+e.currentTarget+
117 | ' self in hover'+self.inHover);
118 | //lets see if we can macth our element...still playing with the right settings here cause browsers seem to have differenes in what they pass as an event.target
119 | var pass=false;
120 | //not good cause it goes on paragraphs but can be used to test for being outside the view element
121 | //if (typeof hitTarget !== 'undefined' && hitTarget === self.$elem.get(0) ){pass=true;}
122 | //if (typeof hitTarget !== 'undefined' && hitTarget !== self.$elem.get(0) ){pass=false;}
123 | //gives info to the inner element too
124 | //else if (typeof self.target !== 'undefined' && self.target === self.$elem.get(0)){pass=true;}//Chrome
125 | //relates to the this pointer the touchmove event was bound to (normally document)
126 | //else if (typeof self.currentTarget !== 'undefined' && self.currentTarget === self.$elem.get(0)){pass=true;}//iPad has it in currentTarget
127 | //e.target might work too
128 | //else if (typeof e.target !== 'undefined' && e.target === self.$elem.get(0)){pass=true;}
129 | //the winner is:
130 | if (typeof e.currentTarget !== 'undefined' && e.currentTarget === self.$elem.get(0)){pass=true;}
131 | if(pass&& !self.inHover){
132 | self.inHover=true;
133 | log('Touchable newHoverIn2');
134 | self.$elem.trigger('newHoverIn2', self);
135 | //e.stopPropagation();//we are talking about touchablemove event here
136 | }
137 | else if (pass===false && self.inHover){
138 | self.inHover=false;
139 | log('Touchable newHoverOut2');
140 | self.$elem.trigger('newHoverOut2', self);
141 | //e.stopPropagation(); //we are talking about touchablemove event here
142 | }
143 | }
144 | }
145 | }
146 | }
147 | })(jQuery);//end closure
148 |
--------------------------------------------------------------------------------
/ietest/ietest.js:
--------------------------------------------------------------------------------
1 | /*
2 | * jQuery Touchable
3 | *
4 | * Simplified BSD License (@see License)
5 | * @author Gregor Schwab
6 | * @copyright (c) 2010 Gregor Schwab
7 | * Usage Command Line: $(elem).Touchable() (@see Readme.md)
8 | * @version 0.0.1
9 | * @requires jQuery
10 | */
11 |
12 |
13 | (function($) {
14 |
15 | $.fn.Touchable = function(conf) {
16 | return this.each(function() {
17 | var t= $(this).data['Touchable']=new Touchable(this, conf);
18 | return t;
19 | });
20 | }
21 | $.fn.newHover = function(fn1, fn2, disableHover) {
22 | return this.each(function() {
23 | $(this).bind('newHoverIn', fn1).bind('newHoverOut', fn2);
24 | });
25 | }
26 | $.fn.newHover2 = function(fn1, fn2) {
27 | return this.each(function() {
28 | $(this).bind('newHoverIn2', fn1).bind('newHoverOut2', fn2);
29 | });
30 | }
31 |
32 | $.Touchable=Touchable;
33 |
34 | /**
35 | * @constructor
36 | */
37 |
38 | function Touchable(elem, conf)
39 | {
40 |
41 | this.logging=false; //set to false to disabele logging gets overwritten by conf see below
42 | var log=function(a){if(self.logging){console.log(arguments);}} ;//private logging function
43 |
44 | this.elem=elem;
45 | this.$elem=$(elem);
46 | this.is_doubleTap=false;
47 | this.is_currentlyTouching=false;
48 | this.isOneFingerGesture = false;
49 |
50 | this.startTouch={x:0,y:0};
51 | this.currentTouch={x:0,y:0};
52 | this.previousTouch={x:0,y:0};
53 | this.currentDelta={x:0,y:0};//measured from previous move event
54 | this.currentStartDelta={x:0,y:0}; //measured from start
55 | this.currentPosition={x:0,y:0};
56 | this.doubleTapTimer=null, this.longTapTimer=null;
57 |
58 | var self=this;
59 |
60 | if (typeof conf!=='undefined'){
61 | if(typeof conf.logging!=='undefined'){this.logging=conf.logging;}
62 | }
63 | //make IE happy
64 | var addEventListener=elem.addEventListener||elem.attachEvent
65 | var removeEventListener = elem.removeEventListener||elem.detachEvent
66 | //add touchstart eventlistener
67 | addEventListener('touchstart', function(){self.$elem.trigger('touchstart')}, false);
68 | addEventListener('touchend', function(){self.$elem.trigger('touchend')}, false);
69 | addEventListener('touchmove', function(){self.$elem.trigger('touchmove')}, false);
70 |
71 | addEventListener('touchstart', touchstart, false);
72 | this.$elem.mousedown(touchstart);
73 |
74 | function touchstart (e) {
75 | if(typeof e.touches!== "undefined")
76 | {
77 | log('Touchable Touchstart touches length ' + e.touches.length);
78 | //only handle 1 or 2 touches
79 | if (e.touches.length !== 1 && e.touches.length !== 2) {
80 | return false;
81 | }
82 | if (self.isCurrentlyTouching) {
83 | return false;
84 | }
85 |
86 |
87 | self.isCurrentlyTouching = true;
88 | if (e.touches.length == 1) { //1 finger
89 | self.isOneFingerGesture = true;
90 | //init pos
91 | self.startTouch.x = self.previousTouch.x = e.touches[0].clientX;
92 | self.startTouch.y = self.previousTouch.y = e.touches[0].clientY;
93 | } else if (e.touches.length == 2) { //two fingers
94 | self.isOneFingerGesture = false;
95 | if (e.touches[0].clientY > e.touches[1].clientY) {//0 is lower
96 | self.startTouch.x = self.previousTouch.x = e.touches[0].clientX;
97 | self.startTouch.y = self.previousTouch.y = e.touches[0].clientY;
98 | } else {
99 | self.startTouch.x = self.previousTouch.x = self.touches[1].clientX;
100 | self.startTouch.y = self.previousTouch.y = self.touches[1].clientY;
101 | }
102 | }
103 |
104 | addEventListener.call(document, 'touchmove', touchmove, false);
105 | addEventListener.call(document, 'touchend', touchend, false);
106 | }else{
107 | log('Touchable Touchstart touches length ' + e.pageX + ' ' + e.pageY);
108 | self.startTouch.x = self.previousTouch.x = e.pageX;
109 | self.startTouch.y = self.previousTouch.y = e.pageY;
110 | $(document).mousemove(touchmove);
111 | $(document).mouseup(touchend);
112 | }
113 | e.preventDefault();
114 |
115 | //setup double tapping
116 | if (!self.inDoubleTap) {
117 | self.inDoubleTap = true;
118 | //setup a timer
119 | self.doubleTapTimer = setTimeout(function() {
120 | self.inDoubleTap = false;
121 | }, 500);
122 | } else {//we are double tapping
123 | // call function to run if double-tap
124 | log('Touchable doubleTap');
125 | self.$elem.trigger('doubleTap', self); //trigger a doubleTap
126 | //reset doubleTap state
127 | clearTimeout(self.doubleTapTimer);
128 | self.inDoubleTap = false;
129 | }
130 | //setup long tapping and long mousedown
131 | //setup a timer
132 | self.longTapTimer = setTimeout(function() {
133 | log('Touchable longTap');
134 | $(self.elem).trigger('longTap', self); //trigger a longTap
135 | }, 1000);
136 |
137 | log('Touchable Tap');
138 | $(self.elem).trigger('tap', self); //trigger a tap
139 | $(self.elem).trigger('touchablestart', self); //trigger a tap
140 |
141 |
142 | }
143 |
144 |
145 | //called on iPad/iPhone when touches started and the finger is moved
146 | function touchmove(e) {
147 |
148 | if (typeof e.touches !== 'undefined'){
149 | log('Touchable Touchsmove touches length ' + e.touches.length);
150 | if (e.touches.length !== 1 && e.touches.length !== 2) //use touches to track all fingers on the screen currently (also the ones not in the pane) if there are more than 2 its a gesture
151 | return false;
152 |
153 | //1 finger
154 | if (e.touches.length == 1 || self.isOneFingerGesture) {//we ignore the second finger if we are already in movement
155 | self.currentTouch.x = e.touches[0].clientX;
156 | self.currentTouch.y = e.touches[0].clientY;
157 | //2 finger
158 | } else if (self.touches.length == 2 && !self.isOneFingerGesture) {//two fingers move , take the upper finger as reference
159 | if (e.touches[0].clientY > e.touches[1].clientY) {//0 is lower
160 | self.currentTouch.x = e.touches[0].clientX;
161 | self.currentTouch.y = e.touches[0].clientY;
162 | } else {
163 | self.currentTouch.x = e.touches[1].clientX;
164 | self.currentTouch.y = e.touches[1].clientY;
165 | }
166 | }
167 | }else{
168 | self.currentTouch.x = e.pageX;
169 | self.currentTouch.y = e.pageY;
170 | }
171 | //if we are moving stop any css animations currently running
172 | $(self.elem).removeClass('webkitAnimate');
173 | //e.preventDefault();
174 |
175 | self.currentDelta.x = (self.currentTouch.x - self.previousTouch.x);///s.currentScale;
176 | self.currentDelta.y = (self.currentTouch.y - self.previousTouch.y);///s.currentScale;
177 | self.currentStartDelta.x = (self.currentTouch.x - self.startTouch.x);///s.currentScale;
178 | self.currentStartDelta.y = (self.currentTouch.y - self.startTouch.y);///s.currentScale;
179 | //just for the records (accumulation)
180 | self.currentPosition.x = self.currentPosition.x + self.currentDelta.x;
181 | self.currentPosition.y = self.currentPosition.y + self.currentDelta.y;
182 | //reset the start position for the next delta
183 | self.previousTouch.x = self.currentTouch.x;
184 | self.previousTouch.y = self.currentTouch.y;
185 | log('Touchable Touchablemove self e.target' + e.target + 'e.currentTarget '+ e.currentTarget +' x:'+ self.currentStartDelta.x);
186 | self.target=e.target;//some browser loose the info here
187 | self.currentTarget=e.currentTarget;//some browser loose the info here
188 | $(self.elem).trigger('touchablemove', self);
189 |
190 | //clear the long tap timer on mousemove
191 | if (self.longTapTimer) clearTimeout(self.longTapTimer);
192 | }
193 | function touchend(e) {
194 | if (typeof e.touches !== 'undefined'){
195 | if (e.targetTouches.length > 0)
196 | return false;
197 | removeEventListener.call(self.elem, 'touchmove', touchmove, true);
198 | removeEventListener.call(self.elem, 'touchend', touchend, true);
199 | }else{
200 | $(document).unbind('mousemove',touchmove);
201 | $(document).unbind('mouseup',touchend);
202 | }
203 |
204 | //e.preventDefault();
205 | self.isCurrentlyTouching = false;
206 | //clear the long tap timer on mouseup
207 | if (self.longTapTimer) clearTimeout(self.longTapTimer);
208 | log('Touchable Touchend self ' + self.currentStartDelta.x);
209 | $(self.elem).trigger('touchableend', self);
210 | log('Touchable: touchableend');
211 |
212 | }
213 | }
214 | })(jQuery);//end closure
215 |
--------------------------------------------------------------------------------
/ietest/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
28 |
29 |
30 |
LongTap and Touchmove are the new hover!
31 |
32 |
42 |
Try to long tap in the box on an iPad or iPhone or just hover on a Desktop
43 |
44 |
45 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/ietest/test.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function(){
2 | $t=$('#Touchable').Hoverable({logging:true});
3 | $t.newHover(function(e, touch){ //hoverIN
4 | $tooltip=$(this).find('.tooltip');
5 | $tooltip.show();
6 | }, function(e, touch){//hoverOut
7 | $tooltip=$(this).find('.tooltip');
8 | $tooltip.hide();
9 | });
10 |
11 | })
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
NEW!!! NOW WORKING EVEN ON IE 7/8
2 |
3 |
Touchable
4 |
5 |
Touchable is a very lightweight (1,96kb) jQuery Class that unifies touch and mouse events over different platforms like desktops and mobile devices with touchscreens (like Android, iPad, iPod Touch, iPhone etc.)
6 | The name Touchable therefore might be a bit confusing, cause we actually generate a generic "Touch" which involves also mouse events. For convenience they are all called "Touches".
7 |
8 |
Touchable really doesn't depend that much on jQuery, so it should work with other libraries like Zepto too. Havent tested that though.
9 |
10 |
Hoverable
11 |
12 |
Hoverable is built upon Touchable and is a very lightweight (1,57kB / 3,04 together with Touchable) jQuery Class that unifies hover events over different platforms like desktops and mobile devices with touchscreens (like Android, iPad, iPod Touch, iPhone etc.)
13 | It introduces a new event called
14 |
15 |
16 | - newHover(2): following my blog article about UI design and my postulate, that Long Tap is the new Hover. Fired when the user hovers with the mouse or longTaps an element. Hovever if you want to you can set it up to a touchmove event too, which will fire a genericHover2 event.
17 |
18 |
19 |
Testing
20 |
21 |
right now I have tested Touchable on the following devices and browsers Chrome, Firefox, Safari, iPad Simulator, iPad, iPhone, Internet Explorer 7/8. But it should work quite everywhere. If you have any bug notes drop me a line.
22 |
23 |
Demo
24 |
25 |
I have setup a demo site for Hoverable on the github page. View source to see how everything works.
26 |
27 |
Usage
28 |
29 |
Right now Touchable supports five basic events and Hoverable adds two generic Hover events, all events when bound with jQuery's bind function get a touch object passed in as second argument
30 |
31 |
TOUCHABLE
32 |
33 |
34 | - touchablemove: fired when the user touched or clicked with the mouse and moves to another position. Right now Touchable supports up to 2 fingers. If 2 fingers are on screen the lower left is taken as a reference.
35 | - touchableend: fired when the user ended a touch
36 | - tap: fired when the user clicks with his mouse or taps with his finger
37 | - longTap: fired when the user stays with his mouse or finger on an item for 1 second
38 | - doubleTap: fired when the user taps two times within half of a second
39 |
40 |
41 |
HOVERABLE
42 |
43 |
44 | - newHover(2): Fired when the user hovers with the mouse or longTaps an element. Hovever if you want to you can set it up to a touchmove event too, which will fire a genericHover2 event.
45 |
46 |
47 |
you use it by initializing Touchable on a view element, like so:
48 |
49 |
var div = $(<div>).Touchable();
50 |
51 |
52 |
or
53 |
54 |
var div = $(<div>).Hoverable(); //which cretaes a Touchable internally
55 |
56 |
57 |
then you bind to the events
58 |
59 |
div.bind('touchmove', function(e, touch){})
60 | div.bind('newHover2', function in(e, touch){}, function out(e, touch){})
61 |
62 |
63 |
notice, that each event gets passed a touch object, besides the normal event object. The Touch object has the following properties:
64 |
65 |
66 | - startTouch: this is where the touch or mousedown event originated
67 | - currentTouch: this is where we are right now with our finger or mouse
68 | - previousTouch: for internal calculations of the previous position of the mouse pointer or finger, used for deltas see below
69 | - currentDelta: measured from previous move event
70 | - currentStartDelta, currentPosition: the relative position, measured from start calculated in different ways, but should be the same
71 |
72 |
73 |
Event targets:
74 |
75 |
76 | - currentTarget: the event target element (may differ on mobile browsers from the ctarget element)
77 | - target: the event target element
78 |
79 |
80 |
Todos
81 |
82 |
83 | - support more gestures like swiping,...
84 |
--------------------------------------------------------------------------------
/min/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dotmaster/Touchable-jQuery-Plugin/c6869640800307061a36d2d36940fff2350f886d/min/.DS_Store
--------------------------------------------------------------------------------
/min/hoverable.min.js:
--------------------------------------------------------------------------------
1 | var c=false;
2 | (function(d){function l(e,f){function g(){a.f&&console.log(arguments)}function j(b){if(b.type==="touchableend"||b.type==="mouseleave"){g("Touchable newHoverOut");return a.a.d("newHoverOut",a)}g("Touchable newHoverIn");a.a.d("newHoverIn",a)}function h(b,i){if(b.type==="touchableend"||b.type==="touchend"){g("Touchable newHoverOut2");a.c=c;return a.a.d("newHoverOut2",a)}else if(b.type==="mouseenter"){g("Touchable newHoverIn2");return a.a.d("newHoverIn2",a)}else if(b.type==="mouseleave"){g("Touchable newHoverOut2");return a.a.d("newHoverOut2",
3 | a)}if(b.type=="touchablemove")if(i instanceof m){g("Touchable target ID/node hitTarget "+document.elementFromPoint(i.j.x,i.j.y)+"e.target"+b.target+" e.currentTarget"+b.currentTarget+" self in hover"+a.c);var k=c;if(typeof b.currentTarget!=="undefined"&&b.currentTarget===a.a.m(0))k=true;if(k&&!a.c){a.c=true;g("Touchable newHoverIn2");a.a.d("newHoverIn2",a)}else if(k===c&&a.c){a.c=c;g("Touchable newHoverOut2");a.a.d("newHoverOut2",a)}}}var a=this;this.f=c;this.l=e;if(!d(e).g)throw Error("Hoverable depends on Touchable! Please be sure to include Touchable in your project.");
4 | this.a=d(e).g();this.c=c;this.target=null;if(typeof f!=="undefined"){this.e=typeof f.e!=="undefined"?f.e:c;if(typeof f.f!=="undefined")this.f=f.f}if(!this.e){this.a.n(j);this.a.b("mouseleave",j)}this.a.b("longTap",j);this.a.b("touchableend",j);if(!this.e){this.a.b("mouseenter",h);this.a.b("mouseleave",h)}a.a.b("touchablestart",function(){a.a.b("touchablemove",h)},c);a.a.b("touchableend",function(b,i){a.a.q("touchablemove",h);h(b,i)},c)}var m=d.g;d.i.k=function(e){return this.h(function(){return d(this).data.Hoverable=
5 | new l(this,e)})};d.i.o=function(e,f){return this.h(function(){d(this).b("newHoverIn",e).b("newHoverOut",f)})};d.i.p=function(e,f){return this.h(function(){d(this).b("newHoverIn2",e).b("newHoverOut2",f)})}})(jQuery);
--------------------------------------------------------------------------------
/min/hoverable_and_touchable.min.js:
--------------------------------------------------------------------------------
1 | var d=true,m=false;
2 | (function(c){function n(h,f){function i(){a.h&&console.log(arguments)}function k(e){if(e.type==="touchableend"||e.type==="mouseleave"){i("Touchable newHoverOut");return a.a.d("newHoverOut",a)}i("Touchable newHoverIn");a.a.d("newHoverIn",a)}function j(e,l){if(e.type==="touchableend"||e.type==="touchend"){i("Touchable newHoverOut2");a.i=m;return a.a.d("newHoverOut2",a)}else if(e.type==="mouseenter"){i("Touchable newHoverIn2");return a.a.d("newHoverIn2",a)}else if(e.type==="mouseleave"){i("Touchable newHoverOut2");return a.a.d("newHoverOut2",
3 | a)}if(e.type=="touchablemove")if(l instanceof g){i("Touchable target ID/node hitTarget "+document.elementFromPoint(l.b.x,l.b.y)+"e.target"+e.target+" e.currentTarget"+e.currentTarget+" self in hover"+a.i);var b=m;if(typeof e.currentTarget!=="undefined"&&e.currentTarget===a.a.B(0))b=d;if(b&&!a.i){a.i=d;i("Touchable newHoverIn2");a.a.d("newHoverIn2",a)}else if(b===m&&a.i){a.i=m;i("Touchable newHoverOut2");a.a.d("newHoverOut2",a)}}}var a=this;this.h=m;this.g=h;if(!c(h).m)throw Error("Hoverable depends on Touchable! Please be sure to include Touchable in your project.");
4 | this.a=c(h).m();this.i=m;this.target=null;if(typeof f!=="undefined"){this.p=typeof f.p!=="undefined"?f.p:m;if(typeof f.h!=="undefined")this.h=f.h}if(!this.p){this.a.G(k);this.a.c("mouseleave",k)}this.a.c("longTap",k);this.a.c("touchableend",k);if(!this.p){this.a.c("mouseenter",j);this.a.c("mouseleave",j)}a.a.c("touchablestart",function(){a.a.c("touchablemove",j)},m);a.a.c("touchableend",function(e,l){a.a.v("touchablemove",j);j(e,l)},m)}var g=c.m;c.k.A=function(h){return this.j(function(){return c(this).data.Hoverable=
5 | new n(this,h)})};c.k.w=function(h,f){return this.j(function(){c(this).c("newHoverIn",h).c("newHoverOut",f)})};c.k.z=function(h,f){return this.j(function(){c(this).c("newHoverIn2",h).c("newHoverOut2",f)})}})(jQuery);
6 | (function(c){function n(g,h){function f(){a.h&&console.log(arguments)}function i(b){if(typeof b.touches!=="undefined"){f("Touchable Touchstart touches length "+b.touches.length);if(b.touches.length!==1&&b.touches.length!==2)return m;if(a.u)return m;a.u=d;if(b.touches.length==1){a.q=d;a.f.x=a.e.x=b.touches[0].clientX;a.f.y=a.e.y=b.touches[0].clientY}else if(b.touches.length==2){a.q=m;if(b.touches[0].clientY>b.touches[1].clientY){a.f.x=a.e.x=b.touches[0].clientX;a.f.y=a.e.y=b.touches[0].clientY}else{a.f.x=
7 | a.e.x=a.touches[1].clientX;a.f.y=a.e.y=a.touches[1].clientY}}e.call(document,"touchmove",k,m);e.call(document,"touchend",j,m)}else{f("Touchable Touchstart touches length "+b.pageX+" "+b.pageY);a.f.x=a.e.x=b.pageX;a.f.y=a.e.y=b.pageY;c(document).H(k);c(document).I(j)}var o=a.f.x,p=a.f.y;(o=document.elementFromPoint?document.elementFromPoint(o,p):"")&&!(o instanceof HTMLAnchorElement)&&!(b.currentTarget instanceof HTMLAnchorElement)&&b.preventDefault();if(a.r){f("Touchable doubleTap");a.a.d("doubleTap",
8 | a);clearTimeout(a.t);a.r=m}else{a.r=d;a.t=setTimeout(function(){a.r=m},500)}a.l=setTimeout(function(){f("Touchable longTap");c(a.g).d("longTap",a)},1E3);f("Touchable Tap");c(a.g).d("tap",a);c(a.g).d("touchablestart",a)}function k(b){if(typeof b.touches!=="undefined"){f("Touchable Touchsmove touches length "+b.touches.length);if(b.touches.length!==1&&b.touches.length!==2)return m;if(b.touches.length==1||a.q){a.b.x=b.touches[0].clientX;a.b.y=b.touches[0].clientY}else if(a.touches.length==2&&!a.q)if(b.touches[0].clientY>
9 | b.touches[1].clientY){a.b.x=b.touches[0].clientX;a.b.y=b.touches[0].clientY}else{a.b.x=b.touches[1].clientX;a.b.y=b.touches[1].clientY}}else{a.b.x=b.pageX;a.b.y=b.pageY}c(a.g).J("webkitAnimate");a.n.x=a.b.x-a.e.x;a.n.y=a.b.y-a.e.y;a.o.x=a.b.x-a.f.x;a.o.y=a.b.y-a.f.y;a.s.x+=a.n.x;a.s.y+=a.n.y;a.e.x=a.b.x;a.e.y=a.b.y;f("Touchable Touchablemove self e.target"+b.target+"e.currentTarget "+b.currentTarget+" x:"+a.o.x);a.target=b.target;a.currentTarget=b.currentTarget;c(a.g).d("touchablemove",a);a.l&&clearTimeout(a.l)}
10 | function j(b){if(typeof b.touches!=="undefined"){if(b.targetTouches.length>0)return m;l.call(a.g,"touchmove",k,d);l.call(a.g,"touchend",j,d)}else{c(document).v("mousemove",k);c(document).v("mouseup",j)}a.u=m;a.l&&clearTimeout(a.l);f("Touchable Touchend self "+a.o.x);c(a.g).d("touchableend",a);f("Touchable: touchableend")}this.h=m;this.g=g;this.a=c(g);this.q=this.C=this.D=m;this.f={x:0,y:0};this.b={x:0,y:0};this.e={x:0,y:0};this.n={x:0,y:0};this.o={x:0,y:0};this.s={x:0,y:0};this.l=this.t=null;var a=
11 | this;if(typeof h!=="undefined")if(typeof h.h!=="undefined")this.h=h.h;var e=g.addEventListener||g.attachEvent,l=g.removeEventListener||g.detachEvent;e.call(g,"touchstart",function(){a.a.d("touchstart")},m);e.call(g,"touchend",function(){a.a.d("touchend")},m);e.call(g,"touchmove",function(){a.a.d("touchmove")},m);e.call(g,"touchstart",i,m);this.a.F(i)}c.k.m=function(g){return this.j(function(){return c(this).data.Touchable=new n(this,g)})};c.k.w=function(g,h){return this.j(function(){c(this).c("newHoverIn",
12 | g).c("newHoverOut",h)})};c.k.z=function(g,h){return this.j(function(){c(this).c("newHoverIn2",g).c("newHoverOut2",h)})};c.m=n})(jQuery);
--------------------------------------------------------------------------------
/min/hoverable_and_touchable.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dotmaster/Touchable-jQuery-Plugin/c6869640800307061a36d2d36940fff2350f886d/min/hoverable_and_touchable.zip
--------------------------------------------------------------------------------
/min/touchable.min.js:
--------------------------------------------------------------------------------
1 | var d=false;
2 | (function(c){function l(e,g){function f(){a.k&&console.log(arguments)}function m(b){if(typeof b.touches!=="undefined"){f("Touchable Touchstart touches length "+b.touches.length);if(b.touches.length!==1&&b.touches.length!==2)return d;if(a.r)return d;a.r=true;if(b.touches.length==1){a.j=true;a.c.x=a.b.x=b.touches[0].clientX;a.c.y=a.b.y=b.touches[0].clientY}else if(b.touches.length==2){a.j=d;if(b.touches[0].clientY>b.touches[1].clientY){a.c.x=a.b.x=b.touches[0].clientX;a.c.y=a.b.y=b.touches[0].clientY}else{a.c.x=a.b.x=
3 | a.touches[1].clientX;a.c.y=a.b.y=a.touches[1].clientY}}h.call(document,"touchmove",i,d);h.call(document,"touchend",j,d)}else{f("Touchable Touchstart touches length "+b.pageX+" "+b.pageY);a.c.x=a.b.x=b.pageX;a.c.y=a.b.y=b.pageY;c(document).z(i);c(document).A(j)}var k=a.c.x,o=a.c.y;(k=document.elementFromPoint?document.elementFromPoint(k,o):"")&&!(k instanceof HTMLAnchorElement)&&!(b.currentTarget instanceof HTMLAnchorElement)&&b.preventDefault();if(a.m){f("Touchable doubleTap");a.f.e("doubleTap",a);
4 | clearTimeout(a.o);a.m=d}else{a.m=true;a.o=setTimeout(function(){a.m=d},500)}a.g=setTimeout(function(){f("Touchable longTap");c(a.d).e("longTap",a)},1E3);f("Touchable Tap");c(a.d).e("tap",a);c(a.d).e("touchablestart",a)}function i(b){if(typeof b.touches!=="undefined"){f("Touchable Touchsmove touches length "+b.touches.length);if(b.touches.length!==1&&b.touches.length!==2)return d;if(b.touches.length==1||a.j){a.a.x=b.touches[0].clientX;a.a.y=b.touches[0].clientY}else if(a.touches.length==2&&!a.j)if(b.touches[0].clientY>
5 | b.touches[1].clientY){a.a.x=b.touches[0].clientX;a.a.y=b.touches[0].clientY}else{a.a.x=b.touches[1].clientX;a.a.y=b.touches[1].clientY}}else{a.a.x=b.pageX;a.a.y=b.pageY}c(a.d).D("webkitAnimate");a.h.x=a.a.x-a.b.x;a.h.y=a.a.y-a.b.y;a.i.x=a.a.x-a.c.x;a.i.y=a.a.y-a.c.y;a.n.x+=a.h.x;a.n.y+=a.h.y;a.b.x=a.a.x;a.b.y=a.a.y;f("Touchable Touchablemove self e.target"+b.target+"e.currentTarget "+b.currentTarget+" x:"+a.i.x);a.target=b.target;a.currentTarget=b.currentTarget;c(a.d).e("touchablemove",a);a.g&&clearTimeout(a.g)}
6 | function j(b){if(typeof b.touches!=="undefined"){if(b.targetTouches.length>0)return d;n.call(a.d,"touchmove",i,true);n.call(a.d,"touchend",j,true)}else{c(document).t("mousemove",i);c(document).t("mouseup",j)}a.r=d;a.g&&clearTimeout(a.g);f("Touchable Touchend self "+a.i.x);c(a.d).e("touchableend",a);f("Touchable: touchableend")}this.k=d;this.d=e;this.f=c(e);this.j=this.u=this.v=d;this.c={x:0,y:0};this.a={x:0,y:0};this.b={x:0,y:0};this.h={x:0,y:0};this.i={x:0,y:0};this.n={x:0,y:0};this.g=this.o=null;
7 | var a=this;if(typeof g!=="undefined")if(typeof g.k!=="undefined")this.k=g.k;var h=e.addEventListener||e.attachEvent,n=e.removeEventListener||e.detachEvent;h.call(e,"touchstart",function(){a.f.e("touchstart")},d);h.call(e,"touchend",function(){a.f.e("touchend")},d);h.call(e,"touchmove",function(){a.f.e("touchmove")},d);h.call(e,"touchstart",m,d);this.f.w(m)}c.q.s=function(e){return this.p(function(){return c(this).data.Touchable=new l(this,e)})};c.q.B=function(e,g){return this.p(function(){c(this).l("newHoverIn",
8 | e).l("newHoverOut",g)})};c.q.C=function(e,g){return this.p(function(){c(this).l("newHoverIn2",e).l("newHoverOut2",g)})};c.s=l})(jQuery);
--------------------------------------------------------------------------------
/touchable.js:
--------------------------------------------------------------------------------
1 | /*
2 | * jQuery Touchable
3 | *
4 | * Simplified BSD License (@see License)
5 | * @author Gregor Schwab
6 | * @copyright (c) 2010 Gregor Schwab
7 | * Usage Command Line: $(elem).Touchable() (@see Readme.md)
8 | * @version 0.0.5
9 | * @requires jQuery
10 | */
11 |
12 | (function($) {
13 |
14 | $.fn.Touchable = function(conf) {
15 |
16 | return this.each(function() {
17 |
18 | var t= $(this).data['Touchable']=new Touchable(this, conf);
19 | return t;
20 |
21 | });
22 |
23 | };
24 |
25 | $.fn.newHover = function(fn1, fn2, disableHover) {
26 |
27 | return this.each(function() {
28 |
29 | $(this).bind('newHoverIn', fn1).bind('newHoverOut', fn2);
30 |
31 | });
32 |
33 | };
34 |
35 | $.fn.newHover2 = function(fn1, fn2) {
36 |
37 | return this.each(function() {
38 |
39 | $(this).bind('newHoverIn2', fn1).bind('newHoverOut2', fn2);
40 |
41 | });
42 |
43 | };
44 |
45 | $.Touchable=Touchable;
46 |
47 | /**
48 | * @constructor
49 | */
50 | function Touchable(elem, conf){
51 |
52 | function addEventListener(el, eventName, eventHandler, capture){
53 |
54 | capture = capture === true ? capture : false;
55 |
56 | if (el.addEventListener){
57 |
58 | el.addEventListener(eventName, eventHandler, capture);
59 |
60 | } else if (el.attachEvent){
61 |
62 | el.attachEvent('on'+eventName, eventHandler);
63 |
64 | }
65 |
66 | }
67 |
68 | function removeEventListener(el, eventName, eventHandler, capture){
69 |
70 | capture = capture === true ? capture : false;
71 |
72 | if (el.removeEventListener){
73 |
74 | el.removeEventListener(eventName,eventHandler, capture);
75 |
76 | }else if (el.detachEvent){
77 |
78 | el.detachEvent ('on'+eventName,eventHandler);
79 |
80 | }
81 |
82 | }
83 |
84 | //private logging function
85 | function log(a){
86 |
87 | if(self.logging){
88 |
89 | if(typeof console !== 'undefined'){
90 |
91 | console.log(a);
92 |
93 | }else{
94 |
95 | $('body').append('
* ' + a);
96 |
97 | }
98 |
99 | }
100 |
101 | }
102 |
103 | function touchstart (e) {
104 |
105 | if(e.originalEvent && typeof e.originalEvent.touches !== 'undefined'){
106 |
107 | log('Touchable Touchstart touches length ' + e.originalEvent.touches.length);
108 |
109 | //only handle 1 or 2 touches
110 | if (e.originalEvent.touches.length !== 1 && e.originalEvent.touches.length !== 2) {
111 |
112 | return false;
113 |
114 | }
115 |
116 | if (self.isCurrentlyTouching) {
117 |
118 | return false;
119 |
120 | }
121 |
122 | self.isCurrentlyTouching = true;
123 |
124 | if (e.originalEvent.touches.length == 1) { //1 finger
125 |
126 | self.isOneFingerGesture = true;
127 | //init pos
128 | self.startTouch.x = self.previousTouch.x = e.originalEvent.touches[0].clientX;
129 | self.startTouch.y = self.previousTouch.y = e.originalEvent.touches[0].clientY;
130 |
131 | } else if (e.originalEvent.touches.length == 2) { //two fingers
132 |
133 | self.isOneFingerGesture = false;
134 |
135 | if (e.originalEvent.touches[0].clientY > e.originalEvent.touches[1].clientY) {//0 is lower
136 |
137 | self.startTouch.x = self.previousTouch.x = e.originalEvent.touches[0].clientX;
138 | self.startTouch.y = self.previousTouch.y = e.originalEvent.touches[0].clientY;
139 |
140 | } else {
141 |
142 | self.startTouch.x = self.previousTouch.x = self.touches[1].clientX;
143 | self.startTouch.y = self.previousTouch.y = self.touches[1].clientY;
144 |
145 | }
146 |
147 | }
148 |
149 | }else{
150 |
151 | log('Touchable Touchstart touches length ' + e.pageX + ' ' + e.pageY);
152 | self.startTouch.x = self.previousTouch.x = e.pageX;
153 | self.startTouch.y = self.previousTouch.y = e.pageY;
154 | $(document).bind('mousemove', touchmove);
155 | $(document).bind('mouseup', touchend);
156 |
157 | }
158 |
159 | //don't shallow links, but all the rest
160 | self.target=e.target;//some browser loose the info here
161 | self.currentTarget=e.currentTarget;//some browser loose the info here so save it for later
162 | var x=self.startTouch.x; var y=self.startTouch.y;
163 | self.hitTarget = ( document.elementFromPoint ) ? (document.elementFromPoint(x, y)):'';
164 |
165 | //setup double tapping
166 | if (!self.inDoubleTap) {
167 |
168 | self.inDoubleTap = true;
169 | //setup a timer
170 | self.doubleTapTimer = setTimeout(function() {
171 |
172 | self.inDoubleTap = false;
173 |
174 | }, 500);
175 |
176 | } else {//we are double tapping
177 |
178 | // call function to run if double-tap
179 | log('Touchable doubleTap');
180 | self.$elem.trigger('doubleTap', self); //trigger a doubleTap
181 | //reset doubleTap state
182 | clearTimeout(self.doubleTapTimer);
183 | self.inDoubleTap = false;
184 |
185 | }
186 |
187 | //setup long tapping and long mousedown
188 | //setup a timer
189 | self.longTapTimer = setTimeout(function() {
190 |
191 | log('Touchable longTap'/*, self.hitTarget, self.target, self.currentTarget, self.elem*/);
192 | $(self.elem).trigger('longTap', self); //trigger a longTap
193 |
194 | }, 1000);
195 | log('Touchable Tap');
196 | $(self.elem).trigger('tap', self); //trigger a tap
197 | $(self.elem).trigger('touchablestart', self); //trigger a tap
198 |
199 | }
200 |
201 |
202 | //called on iPad/iPhone when touches started and the finger is moved
203 | function touchmove(e) {
204 |
205 | if (e.originalEvent && typeof e.originalEvent.touches !== 'undefined'){
206 |
207 | log('Touchable Touchsmove touches length ' + e.originalEvent.touches.length);
208 |
209 | if (e.originalEvent.touches.length !== 1 && e.originalEvent.touches.length !== 2){ //use touches to track all fingers on the screen currently (also the ones not in the pane) if there are more than 2 its a gesture
210 |
211 | return false;
212 |
213 | }
214 |
215 | //1 finger
216 | if (e.originalEvent.touches.length == 1 || self.isOneFingerGesture) {//we ignore the second finger if we are already in movement
217 |
218 | self.currentTouch.x = e.originalEvent.touches[0].clientX;
219 | self.currentTouch.y = e.originalEvent.touches[0].clientY;
220 |
221 | } else if (e.originalEvent.touches.length == 2 && !self.isOneFingerGesture) {//two fingers move , take the upper finger as reference
222 |
223 | if (e.originalEvent.touches[0].clientY > e.originalEvent.touches[1].clientY) {//0 is lower
224 |
225 | self.currentTouch.x = e.originalEvent.touches[0].clientX;
226 | self.currentTouch.y = e.originalEvent.touches[0].clientY;
227 |
228 | } else {
229 |
230 | self.currentTouch.x = e.originalEvent.touches[1].clientX;
231 | self.currentTouch.y = e.originalEvent.touches[1].clientY;
232 |
233 | }
234 |
235 | }
236 |
237 | }else{
238 |
239 | e.preventDefault();
240 | self.currentTouch.x = e.pageX;
241 | self.currentTouch.y = e.pageY;
242 |
243 | }
244 |
245 | //if we are moving stop any css animations currently running
246 | $(self.elem).removeClass('webkitAnimate');
247 | self.currentDelta.x = (self.currentTouch.x - self.previousTouch.x);///s.currentScale;
248 | self.currentDelta.y = (self.currentTouch.y - self.previousTouch.y);///s.currentScale;
249 | self.currentStartDelta.x = (self.currentTouch.x - self.startTouch.x);///s.currentScale;
250 | self.currentStartDelta.y = (self.currentTouch.y - self.startTouch.y);///s.currentScale;
251 | //just for the records (accumulation)
252 | self.currentPosition.x = self.currentPosition.x + self.currentDelta.x;
253 | self.currentPosition.y = self.currentPosition.y + self.currentDelta.y;
254 | //reset the start position for the next delta
255 | self.previousTouch.x = self.currentTouch.x;
256 | self.previousTouch.y = self.currentTouch.y;
257 | log('Touchable Touchablemove self e.target' + e.target + 'e.currentTarget '+ e.currentTarget +' x:'+ self.currentStartDelta.x);
258 | //Target handling
259 | self.target=e.target;//some browser loose the info here
260 | self.currentTarget=e.currentTarget;//some browser loose the info here so save it for later
261 | var x=self.currentTouch.x; var y=self.currentTouch.y;
262 | self.hitTarget = ( document.elementFromPoint ) ? (document.elementFromPoint(x, y)):'';
263 | $(self.elem).trigger('touchablemove', self);
264 |
265 | //clear the long tap timer on mousemove
266 | if (self.longTapTimer){
267 |
268 | clearTimeout(self.longTapTimer);
269 |
270 | }
271 |
272 | }
273 |
274 | function touchend(e) {
275 |
276 | if (e.originalEvent && typeof e.originalEvent.touches !== 'undefined'){
277 |
278 | if (e.originalEvent.targetTouches.length > 0){
279 |
280 | return false;
281 |
282 | }
283 |
284 | }else{
285 |
286 | $(document).unbind('mousemove', touchmove);
287 | $(document).unbind('mouseup', touchend);
288 |
289 | }
290 |
291 | self.isCurrentlyTouching = false;
292 |
293 | //clear the long tap timer on mouseup
294 | if (self.longTapTimer){
295 |
296 | clearTimeout(self.longTapTimer);
297 |
298 | }
299 |
300 | log('Touchable Touchend self ' + self.currentStartDelta.x);
301 | $(self.elem).trigger('touchableend', self);
302 | log('Touchable: touchableend');
303 | log('Touchable: Hittarget click');
304 |
305 | }
306 |
307 | this.logging=false; //set to false to disabele logging gets overwritten by conf see below
308 | this.elem=elem;
309 | this.$elem=$(elem);
310 | this.is_doubleTap=false;
311 | this.is_currentlyTouching=false;
312 | this.isOneFingerGesture = false;
313 | this.startTouch={x:0,y:0};
314 | this.currentTouch={x:0,y:0};
315 | this.previousTouch={x:0,y:0};
316 | this.currentDelta={x:0,y:0};//measured from previous move event
317 | this.currentStartDelta={x:0,y:0}; //measured from start
318 | this.currentPosition={x:0,y:0};
319 | this.doubleTapTimer=null;
320 | this.longTapTimer=null;
321 | this.touchStartSupported=false;
322 |
323 | var self=this;
324 |
325 | if (typeof conf!=='undefined'){
326 |
327 | if(typeof conf.logging!=='undefined'){
328 |
329 | this.logging=conf.logging;
330 |
331 | }
332 |
333 | if(typeof conf.preventClick!=='undefined'){
334 |
335 | this.preventClick=conf.preventClick;
336 |
337 | }
338 |
339 | }
340 |
341 | this.$elem.bind('touchstart', function(e){
342 | self.touchStartSupported = true;
343 | touchstart(e);
344 | });
345 | this.$elem.bind('mousedown', function(e){
346 | if(!self.touchStartSupported){
347 | touchstart(e);
348 | }
349 | return false;
350 | });
351 | this.$elem.bind('touchmove', touchmove);
352 | this.$elem.bind('touchend', touchend);
353 |
354 | if (this.preventClick) {
355 | this.$elem.bind('click', function (e) {
356 | e.preventDefault();
357 | });
358 | }
359 |
360 | }
361 |
362 | })(jQuery);//end closure
363 |
--------------------------------------------------------------------------------
/utils/server.js:
--------------------------------------------------------------------------------
1 | var connect = require('connect')
2 | , express = require('express')
3 | , sys = require('sys');
4 | require('assert');
5 |
6 | //Create Express frontend
7 | var server = global.server = express.createServer();
8 | server.use(connect.staticProvider(__dirname+'/..'));
9 | server.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
10 |
11 |
12 | var port=3000;
13 | server.listen(port);
14 | console.log('Server Listening on http://0.0.0.0:'+port);
15 |
--------------------------------------------------------------------------------
/utils/update-gh-pages.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | var util = require('util');
3 | var Step = require('step');
4 | var exec = require('child_process').exec;
5 |
6 | //var md = require("node-markdown").Markdown;
7 | //var path=require('path'), fs=require('fs');
8 | var nmd = require('nmd')
9 | var errors=[];
10 | var verbose=true;
11 | var callme = function callme(command, err, stdout, stderr){
12 | if(err && err.length==0) return;//reset the empty array passed in
13 | util.print('\n'+ stylize(!err) +'output of command: '+ command + '\n');
14 | (stdout && verbose && err) && util.print('\nstdout:\n' + stdout);
15 | (stderr && verbose && err) && util.print('\nstderr:\n' + stderr);
16 | }
17 | function stylize(ok){
18 | if (ok)
19 | return '\033[32m' + 'OK ' +
20 | '\033[39m';
21 | else
22 | return '\033[35m' + 'FAIL ' +
23 | '\033[39m';
24 | }
25 |
26 |
27 | var commands=[];
28 | var oldCommand="";
29 | var add = function(command){
30 | function stepCommand(command, oldCommand){
31 | return function(err, stdout, stderr){
32 | callme(oldCommand, err, stdout, stderr)
33 | if(command) exec(command, this)
34 | }
35 | }
36 | commands.push(stepCommand(command, oldCommand))
37 | oldCommand = command;
38 | };
39 |
40 | process.chdir('.');
41 | console.log('Working directory: ' + process.cwd());
42 | add('git checkout gh-pages');
43 | add('nmd Readme.md -o index.html');
44 | add('nmd History.md');
45 | add('ln -s hoverable.js demo/hoverable.js');
46 | add('ln -s touchable.js demo/touchable.js');
47 | add('git merge master');
48 | add('git commit');
49 | add('git checkout master');
50 | add('');
51 |
52 | Step.apply(this, commands);
53 |
54 | /*exec('git checkout gh-pages', function(e, so, se){
55 | console.log('')
56 | });*/
--------------------------------------------------------------------------------