mirror of
https://github.com/Hopiu/jquery-mobile.git
synced 2026-03-17 06:20:26 +00:00
382 lines
11 KiB
JavaScript
382 lines
11 KiB
JavaScript
/*
|
|
* jQuery Mobile Framework : "fixHeaderFooter" plugin - on-demand positioning for headers,footers
|
|
* Copyright (c) jQuery Project
|
|
* Dual licensed under the MIT or GPL Version 2 licenses.
|
|
* http://jquery.org/license
|
|
*/
|
|
|
|
(function( $, undefined ) {
|
|
|
|
var slideDownClass = "ui-header-fixed ui-fixed-inline fade",
|
|
slideUpClass = "ui-footer-fixed ui-fixed-inline fade",
|
|
|
|
slideDownSelector = ".ui-header:jqmData(position='fixed')",
|
|
slideUpSelector = ".ui-footer:jqmData(position='fixed')";
|
|
|
|
$.fn.fixHeaderFooter = function( options ) {
|
|
|
|
if ( !$.support.scrollTop || ( $.support.touchOverflow && $.mobile.touchOverflowEnabled ) ) {
|
|
return this;
|
|
}
|
|
|
|
return this.each(function() {
|
|
var $this = $( this );
|
|
|
|
if ( $this.jqmData( "fullscreen" ) ) {
|
|
$this.addClass( "ui-page-fullscreen" );
|
|
}
|
|
|
|
// Should be slidedown
|
|
$this.find( slideDownSelector ).addClass( slideDownClass );
|
|
|
|
// Should be slideup
|
|
$this.find( slideUpSelector ).addClass( slideUpClass );
|
|
});
|
|
};
|
|
|
|
// single controller for all showing,hiding,toggling
|
|
$.mobile.fixedToolbars = (function() {
|
|
|
|
if ( !$.support.scrollTop || ( $.support.touchOverflow && $.mobile.touchOverflowEnabled ) ) {
|
|
return;
|
|
}
|
|
|
|
var stickyFooter, delayTimer,
|
|
currentstate = "inline",
|
|
autoHideMode = false,
|
|
showDelay = 100,
|
|
ignoreTargets = "a,input,textarea,select,button,label,.ui-header-fixed,.ui-footer-fixed",
|
|
toolbarSelector = ".ui-header-fixed:first, .ui-footer-fixed:not(.ui-footer-duplicate):last",
|
|
// for storing quick references to duplicate footers
|
|
supportTouch = $.support.touch,
|
|
touchStartEvent = supportTouch ? "touchstart" : "mousedown",
|
|
touchStopEvent = supportTouch ? "touchend" : "mouseup",
|
|
stateBefore = null,
|
|
scrollTriggered = false,
|
|
touchToggleEnabled = true;
|
|
|
|
function showEventCallback( event ) {
|
|
// An event that affects the dimensions of the visual viewport has
|
|
// been triggered. If the header and/or footer for the current page are in overlay
|
|
// mode, we want to hide them, and then fire off a timer to show them at a later
|
|
// point. Events like a resize can be triggered continuously during a scroll, on
|
|
// some platforms, so the timer is used to delay the actual positioning until the
|
|
// flood of events have subsided.
|
|
//
|
|
// If we are in autoHideMode, we don't do anything because we know the scroll
|
|
// callbacks for the plugin will fire off a show when the scrolling has stopped.
|
|
if ( !autoHideMode && currentstate === "overlay" ) {
|
|
if ( !delayTimer ) {
|
|
$.mobile.fixedToolbars.hide( true );
|
|
}
|
|
|
|
$.mobile.fixedToolbars.startShowTimer();
|
|
}
|
|
}
|
|
|
|
$(function() {
|
|
var $document = $( document ),
|
|
$window = $( window );
|
|
|
|
$document
|
|
.bind( "vmousedown", function( event ) {
|
|
if ( touchToggleEnabled ) {
|
|
stateBefore = currentstate;
|
|
}
|
|
})
|
|
.bind( "vclick", function( event ) {
|
|
if ( touchToggleEnabled ) {
|
|
|
|
if ( $(event.target).closest( ignoreTargets ).length ) {
|
|
return;
|
|
}
|
|
|
|
if ( !scrollTriggered ) {
|
|
$.mobile.fixedToolbars.toggle( stateBefore );
|
|
stateBefore = null;
|
|
}
|
|
}
|
|
})
|
|
.bind( "silentscroll", showEventCallback );
|
|
|
|
|
|
// The below checks first for a $(document).scrollTop() value, and if zero, binds scroll events to $(window) instead.
|
|
// If the scrollTop value is actually zero, both will return zero anyway.
|
|
//
|
|
// Works with $(document), not $(window) : Opera Mobile (WinMO phone; kinda broken anyway)
|
|
// Works with $(window), not $(document) : IE 7/8
|
|
// Works with either $(window) or $(document) : Chrome, FF 3.6/4, Android 1.6/2.1, iOS
|
|
// Needs work either way : BB5, Opera Mobile (iOS)
|
|
|
|
( ( $document.scrollTop() === 0 ) ? $window : $document )
|
|
.bind( "scrollstart", function( event ) {
|
|
|
|
scrollTriggered = true;
|
|
|
|
if ( stateBefore === null ) {
|
|
stateBefore = currentstate;
|
|
}
|
|
|
|
// We only enter autoHideMode if the headers/footers are in
|
|
// an overlay state or the show timer was started. If the
|
|
// show timer is set, clear it so the headers/footers don't
|
|
// show up until after we're done scrolling.
|
|
var isOverlayState = stateBefore == "overlay";
|
|
|
|
autoHideMode = isOverlayState || !!delayTimer;
|
|
|
|
if ( autoHideMode ) {
|
|
$.mobile.fixedToolbars.clearShowTimer();
|
|
|
|
if ( isOverlayState ) {
|
|
$.mobile.fixedToolbars.hide( true );
|
|
}
|
|
}
|
|
})
|
|
.bind( "scrollstop", function( event ) {
|
|
|
|
if ( $( event.target ).closest( ignoreTargets ).length ) {
|
|
return;
|
|
}
|
|
|
|
scrollTriggered = false;
|
|
|
|
if ( autoHideMode ) {
|
|
$.mobile.fixedToolbars.startShowTimer();
|
|
autoHideMode = false;
|
|
}
|
|
stateBefore = null;
|
|
});
|
|
|
|
$window.bind( "resize", showEventCallback );
|
|
});
|
|
|
|
// 1. Before page is shown, check for duplicate footer
|
|
// 2. After page is shown, append footer to new page
|
|
$( ".ui-page" )
|
|
.live( "pagebeforeshow", function( event, ui ) {
|
|
|
|
var page = $( event.target ),
|
|
footer = page.find( ":jqmData(role='footer')" ),
|
|
id = footer.data( "id" ),
|
|
prevPage = ui.prevPage,
|
|
prevFooter = prevPage && prevPage.find( ":jqmData(role='footer')" ),
|
|
prevFooterMatches = prevFooter.length && prevFooter.jqmData( "id" ) === id;
|
|
|
|
if ( id && prevFooterMatches ) {
|
|
stickyFooter = footer;
|
|
setTop( stickyFooter.removeClass( "fade in out" ).appendTo( $.mobile.pageContainer ) );
|
|
}
|
|
})
|
|
.live( "pageshow", function( event, ui ) {
|
|
|
|
var $this = $( this );
|
|
|
|
if ( stickyFooter && stickyFooter.length ) {
|
|
|
|
setTimeout(function() {
|
|
setTop( stickyFooter.appendTo( $this ).addClass( "fade" ) );
|
|
stickyFooter = null;
|
|
}, 500);
|
|
}
|
|
|
|
$.mobile.fixedToolbars.show( true, this );
|
|
});
|
|
|
|
// When a collapsiable is hidden or shown we need to trigger the fixed toolbar to reposition itself (#1635)
|
|
$( ".ui-collapsible-contain" ).live( "collapse expand", showEventCallback );
|
|
|
|
// element.getBoundingClientRect() is broken in iOS 3.2.1 on the iPad. The
|
|
// coordinates inside of the rect it returns don't have the page scroll position
|
|
// factored out of it like the other platforms do. To get around this,
|
|
// we'll just calculate the top offset the old fashioned way until core has
|
|
// a chance to figure out how to handle this situation.
|
|
//
|
|
// TODO: We'll need to get rid of getOffsetTop() once a fix gets folded into core.
|
|
|
|
function getOffsetTop( ele ) {
|
|
var top = 0,
|
|
op, body;
|
|
|
|
if ( ele ) {
|
|
body = document.body;
|
|
op = ele.offsetParent;
|
|
top = ele.offsetTop;
|
|
|
|
while ( ele && ele != body ) {
|
|
top += ele.scrollTop || 0;
|
|
|
|
if ( ele == op ) {
|
|
top += op.offsetTop;
|
|
op = ele.offsetParent;
|
|
}
|
|
|
|
ele = ele.parentNode;
|
|
}
|
|
}
|
|
return top;
|
|
}
|
|
|
|
function setTop( el ) {
|
|
var fromTop = $(window).scrollTop(),
|
|
thisTop = getOffsetTop( el[ 0 ] ), // el.offset().top returns the wrong value on iPad iOS 3.2.1, call our workaround instead.
|
|
thisCSStop = el.css( "top" ) == "auto" ? 0 : parseFloat(el.css( "top" )),
|
|
screenHeight = window.innerHeight,
|
|
thisHeight = el.outerHeight(),
|
|
useRelative = el.parents( ".ui-page:not(.ui-page-fullscreen)" ).length,
|
|
relval;
|
|
|
|
if ( el.is( ".ui-header-fixed" ) ) {
|
|
|
|
relval = fromTop - thisTop + thisCSStop;
|
|
|
|
if ( relval < thisTop ) {
|
|
relval = 0;
|
|
}
|
|
|
|
return el.css( "top", useRelative ? relval : fromTop );
|
|
} else {
|
|
// relval = -1 * (thisTop - (fromTop + screenHeight) + thisCSStop + thisHeight);
|
|
// if ( relval > thisTop ) { relval = 0; }
|
|
relval = fromTop + screenHeight - thisHeight - (thisTop - thisCSStop );
|
|
|
|
return el.css( "top", useRelative ? relval : fromTop + screenHeight - thisHeight );
|
|
}
|
|
}
|
|
|
|
// Exposed methods
|
|
return {
|
|
|
|
show: function( immediately, page ) {
|
|
|
|
$.mobile.fixedToolbars.clearShowTimer();
|
|
|
|
currentstate = "overlay";
|
|
|
|
var $ap = page ? $( page ) :
|
|
( $.mobile.activePage ? $.mobile.activePage :
|
|
$( ".ui-page-active" ) );
|
|
|
|
return $ap.children( toolbarSelector ).each(function() {
|
|
|
|
var el = $( this ),
|
|
fromTop = $( window ).scrollTop(),
|
|
// el.offset().top returns the wrong value on iPad iOS 3.2.1, call our workaround instead.
|
|
thisTop = getOffsetTop( el[ 0 ] ),
|
|
screenHeight = window.innerHeight,
|
|
thisHeight = el.outerHeight(),
|
|
alreadyVisible = ( el.is( ".ui-header-fixed" ) && fromTop <= thisTop + thisHeight ) ||
|
|
( el.is( ".ui-footer-fixed" ) && thisTop <= fromTop + screenHeight );
|
|
|
|
// Add state class
|
|
el.addClass( "ui-fixed-overlay" ).removeClass( "ui-fixed-inline" );
|
|
|
|
if ( !alreadyVisible && !immediately ) {
|
|
el.animationComplete(function() {
|
|
el.removeClass( "in" );
|
|
}).addClass( "in" );
|
|
}
|
|
setTop(el);
|
|
});
|
|
},
|
|
|
|
hide: function( immediately ) {
|
|
|
|
currentstate = "inline";
|
|
|
|
var $ap = $.mobile.activePage ? $.mobile.activePage :
|
|
$( ".ui-page-active" );
|
|
|
|
return $ap.children( toolbarSelector ).each(function() {
|
|
|
|
var el = $(this),
|
|
thisCSStop = el.css( "top" ),
|
|
classes;
|
|
|
|
thisCSStop = thisCSStop == "auto" ? 0 :
|
|
parseFloat(thisCSStop);
|
|
|
|
// Add state class
|
|
el.addClass( "ui-fixed-inline" ).removeClass( "ui-fixed-overlay" );
|
|
|
|
if ( thisCSStop < 0 || ( el.is( ".ui-header-fixed" ) && thisCSStop !== 0 ) ) {
|
|
|
|
if ( immediately ) {
|
|
el.css( "top", 0);
|
|
} else {
|
|
|
|
if ( el.css( "top" ) !== "auto" && parseFloat( el.css( "top" ) ) !== 0 ) {
|
|
|
|
classes = "out reverse";
|
|
|
|
el.animationComplete(function() {
|
|
el.removeClass( classes ).css( "top", 0 );
|
|
}).addClass( classes );
|
|
}
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
startShowTimer: function() {
|
|
|
|
$.mobile.fixedToolbars.clearShowTimer();
|
|
|
|
var args = [].slice.call( arguments );
|
|
|
|
delayTimer = setTimeout(function() {
|
|
delayTimer = undefined;
|
|
$.mobile.fixedToolbars.show.apply( null, args );
|
|
}, showDelay);
|
|
},
|
|
|
|
clearShowTimer: function() {
|
|
if ( delayTimer ) {
|
|
clearTimeout( delayTimer );
|
|
}
|
|
delayTimer = undefined;
|
|
},
|
|
|
|
toggle: function( from ) {
|
|
if ( from ) {
|
|
currentstate = from;
|
|
}
|
|
return ( currentstate === "overlay" ) ? $.mobile.fixedToolbars.hide() :
|
|
$.mobile.fixedToolbars.show();
|
|
},
|
|
|
|
setTouchToggleEnabled: function( enabled ) {
|
|
touchToggleEnabled = enabled;
|
|
}
|
|
};
|
|
})();
|
|
|
|
//auto self-init widgets
|
|
$( document ).bind( "pagecreate create", function( event ) {
|
|
|
|
if ( $( ":jqmData(position='fixed')", event.target ).length ) {
|
|
|
|
$( event.target ).each(function() {
|
|
|
|
if ( !$.support.scrollTop || ( $.support.touchOverflow && $.mobile.touchOverflowEnabled ) ) {
|
|
return this;
|
|
}
|
|
|
|
var $this = $( this );
|
|
|
|
if ( $this.jqmData( "fullscreen" ) ) {
|
|
$this.addClass( "ui-page-fullscreen" );
|
|
}
|
|
|
|
// Should be slidedown
|
|
$this.find( slideDownSelector ).addClass( slideDownClass );
|
|
|
|
// Should be slideup
|
|
$this.find( slideUpSelector ).addClass( slideUpClass );
|
|
|
|
})
|
|
|
|
}
|
|
});
|
|
|
|
})( jQuery );
|