2010-09-13 22:22:51 +00:00
/ * !
* jQuery Mobile
* http : //jquerymobile.com/
*
* Copyright 2010 , jQuery Project
* Dual licensed under the MIT or GPL Version 2 licenses .
* http : //jquery.org/license
* /
2010-09-20 20:48:45 +00:00
( function ( jQuery , window , undefined ) {
2010-09-14 15:48:06 +00:00
// if we're missing support for any of these, then we're a C-grade browser
2010-09-20 20:48:45 +00:00
if ( ! jQuery . support . display || ! jQuery . support . position || ! jQuery . support . overflow || ! jQuery . support . floatclear ) {
2010-09-14 15:48:06 +00:00
return ;
}
2010-09-20 22:21:53 +00:00
//these properties should be made easy to override externally
jQuery . mobile = { } ;
jQuery . extend ( jQuery . mobile , {
subPageUrlKey : 'ui-page' //define the key used in urls for sub-pages. Defaults to &ui-page=
} ) ;
2010-09-14 15:48:06 +00:00
2010-09-20 20:48:45 +00:00
var $window = jQuery ( window ) ,
$html = jQuery ( 'html' ) ,
$head = jQuery ( 'head' ) ,
2010-09-10 22:23:13 +00:00
$body ,
2010-10-03 20:43:50 +00:00
$loader = jQuery ( '<div class="ui-loader ui-body-a ui-corner-all"><span class="ui-icon ui-icon-loading spin"></span><h1>loading.</h1></div>' ) ,
2010-09-10 22:23:13 +00:00
startPage ,
startPageId = 'ui-page-start' ,
activePageClass = 'ui-page-active' ,
2010-09-17 20:51:02 +00:00
pageTransition ,
2010-09-11 12:49:54 +00:00
transitions = 'slide slideup slidedown pop flip fade dissolve swap' ,
2010-09-10 22:23:13 +00:00
transitionDuration = 350 ,
backBtnText = "Back" ,
2010-09-17 20:51:02 +00:00
urlStack = [ {
url : location . hash . replace ( /^#/ , "" ) ,
transition : "slide"
} ] ,
2010-09-17 20:27:10 +00:00
nextPageRole = null ,
noCache = '.ui-dialog' ;
2010-09-10 22:23:13 +00:00
2010-09-17 20:51:02 +00:00
// hide address bar
function hideBrowserChrome ( ) {
// prevent scrollstart and scrollstop events
2010-09-20 20:48:45 +00:00
jQuery . event . special . scrollstart . enabled = false ;
2010-10-03 20:43:50 +00:00
setTimeout ( function ( ) {
window . scrollTo ( 0 , 0 ) ;
} , 0 ) ;
2010-09-17 20:51:02 +00:00
setTimeout ( function ( ) {
2010-09-20 20:48:45 +00:00
jQuery . event . special . scrollstart . enabled = true ;
2010-09-17 20:51:02 +00:00
} , 150 ) ;
}
2010-09-10 22:23:13 +00:00
2010-10-03 13:41:45 +00:00
function getBaseURL ( ) {
var newBaseURL = location . hash . replace ( /#/ , '' ) . split ( '/' ) ;
if ( newBaseURL . length && /[.|&]/ . test ( newBaseURL [ newBaseURL . length - 1 ] ) ) {
newBaseURL . pop ( ) ;
}
newBaseURL = newBaseURL . join ( '/' ) ;
if ( newBaseURL !== "" && newBaseURL . charAt ( newBaseURL . length - 1 ) !== '/' ) { newBaseURL += '/' ; }
return newBaseURL ;
}
2010-10-03 13:48:52 +00:00
function setBaseURL ( ) {
//set base url for new page assets
$ ( '#ui-base' ) . attr ( 'href' , getBaseURL ( ) ) ;
}
2010-10-03 13:41:45 +00:00
function resetBaseURL ( ) {
$ ( '#ui-base' ) . attr ( 'href' , location . pathname ) ;
}
2010-09-17 20:51:02 +00:00
// send a link through hash tracking
2010-09-20 20:48:45 +00:00
jQuery . fn . ajaxClick = function ( ) {
2010-10-03 13:41:45 +00:00
var href = jQuery ( this ) . attr ( "href" ) ;
2010-09-20 20:48:45 +00:00
pageTransition = jQuery ( this ) . attr ( "data-transition" ) || "slide" ;
2010-10-03 13:41:45 +00:00
nextPageRole = jQuery ( this ) . attr ( "data-rel" ) ;
2010-10-03 13:58:26 +00:00
2010-10-03 13:41:45 +00:00
//find new base for url building
var newBaseURL = getBaseURL ( ) ;
2010-10-02 19:41:57 +00:00
//if href is absolute but local, or a local ID, no base needed
if ( /^\// . test ( href ) || ( /https?:\/\// . test ( href ) && ! ! ( href ) . match ( location . hostname ) ) || /^#/ . test ( href ) ) {
2010-10-03 13:41:45 +00:00
newBaseURL = '' ;
2010-10-02 19:41:57 +00:00
}
// set href to relative path using baseURL and
if ( ! /https?:\/\// . test ( href ) ) {
2010-10-03 13:41:45 +00:00
href = newBaseURL + href ;
2010-10-02 19:41:57 +00:00
}
2010-10-03 13:41:45 +00:00
2010-10-02 16:18:45 +00:00
//if it's a non-local-anchor and Ajax is not supported, or if it's an external link, go to page without ajax
2010-10-02 19:41:57 +00:00
if ( ( /^[^#]/ . test ( href ) && ! jQuery . support . ajax ) || ( /https?:\/\// . test ( href ) && ! ! ! href . match ( location . hostname ) ) ) {
location = href
2010-09-10 22:23:13 +00:00
}
2010-10-02 16:18:45 +00:00
else {
// let the hashchange event handler take care of requesting the page via ajax
location . hash = href ;
}
2010-09-17 20:51:02 +00:00
return this ;
2010-09-10 22:23:13 +00:00
} ;
2010-09-17 20:51:02 +00:00
// ajaxify all navigable links
2010-10-03 15:20:14 +00:00
jQuery ( "a:not([href=#]):not([target]):not([rel=external])" ) . live ( "click" , function ( event ) {
2010-09-20 20:48:45 +00:00
jQuery ( this ) . ajaxClick ( ) ;
2010-09-10 22:23:13 +00:00
return false ;
} ) ;
2010-10-03 20:43:50 +00:00
// turn on/off page loading message.
2010-09-17 20:51:02 +00:00
function pageLoading ( done ) {
if ( done ) {
$html . removeClass ( "ui-loading" ) ;
} else {
2010-10-03 20:43:50 +00:00
$loader . appendTo ( 'body' ) ;
2010-09-17 20:51:02 +00:00
$html . addClass ( "ui-loading" ) ;
2010-09-10 22:23:13 +00:00
}
} ;
2010-09-17 20:51:02 +00:00
// transition between pages - based on transitions from jQtouch
function changePage ( from , to , transition , back ) {
2010-09-20 20:48:45 +00:00
jQuery ( document . activeElement ) . blur ( ) ;
2010-09-10 22:23:13 +00:00
2010-10-04 22:25:11 +00:00
to . appendTo ( $body ) ;
2010-10-04 21:46:58 +00:00
//trigger before show/hide events
from . trigger ( "beforepagehide" , { nextPage : to } ) ;
to . trigger ( "beforepageshow" , { prevPage : from } ) ;
2010-09-17 20:51:02 +00:00
// animate in / out
from . addClass ( transition + " out " + ( back ? "reverse" : "" ) ) ;
2010-10-04 22:25:11 +00:00
to . addClass ( activePageClass + " " + transition +
2010-09-17 20:51:02 +00:00
" in " + ( back ? "reverse" : "" ) ) ;
2010-09-10 22:23:13 +00:00
2010-09-17 20:51:02 +00:00
// callback - remove classes, etc
to . animationComplete ( function ( ) {
from . add ( to ) . removeClass ( " out in reverse " + transitions ) ;
from . removeClass ( activePageClass ) ;
2010-10-04 21:46:58 +00:00
//trigger show/hide events
from . trigger ( "pagehide" , { nextPage : to } ) ;
to . trigger ( "pageshow" , { prevPage : from } ) ;
2010-09-17 20:51:02 +00:00
pageLoading ( true ) ;
2010-09-10 22:23:13 +00:00
} ) ;
} ;
2010-09-20 20:48:45 +00:00
jQuery ( function ( ) {
$body = jQuery ( "body" ) ;
2010-09-17 20:51:02 +00:00
pageLoading ( ) ;
// needs to be bound at domready (for IE6)
// find or load content, make it active
$window . bind ( "hashchange" , function ( e ) {
var url = location . hash . replace ( /^#/ , "" ) ,
stackLength = urlStack . length ,
// pageTransition only exists if the user clicked a link
back = ! pageTransition && stackLength > 1 &&
urlStack [ stackLength - 2 ] . url === url ,
2010-09-20 22:21:53 +00:00
transition = pageTransition || "slide" ,
fileUrl = url ;
2010-09-17 21:24:12 +00:00
pageTransition = undefined ;
2010-09-17 20:51:02 +00:00
2010-10-03 13:58:26 +00:00
//reset base to pathname for new request
resetBaseURL ( ) ;
2010-09-17 20:51:02 +00:00
// if the new href is the same as the previous one
if ( back ) {
transition = urlStack . pop ( ) . transition ;
} else {
urlStack . push ( { url : url , transition : transition } ) ;
}
//remove any pages that shouldn't cache
2010-09-20 20:48:45 +00:00
jQuery ( noCache ) . remove ( ) ;
2010-09-17 20:51:02 +00:00
//function for setting role of next page
function setPageRole ( newPage ) {
if ( nextPageRole ) {
newPage . attr ( "data-role" , nextPageRole ) ;
nextPageRole = undefined ;
}
}
if ( url ) {
// see if content is present already
2010-09-20 20:48:45 +00:00
var localDiv = jQuery ( "[id='" + url + "']" ) ;
2010-09-17 20:51:02 +00:00
if ( localDiv . length ) {
if ( localDiv . is ( "[data-role]" ) ) {
setPageRole ( localDiv ) ;
}
2010-10-03 13:48:52 +00:00
setBaseURL ( ) ;
2010-09-19 14:32:46 +00:00
mobilize ( localDiv ) ;
2010-09-20 20:48:45 +00:00
changePage ( jQuery ( ".ui-page-active" ) , localDiv , transition , back ) ;
2010-09-17 20:51:02 +00:00
} else { //ajax it in
pageLoading ( ) ;
2010-09-20 22:21:53 +00:00
if ( url . match ( '&' + jQuery . mobile . subPageUrlKey ) ) {
fileUrl = url . split ( '&' + jQuery . mobile . subPageUrlKey ) [ 0 ] ;
}
2010-09-20 20:48:45 +00:00
var newPage = jQuery ( "<div>" )
2010-09-17 20:51:02 +00:00
. appendTo ( $body )
2010-09-20 22:21:53 +00:00
. load ( fileUrl + ' [data-role="page"]' , function ( ) {
2010-09-17 20:51:02 +00:00
// TODO: test this (avoids querying the dom for new element):
2010-09-20 20:48:45 +00:00
// var newPage = jQuery( this ).find( ".ui-page" ).eq( 0 )
2010-09-17 20:51:02 +00:00
// .attr( "id", url );
2010-09-20 20:48:45 +00:00
// jQuery( this ).replaceWith( newPage );
2010-09-17 20:51:02 +00:00
// setPageRole( newPage );
// mobilize( newPage );
2010-09-20 20:48:45 +00:00
// changePage( jQuery( ".ui-page-active" ), newPage, transition, back );
jQuery ( this ) . replaceWith (
2010-09-20 22:21:53 +00:00
jQuery ( this ) . find ( '[data-role="page"]' ) . eq ( 0 ) . attr ( "id" , fileUrl ) ) ;
var newPage = jQuery ( "[id='" + fileUrl + "']" ) ;
2010-09-17 20:51:02 +00:00
setPageRole ( newPage ) ;
mobilize ( newPage ) ;
2010-09-20 22:21:53 +00:00
newPage = jQuery ( "[id='" + url + "']" ) ;
2010-09-20 20:48:45 +00:00
changePage ( jQuery ( ".ui-page-active" ) , newPage , transition , back ) ;
2010-09-17 20:51:02 +00:00
} ) ;
2010-10-03 13:41:45 +00:00
2010-10-03 13:48:52 +00:00
setBaseURL ( ) ;
2010-09-17 20:51:02 +00:00
}
} else {
// either we've backed up to the root page url
// or it's the first page load with no hash present
2010-09-20 20:48:45 +00:00
var currentPage = jQuery ( ".ui-page-active" ) ;
2010-09-17 20:51:02 +00:00
if ( currentPage . length && ! startPage . is ( ".ui-page-active" ) ) {
changePage ( currentPage , startPage , transition , back ) ;
} else {
2010-10-06 01:24:36 +00:00
startPage . trigger ( "beforepageshow" , { prevPage : $ ( '' ) } ) ;
2010-09-17 20:51:02 +00:00
startPage . addClass ( activePageClass ) ;
2010-10-04 23:17:54 +00:00
//FIXME: when there's no prevPage, is passing an empty jQuery obj proper style?
startPage . trigger ( "pageshow" , { prevPage : $ ( '' ) } ) ;
2010-09-17 20:51:02 +00:00
pageLoading ( true ) ;
}
}
} ) ;
2010-10-03 20:01:10 +00:00
} ) ;
2010-09-17 20:51:02 +00:00
//add orientation class on flip/resize.
$window . bind ( "orientationchange" , function ( event , data ) {
$html . removeClass ( "portrait landscape" ) . addClass ( data . orientation ) ;
} ) ;
//add mobile, loading classes to doc
$html . addClass ( 'ui-mobile' ) ;
2010-09-22 17:54:52 +00:00
//insert mobile meta - these will need to be configurable somehow.
$head . append ( '<meta name="viewport" content="width=device-width, minimum-scale=1.0, maximum-scale=1.0" />' +
'<link rel="apple-touch-icon" href="images/homeicon.png" />' +
'<link rel="apple-touch-startup-image" href="images/startscreen.png" />' +
'<meta name="apple-mobile-web-app-capable" content="yes" />' +
2010-10-03 13:41:45 +00:00
'<meta name="apple-mobile-web-app-status-bar-style" content="default" />' +
'<base href="" id="ui-base" />' ) ;
2010-10-03 13:58:26 +00:00
//set base href to pathname
2010-10-03 13:41:45 +00:00
resetBaseURL ( ) ;
2010-09-17 20:51:02 +00:00
2010-09-10 22:23:13 +00:00
//potential (probably incomplete) fallback to workaround lack of animation callbacks.
//should this be extended into a full special event?
// note: Expects CSS animations use transitionDuration (350ms)
2010-09-20 20:48:45 +00:00
jQuery . fn . animationComplete = function ( callback ) {
if ( jQuery . support . WebKitAnimationEvent ) {
return jQuery ( this ) . one ( 'webkitAnimationEnd' , callback ) ; //check out transitionEnd (opera per Paul's request)
2010-09-10 22:23:13 +00:00
}
else {
setTimeout ( callback , transitionDuration ) ;
}
2010-10-03 20:01:10 +00:00
} ;
2010-09-17 20:51:02 +00:00
2010-09-10 22:23:13 +00:00
//markup-driven enhancements, to be called on any ui-page upon loading
function mobilize ( $el ) {
2010-09-12 15:33:23 +00:00
//to-do: make sure this only runs one time on a page (or maybe per component)
2010-09-21 16:24:12 +00:00
return $el . not ( '[data-mobilized]' ) . each ( function ( ) {
2010-09-27 18:39:57 +00:00
var $el = $ ( this ) ;
2010-10-04 22:00:31 +00:00
$el . trigger ( "beforeload" ) ;
2010-10-04 20:03:54 +00:00
//some of the form elements currently rely on the presence of ui-page and ui-content
// classes so we'll handle page and content roles outside of the main role processing
// loop below.
$el . find ( '[data-role=page],[data-role=content]' ) . andSelf ( ) . each ( function ( ) {
var $this = $ ( this ) ;
$this . addClass ( 'ui-' + $this . attr ( "data-role" ) ) ;
} ) ;
2010-09-27 18:39:57 +00:00
2010-09-21 16:24:12 +00:00
//hide no-js content
2010-09-27 18:28:47 +00:00
$el . find ( '[data-role="nojs"]' ) . addClass ( 'ui-nojs' ) ;
2010-10-06 04:36:32 +00:00
//replace HTML5 input types that have crap browser implementations
2010-10-06 15:46:27 +00:00
$el . find ( 'input' ) . not ( '[type=text],[type=submit],[type=reset],[type=image],[type=button]' ) . each ( function ( ) {
$ ( this ) . replaceWith ( $ ( '<div>' ) . html ( $ ( this ) . clone ( ) ) . html ( ) . replace ( /type="([a-zA-Z]+)"/ , 'data-type="$1"' ) ) ;
2010-10-06 04:36:32 +00:00
} ) ;
2010-09-27 18:28:47 +00:00
$el . find ( 'input[type=radio],input[type=checkbox]' ) . customCheckboxRadio ( ) ;
2010-10-06 04:01:38 +00:00
$el . find ( 'button, input[type=submit], input[type=reset], input[type=image]' ) . not ( '.ui-nojs' ) . customButton ( ) ;
2010-10-06 04:44:03 +00:00
$el . find ( 'input[type=text],input[type=password],textarea' ) . customTextInput ( ) ;
2010-09-27 18:28:47 +00:00
$el . find ( "input, select" ) . filter ( '[data-role="slider"]' ) . slider ( ) ;
$el . find ( 'select' ) . not ( '[data-role="slider"]' ) . customSelect ( ) ;
2010-10-06 04:36:32 +00:00
2010-10-06 04:11:02 +00:00
2010-09-21 16:24:12 +00:00
//pre-find data els
2010-09-21 20:55:08 +00:00
var $dataEls = $el . find ( '[data-role]' ) . andSelf ( ) . each ( function ( ) {
var $this = $ ( this ) ,
role = $this . attr ( "data-role" ) ;
switch ( role ) {
case 'header' :
case 'footer' :
2010-10-01 22:45:21 +00:00
$this . addClass ( 'ui-bar-' + ( $ ( this ) . data ( 'theme' ) ? $ ( this ) . data ( 'theme' ) : 'a' ) ) ;
case 'page' :
case 'content' :
2010-09-21 20:55:08 +00:00
$this . addClass ( 'ui-' + role ) ;
break ;
2010-09-21 21:18:00 +00:00
case 'collapsible' :
2010-09-21 20:55:08 +00:00
case 'fieldcontain' :
2010-10-05 18:06:28 +00:00
case 'navlist' :
2010-09-21 20:55:08 +00:00
case 'listview' :
case 'dialog' :
case 'ajaxform' :
$this [ role ] ( ) ;
break ;
2010-09-27 18:28:47 +00:00
case 'controlgroup' :
// FIXME for some reason this has to come before the form control stuff (see above)
$this . controlgroup ( {
direction : $this . attr ( "data-type" )
} ) ;
break ;
2010-09-21 20:55:08 +00:00
}
} ) ;
2010-09-21 16:24:12 +00:00
//fix toolbars
$el . fixHeaderFooter ( ) ;
//add back buttons to headers that don't have them
2010-09-27 18:28:47 +00:00
// FIXME make that optional?
// TODO don't do that on devices that have a native back button?
2010-10-06 08:01:23 +00:00
var backBtn = $el . find ( '.ui-header:not(.ui-listbox-header) a.ui-back' ) ;
2010-09-10 22:23:13 +00:00
if ( ! backBtn . length ) {
2010-10-06 08:01:23 +00:00
backBtn = jQuery ( '<a href="#" class="ui-back" data-icon="arrow-l"></a>' ) . appendTo ( $el . find ( '.ui-header:not(.ui-listbox-header)' ) ) ;
2010-09-10 22:23:13 +00:00
}
2010-09-21 16:24:12 +00:00
//buttons from links in headers,footers,bars, or with data-role
$dataEls . filter ( '[data-role="button"]' ) . add ( '.ui-header a, .ui-footer a, .ui-bar a' ) . not ( '.ui-btn' ) . buttonMarkup ( ) ;
//links within content areas
$el . find ( '.ui-body a:not(.ui-btn):not(.ui-link-inherit)' ) . addClass ( 'ui-link' ) ;
//make all back buttons mimic the back button (pre-js, these links are usually "home" links)
2010-09-10 22:23:13 +00:00
backBtn
. click ( function ( ) {
2010-09-27 18:28:47 +00:00
// FIXME if you navigated from some page directly to subpage (ala .../#_form-controls.html), back button will actually go to the previous page, instead of the parent page
2010-09-10 22:23:13 +00:00
history . go ( - 1 ) ;
return false ;
} )
2010-09-21 16:24:12 +00:00
. find ( '.ui-btn-text' ) . text ( backBtnText ) ;
2010-10-04 22:00:31 +00:00
$el . attr ( 'data-mobilized' , true ) ;
$el . trigger ( "load" ) ;
2010-09-10 22:23:13 +00:00
} ) ;
} ;
2010-09-19 21:57:06 +00:00
2010-09-20 20:48:45 +00:00
jQuery . extend ( {
2010-09-19 22:09:05 +00:00
mobilize : mobilize ,
pageLoading : pageLoading ,
changePage : changePage ,
hideBrowserChrome : hideBrowserChrome
} ) ;
2010-09-10 22:23:13 +00:00
//dom-ready
2010-09-20 20:48:45 +00:00
jQuery ( function ( ) {
2010-09-18 16:20:35 +00:00
2010-09-10 22:23:13 +00:00
//set up active page - mobilize it!
2010-09-20 20:48:45 +00:00
startPage = jQuery ( '[data-role="page"]:first' ) ;
2010-09-10 22:23:13 +00:00
//make sure it has an ID - for finding it later
if ( ! startPage . attr ( 'id' ) ) {
startPage . attr ( 'id' , startPageId ) ;
}
2010-09-19 14:47:27 +00:00
//mobilize all pages present
2010-09-20 20:48:45 +00:00
mobilize ( jQuery ( '[data-role="page"]' ) ) ;
2010-09-19 14:47:27 +00:00
2010-09-10 22:23:13 +00:00
//trigger a new hashchange, hash or not
$window . trigger ( "hashchange" ) ;
//update orientation
2010-09-20 20:48:45 +00:00
$html . addClass ( jQuery . event . special . orientationchange . orientation ( $window ) ) ;
2010-09-11 12:24:36 +00:00
2010-09-10 22:23:13 +00:00
//some debug stuff for the events pages
2010-09-20 20:48:45 +00:00
jQuery ( 'body' ) . bind ( 'scrollstart scrollstop swipe swipeleft swiperight tap taphold turn' , function ( e ) {
jQuery ( '#eventlogger' ) . prepend ( '<div>Event fired: ' + e . type + '</div>' ) ;
2010-09-10 22:23:13 +00:00
} ) ;
} ) ;
2010-10-03 20:43:50 +00:00
$window . load ( hideBrowserChrome ) ;
2010-09-13 22:22:51 +00:00
} ) ( jQuery , this ) ;