PageRenderTime 51ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/js/jquery.mobile.fixHeaderFooter.js

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