added new script and associated styles for handling "fixed" toolbars using native support for CSS position: fixed, where possible. Non-supporting platforms will fall back to inline positioning, either by gracefully degrading on their own or by opting them out through a blacklist (fixed positioning detection's got issues...).

unit tests and api documentation coming next, but for the most part, the API is the same as before.
This commit is contained in:
scottjehl 2011-12-16 19:17:54 +07:00
parent 69324e31f0
commit 103f409c47
6 changed files with 309 additions and 51 deletions

View file

@ -31,8 +31,7 @@ JSFILES = js/jquery.ui.widget.js \
js/jquery.mobile.buttonMarkup.js \
js/jquery.mobile.controlGroup.js \
js/jquery.mobile.links.js \
js/jquery.mobile.fixHeaderFooter.js \
js/jquery.mobile.fixHeaderFooter.native.js \
js/jquery.mobile.fixedtoolbar.js \
js/jquery.mobile.init.js
# The files to include when compiling the CSS files

View file

@ -71,8 +71,7 @@ div.ui-mobile-viewport { overflow-x: hidden; }
.ui-bar { font-size: 16px; margin: 0; }
.ui-bar h1, .ui-bar h2, .ui-bar h3, .ui-bar h4, .ui-bar h5, .ui-bar h6 { margin: 0; padding: 0; font-size: 16px; display: inline-block; }
.ui-header, .ui-footer { display: block; }
.ui-page .ui-header, .ui-page .ui-footer { position: relative; }
.ui-header, .ui-footer { position: relative; border-left-width: 0; border-right-width: 0; }
.ui-header .ui-btn-left { position: absolute; left: 10px; top: .4em; }
.ui-header .ui-btn-right { position: absolute; right: 10px; top: .4em; }
.ui-header .ui-title, .ui-footer .ui-title { min-height: 1.1em; text-align: center; font-size: 16px; display: block; margin: .6em 90px .8em; padding: 0; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; outline: 0 !important; }
@ -80,44 +79,6 @@ div.ui-mobile-viewport { overflow-x: hidden; }
/*content area*/
.ui-content { border-width: 0; overflow: visible; overflow-x: hidden; padding: 15px; }
.ui-page-fullscreen .ui-content { padding:0; }
/* native fixed headers and footers */
.ui-mobile-touch-overflow.ui-page.ui-native-fixed,
.ui-mobile-touch-overflow.ui-page.ui-native-fullscreen {
overflow: visible;
}
.ui-mobile-touch-overflow.ui-native-fixed .ui-header,
.ui-mobile-touch-overflow.ui-native-fixed .ui-footer {
position: fixed;
left: 0;
right: 0;
top: 0;
z-index: 200;
}
.ui-mobile-touch-overflow.ui-page.ui-native-fixed .ui-footer {
top: auto;
bottom: 0;
}
.ui-mobile-touch-overflow.ui-native-fixed .ui-content {
padding-top: 2.5em;
padding-bottom: 3em;
top: 0;
bottom: 0;
height: auto;
position: absolute;
}
.ui-mobile-touch-overflow.ui-native-fullscreen .ui-content {
padding-top: 0;
padding-bottom: 0;
}
.ui-mobile-touch-overflow.ui-native-fullscreen .ui-header,
.ui-mobile-touch-overflow.ui-native-fullscreen .ui-footer {
opacity: .9;
}
.ui-native-bars-hidden {
display: none;
}
/* icons sizing */
.ui-icon { width: 18px; height: 18px; }

View file

@ -1,8 +1,37 @@
/* fixed page header & footer configuration */
.ui-header, .ui-footer, .ui-page-fullscreen .ui-header, .ui-page-fullscreen .ui-footer { position: absolute; overflow: hidden; width: 100%; border-left-width: 0; border-right-width: 0; }
.ui-header-fixed, .ui-footer-fixed {
.ui-header,
.ui-footer {
overflow: hidden;
width: 100%;
}
.ui-header-fixed,
.ui-footer-fixed {
left: 0;
right: 0;
position: fixed;
z-index: 1000;
-webkit-transform: translateZ(0); /* Force header/footer rendering to go through the same rendering pipeline as native page scrolling. */
}
.ui-footer-duplicate, .ui-page-fullscreen .ui-fixed-inline { display: none; }
.ui-page-fullscreen .ui-header, .ui-page-fullscreen .ui-footer { opacity: .9; }
.ui-header-fixed {
top: 0;
}
.ui-footer-fixed {
bottom: 0;
}
.ui-header-fullscreen,
.ui-footer-fullscreen {
opacity: .9;
}
.ui-page-header-fixed {
padding-top: 2.5em;
}
.ui-page-footer-fixed {
padding-bottom: 3em;
}
.ui-page-fullscreen .ui-content {
padding: 0;
}
.ui-fixed-hidden,
.ui-footer-duplicate {
display: none;
}

View file

@ -13,9 +13,9 @@
</head>
<body>
<div data-role="page" data-fullscreen="true" class="type-interior">
<div data-role="page" class="type-interior">
<div data-role="header" data-position="fixed" data-theme="f">
<div data-role="header" data-position="fixed" data-theme="f" data-fullscreen="true">
<h1>Fullscreen fixed header</h1>
<a href="../../" data-icon="home" data-iconpos="notext" data-direction="reverse" class="ui-btn-right jqm-home">Home</a>
</div>
@ -57,7 +57,7 @@
</div><!-- /content -->
<div data-role="footer" class="footer-docs" data-theme="a" data-position="fixed">
<div data-role="footer" class="footer-docs" data-theme="a" data-position="fixed" data-fullscreen="true">
<h1>Fullscreen Fixed Footer</h1>
</div>

View file

@ -34,8 +34,7 @@ $files = array(
'jquery.mobile.buttonMarkup.js',
'jquery.mobile.controlGroup.js',
'jquery.mobile.links.js',
'jquery.mobile.fixHeaderFooter.js',
'jquery.mobile.fixHeaderFooter.native.js',
'jquery.mobile.fixedToolbar.js',
'jquery.mobile.init.js'
);

View file

@ -0,0 +1,270 @@
/*
* "fixedtoolbar" plugin - behavior for "fixed" headers and footers
*/
(function( $, undefined ) {
$.widget( "mobile.fixedtoolbar", $.mobile.widget, {
options: {
visibleOnPageShow: true,
togglePageZoom: true,
transition: "fade", //can be false, fade, slide (slide maps to vertical slides)
fullscreen: false,
tapToggle: true,
scrollToggle: false,
// Browser detection! Weeee, here we go...
// Unfortunately, position:fixed is costly, not to mention probably impossible, to feature-detect accurately.
// Some tests exist, but they currently return false results in critical devices and browsers,
// which could lead to a broken experience.
// Testing is also pretty obtrusive to page load here, requiring injected elements and scrolling the window
// For that reason, the following function serves to rule out some browsers with known issues
// This is a plugin option like any other, so feel free to improve or overwrite it
supportBlacklist: function(){
var blacklisted = false,
ua = navigator.userAgent,
platform = navigator.platform,
w = window;
// iOS 4.3 and older
if(
// Platform is iPhone/Pad/Touch
( platform.indexOf( "iPhone" ) > -1 || platform.indexOf( "iPad" ) > -1 || platform.indexOf( "iPod" ) > -1 ) &&
// Rendering engine is Webkit
ua.match( /(AppleWebKit)\/([0-9\.]+) / ) &&
// Webkit version is less than 534 (ios5)
RegExp.$1 && ( RegExp.$2 && RegExp.$2.split( "." )[0] < 534 )
){
blacklisted = true;
}
// Opera Mini
if( operamini = w.operamini && ({}).toString.call( w.operamini ) === "[object OperaMini]" ){
blacklisted = true;
}
//Android lte 2.1
/*
if( ... ){
blacklisted = true;
}
*/
return blacklisted;
},
initSelector: ":jqmData(position='fixed')"
},
_create: function() {
var self = this,
o = self.options,
$el = self.element,
tbtype = $el.is( ".ui-header" ) ? "header" : "footer",
$page = $el.closest(".ui-page");
// Feature detecting support for
if( o.supportBlacklist() ){
self.destroy();
return;
}
$el.addClass( "ui-"+ tbtype +"-fixed" );
// "fullscreen" overlay positioning
// NOTE - this used to be only "data-fullscreen" on page element. Support both or deprecate page?
if( $el.jqmData( "fullscreen" ) || $page.jqmData( "fullscreen" ) ){
$el.addClass( "ui-" + tbtype + "-fullscreen" );
}
// If not fullscreen, add class to page to set top or bottom padding
else{
$page.addClass( "ui-page-" + tbtype + "-fixed" );
}
self._addTransitionClass();
self._bindPageEvents();
self._bindToggleHandlers();
},
_addTransitionClass: function(){
var tclass = this.options.transition;
if( tclass ){
// use appropriate slide for header or footer
if( tclass === "slide" ){
tclass = this.element.is( ".ui-header" ) ? "slidedown" : "slideup";
}
this.element.addClass( tclass );
}
},
/* Note: this is all that's needed to make iOS 4.3 and Android 2.1 fix their positioning mistakes after scrolling
it won't fully patch a "fixed effect", but rather just repositions after scrollstop
_fixFixedSupport: function(){
var $el = this.element,
tbtype = $el.is( ".ui-header" ) ? "header" : "footer";
$( window )
.bind( "scrollstop", function(){
// TODO: check if toolbars are not positioned correctly on screen, then proceed
if( tbtype === "header" ){
$el.css( "top", $( window ).scrollTop() );
}
else {
$el.css( "bottom", -$( window ).scrollTop() );
}
})
},
*/
_bindPageEvents: function(){
var self = this,
o = self.options,
$el = self.element;
//page event bindings
$el.closest( ".ui-page" )
.bind( "pagebeforeshow", function(){
if( o.togglePageZoom ){
self.disablePageZoom();
}
if( o.visibleOnPageShow ){
self.show();
}
} )
.bind( "pagehide", function(){
if( o.togglePageZoom ){
self.restorePageZoom();
}
});
},
_visible: false,
show: function(){
var hideClass = "ui-fixed-hidden";
if( this.options.transition ){
this.element
.removeClass( "out " + hideClass )
.addClass( "in" );
}
else {
this.element.removeClass( hideClass );
}
this._visible = true;
},
hide: function(){
var hideClass = "ui-fixed-hidden";
if( this.options.transition ){
this.element
.removeClass( "in" )
.addClass( "out" )
.animationComplete(function(){
$(this).addClass( hideClass );
});
}
else {
this.element.addClass( hideClass );
}
this._visible = false;
},
toggle: function(){
this[ this._visible ? "hide" : "show" ]();
},
_visibleBeforeScroll: null,
_bindToggleHandlers: function(){
var self = this,
o = self.options,
$el = self.element;
// tap toggle
$el.closest( ".ui-page" )
.bind( "vclick", function(){
if( o.tapToggle ){
self.toggle();
}
});
// scroll toggle
$( window )
.bind( "scrollstart", function(){
if( o.scrollToggle && self.visible ){
self.hide();
}
})
.bind( "scrollstop", function(){
if( o.scrollToggle && self._visibleBeforeScroll ){
self.hide();
}
});
},
destroy: function(){
this.element.removeClass( "ui-header-fixed ui-footer-fixed ui-header-fullscreen ui-footer-fullscreen in out fade slidedown slideup ui-fixed-hidden" )
this.element.closest( ".ui-page" ).removeClass( "ui-page-header-fixed ui-page-footer-fixed" );
},
// for caching reference to meta viewport elem
_metaViewport: null,
// on pageshow, does a meta viewport element exist in the head?
_metaViewportPreexists: false,
// used for storing value of meta viewport content at page show, for restoration on hide
_metaViewportContent: "",
// Fixed toolbars require page zoom to be disabled, otherwise usability issues crop up
// This method is meant to disable zoom while a fixed-positioned toolbar page is visible
disablePageZoom: function(){
if( !this.options.togglePageZoom ){
return;
}
var cont = "user-scalable=no";
this._metaViewport = $( "meta[name='viewport']" );
this._metaViewportPreexists = this._metaViewport.length;
var currentContent = this._metaViewport.attr( "content" );
// If scaling's already disabled, or another plugin is handling it on this page already
if( currentContent.indexOf( cont ) > -1 ){
return;
}
else {
this._metaViewportContent = currentContent;
}
if( !this._metaViewportPreexists ){
this._metaViewport = $( "<meta>", { "name": "viewport", "content": cont } ).prependTo( "head" );
}
else{
this._metaViewport.attr( "content", this._metaViewportContent + ", " + cont );
}
},
// restore the meta viewport tag to its original state, or remove it
restorePageZoom: function(){
if( !this.options.togglePageZoom ){
return;
}
if( this._metaViewportPreexists ){
this._metaViewport.attr( "content", this._metaViewportContent );
}
else {
this._metaViewport.remove();
}
}
});
//auto self-init widgets
$( document ).bind( "pagecreate create", function( e ){
$( $.mobile.fixedtoolbar.prototype.options.initSelector, e.target ).fixedtoolbar();
});
})( jQuery );