3bb56138498e7cf1cc3b75dda1da96396216a14e
[platform/framework/web/web-ui-fw.git] / libs / js / jquery-mobile-1.0.1pre / js / jquery.mobile.fixHeaderFooter.js
1 /*
2 * "fixHeaderFooter" plugin - on-demand positioning for headers,footers
3 */
4
5 (function( $, undefined ) {
6
7 var slideDownClass = "ui-header-fixed ui-fixed-inline fade",
8         slideUpClass = "ui-footer-fixed ui-fixed-inline fade",
9
10         slideDownSelector = ".ui-header:jqmData(position='fixed')",
11         slideUpSelector = ".ui-footer:jqmData(position='fixed')";
12
13 $.fn.fixHeaderFooter = function( options ) {
14
15         if ( !$.support.scrollTop || ( $.support.touchOverflow && $.mobile.touchOverflowEnabled ) ) {
16                 return this;
17         }
18
19         return this.each(function() {
20                 var $this = $( this );
21
22                 if ( $this.jqmData( "fullscreen" ) ) {
23                         $this.addClass( "ui-page-fullscreen" );
24                 }
25
26                 // Should be slidedown
27                 $this.find( slideDownSelector ).addClass( slideDownClass );
28
29                 // Should be slideup
30                 $this.find( slideUpSelector ).addClass( slideUpClass );
31         });
32 };
33
34 // single controller for all showing,hiding,toggling
35 $.mobile.fixedToolbars = (function() {
36
37         if ( !$.support.scrollTop || ( $.support.touchOverflow && $.mobile.touchOverflowEnabled ) ) {
38                 return;
39         }
40
41         var stickyFooter, delayTimer,
42                 currentstate = "inline",
43                 autoHideMode = false,
44                 showDelay = 100,
45                 ignoreTargets = "a,input,textarea,select,button,label,.ui-header-fixed,.ui-footer-fixed",
46                 toolbarSelector = ".ui-header-fixed:first, .ui-footer-fixed:not(.ui-footer-duplicate):last",
47                 // for storing quick references to duplicate footers
48                 supportTouch = $.support.touch,
49                 touchStartEvent = supportTouch ? "touchstart" : "mousedown",
50                 touchStopEvent = supportTouch ? "touchend" : "mouseup",
51                 stateBefore = null,
52                 scrollTriggered = false,
53                 touchToggleEnabled = true;
54
55         function showEventCallback( event ) {
56                 // An event that affects the dimensions of the visual viewport has
57                 // been triggered. If the header and/or footer for the current page are in overlay
58                 // mode, we want to hide them, and then fire off a timer to show them at a later
59                 // point. Events like a resize can be triggered continuously during a scroll, on
60                 // some platforms, so the timer is used to delay the actual positioning until the
61                 // flood of events have subsided.
62                 //
63                 // If we are in autoHideMode, we don't do anything because we know the scroll
64                 // callbacks for the plugin will fire off a show when the scrolling has stopped.
65                 if ( !autoHideMode && currentstate === "overlay" ) {
66                         if ( !delayTimer ) {
67                                 $.mobile.fixedToolbars.hide( true );
68                         }
69
70                         $.mobile.fixedToolbars.startShowTimer();
71                 }
72         }
73
74         $(function() {
75                 var $document = $( document ),
76                         $window = $( window );
77
78                 $document
79                         .bind( "vmousedown", function( event ) {
80                                 if ( touchToggleEnabled ) {
81                                         stateBefore = currentstate;
82                                 }
83                         })
84                         .bind( "vclick", function( event ) {
85                                 if ( touchToggleEnabled ) {
86
87                                         if ( $(event.target).closest( ignoreTargets ).length ) {
88                                                 return;
89                                         }
90
91                                         if ( !scrollTriggered ) {
92                                                 $.mobile.fixedToolbars.toggle( stateBefore );
93                                                 stateBefore = null;
94                                         }
95                                 }
96                         })
97                         .bind( "silentscroll", showEventCallback );
98
99
100                 // The below checks first for a $(document).scrollTop() value, and if zero, binds scroll events to $(window) instead.
101                 // If the scrollTop value is actually zero, both will return zero anyway.
102                 //
103                 // Works with $(document), not $(window) : Opera Mobile (WinMO phone; kinda broken anyway)
104                 // Works with $(window), not $(document) : IE 7/8
105                 // Works with either $(window) or $(document) : Chrome, FF 3.6/4, Android 1.6/2.1, iOS
106                 // Needs work either way : BB5, Opera Mobile (iOS)
107
108                 ( ( $document.scrollTop() === 0 ) ? $window : $document )
109                         .bind( "scrollstart", function( event ) {
110
111                                 scrollTriggered = true;
112
113                                 if ( stateBefore === null ) {
114                                         stateBefore = currentstate;
115                                 }
116
117                                 // We only enter autoHideMode if the headers/footers are in
118                                 // an overlay state or the show timer was started. If the
119                                 // show timer is set, clear it so the headers/footers don't
120                                 // show up until after we're done scrolling.
121                                 var isOverlayState = stateBefore == "overlay";
122
123                                 autoHideMode = isOverlayState || !!delayTimer;
124
125                                 if ( autoHideMode ) {
126                                         $.mobile.fixedToolbars.clearShowTimer();
127
128                                         if ( isOverlayState ) {
129                                                 $.mobile.fixedToolbars.hide( true );
130                                         }
131                                 }
132                         })
133                         .bind( "scrollstop", function( event ) {
134
135                                 if ( $( event.target ).closest( ignoreTargets ).length ) {
136                                         return;
137                                 }
138
139                                 scrollTriggered = false;
140
141                                 if ( autoHideMode ) {
142                                         $.mobile.fixedToolbars.startShowTimer();
143                                         autoHideMode = false;
144                                 }
145                                 stateBefore = null;
146                         });
147
148                         $window.bind( "resize updatelayout", showEventCallback );
149         });
150
151         // 1. Before page is shown, check for duplicate footer
152         // 2. After page is shown, append footer to new page
153         $( ".ui-page" )
154                 .live( "pagebeforeshow", function( event, ui ) {
155
156                         var page = $( event.target ),
157                                 footer = page.find( ":jqmData(role='footer')" ),
158                                 id = footer.data( "id" ),
159                                 prevPage = ui.prevPage,
160                                 prevFooter = prevPage && prevPage.find( ":jqmData(role='footer')" ),
161                                 prevFooterMatches = prevFooter.length && prevFooter.jqmData( "id" ) === id;
162
163                         if ( id && prevFooterMatches ) {
164                                 stickyFooter = footer;
165                                 setTop( stickyFooter.removeClass( "fade in out" ).appendTo( $.mobile.pageContainer ) );
166                         }
167                 })
168                 .live( "pageshow", function( event, ui ) {
169
170                         var $this = $( this );
171
172                         if ( stickyFooter && stickyFooter.length ) {
173
174                                 setTimeout(function() {
175                                         setTop( stickyFooter.appendTo( $this ).addClass( "fade" ) );
176                                         stickyFooter = null;
177                                 }, 500);
178                         }
179
180                         $.mobile.fixedToolbars.show( true, this );
181                 });
182
183         // When a collapsiable is hidden or shown we need to trigger the fixed toolbar to reposition itself (#1635)
184         $( ".ui-collapsible-contain" ).live( "collapse expand", showEventCallback );
185
186         // element.getBoundingClientRect() is broken in iOS 3.2.1 on the iPad. The
187         // coordinates inside of the rect it returns don't have the page scroll position
188         // factored out of it like the other platforms do. To get around this,
189         // we'll just calculate the top offset the old fashioned way until core has
190         // a chance to figure out how to handle this situation.
191         //
192         // TODO: We'll need to get rid of getOffsetTop() once a fix gets folded into core.
193
194         function getOffsetTop( ele ) {
195                 var top = 0,
196                         op, body;
197
198                 if ( ele ) {
199                         body = document.body;
200                         op = ele.offsetParent;
201                         top = ele.offsetTop;
202
203                         while ( ele && ele != body ) {
204                                 top += ele.scrollTop || 0;
205
206                                 if ( ele == op ) {
207                                         top += op.offsetTop;
208                                         op = ele.offsetParent;
209                                 }
210
211                                 ele = ele.parentNode;
212                         }
213                 }
214                 return top;
215         }
216
217         function setTop( el ) {
218                 var fromTop = $(window).scrollTop(),
219                         thisTop = getOffsetTop( el[ 0 ] ), // el.offset().top returns the wrong value on iPad iOS 3.2.1, call our workaround instead.
220                         thisCSStop = el.css( "top" ) == "auto" ? 0 : parseFloat(el.css( "top" )),
221                         screenHeight = window.innerHeight,
222                         thisHeight = el.outerHeight(),
223                         useRelative = el.parents( ".ui-page:not(.ui-page-fullscreen)" ).length,
224                         relval;
225
226                 if ( el.is( ".ui-header-fixed" ) ) {
227
228                         relval = fromTop - thisTop + thisCSStop;
229
230                         if ( relval < thisTop ) {
231                                 relval = 0;
232                         }
233
234                         return el.css( "top", useRelative ? relval : fromTop );
235                 } else {
236                         // relval = -1 * (thisTop - (fromTop + screenHeight) + thisCSStop + thisHeight);
237                         // if ( relval > thisTop ) { relval = 0; }
238                         relval = fromTop + screenHeight - thisHeight - (thisTop - thisCSStop );
239
240                         return el.css( "top", useRelative ? relval : fromTop + screenHeight - thisHeight );
241                 }
242         }
243
244         // Exposed methods
245         return {
246
247                 show: function( immediately, page ) {
248
249                         $.mobile.fixedToolbars.clearShowTimer();
250
251                         currentstate = "overlay";
252
253                         var $ap = page ? $( page ) :
254                                         ( $.mobile.activePage ? $.mobile.activePage :
255                                                 $( ".ui-page-active" ) );
256
257                         return $ap.children( toolbarSelector ).each(function() {
258
259                                 var el = $( this ),
260                                         fromTop = $( window ).scrollTop(),
261                                         // el.offset().top returns the wrong value on iPad iOS 3.2.1, call our workaround instead.
262                                         thisTop = getOffsetTop( el[ 0 ] ),
263                                         screenHeight = window.innerHeight,
264                                         thisHeight = el.outerHeight(),
265                                         alreadyVisible = ( el.is( ".ui-header-fixed" ) && fromTop <= thisTop + thisHeight ) ||
266                                                                                                                 ( el.is( ".ui-footer-fixed" ) && thisTop <= fromTop + screenHeight );
267
268                                 // Add state class
269                                 el.addClass( "ui-fixed-overlay" ).removeClass( "ui-fixed-inline" );
270
271                                 if ( !alreadyVisible && !immediately ) {
272                                         el.animationComplete(function() {
273                                                 el.removeClass( "in" );
274                                         }).addClass( "in" );
275                                 }
276                                 setTop(el);
277                         });
278                 },
279
280                 hide: function( immediately ) {
281
282                         currentstate = "inline";
283
284                         var $ap = $.mobile.activePage ? $.mobile.activePage :
285                                                                         $( ".ui-page-active" );
286
287                         return $ap.children( toolbarSelector ).each(function() {
288
289                                 var el = $(this),
290                                         thisCSStop = el.css( "top" ),
291                                         classes;
292
293                                 thisCSStop = thisCSStop == "auto" ? 0 :
294                                                                                         parseFloat(thisCSStop);
295
296                                 // Add state class
297                                 el.addClass( "ui-fixed-inline" ).removeClass( "ui-fixed-overlay" );
298
299                                 if ( thisCSStop < 0 || ( el.is( ".ui-header-fixed" ) && thisCSStop !== 0 ) ) {
300
301                                         if ( immediately ) {
302                                                 el.css( "top", 0);
303                                         } else {
304
305                                                 if ( el.css( "top" ) !== "auto" && parseFloat( el.css( "top" ) ) !== 0 ) {
306
307                                                         classes = "out reverse";
308
309                                                         el.animationComplete(function() {
310                                                                 el.removeClass( classes ).css( "top", 0 );
311                                                         }).addClass( classes );
312                                                 }
313                                         }
314                                 }
315                         });
316                 },
317
318                 startShowTimer: function() {
319
320                         $.mobile.fixedToolbars.clearShowTimer();
321
322                         var args = [].slice.call( arguments );
323
324                         delayTimer = setTimeout(function() {
325                                 delayTimer = undefined;
326                                 $.mobile.fixedToolbars.show.apply( null, args );
327                         }, showDelay);
328                 },
329
330                 clearShowTimer: function() {
331                         if ( delayTimer ) {
332                                 clearTimeout( delayTimer );
333                         }
334                         delayTimer = undefined;
335                 },
336
337                 toggle: function( from ) {
338                         if ( from ) {
339                                 currentstate = from;
340                         }
341                         return ( currentstate === "overlay" ) ? $.mobile.fixedToolbars.hide() :
342                                                                 $.mobile.fixedToolbars.show();
343                 },
344
345                 setTouchToggleEnabled: function( enabled ) {
346                         touchToggleEnabled = enabled;
347                 }
348         };
349 })();
350
351 //auto self-init widgets
352 $( document ).bind( "pagecreate create", function( event ) {
353
354         if ( $( ":jqmData(position='fixed')", event.target ).length ) {
355
356                 $( event.target ).each(function() {
357
358                         if ( !$.support.scrollTop || ( $.support.touchOverflow && $.mobile.touchOverflowEnabled ) ) {
359                                 return this;
360                         }
361
362                         var $this = $( this );
363
364                         if ( $this.jqmData( "fullscreen" ) ) {
365                                 $this.addClass( "ui-page-fullscreen" );
366                         }
367
368                         // Should be slidedown
369                         $this.find( slideDownSelector ).addClass( slideDownClass );
370
371                         // Should be slideup
372                         $this.find( slideUpSelector ).addClass( slideUpClass );
373
374                 })
375
376         }
377 });
378
379 })( jQuery );