PageRenderTime 25ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/js/jquery.mobile.fixedToolbar.js

http://github.com/jquery/jquery-mobile
JavaScript | 274 lines | 206 code | 38 blank | 30 comment | 58 complexity | f0b4682307cd0e9e96479a724bf8ddd6 MD5 | raw file
  1. //>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude);
  2. //>>description: Behavior for "fixed" headers and footers
  3. //>>label: Fixedtoolbar
  4. define( [ "jquery", "./jquery.mobile.widget", "./jquery.mobile.core", "./jquery.mobile.navigation", "./jquery.mobile.page", "./jquery.mobile.page.sections", "./jquery.mobile.zoom" ], function( $ ) {
  5. //>>excludeEnd("jqmBuildExclude");
  6. (function( $, undefined ) {
  7. $.widget( "mobile.fixedtoolbar", $.mobile.widget, {
  8. options: {
  9. visibleOnPageShow: true,
  10. disablePageZoom: true,
  11. transition: "slide", //can be none, fade, slide (slide maps to slideup or slidedown)
  12. fullscreen: false,
  13. tapToggle: true,
  14. tapToggleBlacklist: "a, input, select, textarea, .ui-header-fixed, .ui-footer-fixed",
  15. hideDuringFocus: "input, textarea, select",
  16. updatePagePadding: true,
  17. trackPersistentToolbars: true,
  18. // Browser detection! Weeee, here we go...
  19. // Unfortunately, position:fixed is costly, not to mention probably impossible, to feature-detect accurately.
  20. // Some tests exist, but they currently return false results in critical devices and browsers, which could lead to a broken experience.
  21. // Testing fixed positioning is also pretty obtrusive to page load, requiring injected elements and scrolling the window
  22. // The following function serves to rule out some popular browsers with known fixed-positioning issues
  23. // This is a plugin option like any other, so feel free to improve or overwrite it
  24. supportBlacklist: function(){
  25. var ua = navigator.userAgent,
  26. platform = navigator.platform,
  27. // Rendering engine is Webkit, and capture major version
  28. wkmatch = ua.match( /AppleWebKit\/([0-9]+)/ ),
  29. wkversion = !!wkmatch && wkmatch[ 1 ],
  30. ffmatch = ua.match( /Fennec\/([0-9]+)/ ),
  31. ffversion = !!ffmatch && ffmatch[ 1 ],
  32. operammobilematch = ua.match( /Opera Mobile\/([0-9]+)/ ),
  33. omversion = !!operammobilematch && operammobilematch[ 1 ],
  34. w = window;
  35. if(
  36. // iOS 4.3 and older : Platform is iPhone/Pad/Touch and Webkit version is less than 534 (ios5)
  37. ( ( platform.indexOf( "iPhone" ) > -1 || platform.indexOf( "iPad" ) > -1 || platform.indexOf( "iPod" ) > -1 ) && wkversion && wkversion < 534 )
  38. ||
  39. // Opera Mini
  40. ( w.operamini && ({}).toString.call( w.operamini ) === "[object OperaMini]" )
  41. ||
  42. ( operammobilematch && omverson < 7458 )
  43. ||
  44. //Android lte 2.1: Platform is Android and Webkit version is less than 533 (Android 2.2)
  45. ( ua.indexOf( "Android" ) > -1 && wkversion && wkversion < 533 )
  46. ||
  47. // Firefox Mobile before 6.0 -
  48. ( ffversion && ffversion < 6 )
  49. ||
  50. // WebOS less than 3
  51. ( "palmGetResource" in window && wkversion && wkversion < 534 )
  52. ){
  53. return true;
  54. }
  55. return false;
  56. },
  57. initSelector: ":jqmData(position='fixed')"
  58. },
  59. _create: function() {
  60. var self = this,
  61. o = self.options,
  62. $el = self.element,
  63. tbtype = $el.is( ".ui-header" ) ? "header" : "footer",
  64. $page = $el.closest(".ui-page");
  65. // Feature detecting support for
  66. if( o.supportBlacklist() ){
  67. self.destroy();
  68. return;
  69. }
  70. $el.addClass( "ui-"+ tbtype +"-fixed" );
  71. // "fullscreen" overlay positioning
  72. if( $el.jqmData( "fullscreen" ) ){
  73. $el.addClass( "ui-"+ tbtype +"-fullscreen" );
  74. $page.addClass( "ui-page-" + tbtype + "-fullscreen" );
  75. }
  76. // If not fullscreen, add class to page to set top or bottom padding
  77. else{
  78. $page.addClass( "ui-page-" + tbtype + "-fixed" );
  79. }
  80. self._addTransitionClass();
  81. self._bindPageEvents();
  82. self._bindToggleHandlers();
  83. },
  84. _addTransitionClass: function(){
  85. var tclass = this.options.transition;
  86. if( tclass && tclass !== "none" ){
  87. // use appropriate slide for header or footer
  88. if( tclass === "slide" ){
  89. tclass = this.element.is( ".ui-header" ) ? "slidedown" : "slideup";
  90. }
  91. this.element.addClass( tclass );
  92. }
  93. },
  94. _bindPageEvents: function(){
  95. var self = this,
  96. o = self.options,
  97. $el = self.element;
  98. //page event bindings
  99. // Fixed toolbars require page zoom to be disabled, otherwise usability issues crop up
  100. // This method is meant to disable zoom while a fixed-positioned toolbar page is visible
  101. $el.closest( ".ui-page" )
  102. .bind( "pagebeforeshow", function(){
  103. if( o.disablePageZoom ){
  104. $.mobile.zoom.disable( true );
  105. }
  106. if( o.visibleOnPageShow ){
  107. self.show( true );
  108. }
  109. } )
  110. .bind( "webkitAnimationStart animationstart updatelayout", function(){
  111. if( o.updatePagePadding ){
  112. self.updatePagePadding();
  113. }
  114. })
  115. .bind( "pageshow", function(){
  116. self.updatePagePadding();
  117. if( o.updatePagePadding ){
  118. $( window ).bind( "throttledresize." + self.widgetName, function(){
  119. self.updatePagePadding();
  120. });
  121. }
  122. })
  123. .bind( "pagebeforehide", function( e, ui ){
  124. if( o.disablePageZoom ){
  125. $.mobile.zoom.enable( true );
  126. }
  127. if( o.updatePagePadding ){
  128. $( window ).unbind( "throttledresize." + self.widgetName );
  129. }
  130. if( o.trackPersistentToolbars ){
  131. var thisFooter = $( ".ui-footer-fixed:jqmData(id)", this ),
  132. thisHeader = $( ".ui-header-fixed:jqmData(id)", this ),
  133. nextFooter = thisFooter.length && ui.nextPage && $( ".ui-footer-fixed:jqmData(id='" + thisFooter.jqmData( "id" ) + "')", ui.nextPage ),
  134. nextHeader = thisHeader.length && ui.nextPage && $( ".ui-header-fixed:jqmData(id='" + thisHeader.jqmData( "id" ) + "')", ui.nextPage );
  135. if( nextFooter.length || nextHeader.length ){
  136. nextFooter.add( nextHeader ).appendTo( $.mobile.pageContainer );
  137. ui.nextPage.one( "pageshow", function(){
  138. nextFooter.add( nextHeader ).appendTo( this );
  139. } );
  140. }
  141. }
  142. });
  143. },
  144. _visible: false,
  145. // This will set the content element's top or bottom padding equal to the toolbar's height
  146. updatePagePadding: function() {
  147. var $el = this.element,
  148. header = $el.is( ".ui-header" );
  149. // This behavior only applies to "fixed", not "fullscreen"
  150. if( this.options.fullscreen ){ return; }
  151. $el.closest( ".ui-page" ).css( "padding-" + ( header ? "top" : "bottom" ), $el.height() );
  152. },
  153. show: function( notransition ){
  154. var hideClass = "ui-fixed-hidden",
  155. $el = this.element,
  156. $win = $( window ),
  157. scroll = $win.scrollTop(),
  158. elHeight = $el.height(),
  159. pHeight = $el.closest( ".ui-page" ).height(),
  160. viewportHeight = Math.min( screen.height, $win.height() ),
  161. tbtype = $el.is( ".ui-header" ) ? "header" : "footer";
  162. if( !notransition && ( this.options.transition && this.options.transition !== "none" &&
  163. (
  164. ( tbtype === "header" && !this.options.fullscreen && scroll > elHeight ) ||
  165. ( tbtype === "footer" && !this.options.fullscreen && scroll + viewportHeight < pHeight - elHeight )
  166. ) || this.options.fullscreen ) ){
  167. $el
  168. .removeClass( "out " + hideClass )
  169. .addClass( "in" );
  170. }
  171. else {
  172. $el.removeClass( hideClass );
  173. }
  174. this._visible = true;
  175. },
  176. hide: function( notransition ){
  177. var hideClass = "ui-fixed-hidden",
  178. $el = this.element,
  179. $win = $( window ),
  180. scroll = $win.scrollTop(),
  181. elHeight = $el.height(),
  182. pHeight = $el.closest( ".ui-page" ).height(),
  183. viewportHeight = Math.min( screen.height, $win.height() ),
  184. tbtype = $el.is( ".ui-header" ) ? "header" : "footer",
  185. // if it's a slide transition, our new transitions need the reverse class as well to slide outward
  186. outclass = "out" + ( this.options.transition === "slide" ? " reverse" : "" );
  187. if( !notransition && ( this.options.transition && this.options.transition !== "none" &&
  188. (
  189. ( tbtype === "header" && !this.options.fullscreen && scroll > elHeight ) ||
  190. ( tbtype === "footer" && !this.options.fullscreen && scroll + viewportHeight < pHeight - elHeight )
  191. ) || this.options.fullscreen ) ){
  192. $el
  193. .addClass( outclass )
  194. .removeClass( "in" )
  195. .animationComplete( function(){
  196. $el.addClass( hideClass ).removeClass( outclass );
  197. });
  198. }
  199. else {
  200. $el.addClass( hideClass ).removeClass( outclass );
  201. }
  202. this._visible = false;
  203. },
  204. toggle: function(){
  205. this[ this._visible ? "hide" : "show" ]();
  206. },
  207. _bindToggleHandlers: function(){
  208. var self = this,
  209. o = self.options,
  210. $el = self.element;
  211. // tap toggle
  212. $el.closest( ".ui-page" )
  213. .bind( "vclick", function( e ){
  214. if( o.tapToggle && !$( e.target ).closest( o.tapToggleBlacklist ).length ){
  215. self.toggle();
  216. }
  217. })
  218. .bind( "focusin focusout", function( e ){
  219. if( screen.width < 500 && $( e.target ).is( o.hideDuringFocus ) && !$( e.target ).closest( ".ui-header-fixed, .ui-footer-fixed" ).length ){
  220. self[ ( e.type === "focusin" && self._visible ) ? "hide" : "show" ]();
  221. }
  222. });
  223. },
  224. destroy: function(){
  225. this.element.removeClass( "ui-header-fixed ui-footer-fixed ui-header-fullscreen ui-footer-fullscreen in out fade slidedown slideup ui-fixed-hidden" )
  226. this.element.closest( ".ui-page" ).removeClass( "ui-page-header-fixed ui-page-footer-fixed ui-page-header-fullscreen ui-page-footer-fullscreen" );
  227. }
  228. });
  229. //auto self-init widgets
  230. $( document ).bind( "pagecreate create", function( e ){
  231. $( $.mobile.fixedtoolbar.prototype.options.initSelector, e.target ).fixedtoolbar();
  232. });
  233. })( jQuery );
  234. //>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude);
  235. });
  236. //>>excludeEnd("jqmBuildExclude");