This commit decouples all widgets from the page plugin so that they can be used ad-hoc.

- Internally, each plugin self-initializes by binding to the pagecreate event.

- Unit tests have been added and adjusted to support some internal changes involved in this commit.

- In the process, the portions of the page plugin that were used to enhance the header,content,and footer sections of a native-app style page layout are now located in jquery.mobile.page.sections.js.

- No public API options have changed, except that the page plugin no longer has options for keepNative, and degradeInputs, as plugins now handle these internally (keepNative was never documented, and degradeInputs only affected slider, so it lives there now. Page options related to the page sections are now located in the page.sections script, but they are still configurable via the page plugin's options api.

- Make, Ant, and index files are updated with a new load order for all JS files.
This commit is contained in:
scottjehl 2011-07-19 19:05:35 -04:00
parent c7b63920e7
commit 2a6c7fc1b9
27 changed files with 579 additions and 409 deletions

View file

@ -46,21 +46,25 @@ JSFILES = js/jquery.ui.widget.js \
js/jquery.mobile.core.js \
js/jquery.mobile.navigation.js \
js/jquery.mobile.transition.js \
js/jquery.mobile.fixHeaderFooter.js \
js/jquery.mobile.dialog.js \
js/jquery.mobile.page.sections.js \
js/jquery.mobile.collapsible.js \
js/jquery.mobile.fieldContain.js \
js/jquery.mobile.grid.js \
js/jquery.mobile.navbar.js \
js/jquery.mobile.listview.js \
js/jquery.mobile.listview.filter.js \
js/jquery.mobile.nojs.js \
js/jquery.mobile.forms.checkboxradio.js \
js/jquery.mobile.forms.button.js \
js/jquery.mobile.forms.slider.js \
js/jquery.mobile.forms.textinput.js \
js/jquery.mobile.forms.select.js \
js/jquery.mobile.buttonMarkup.js \
js/jquery.mobile.forms.button.js \
js/jquery.mobile.forms.slider.js \
js/jquery.mobile.collapsible.js \
js/jquery.mobile.controlGroup.js \
js/jquery.mobile.fieldContain.js \
js/jquery.mobile.listview.js \
js/jquery.mobile.listview.filter.js \
js/jquery.mobile.dialog.js \
js/jquery.mobile.navbar.js \
js/jquery.mobile.grid.js \
js/jquery.mobile.links.js \
js/jquery.mobile.fixHeaderFooter.js \
js/jquery.mobile.media.classes.js \
js/jquery.mobile.init.js
# The files to include when compiling the CSS files

View file

@ -19,33 +19,37 @@
jquery.mobile.forms.textinput.css,
jquery.mobile.listview.css,
jquery.mobile.forms.slider.css"/>
<property name="js-sources" value="jquery.ui.widget.js,
jquery.mobile.widget.js,
jquery.mobile.media.js,
jquery.mobile.support.js,
jquery.mobile.vmouse.js,
jquery.mobile.event.js,
jquery.mobile.hashchange.js,
jquery.mobile.page.js,
jquery.mobile.core.js,
jquery.mobile.navigation.js,
jquery.mobile.transition.js,
jquery.mobile.fixHeaderFooter.js,
jquery.mobile.forms.checkboxradio.js,
jquery.mobile.forms.textinput.js,
jquery.mobile.forms.select.js,
jquery.mobile.buttonMarkup.js,
jquery.mobile.forms.button.js,
jquery.mobile.forms.slider.js,
jquery.mobile.collapsible.js,
jquery.mobile.controlGroup.js,
jquery.mobile.fieldContain.js,
jquery.mobile.listview.js,
jquery.mobile.listview.filter.js,
jquery.mobile.dialog.js,
jquery.mobile.navbar.js,
jquery.mobile.grid.js,
jquery.mobile.init.js"/>
<property name="js-sources" value="js/jquery.ui.widget.js,
js/jquery.mobile.widget.js,
js/jquery.mobile.media.js,
js/jquery.mobile.support.js,
js/jquery.mobile.vmouse.js,
js/jquery.mobile.event.js,
js/jquery.mobile.hashchange.js,
js/jquery.mobile.page.js,
js/jquery.mobile.core.js,
js/jquery.mobile.navigation.js,
js/jquery.mobile.transition.js,
js/jquery.mobile.dialog.js,
js/jquery.mobile.page.sections.js,
js/jquery.mobile.collapsible.js,
js/jquery.mobile.fieldContain.js,
js/jquery.mobile.grid.js,
js/jquery.mobile.navbar.js,
js/jquery.mobile.listview.js,
js/jquery.mobile.listview.filter.js,
js/jquery.mobile.nojs.js,
js/jquery.mobile.forms.checkboxradio.js,
js/jquery.mobile.forms.button.js,
js/jquery.mobile.forms.slider.js,
js/jquery.mobile.forms.textinput.js,
js/jquery.mobile.forms.select.js,
js/jquery.mobile.buttonMarkup.js,
js/jquery.mobile.controlGroup.js,
js/jquery.mobile.links.js,
js/jquery.mobile.fixHeaderFooter.js,
js/jquery.mobile.media.classes.js,
js/jquery.mobile.init.js"/>
<target name="merge">
<antcall target="merge_css" />

View file

@ -12,21 +12,26 @@ $elements = array(
'jquery.mobile.core.js',
'jquery.mobile.navigation.js',
'jquery.mobile.transition.js',
'jquery.mobile.fixHeaderFooter.js',
'jquery.mobile.dialog.js',
'jquery.mobile.page.sections.js',
'jquery.mobile.collapsible.js',
'jquery.mobile.fieldContain.js',
'jquery.mobile.grid.js',
'jquery.mobile.navbar.js',
'jquery.mobile.listview.js',
'jquery.mobile.listview.filter.js',
'jquery.mobile.nojs.js',
'jquery.mobile.forms.checkboxradio.js',
'jquery.mobile.forms.button.js',
'jquery.mobile.forms.slider.js',
'jquery.mobile.forms.textinput.js',
'jquery.mobile.forms.select.js',
'jquery.mobile.buttonMarkup.js',
'jquery.mobile.forms.button.js',
'jquery.mobile.forms.slider.js',
'jquery.mobile.collapsible.js',
'jquery.mobile.controlGroup.js',
'jquery.mobile.fieldContain.js',
'jquery.mobile.listview.js',
'jquery.mobile.listview.filter.js',
'jquery.mobile.dialog.js',
'jquery.mobile.navbar.js',
'jquery.mobile.grid.js',
'jquery.mobile.links.js',
'jquery.mobile.fixHeaderFooter.js',
'jquery.mobile.media.classes.js',
'jquery.mobile.init.js'
);

View file

@ -138,4 +138,13 @@ var attachEvents = function() {
attachEvents = null;
};
//links in bars, or those with data-role become buttons
//auto self-init widgets
$( document ).bind( "pagecreate", function( e ){
$( ":jqmData(role='button'), .ui-bar > a, .ui-header > a, .ui-footer > a", e.target )
.not( ".ui-btn, :jqmData(role='none'), :jqmData(role='nojs')" )
.buttonMarkup();
});
})( jQuery );

View file

@ -6,6 +6,11 @@
*/
(function( $, undefined ) {
//auto self-init widgets
$( document ).bind( "pagecreate", function( e ){
$( ":jqmData(role='collapsible')", e.target ).collapsible();
});
$.widget( "mobile.collapsible", $.mobile.widget, {
options: {
expandCueText: " click to expand contents",

View file

@ -4,15 +4,22 @@
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*/
(function($, undefined ) {
(function( $, undefined ) {
//auto self-init widgets
$( document ).bind( "pagecreate", function( e ){
$( ":jqmData(role='controlgroup')", e.target ).controlgroup({ excludeInvisible: false });
});
$.fn.controlgroup = function( options ) {
return this.each(function() {
var $el = $( this ),
o = $.extend({
direction: $el.jqmData( "type" ) || "vertical",
shadow: false
shadow: false,
excludeInvisible: true
}, options ),
groupheading = $el.find( ">legend" ),
flCorners = o.direction == "horizontal" ? [ "ui-corner-left", "ui-corner-right" ] : [ "ui-corner-top", "ui-corner-bottom" ],
@ -36,7 +43,7 @@ $.fn.controlgroup = function( options ) {
.filter( ":last" ).addClass( flCorners[ 1 ] ).addClass( "ui-controlgroup-last" );
}
flipClasses( $el.find( ".ui-btn" + ( o.dontFilterOutInvisible ? "" : ":visible" ) ) );
flipClasses( $el.find( ".ui-btn" + ( o.excludeInvisible ? ":visible" : "" ) ) );
flipClasses( $el.find( ".ui-btn-inner" ) );
if ( o.shadow ) {

View file

@ -6,25 +6,30 @@
(function( $, window, undefined ) {
//auto self-init widgets
$( ":jqmData(role='dialog')" ).live( "pagecreate", function(){
$( this ).dialog();
});
$.widget( "mobile.dialog", $.mobile.widget, {
options: {
closeBtnText: "Close"
closeBtnText : "Close",
theme : "a"
},
_create: function() {
var $el = this.element;
$el.jqmData( "theme", this.options.theme );
// Class the markup for dialog styling
// Set aria role
$el.attr( "role", "dialog" )
.addClass( "ui-page ui-dialog ui-body-a" )
.find( ":jqmData(role=header)" )
.addClass( "ui-dialog" )
.find( ":jqmData(role='header')" )
.addClass( "ui-corner-top ui-overlay-shadow" )
.prepend( "<a href='#' data-" + $.mobile.ns + "icon='delete' data-" + $.mobile.ns + "rel='back' data-" + $.mobile.ns + "iconpos='notext'>"+ this.options.closeBtnText + "</a>" )
.end()
.find( ".ui-content:not([class*='ui-body-'])" )
.addClass( 'ui-body-c' )
.end()
.find( ".ui-content,:jqmData(role='footer')" )
.find( ":jqmData(role='content'),:jqmData(role='footer')" )
.last()
.addClass( "ui-corner-bottom ui-overlay-shadow" );

View file

@ -7,6 +7,11 @@
(function( $, undefined ) {
//auto self-init widgets
$( document ).bind( "pagecreate", function( e ){
$( ":jqmData(role='fieldcontain')", e.target ).fieldcontain();
});
$.fn.fieldcontain = function( options ) {
return this.addClass( "ui-field-contain ui-body ui-br" );
};

View file

@ -6,6 +6,35 @@
*/
(function( $, undefined ) {
//auto self-init widgets
$( document ).bind( "pagecreate", function( e ){
if( $( ":jqmData(position='fixed')", e.target ).length ){
$( e.target ).each(function(){
if ( !$.support.scrollTop ) {
return this;
}
var $this = $( this );
if ( $this.jqmData( "fullscreen" ) ) {
$this.addClass( "ui-page-fullscreen" );
}
// Should be slidedown
$this.find( ".ui-header:jqmData(position='fixed')" ).addClass( "ui-header-fixed ui-fixed-inline fade" );
// Should be slideup
$this.find( ".ui-footer:jqmData(position='fixed')" ).addClass( "ui-footer-fixed ui-fixed-inline fade" );
})
}
});
$.fn.fixHeaderFooter = function( options ) {

View file

@ -7,6 +7,13 @@
(function( $, undefined ) {
//auto self-init widgets
$( document ).bind( "pagecreate", function( e ){
$( "button, [type='button'], [type='submit'], [type='reset'], [type='image']", e.target )
.not( ":jqmData(role='none'), :jqmData(role='nojs')" )
.button();
});
$.widget( "mobile.button", $.mobile.widget, {
options: {
theme: null,

View file

@ -7,6 +7,13 @@
(function( $, undefined ) {
//auto self-init widgets
$( document ).bind( "pagecreate", function( e ){
$( "input[type='checkbox'],input[type='radio']", e.target )
.not( ":jqmData(role='none'), :jqmData(role='nojs')" )
.checkboxradio();
});
$.widget( "mobile.checkboxradio", $.mobile.widget, {
options: {
theme: null

View file

@ -7,6 +7,13 @@
(function( $, undefined ) {
//auto self-init widgets
$( document ).bind( "pagecreate", function( e ){
$( "select:not(:jqmData(role='slider'))", e.target )
.not( ":jqmData(role='none'), :jqmData(role='nojs')" )
.selectmenu();
});
$.widget( "mobile.selectmenu", $.mobile.widget, {
options: {
theme: null,

View file

@ -7,6 +7,23 @@
( function( $, undefined ) {
//auto self-init widgets
$( document ).bind( "pagecreate", function( e ){
var nativeSel = ":jqmData(role='none'), :jqmData(role='nojs')";
//degrade range back to number type
$( "input[type='range']:not("+ nativeSel +")", e.target ).each(function(){
$(this).replaceWith(
$( "<div>" ).html( $(this).clone() ).html()
.replace( /\s+type=["']?\w+['"]?/, " type=\"number\" data-" + $.mobile.ns + "role=\"slider\" " ) );
});
//now self-init
$( ":jqmData(role='slider'):not("+ nativeSel +")", e.target ).slider();
});
$.widget( "mobile.slider", $.mobile.widget, {
options: {
theme: null,

View file

@ -7,6 +7,13 @@
(function( $, undefined ) {
//auto self-init widgets
$( document ).bind( "pagecreate", function( e ){
$( "input[type='text'], input[type='search'], input[type='number'], input[type='password'], textarea", e.target )
.not( ":jqmData(role='none'), :jqmData(role='nojs')" )
.textinput();
});
$.widget( "mobile.textinput", $.mobile.widget, {
options: {
theme: null

20
js/jquery.mobile.links.js Normal file
View file

@ -0,0 +1,20 @@
/*
* jQuery Mobile Framework : "fieldcontain" plugin - simple class additions to make form row separators
* Copyright (c) jQuery Project
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*/
(function( $, undefined ) {
$( document ).bind( "pagecreate", function( e ){
//links within content areas
$( e.target )
.find( "a" )
.not( ".ui-btn, .ui-link-inherit, :jqmData(role='none'), :jqmData(role='nojs')" )
.addClass( "ui-link" );
});
})( jQuery );

View file

@ -6,6 +6,12 @@
*/
(function( $, undefined ) {
//auto self-init widgets
$( document ).bind( "pagecreate", function( e ){
$( ":jqmData(role='listview')", e.target ).listview();
});
//Keeps track of the number of lists per page UID
//This allows support for multiple nested list in the same page
//https://github.com/jquery/jquery-mobile/issues/1617

View file

@ -0,0 +1,102 @@
/*
* jQuery Mobile Framework : resolution and CSS media query related helpers and behavior
* Copyright (c) jQuery Project
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*/
(function( $, undefined ) {
var $window = $( window ),
$html = $( "html" ),
//media-query-like width breakpoints, which are translated to classes on the html element
resolutionBreakpoints = [ 320, 480, 768, 1024 ];
/*
private function for adding/removing breakpoint classes to HTML element for faux media-query support
It does not require media query support, instead using JS to detect screen width > cross-browser support
This function is called on orientationchange, resize, and mobileinit, and is bound via the 'htmlclass' event namespace
*/
function detectResolutionBreakpoints() {
var currWidth = $window.width(),
minPrefix = "min-width-",
maxPrefix = "max-width-",
minBreakpoints = [],
maxBreakpoints = [],
unit = "px",
breakpointClasses;
$html.removeClass( minPrefix + resolutionBreakpoints.join(unit + " " + minPrefix) + unit + " " +
maxPrefix + resolutionBreakpoints.join( unit + " " + maxPrefix) + unit );
$.each( resolutionBreakpoints, function( i, breakPoint ) {
if( currWidth >= breakPoint ) {
minBreakpoints.push( minPrefix + breakPoint + unit );
}
if( currWidth <= breakPoint ) {
maxBreakpoints.push( maxPrefix + breakPoint + unit );
}
});
if ( minBreakpoints.length ) {
breakpointClasses = minBreakpoints.join(" ");
}
if ( maxBreakpoints.length ) {
breakpointClasses += " " + maxBreakpoints.join(" ");
}
$html.addClass( breakpointClasses );
};
/* $.mobile.addResolutionBreakpoints method:
pass either a number or an array of numbers and they'll be added to the min/max breakpoint classes
Examples:
$.mobile.addResolutionBreakpoints( 500 );
$.mobile.addResolutionBreakpoints( [500, 1200] );
*/
$.mobile.addResolutionBreakpoints = function( newbps ) {
if( $.type( newbps ) === "array" ){
resolutionBreakpoints = resolutionBreakpoints.concat( newbps );
} else {
resolutionBreakpoints.push( newbps );
}
resolutionBreakpoints.sort(function( a, b ) {
return a - b;
});
detectResolutionBreakpoints();
};
/* on mobileinit, add classes to HTML element
and set handlers to update those on orientationchange and resize
*/
$( document ).bind( "mobileinit.htmlclass", function() {
// bind to orientationchange and resize
// to add classes to HTML element for min/max breakpoints and orientation
var ev = $.support.orientation;
$window.bind( "orientationchange.htmlclass throttledResize.htmlclass", function( event ) {
// add orientation class to HTML element on flip/resize.
if ( event.orientation ) {
$html.removeClass( "portrait landscape" ).addClass( event.orientation );
}
// add classes to HTML element for min/max breakpoints
detectResolutionBreakpoints();
});
});
/* Manually trigger an orientationchange event when the dom ready event fires.
This will ensure that any viewport meta tag that may have been injected
has taken effect already, allowing us to properly calculate the width of the
document.
*/
$(function() {
//trigger event manually
$window.trigger( "orientationchange.htmlclass" );
});
})(jQuery);

View file

@ -7,11 +7,7 @@
(function( $, undefined ) {
var $window = $( window ),
$html = $( "html" ),
//media-query-like width breakpoints, which are translated to classes on the html element
resolutionBreakpoints = [ 320, 480, 768, 1024 ];
$html = $( "html" );
/* $.mobile.media method: pass a CSS media type or query and get a bool return
note: this feature relies on actual media query support for media queries, though types will work most anywhere
@ -48,91 +44,4 @@ $.mobile.media = (function() {
};
})();
/*
private function for adding/removing breakpoint classes to HTML element for faux media-query support
It does not require media query support, instead using JS to detect screen width > cross-browser support
This function is called on orientationchange, resize, and mobileinit, and is bound via the 'htmlclass' event namespace
*/
function detectResolutionBreakpoints() {
var currWidth = $window.width(),
minPrefix = "min-width-",
maxPrefix = "max-width-",
minBreakpoints = [],
maxBreakpoints = [],
unit = "px",
breakpointClasses;
$html.removeClass( minPrefix + resolutionBreakpoints.join(unit + " " + minPrefix) + unit + " " +
maxPrefix + resolutionBreakpoints.join( unit + " " + maxPrefix) + unit );
$.each( resolutionBreakpoints, function( i, breakPoint ) {
if( currWidth >= breakPoint ) {
minBreakpoints.push( minPrefix + breakPoint + unit );
}
if( currWidth <= breakPoint ) {
maxBreakpoints.push( maxPrefix + breakPoint + unit );
}
});
if ( minBreakpoints.length ) {
breakpointClasses = minBreakpoints.join(" ");
}
if ( maxBreakpoints.length ) {
breakpointClasses += " " + maxBreakpoints.join(" ");
}
$html.addClass( breakpointClasses );
};
/* $.mobile.addResolutionBreakpoints method:
pass either a number or an array of numbers and they'll be added to the min/max breakpoint classes
Examples:
$.mobile.addResolutionBreakpoints( 500 );
$.mobile.addResolutionBreakpoints( [500, 1200] );
*/
$.mobile.addResolutionBreakpoints = function( newbps ) {
if( $.type( newbps ) === "array" ){
resolutionBreakpoints = resolutionBreakpoints.concat( newbps );
} else {
resolutionBreakpoints.push( newbps );
}
resolutionBreakpoints.sort(function( a, b ) {
return a - b;
});
detectResolutionBreakpoints();
};
/* on mobileinit, add classes to HTML element
and set handlers to update those on orientationchange and resize
*/
$( document ).bind( "mobileinit.htmlclass", function() {
// bind to orientationchange and resize
// to add classes to HTML element for min/max breakpoints and orientation
var ev = $.support.orientation;
$window.bind( "orientationchange.htmlclass throttledResize.htmlclass", function( event ) {
// add orientation class to HTML element on flip/resize.
if ( event.orientation ) {
$html.removeClass( "portrait landscape" ).addClass( event.orientation );
}
// add classes to HTML element for min/max breakpoints
detectResolutionBreakpoints();
});
});
/* Manually trigger an orientationchange event when the dom ready event fires.
This will ensure that any viewport meta tag that may have been injected
has taken effect already, allowing us to properly calculate the width of the
document.
*/
$(function() {
//trigger event manually
$window.trigger( "orientationchange.htmlclass" );
});
})(jQuery);

View file

@ -7,6 +7,11 @@
(function( $, undefined ) {
//auto self-init widgets
$( document ).bind( "pagecreate", function( e ){
$( ":jqmData(role='navbar')", e.target ).navbar();
});
$.widget( "mobile.navbar", $.mobile.widget, {
options: {
iconpos: "top",

15
js/jquery.mobile.nojs.js Normal file
View file

@ -0,0 +1,15 @@
/*
* jQuery Mobile Framework : "fieldcontain" plugin - simple class additions to make form row separators
* Copyright (c) jQuery Project
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*/
(function( $, undefined ) {
$( document ).bind( "pagecreate", function( e ){
$( ":jqmData(role='nojs')", e.target ).addClass( "ui-nojs" );
});
})( jQuery );

View file

@ -9,220 +9,18 @@
$.widget( "mobile.page", $.mobile.widget, {
options: {
backBtnText: "Back",
addBackBtn: false,
backBtnTheme: null,
degradeInputs: {
color: false,
date: false,
datetime: false,
"datetime-local": false,
email: false,
month: false,
number: false,
range: "number",
search: true,
tel: false,
time: false,
url: false,
week: false
},
keepNative: null
theme: "c"
},
_create: function() {
var $elem = this.element,
o = this.options;
this.keepNative = ":jqmData(role='none'), :jqmData(role='nojs')" +
( o.keepNative ? ", " + o.keepNative : "" );
if ( this._trigger( "beforeCreate" ) === false ) {
return;
}
// 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.
$elem.find( ":jqmData(role='page'), :jqmData(role='content')" ).andSelf().each(function() {
var $this = $( this );
$this.addClass( "ui-" + $this.jqmData( "role" ) );
});
$elem.find( ":jqmData(role='nojs')" ).addClass( "ui-nojs" );
// Pre-find data els
var $dataEls = $elem.find( ":jqmData(role)" ).andSelf().each(function() {
var $this = $( this ),
role = $this.jqmData( "role" ),
theme = $this.jqmData( "theme" ),
$headeranchors,
leftbtn, rightbtn, backBtn;
//apply theming and markup modifications to page,header,content,footer
if ( role === "header" || role === "footer" ) {
$this.addClass( "ui-bar-" + (theme || $this.parent( ":jqmData(role='page')" ).jqmData( "theme" ) || "a") );
// Add ARIA role
$this.attr( "role", role === "header" ? "banner" : "contentinfo" );
// Right,left buttons
$headeranchors = $this.children( "a" );
leftbtn = $headeranchors.hasClass( "ui-btn-left" );
rightbtn = $headeranchors.hasClass( "ui-btn-right" );
if ( !leftbtn ) {
leftbtn = $headeranchors.eq( 0 ).not( ".ui-btn-right" ).addClass( "ui-btn-left" ).length;
}
if ( !rightbtn ) {
rightbtn = $headeranchors.eq( 1 ).addClass( "ui-btn-right" ).length;
}
// Auto-add back btn on pages beyond first view
if ( o.addBackBtn && role === "header" &&
$( ".ui-page" ).length > 1 &&
$elem.jqmData( "url" ) !== $.mobile.path.stripHash( location.hash ) &&
!leftbtn && $this.jqmData( "backbtn" ) !== false ) {
backBtn = $( "<a href='#' class='ui-btn-left' data-"+ $.mobile.ns +"rel='back' data-"+ $.mobile.ns +"icon='arrow-l'>"+ o.backBtnText +"</a>" ).prependTo( $this );
// If theme is provided, override default inheritance
if ( o.backBtnTheme ) {
backBtn.attr( "data-"+ $.mobile.ns +"theme", o.backBtnTheme );
}
}
// Page title
$this.children( "h1, h2, h3, h4, h5, h6" )
.addClass( "ui-title" )
// Regardless of h element number in src, it becomes h1 for the enhanced page
.attr({
"tabindex": "0",
"role": "heading",
"aria-level": "1"
});
} else if ( role === "content" ) {
if ( theme ) {
$this.addClass( "ui-body-" + theme );
}
// Add ARIA role
$this.attr( "role", "main" );
} else if ( role === "page" ) {
$this.addClass( "ui-body-" + (theme || "c") );
}
switch ( role ) {
case "header":
case "footer":
case "page":
case "content":
$this.addClass( "ui-" + role );
break;
case "collapsible":
case "fieldcontain":
case "navbar":
case "listview":
case "dialog":
$this[ role ]();
break;
}
});
//enhance form controls
this._enhanceControls();
//links in bars, or those with data-role become buttons
$elem.find( ":jqmData(role='button'), .ui-bar > a, .ui-header > a, .ui-footer > a" )
.not( ".ui-btn" )
.not( this.keepNative )
.buttonMarkup();
$elem.find( ":jqmData(role='controlgroup')" )
.controlgroup({
dontFilterOutInvisible: true
});
// Links within content areas
$elem.find( "a:not(.ui-btn):not(.ui-link-inherit)" )
.not( this.keepNative )
.addClass( "ui-link" );
// Fix toolbars
$elem.fixHeaderFooter();
},
_typeAttributeRegex: /\s+type=["']?\w+['"]?/,
_enhanceControls: function() {
var o = this.options,
self = this,
allControls, nonNativeControls, textInputs;
// degrade inputs to avoid poorly implemented native functionality
this.element.find( "input" ).not(this.keepNative).each(function() {
var $this = $( this ),
type = this.getAttribute( "type" ),
optType = o.degradeInputs[ type ] || "text";
if ( o.degradeInputs[ type ] ) {
$this.replaceWith(
$( "<div>" ).html( $this.clone() ).html()
.replace( self._typeAttributeRegex, " type=\"" + optType + "\" data-" + $.mobile.ns + "type=\"" + type + "\" " )
);
}
});
// We re-find form elements since the degredation code above
// may have injected new elements. We cache the non-native control
// query to reduce the number of times we search through the entire page.
allControls = this.element.find("input, textarea, select, button");
nonNativeControls = allControls.not(this.keepNative);
// XXX: Temporary workaround for issue 785. Turn off autocorrect and
// autocomplete since the popup they use can't be dismissed by
// the user. Note that we test for the presence of the feature
// by looking for the autocorrect property on the input element.
textInputs = allControls.filter( "input[type=text]" );
if ( textInputs.length && typeof textInputs[0].autocorrect !== "undefined" ) {
textInputs.each(function() {
// Set the attribute instead of the property just in case there
// is code that attempts to make modifications via HTML.
this.setAttribute( "autocorrect", "off" );
this.setAttribute( "autocomplete", "off" );
});
}
// enchance form controls
nonNativeControls
.filter( "[type='radio'], [type='checkbox']" )
.checkboxradio();
nonNativeControls
.filter( "button, [type='button'], [type='submit'], [type='reset'], [type='image']" )
.button();
nonNativeControls
.filter( "input, textarea" )
.not( "[type='radio'], [type='checkbox'], [type='button'], [type='submit'], [type='reset'], [type='image'], [type='hidden']" )
.textinput();
nonNativeControls
.filter( "input, select" )
.filter( ":jqmData(role='slider'), :jqmData(type='range')" )
.slider();
nonNativeControls
.filter( "select:not(:jqmData(role='slider'))" )
.selectmenu();
$elem.addClass( "ui-page ui-body-" + o.theme );
}
});

View file

@ -0,0 +1,91 @@
/*
* jQuery Mobile Framework : This plugin handles theming and layout of headers, footers, and content areas
* Copyright (c) jQuery Project
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*/
(function( $, undefined ) {
$.mobile.page.prototype.options.backBtnText = "Back";
$.mobile.page.prototype.options.addBackBtn = false;
$.mobile.page.prototype.options.backBtnTheme = null;
$.mobile.page.prototype.options.headerTheme = "a";
$.mobile.page.prototype.options.footerTheme = "a";
$.mobile.page.prototype.options.contentTheme = null;
$( ":jqmData(role='page'), :jqmData(role='dialog')" ).live( "pagecreate", function( e ) {
var $page = $( this ),
o = $page.data( "page" ).options,
pageTheme = o.theme;
$( ":jqmData(role='header'), :jqmData(role='footer'), :jqmData(role='content')", this ).each(function() {
var $this = $( this ),
role = $this.jqmData( "role" ),
theme = $this.jqmData( "theme" ),
$headeranchors,
leftbtn,
rightbtn,
backBtn;
$this.addClass( "ui-" + role );
//apply theming and markup modifications to page,header,content,footer
if ( role === "header" || role === "footer" ) {
var thisTheme = theme || pageTheme || ( role === "header" ? o.headerTheme : o.footerTheme );
//add theme class
$this.addClass( "ui-bar-" + thisTheme );
// Add ARIA role
$this.attr( "role", role === "header" ? "banner" : "contentinfo" );
// Right,left buttons
$headeranchors = $this.children( "a" );
leftbtn = $headeranchors.hasClass( "ui-btn-left" );
rightbtn = $headeranchors.hasClass( "ui-btn-right" );
if ( !leftbtn ) {
leftbtn = $headeranchors.eq( 0 ).not( ".ui-btn-right" ).addClass( "ui-btn-left" ).length;
}
if ( !rightbtn ) {
rightbtn = $headeranchors.eq( 1 ).addClass( "ui-btn-right" ).length;
}
// Auto-add back btn on pages beyond first view
if ( o.addBackBtn && role === "header" &&
$( ".ui-page" ).length > 1 &&
$elem.jqmData( "url" ) !== $.mobile.path.stripHash( location.hash ) &&
!leftbtn ) {
backBtn = $( "<a href='#' class='ui-btn-left' data-"+ $.mobile.ns +"rel='back' data-"+ $.mobile.ns +"icon='arrow-l'>"+ o.backBtnText +"</a>" ).prependTo( $this );
// If theme is provided, override default inheritance
backBtn.attr( "data-"+ $.mobile.ns +"theme", o.backBtnTheme || thisTheme );
}
// Page title
$this.children( "h1, h2, h3, h4, h5, h6" )
.addClass( "ui-title" )
// Regardless of h element number in src, it becomes h1 for the enhanced page
.attr({
"tabindex": "0",
"role": "heading",
"aria-level": "1"
});
} else if ( role === "content" ) {
$this.addClass( "ui-body-" + ( theme || pageTheme || o.contentTheme ) );
// Add ARIA role
$this.attr( "role", "main" );
}
});
});
})( jQuery );

View file

@ -0,0 +1,53 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>jQuery Mobile Page Test Suite</title>
<script src="../../../js/jquery.js"></script>
<script src="../jquery.setNameSpace.js"></script>
<script src="../../../js/"></script>
<script src="../../../tests/jquery.testHelper.js"></script>
<link rel="stylesheet" href="../../../external/qunit.css"/>
<script src="../../../external/qunit.js"></script>
<script src="page_core.js"></script>
</head>
<body>
<h1 id="qunit-header">jQuery Mobile Page Test Suite</h1>
<h2 id="qunit-banner"></h2>
<h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests">
</ol>
<div id="qunit-fixture">
<div data-nstest-role="page">
<div data-nstest-role="header">
<div>
<a href="foo">foo</a>
</div>
<a href="foo">foo</a>
</div><!-- /header -->
<div data-nstest-role="footer">
<div>
<a href="foo">foo</a>
</div>
<a href="foo">foo</a>
</div><!-- /header -->
<div class="ui-bar">
<div>
<a href="foo">foo</a>
</div>
<a href="foo">foo</a>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,36 @@
/*
* mobile page unit tests
*/
(function($){
var libName = 'jquery.mobile.page.js';
module(libName);
test( "nested header anchors aren't altered", function(){
ok(!$('.ui-header > div > a').hasClass('ui-btn'));
});
test( "nested footer anchors aren't altered", function(){
ok(!$('.ui-footer > div > a').hasClass('ui-btn'));
});
test( "nested bar anchors aren't styled", function(){
ok(!$('.ui-bar > div > a').hasClass('ui-btn'));
});
test( "unnested footer anchors are styled", function(){
ok($('.ui-footer > a').hasClass('ui-btn'));
});
test( "unnested footer anchors are styled", function(){
ok($('.ui-footer > a').hasClass('ui-btn'));
});
test( "unnested bar anchors are styled", function(){
ok($('.ui-bar > a').hasClass('ui-btn'));
});
test( "no auto-generated back button exists on first page", function(){
ok( !$(".ui-header > :jqmData(rel='back')").length );
});
})(jQuery);

View file

@ -25,29 +25,12 @@
</ol>
<div id="qunit-fixture">
<div data-nstest-role="page">
<div data-nstest-role="header">
<div>
<a href="foo">foo</a>
</div>
<a href="foo">foo</a>
</div><!-- /header -->
<div data-nstest-role="footer">
<div>
<a href="foo">foo</a>
</div>
<a href="foo">foo</a>
</div><!-- /header -->
<div class="ui-bar">
<div>
<a href="foo">foo</a>
</div>
<a href="foo">foo</a>
</div>
</div>
<div id="a" data-nstest-role="page"></div>
<div id="b" data-nstest-role="page" data-nstest-theme="e"></div>
<div id="c" data-nstest-role="page"></div>
</div>
</body>
</html>

View file

@ -2,47 +2,77 @@
* mobile page unit tests
*/
(function($){
var libName = 'jquery.mobile.page.js',
typeAttributeRegex = $.mobile.page.prototype._typeAttributeRegex;
var libName = 'jquery.mobile.page.sections.js',
themedefault = $.mobile.page.prototype.options.theme;
module(libName);
var eventStack = [],
etargets = [],
cEvents=[],
cTargets=[];
test( "nested header anchors aren't altered", function(){
ok(!$('.ui-header > div > a').hasClass('ui-btn'));
$( document ).bind( "pagebeforecreate pagecreate", function( e ){
eventStack.push( e.type );
etargets.push( e.target );
});
$("#c").live( "pagebeforecreate", function( e ){
cEvents.push( e.type );
cTargets.push( e.target );
return false;
});
test( "nested footer anchors aren't altered", function(){
ok(!$('.ui-footer > div > a').hasClass('ui-btn'));
test( "pagecreate event fires when page is created", function(){
ok( eventStack[0] === "pagecreate" || eventStack[1] === "pagecreate" );
});
test( "pagebeforecreate event fires when page is created", function(){
ok( eventStack[0] === "pagebeforecreate" || eventStack[1] === "pagebeforecreate" );
});
test( "pagebeforecreate fires before pagecreate", function(){
ok( eventStack[0] === "pagebeforecreate" );
});
test( "target of pagebeforecreate event was div #a", function(){
ok( $( etargets[0] ).is("#a") );
});
test( "target of pagecreate event was div #a" , function(){
ok( $( etargets[0] ).is("#a") );
});
test( "page element has ui-page class" , function(){
ok( $( "#a" ).hasClass( "ui-page" ) );
});
test( "page element has default body theme when not overidden" , function(){
ok( $( "#a" ).hasClass( "ui-body-" + themedefault ) );
});
test( "B page has non-default theme matching its data-theme attr" , function(){
$( "#b" ).page();
var btheme = $( "#b" ).jqmData( "theme" );
ok( $( "#b" ).hasClass( "ui-body-" + btheme ) );
});
test( "nested bar anchors aren't styled", function(){
ok(!$('.ui-bar > div > a').hasClass('ui-btn'));
});
test( "unnested footer anchors are styled", function(){
ok($('.ui-footer > a').hasClass('ui-btn'));
});
test( "Binding to pagebeforecreate and returning false prevents pagecreate event from firing" , function(){
$("#c").page();
test( "unnested footer anchors are styled", function(){
ok($('.ui-footer > a').hasClass('ui-btn'));
ok( cEvents[0] === "pagebeforecreate" );
ok( !cTargets[1] );
});
test( "unnested bar anchors are styled", function(){
ok($('.ui-bar > a').hasClass('ui-btn'));
test( "Binding to pagebeforecreate and returning false prevents classes from being applied to page" , function(){
ok( !$( "#b" ).hasClass( "ui-body-" + themedefault ) );
ok( !$( "#b" ).hasClass( "ui-page" ) );
});
test( "no auto-generated back button exists on first page", function(){
ok( !$(".ui-header > :jqmData(rel='back')").length );
});
test( "input type replacement regex works properly", function(){
ok(typeAttributeRegex.test( "<input type=range" ), "test no quotes" );
ok(typeAttributeRegex.test( "<input type='range'" ), "test single quotes" );
ok(typeAttributeRegex.test( "<input type=\"range\"" ), "test double quotes" );
ok(typeAttributeRegex.test( "<input type=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_"), "test \w" );
ok(typeAttributeRegex.test( "<input type=\"range\"" ), "test many preceding spaces" );
ok(typeAttributeRegex.test( "<input type='range'>" ), "test final attribute (FF)" );
ok(!typeAttributeRegex.test( "<inputtype=\"range\"" ), "requires preceding space" );
});
})(jQuery);

View file

@ -99,6 +99,10 @@
slider.keyup();
same(slider.val(), "200");
});
test( "input type should degrade to number when slider is created", function(){
same($("#range-slider-up").attr( "type" ), "number");
});
// generic switch test function
var sliderSwitchTest = function(opts){