source: extensions/iPiwigo/www/jqtouch/jqtouch.js @ 32211

Last change on this file since 32211 was 9188, checked in by Polly, 14 years ago

Adding the Phonegap www folder needed to compile.

  • Property svn:executable set to *
File size: 22.7 KB
Line 
1/*
2
3            _/    _/_/    _/_/_/_/_/                              _/       
4               _/    _/      _/      _/_/    _/    _/    _/_/_/  _/_/_/   
5          _/  _/  _/_/      _/    _/    _/  _/    _/  _/        _/    _/   
6         _/  _/    _/      _/    _/    _/  _/    _/  _/        _/    _/   
7        _/    _/_/  _/    _/      _/_/      _/_/_/    _/_/_/  _/    _/     
8       _/                                                                 
9    _/
10
11    Created by David Kaneda <http://www.davidkaneda.com>
12    Documentation and issue tracking on Google Code <http://code.google.com/p/jqtouch/>
13   
14    Special thanks to Jonathan Stark <http://jonathanstark.com/>
15    and pinch/zoom <http://www.pinchzoom.com/>
16   
17    (c) 2009 by jQTouch project members.
18    See LICENSE.txt for license.
19   
20    $Revision: 109 $
21    $Date: 2009-10-06 12:23:30 -0400 (Tue, 06 Oct 2009) $
22    $LastChangedBy: davidcolbykaneda $
23
24*/
25
26(function($) {
27    $.jQTouch = function(options) {
28       
29        // Set support values
30        $.support.WebKitCSSMatrix = (typeof WebKitCSSMatrix == "object");
31        $.support.touch = (typeof Touch == "object");
32        $.support.WebKitAnimationEvent = (typeof WebKitTransitionEvent == "object");
33       
34        // Initialize internal variables
35        var $body, 
36            $head=$('head'), 
37            hist=[], 
38            newPageCount=0, 
39            jQTSettings={}, 
40            hashCheck, 
41            currentPage, 
42            orientation, 
43            isMobileWebKit = RegExp(" Mobile/").test(navigator.userAgent), 
44            tapReady=true,
45            lastAnimationTime=0,
46            touchSelectors=[],
47            publicObj={},
48            extensions=$.jQTouch.prototype.extensions,
49            defaultAnimations=['slide','flip','slideup','swap','cube','pop','dissolve','fade','back'], 
50            animations=[], 
51            hairextensions='';
52
53        // Get the party started
54        init(options);
55
56        function init(options) {
57           
58            var defaults = {
59                addGlossToIcon: true,
60                backSelector: '.back, .cancel, .goback',
61                cacheGetRequests: true,
62                cubeSelector: '.cube',
63                dissolveSelector: '.dissolve',
64                fadeSelector: '.fade',
65                fixedViewport: true,
66                flipSelector: '.flip',
67                formSelector: 'form',
68                fullScreen: true,
69                fullScreenClass: 'fullscreen',
70                icon: null,
71                touchSelector: 'a, .touch',
72                popSelector: '.pop',
73                preloadImages: false,
74                slideSelector: 'body > * > ul li a',
75                slideupSelector: '.slideup',
76                startupScreen: null,
77                statusBar: 'default', // other options: black-translucent, black
78                submitSelector: '.submit',
79                swapSelector: '.swap',
80                useAnimations: true,
81                useFastTouch: true // Experimental.
82            };
83            jQTSettings = $.extend({}, defaults, options);
84           
85            // Preload images
86            if (jQTSettings.preloadImages) {
87                for (var i = jQTSettings.preloadImages.length - 1; i >= 0; i--){
88                    (new Image()).src = jQTSettings.preloadImages[i];
89                };
90            }
91            // Set icon
92            if (jQTSettings.icon) {
93                var precomposed = (jQTSettings.addGlossToIcon) ? '' : '-precomposed';
94                hairextensions += '<link rel="apple-touch-icon' + precomposed + '" href="' + jQTSettings.icon + '" />';
95            }
96            // Set startup screen
97            if (jQTSettings.startupScreen) {
98                hairextensions += '<link rel="apple-touch-startup-image" href="' + jQTSettings.startupScreen + '" />';
99            }
100            // Set viewport
101            if (jQTSettings.fixedViewport) {
102                hairextensions += '<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0;"/>';
103            }
104            // Set full-screen
105            if (jQTSettings.fullScreen) {
106                hairextensions += '<meta name="apple-mobile-web-app-capable" content="yes" />';
107                if (jQTSettings.statusBar) {
108                    hairextensions += '<meta name="apple-mobile-web-app-status-bar-style" content="' + jQTSettings.statusBar + '" />';
109                }
110            }
111            if (hairextensions) $head.append(hairextensions);
112
113            // Initialize on document load:
114            $(document).ready(function(){
115
116                // Add extensions
117                for (var i in extensions)
118                {
119                    var fn = extensions[i];
120                    if ($.isFunction(fn))
121                    {
122                        $.extend(publicObj, fn(publicObj));
123                    }
124                }
125               
126                // Add animations
127                for (var i in defaultAnimations)
128                {
129                    var name = defaultAnimations[i];
130                    var selector = jQTSettings[name + 'Selector'];
131                    if (typeof(selector) == 'string') {
132                        addAnimation({name:name, selector:selector});
133                    }
134                }
135
136                touchSelectors.push('input');
137                touchSelectors.push(jQTSettings.touchSelector);
138                touchSelectors.push(jQTSettings.backSelector);
139                touchSelectors.push(jQTSettings.submitSelector);
140                $(touchSelectors.join(', ')).css('-webkit-touch-callout', 'none');
141                $(jQTSettings.backSelector).tap(liveTap);
142                $(jQTSettings.submitSelector).tap(submitParentForm);
143
144                $body = $('body');
145               
146                if (jQTSettings.fullScreenClass && window.navigator.standalone == true) {
147                    $body.addClass(jQTSettings.fullScreenClass + ' ' + jQTSettings.statusBar);
148                }
149
150                // Create custom live events
151                $body
152                    .bind('touchstart', handleTouch)
153                    .bind('orientationchange', updateOrientation)
154                    .trigger('orientationchange')
155                    .submit(submitForm);
156                   
157                if (jQTSettings.useFastTouch && $.support.touch)
158                {
159                    $body.click(function(e){
160                        var $el = $(e.target);
161                        if ($el.attr('target') == '_blank' || $el.attr('rel') == 'external' || $el.is('input[type="checkbox"]'))
162                        {
163                            return true;
164                        } else {
165                            return false;
166                        }
167                    });
168                   
169                    // This additionally gets rid of form focusses
170                    $body.mousedown(function(e){
171                        var timeDiff = (new Date()).getTime() - lastAnimationTime;
172                        if (timeDiff < 200)
173                        {
174                            return false;
175                        }
176                    });
177                }
178
179                // Make sure exactly one child of body has "current" class
180                if ($('body > .current').length == 0) {
181                    currentPage = $('body > *:first');
182                } else {
183                    currentPage = $('body > .current:first');
184                    $('body > .current').removeClass('current');
185                }
186               
187                // Go to the top of the "current" page
188                $(currentPage).addClass('current');
189                location.hash = $(currentPage).attr('id');
190                addPageToHistory(currentPage);
191                scrollTo(0, 0);
192                dumbLoopStart();
193            });
194        }
195       
196        // PUBLIC FUNCTIONS
197        function goBack(to) {
198            // Init the param
199            if (hist.length > 1) {
200                var numberOfPages = Math.min(parseInt(to || 1, 10), hist.length-1);
201
202                // Search through the history for an ID
203                if( isNaN(numberOfPages) && typeof(to) === "string" && to != '#' ) {
204                    for( var i=1, length=hist.length; i < length; i++ ) {
205                        if( '#' + hist[i].id === to ) {
206                            numberOfPages = i;
207                            break;
208                        }
209                    }
210                }
211
212                // If still nothing, assume one
213                if( isNaN(numberOfPages) || numberOfPages < 1 ) {
214                    numberOfPages = 1;
215                };
216
217                // Grab the current page for the "from" info
218                var animation = hist[0].animation;
219                var fromPage = hist[0].page;
220
221                // Remove all pages in front of the target page
222                hist.splice(0, numberOfPages);
223
224                // Grab the target page
225                var toPage = hist[0].page;
226
227                // Make the animations
228                animatePages(fromPage, toPage, animation, true);
229               
230                return publicObj;
231            } else {
232                console.error('No pages in history.');
233                return false;
234            }
235        }
236        function goTo(toPage, animation) {
237            var fromPage = hist[0].page;
238           
239            if (typeof(toPage) === 'string') {
240                toPage = $(toPage);
241            }
242            if (typeof(animation) === 'string') {
243                for (var i = animations.length - 1; i >= 0; i--){
244                    if (animations[i].name === animation)
245                    {
246                        animation = animations[i];
247                        break;
248                    }
249                }
250            }
251            if (animatePages(fromPage, toPage, animation)) {
252                addPageToHistory(toPage, animation);
253                return publicObj;
254            }
255            else
256            {
257                console.error('Could not animate pages.');
258                return false;
259            }
260        }
261        function getOrientation() {
262            return orientation;
263        }
264
265        // PRIVATE FUNCTIONS
266        function liveTap(e){
267           
268            // Grab the clicked element
269            var $el = $(e.target);
270
271            if ($el.attr('nodeName')!=='A'){
272                $el = $el.parent('a');
273            }
274           
275            var target = $el.attr('target'), 
276            hash = $el.attr('hash'), 
277            animation=null;
278           
279            if (tapReady == false || !$el.length) {
280                console.warn('Not able to tap element.')
281                return false;
282            }
283           
284            if ($el.attr('target') == '_blank' || $el.attr('rel') == 'external')
285            {
286                return true;
287            }
288           
289            // Figure out the animation to use
290            for (var i = animations.length - 1; i >= 0; i--){
291                if ($el.is(animations[i].selector)) {
292                    animation = animations[i];
293                    break;
294                }
295            };
296
297            // User clicked an internal link, fullscreen mode
298            if (target == '_webapp') {
299                window.location = $el.attr('href');
300            }
301            // User clicked a back button
302            else if ($el.is(jQTSettings.backSelector)) {
303                goBack(hash);
304            }
305            // Branch on internal or external href
306            else if (hash && hash!='#') {
307                $el.addClass('active');
308                goTo($(hash).data('referrer', $el), animation);
309            } else {
310                $el.addClass('loading active');
311                showPageByHref($el.attr('href'), {
312                    animation: animation,
313                    callback: function(){ 
314                        $el.removeClass('loading'); setTimeout($.fn.unselect, 250, $el);
315                    },
316                    $referrer: $el
317                });
318            }
319            return false;
320        }
321        function addPageToHistory(page, animation) {
322            // Grab some info
323            var pageId = page.attr('id');
324
325            // Prepend info to page history
326            hist.unshift({
327                page: page, 
328                animation: animation, 
329                id: pageId
330            });
331        }
332        function animatePages(fromPage, toPage, animation, backwards) {
333            // Error check for target page
334            if(toPage.length === 0){
335                $.fn.unselect();
336                console.error('Target element is missing.');
337                return false;
338            }
339           
340            // Collapse the keyboard
341            $(':focus').blur();
342
343            // Make sure we are scrolled up to hide location bar
344            scrollTo(0, 0);
345           
346            // Define callback to run after animation completes
347            var callback = function(event){
348
349                if (animation)
350                {
351                    toPage.removeClass('in reverse ' + animation.name);
352                    fromPage.removeClass('current out reverse ' + animation.name);
353                }
354                else
355                {
356                    fromPage.removeClass('current');
357                }
358
359                toPage.trigger('pageAnimationEnd', { direction: 'in' });
360                fromPage.trigger('pageAnimationEnd', { direction: 'out' });
361               
362                clearInterval(dumbLoop);
363                currentPage = toPage;
364                location.hash = currentPage.attr('id');
365                dumbLoopStart();
366
367                var $originallink = toPage.data('referrer');
368                if ($originallink) {
369                    $originallink.unselect();
370                }
371                lastAnimationTime = (new Date()).getTime();
372                tapReady = true;
373            }
374
375            fromPage.trigger('pageAnimationStart', { direction: 'out' });
376            toPage.trigger('pageAnimationStart', { direction: 'in' });
377
378            if ($.support.WebKitAnimationEvent && animation && jQTSettings.useAnimations) {
379                toPage.one('webkitAnimationEnd', callback);
380                tapReady = false;
381                toPage.addClass(animation.name + ' in current ' + (backwards ? ' reverse' : ''));
382                fromPage.addClass(animation.name + ' out' + (backwards ? ' reverse' : ''));
383            } else {
384                toPage.addClass('current');
385                callback();
386            }
387
388            return true;
389        }
390        function dumbLoopStart() {
391            dumbLoop = setInterval(function(){
392                var curid = currentPage.attr('id');
393                if (location.hash == '') {
394                    location.hash = '#' + curid;
395                } else if (location.hash != '#' + curid) {
396                    try {
397                        goBack(location.hash)
398                    } catch(e) {
399                        console.error('Unknown hash change.');
400                    }
401                }
402            }, 100);
403        }
404        function insertPages(nodes, animation) {
405            var targetPage = null;
406            $(nodes).each(function(index, node){
407                var $node = $(this);
408                if (!$node.attr('id')) {
409                    $node.attr('id', 'page-' + (++newPageCount));
410                }
411                $node.appendTo($body);
412                if ($node.hasClass('current') || !targetPage ) {
413                    targetPage = $node;
414                }
415            });
416            if (targetPage !== null) {
417                goTo(targetPage, animation);
418                return targetPage;
419            }
420            else
421            {
422                return false;
423            }
424        }
425        function showPageByHref(href, options) {
426            var defaults = {
427                data: null,
428                method: 'GET',
429                animation: null,
430                callback: null,
431                $referrer: null
432            };
433           
434            var settings = $.extend({}, defaults, options);
435
436            if (href != '#')
437            {
438                $.ajax({
439                    url: href,
440                    data: settings.data,
441                    type: settings.method,
442                    success: function (data, textStatus) {
443                        var firstPage = insertPages(data, settings.animation);
444                        if (firstPage)
445                        {
446                            if (settings.method == 'GET' && jQTSettings.cacheGetRequests && settings.$referrer)
447                            {
448                                settings.$referrer.attr('href', '#' + firstPage.attr('id'));
449                            }
450                            if (settings.callback) {
451                                settings.callback(true);
452                            }
453                        }
454                    },
455                    error: function (data) {
456                        if (settings.$referrer) settings.$referrer.unselect();
457                        if (settings.callback) {
458                            settings.callback(false);
459                        }
460                    }
461                });
462            }
463            else if ($referrer)
464            {
465                $referrer.unselect();
466            }
467        }
468        function submitForm(e, callback){
469            var $form = (typeof(e)==='string') ? $(e) : $(e.target);
470
471            if ($form.length && $form.is(jQTSettings.formSelector) && $form.attr('action')) {
472                showPageByHref($form.attr('action'), {
473                    data: $form.serialize(),
474                    method: $form.attr('method') || "POST",
475                    animation: animations[0] || null,
476                    callback: callback
477                });
478                return false;
479            }
480            return true;
481        }
482        function submitParentForm(e){
483            var $form = $(this).closest('form');
484            if ($form.length)
485            {
486                evt = jQuery.Event("submit");
487                evt.preventDefault();
488                $form.trigger(evt);
489                return false;
490            }
491            return true;
492        }
493        function addAnimation(animation) {
494            if (typeof(animation.selector) == 'string' && typeof(animation.name) == 'string') {
495                animations.push(animation);
496                $(animation.selector).tap(liveTap);
497                touchSelectors.push(animation.selector);
498            }
499        }
500        function updateOrientation() {
501            orientation = window.innerWidth < window.innerHeight ? 'profile' : 'landscape';
502            $body.removeClass('profile landscape').addClass(orientation).trigger('turn', {orientation: orientation});
503            // scrollTo(0, 0);
504        }
505        function handleTouch(e) {
506
507            var $el = $(e.target);
508
509            // Only handle touchSelectors
510            if (!$(e.target).is(touchSelectors.join(', ')))
511            {
512                var $link = $(e.target).closest('a');
513               
514                if ($link.length){
515                    $el = $link;
516                } else {
517                    return;
518                }
519            }
520            if (event)
521            {
522                var hoverTimeout = null,
523                    startX = event.changedTouches[0].clientX,
524                    startY = event.changedTouches[0].clientY,
525                    startTime = (new Date).getTime(),
526                    deltaX = 0,
527                    deltaY = 0,
528                    deltaT = 0;
529
530                // Let's bind these after the fact, so we can keep some internal values
531                $el.bind('touchmove', touchmove).bind('touchend', touchend);
532
533                hoverTimeout = setTimeout(function(){
534                    $el.makeActive();
535                }, 100);
536               
537            }
538
539            // Private touch functions (TODO: insert dirty joke)
540            function touchmove(e) {
541               
542                updateChanges();
543                var absX = Math.abs(deltaX);
544                var absY = Math.abs(deltaY);
545                               
546                // Check for swipe
547                if (absX > absY && (absX > 35) && deltaT < 1000) {
548                    $el.trigger('swipe', {direction: (deltaX < 0) ? 'left' : 'right'}).unbind('touchmove touchend');
549                } else if (absY > 1) {
550                    $el.removeClass('active');
551                }
552
553                clearTimeout(hoverTimeout);
554            } 
555           
556            function touchend(){
557                updateChanges();
558           
559                if (deltaY === 0 && deltaX === 0) {
560                    $el.makeActive();
561                    // New approach:
562                    // Fake the double click?
563                    // TODO: Try with all click events (no tap)
564                    // if (deltaT < 40)
565                    // {
566                    //     setTimeout(function(){
567                    //        $el.trigger('touchstart')
568                    //          .trigger('touchend');
569                    //     }, 0);
570                    // }
571                    $el.trigger('tap');
572                } else {
573                    $el.removeClass('active');
574                }
575                $el.unbind('touchmove touchend');
576                clearTimeout(hoverTimeout);
577            }
578           
579            function updateChanges(){
580                var first = event.changedTouches[0] || null;
581                deltaX = first.pageX - startX;
582                deltaY = first.pageY - startY;
583                deltaT = (new Date).getTime() - startTime;
584            }
585
586        } // End touch handler
587
588        // Public jQuery Fns
589        $.fn.unselect = function(obj) {
590            if (obj) {
591                obj.removeClass('active');
592            } else {
593                $('.active').removeClass('active');
594            }
595        }
596        $.fn.makeActive = function(){
597            return $(this).addClass('active');
598        }
599        $.fn.swipe = function(fn) {
600            if ($.isFunction(fn))
601            {
602                return this.each(function(i, el){
603                    $(el).bind('swipe', fn); 
604                });
605            }
606        }
607        $.fn.tap = function(fn){
608            if ($.isFunction(fn))
609            {
610                var tapEvent = (jQTSettings.useFastTouch && $.support.touch) ? 'tap' : 'click';
611                return $(this).live(tapEvent, fn);
612            } else {
613                $(this).trigger('tap');
614            }
615        }
616       
617        publicObj = {
618            getOrientation: getOrientation,
619            goBack: goBack,
620            goTo: goTo,
621            addAnimation: addAnimation,
622            submitForm: submitForm
623        }
624
625        return publicObj;
626    }
627   
628    // Extensions directly manipulate the jQTouch object, before it's initialized.
629    $.jQTouch.prototype.extensions = [];
630    $.jQTouch.addExtension = function(extension){
631        $.jQTouch.prototype.extensions.push(extension);
632    }
633
634})(jQuery);
Note: See TracBrowser for help on using the repository browser.