whitespace and comment merge conflict resolved

This commit is contained in:
John Bender 2011-08-17 11:09:05 -07:00
commit b7d5df786f
33 changed files with 1699 additions and 898 deletions

View file

@ -61,6 +61,7 @@ JSFILES = js/jquery.ui.widget.js \
js/jquery.mobile.forms.button.js \
js/jquery.mobile.forms.slider.js \
js/jquery.mobile.forms.textinput.js \
js/jquery.mobile.forms.select.custom.js \
js/jquery.mobile.forms.select.js \
js/jquery.mobile.buttonMarkup.js \
js/jquery.mobile.controlGroup.js \
@ -127,7 +128,7 @@ notify:
@@echo "The files have been built and are in " $$(pwd)/${OUTPUT}
# Pull the latest commits. This is used for the nightly build but can be used to save some keystrokes
pull:
pull:
@@git pull --quiet
# Zip the 4 files and the theme images into one convenient package
@ -162,7 +163,7 @@ nightly: pull zip
# Change the empty paths to the location of this nightly file
@@find ${VER} -type f -name '*.html' -exec sed -i 's|href="themes/default/"|href="${NIGHTLY_WEBPATH}/${DIR}.min.css"|g' {} \;
@@find ${VER} -type f -name '*.html' -exec sed -i 's|src="js/jquery.js"|src="http://code.jquery.com/jquery-${JQUERY}.min.js"|' {} \;
@@find ${VER} -type f -name '*.html' -exec sed -i 's|src="js/"|src="${NIGHTLY_WEBPATH}/${DIR}.min.js"|g' {} \;
@@find ${VER} -type f -name '*.html' -exec sed -i 's|src="js/"|src="${NIGHTLY_WEBPATH}/${DIR}.min.js"|g' {} \;
# Move the demos into the output folder
@@mv ${VER} ${OUTPUT}/demos

View file

@ -31,7 +31,7 @@
js/jquery.mobile.navigation.js,
js/jquery.mobile.navigation.pushstate.js,
js/jquery.mobile.transition.js,
js/jquery.mobile.degradeInputs.js,
js/jquery.mobile.degradeInputs.js,
js/jquery.mobile.dialog.js,
js/jquery.mobile.page.sections.js,
js/jquery.mobile.collapsible.js,
@ -45,6 +45,7 @@
js/jquery.mobile.forms.button.js,
js/jquery.mobile.forms.slider.js,
js/jquery.mobile.forms.textinput.js,
js/jquery.mobile.forms.select.custom.js,
js/jquery.mobile.forms.select.js,
js/jquery.mobile.buttonMarkup.js,
js/jquery.mobile.controlGroup.js,

View file

@ -0,0 +1,102 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>jQuery Mobile Docs - Slider events</title>
<link rel="stylesheet" href="../../../themes/default/" />
<link rel="stylesheet" href="../../_assets/css/jqm-docs.css"/>
<script src="../../../js/jquery.js"></script>
<script src="../../../experiments/themeswitcher/jquery.mobile.themeswitcher.js"></script>
<script src="../../_assets/js/jqm-docs.js"></script>
<script src="../../../js/"></script>
</head>
<body>
<div data-role="page" class="type-interior">
<div data-role="header" data-theme="f">
<h1>Sliders</h1>
<a href="../../../" data-icon="home" data-iconpos="notext" data-direction="reverse" class="ui-btn-right jqm-home">Home</a>
</div><!-- /header -->
<div data-role="content">
<div class="content-primary">
<form action="#" method="get">
<h2>Sliders</h2>
<ul data-role="controlgroup" data-type="horizontal" class="localnav">
<li><a href="index.html" data-role="button" data-transition="fade">Basics</a></li>
<li><a href="options.html" data-role="button" data-transition="fade">Options</a></li>
<li><a href="methods.html" data-role="button" data-transition="fade">Methods</a></li>
<li><a href="events.html" data-role="button" data-transition="fade" class="ui-btn-active">Events</a></li>
</ul>
<p>Since the input field is changed by slider, you can watch for events on the input field instead of needing to go through the slider plugin. Bind to the change event.</p>
<pre><code>
$( ".selector" ).bind( "change", function(event, ui) {
...
});
</code></pre>
<p>The slider plugin has the following custom event:</p>
<dl>
<dt><code>create</code> triggered when a slider is created</dt>
<dd>
<p>This event is used to find out when a custom slider was created. It is not used to create a custom slider. The slider create event can be used like this: </p>
<pre><code>
$( ".selector" ).slider({
create: function(event, ui) { ... }
});
</code></pre>
</dd>
</dl>
</form>
</div><!--/content-primary -->
<div class="content-secondary">
<div data-role="collapsible" data-collapsed="true" data-theme="b">
<h3>More in this section</h3>
<ul data-role="listview" data-theme="c" data-dividertheme="d">
<li data-role="list-divider">Form elements</li>
<li><a href="../docs-forms.html">Form basics</a></li>
<li><a href="../forms-all.html">Form element gallery</a></li>
<li><a href="../texts/index.html">Text inputs</a></li>
<li><a href="../forms-search.html">Search inputs</a></li>
<li data-theme="a"><a href="index.html">Slider</a></li>
<li><a href="../forms-switch.html">Flip toggle switch</a></li>
<li><a href="../radiobuttons/index.html">Radio buttons</a></li>
<li><a href="../checkboxes/index.html">Checkboxes</a></li>
<li><a href="../selects/index.html">Select menus</a></li>
<li><a href="../forms-themes.html">Theming forms</a></li>
<li><a href="../forms-all-native.html">Native form elements</a></li>
<li><a href="../forms-sample.html">Submitting forms</a></li>
<li><a href="../plugin-eventsmethods.html">Plugin methods</a></li>
</ul>
</div>
</div>
</div><!-- /content -->
<div data-role="footer" class="footer-docs" data-theme="c">
<p>&copy; 2011 The jQuery Project</p>
</div>
</div><!-- /page -->
</body>
</html>

View file

@ -0,0 +1,112 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>jQuery Mobile Docs - Sliders</title>
<link rel="stylesheet" href="../../../themes/default/" />
<link rel="stylesheet" href="../../_assets/css/jqm-docs.css"/>
<script src="../../../js/jquery.js"></script>
<script src="../../../experiments/themeswitcher/jquery.mobile.themeswitcher.js"></script>
<script src="../../_assets/js/jqm-docs.js"></script>
<script src="../../../js/"></script>
</head>
<body>
<div data-role="page" class="type-interior">
<div data-role="header" data-theme="f">
<h1>Sliders</h1>
<a href="../../../" data-icon="home" data-iconpos="notext" data-direction="reverse" class="ui-btn-right jqm-home">Home</a>
</div><!-- /header -->
<div data-role="content">
<div class="content-primary">
<form action="#" method="get">
<h2>Sliders</h2>
<ul data-role="controlgroup" data-type="horizontal" class="localnav">
<li><a href="index.html" data-role="button" data-transition="fade" class="ui-btn-active">Basics</a></li>
<li><a href="options.html" data-role="button" data-transition="fade">Options</a></li>
<li><a href="methods.html" data-role="button" data-transition="fade">Methods</a></li>
<li><a href="events.html" data-role="button" data-transition="fade">Events</a></li>
</ul>
<p>To add a slider widget to your page, start with an <code>input</code> with a new HTML5 <code>type="range"</code> attribute. Specify the <code>value</code> (current value), <code>min</code> and <code>max</code> attribute values to configure the slider. The framework will parse these attributes to configure the slider. </p>
<p>As you drag the slider, the input will update and vice-versa so they are always in sync so you can submit the slider value with form in a simple way. Set the <code>for</code> attribute of the <code>label</code> to match the ID of the <code>input</code> so they are semantically associated and wrap them in a <code>div</code> with the <code> data-role="fieldcontain"</code> attribute to group them.</p>
<pre><code>
&lt;div data-role=&quot;fieldcontain&quot;&gt;
&lt;label for=&quot;slider&quot;&gt;Input slider:&lt;/label&gt;
&lt;input type=&quot;range&quot; name=&quot;slider&quot; id=&quot;slider&quot; value=&quot;0&quot; min=&quot;0&quot; max=&quot;100&quot; /&gt;
&lt;/div&gt;
</code></pre>
<p>An example of a slider and input is displayed like this:</p>
<div data-role="fieldcontain">
<label for="slider-1">Input slider:</label>
<input type="range" name="slider-1" id="slider-1" value="0" min="0" max="100" data-theme="b" data-track-theme="a" />
</div>
<p>By setting the <code>min</code> and <code>max</code>attributes you can configure the allowable number range of the slider track. The <code>value</code> of the input is used to configure the starting position of the handle and the value populated in the text input.</p>
<p>The slider with a min of 500, max of 5,000 and initial value of 2,500</p>
<div data-role="fieldcontain">
<label for="slider-2">Input slider:</label>
<input type="range" name="slider-2" id="slider-2" value="2500" min="500" max="5000" />
</div>
<p>Sliders also respond to the keyboards shortcuts. To increase the current value the Right Arrow, Up Arrow, and Page Up keys can be used. To decrease the current value the Left Arrow, Down Arrow, and Page Down keys can be used. To move the slider to its minimum or maximum value use the Home and End keys respectively.</p>
<h2>Refreshing a slider</h2>
<p>If you manipulate a slider via JavaScript, you must call the refresh method on it to update the visual styling. Here is an example:</p>
<code>
$("input[type=range]").val(60).slider("refresh");
</code>
</form>
</div><!--/content-primary -->
<div class="content-secondary">
<div data-role="collapsible" data-collapsed="true" data-theme="b">
<h3>More in this section</h3>
<ul data-role="listview" data-theme="c" data-dividertheme="d">
<li data-role="list-divider">Form elements</li>
<li><a href="../docs-forms.html">Form basics</a></li>
<li><a href="../forms-all.html">Form element gallery</a></li>
<li><a href="../texts/index.html">Text inputs</a></li>
<li><a href="../forms-search.html">Search inputs</a></li>
<li data-theme="a"><a href="index.html">Slider</a></li>
<li><a href="../forms-switch.html">Flip toggle switch</a></li>
<li><a href="../radiobuttons/index.html">Radio buttons</a></li>
<li><a href="../checkboxes/index.html">Checkboxes</a></li>
<li><a href="../selects/index.html">Select menus</a></li>
<li><a href="../forms-themes.html">Theming forms</a></li>
<li><a href="../forms-all-native.html">Native form elements</a></li>
<li><a href="../forms-sample.html">Submitting forms</a></li>
<li><a href="../plugin-eventsmethods.html">Plugin methods</a></li>
</ul>
</div>
</div>
</div><!-- /content -->
<div data-role="footer" class="footer-docs" data-theme="c">
<p>&copy; 2011 The jQuery Project</p>
</div>
</div><!-- /page -->
</body>
</html>

View file

@ -0,0 +1,108 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>jQuery Mobile Docs - Slider methods</title>
<link rel="stylesheet" href="../../../themes/default/" />
<link rel="stylesheet" href="../../_assets/css/jqm-docs.css"/>
<script src="../../../js/jquery.js"></script>
<script src="../../../experiments/themeswitcher/jquery.mobile.themeswitcher.js"></script>
<script src="../../_assets/js/jqm-docs.js"></script>
<script src="../../../js/"></script>
</head>
<body>
<div data-role="page" class="type-interior">
<div data-role="header" data-theme="f">
<h2>Sliders</h2>
<a href="../../../" data-icon="home" data-iconpos="notext" data-direction="reverse" class="ui-btn-right jqm-home">Home</a>
</div><!-- /header -->
<div data-role="content">
<div class="content-primary">
<form action="#" method="get">
<h2>Sliders</h2>
<ul data-role="controlgroup" data-type="horizontal" class="localnav">
<li><a href="index.html" data-role="button" data-transition="fade">Basics</a></li>
<li><a href="options.html" data-role="button" data-transition="fade">Options</a></li>
<li><a href="methods.html" data-role="button" data-transition="fade" class="ui-btn-active">Methods</a></li>
<li><a href="events.html" data-role="button" data-transition="fade">Events</a></li>
</ul>
<p>The slider plugin has the following methods:</p>
<dl>
<dt><code>enable</code> enable a disabled select</dt>
<dd>
<pre><code>
$('select').slider('enable');
</code></pre>
</dd>
<dt><code>disable</code> disable a select.</dt>
<dd>
<pre><code>
$('select').slider('disable');
</code></pre>
</dd>
<dt><code>refresh</code> update the slider</dt>
<dd>
This is used to update the slider to reflect the native input element's value. Also, if you pass a true argument you can force the rebuild to happen.
<pre><code>
//refresh value
$('select').slider('refresh');
//refresh and force rebuild
$('select').slider('refresh', true);
</code></pre>
</dd>
</dl>
</form>
</div><!--/content-primary -->
<div class="content-secondary">
<div data-role="collapsible" data-collapsed="true" data-theme="b">
<h3>More in this section</h3>
<ul data-role="listview" data-theme="c" data-dividertheme="d">
<li data-role="list-divider">Form elements</li>
<li><a href="../docs-forms.html">Form basics</a></li>
<li><a href="../forms-all.html">Form element gallery</a></li>
<li><a href="../texts/index.html">Text inputs</a></li>
<li><a href="../forms-search.html">Search inputs</a></li>
<li data-theme="a"><a href="index.html">Slider</a></li>
<li><a href="../forms-switch.html">Flip toggle switch</a></li>
<li><a href="../radiobuttons/index.html">Radio buttons</a></li>
<li><a href="../checkboxes/index.html">Checkboxes</a></li>
<li><a href="../selects/index.html">Select menus</a></li>
<li><a href="../forms-themes.html">Theming forms</a></li>
<li><a href="../forms-all-native.html">Native form elements</a></li>
<li><a href="../forms-sample.html">Submitting forms</a></li>
<li><a href="../plugin-eventsmethods.html">Plugin methods</a></li>
</ul>
</div>
</div>
</div><!-- /content -->
<div data-role="footer" class="footer-docs" data-theme="c">
<p>&copy; 2011 The jQuery Project</p>
</div>
</div><!-- /page -->
</body>
</html>

View file

@ -0,0 +1,101 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>jQuery Mobile Docs - Slider options</title>
<link rel="stylesheet" href="../../../themes/default/" />
<link rel="stylesheet" href="../../_assets/css/jqm-docs.css"/>
<script src="../../../js/jquery.js"></script>
<script src="../../../experiments/themeswitcher/jquery.mobile.themeswitcher.js"></script>
<script src="../../_assets/js/jqm-docs.js"></script>
<script src="../../../js/"></script>
</head>
<body>
<div data-role="page" class="type-interior">
<div data-role="header" data-theme="f">
<h2>Sliders</h2>
<a href="../../../" data-icon="home" data-iconpos="notext" data-direction="reverse" class="ui-btn-right jqm-home">Home</a>
</div><!-- /header -->
<div data-role="content">
<div class="content-primary">
<form action="#" method="get">
<h2>Sliders</h2>
<ul data-role="controlgroup" data-type="horizontal" class="localnav">
<li><a href="index.html" data-role="button" data-transition="fade">Basics</a></li>
<li><a href="options.html" data-role="button" data-transition="fade" class="ui-btn-active">Options</a></li>
<li><a href="methods.html" data-role="button" data-transition="fade">Methods</a></li>
<li><a href="events.html" data-role="button" data-transition="fade">Events</a></li>
</ul>
<p>The slider plugin has the following options:</p>
<dl>
<dt><code>initSelector</code> <em>CSS selector string</em></dt>
<dd>
<p class="default">default: "input[type='range'], :jqmData(type='range'), :jqmData(role='slider')"</p>
<p>This is used to define the selectors (element types, data roles, etc.) that should be used as the trigger to automatic initialization of the widget plugin. To affect all selects, this option can be set by binding to the <a href="../../api/globalconfig.html">mobileinit event</a>:</p>
<pre><code>$( document ).bind( "mobileinit", function(){
<strong>$.mobile.slider.prototype.options.initSelector = ".myslider";</strong>
});
</code></pre>
</dd>
<dt><code>theme</code> <em>string</em></dt>
<dd>
<p class="default">default: null, inherited from parent</p>
<p>Sets the theme swatch color scheme for the select element. This is a single letter from a-z that maps to the swatches included in your theme. By default, a select will inherit the same swatch color as it's parent container if not explicitly set. This option is also exposed as a data attribute: <code>data-theme=&quot;a&quot;</code></p>
<pre><code>$('input').slider(<strong>{ theme: "a" }</strong>);</code></pre>
</dd>
</dl>
</form>
</div><!--/content-primary -->
<div class="content-secondary">
<div data-role="collapsible" data-collapsed="true" data-theme="b">
<h3>More in this section</h3>
<ul data-role="listview" data-theme="c" data-dividertheme="d">
<li data-role="list-divider">Form elements</li>
<li><a href="../docs-forms.html">Form basics</a></li>
<li><a href="../forms-all.html">Form element gallery</a></li>
<li><a href="../texts/index.html">Text inputs</a></li>
<li><a href="../forms-search.html">Search inputs</a></li>
<li data-theme="a"><a href="index.html">Slider</a></li>
<li><a href="../forms-switch.html">Flip toggle switch</a></li>
<li><a href="../radiobuttons/index.html">Radio buttons</a></li>
<li><a href="../checkboxes/index.html">Checkboxes</a></li>
<li><a href="../selects/index.html">Select menus</a></li>
<li><a href="../forms-themes.html">Theming forms</a></li>
<li><a href="../forms-all-native.html">Native form elements</a></li>
<li><a href="../forms-sample.html">Submitting forms</a></li>
<li><a href="../plugin-eventsmethods.html">Plugin methods</a></li>
</ul>
</div>
</div>
</div><!-- /content -->
<div data-role="footer" class="footer-docs" data-theme="c">
<p>&copy; 2011 The jQuery Project</p>
</div>
</div><!-- /page -->
</body>
</html>

View file

@ -14,7 +14,7 @@
<body>
<div data-role="page" class="type-interior">
<div data-role="page">
<div data-role="header" data-theme="a">
<h1>Purchase?</h1>

View file

@ -13,7 +13,7 @@ $elements = array(
'jquery.mobile.navigation.js',
'jquery.mobile.navigation.pushstate.js',
'jquery.mobile.transition.js',
'jquery.mobile.degradeInputs.js',
'jquery.mobile.degradeInputs.js',
'jquery.mobile.dialog.js',
'jquery.mobile.page.sections.js',
'jquery.mobile.collapsible.js',
@ -27,6 +27,7 @@ $elements = array(
'jquery.mobile.forms.button.js',
'jquery.mobile.forms.slider.js',
'jquery.mobile.forms.textinput.js',
'jquery.mobile.forms.select.custom.js',
'jquery.mobile.forms.select.js',
'jquery.mobile.buttonMarkup.js',
'jquery.mobile.controlGroup.js',

View file

@ -0,0 +1,469 @@
/*
* jQuery Mobile Framework : custom "selectmenu" plugin
* Copyright (c) jQuery Project
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*/
(function( $, undefined ) {
var extendSelect = function( widget ){
var select = widget.select,
selectID = widget.selectID,
label = widget.label,
thisPage = widget.select.closest( ".ui-page" ),
screen = $( "<div>", {"class": "ui-selectmenu-screen ui-screen-hidden"} ).appendTo( thisPage ),
selectOptions = widget.select.find("option"),
isMultiple = widget.isMultiple = widget.select[ 0 ].multiple,
buttonId = selectID + "-button",
menuId = selectID + "-menu",
menuPage = $( "<div data-" + $.mobile.ns + "role='dialog' data-" +$.mobile.ns + "theme='"+ widget.options.menuPageTheme +"'>" +
"<div data-" + $.mobile.ns + "role='header'>" +
"<div class='ui-title'>" + label.text() + "</div>"+
"</div>"+
"<div data-" + $.mobile.ns + "role='content'></div>"+
"</div>" ).appendTo( $.mobile.pageContainer ).page(),
listbox = $("<div>", { "class": "ui-selectmenu ui-selectmenu-hidden ui-overlay-shadow ui-corner-all ui-body-" + widget.options.overlayTheme + " " + $.mobile.defaultDialogTransition } ).insertAfter(screen),
list = $( "<ul>", {
"class": "ui-selectmenu-list",
"id": menuId,
"role": "listbox",
"aria-labelledby": buttonId
}).attr( "data-" + $.mobile.ns + "theme", widget.options.theme ).appendTo( listbox ),
header = $( "<div>", {
"class": "ui-header ui-bar-" + widget.options.theme
}).prependTo( listbox ),
headerTitle = $( "<h1>", {
"class": "ui-title"
}).appendTo( header ),
headerClose = $( "<a>", {
"text": widget.options.closeText,
"href": "#",
"class": "ui-btn-left"
}).attr( "data-" + $.mobile.ns + "iconpos", "notext" ).attr( "data-" + $.mobile.ns + "icon", "delete" ).appendTo( header ).buttonMarkup(),
menuPageContent = menuPage.find( ".ui-content" ),
menuPageClose = menuPage.find( ".ui-header a" );
$.extend( widget, {
select: widget.select,
selectID: selectID,
buttonId: buttonId,
menuId: menuId,
thisPage: thisPage,
menuPage: menuPage,
label: label,
screen: screen,
selectOptions: selectOptions,
isMultiple: isMultiple,
theme: widget.options.theme,
listbox: listbox,
list: list,
header: header,
headerTitle: headerTitle,
headerClose: headerClose,
menuPageContent: menuPageContent,
menuPageClose: menuPageClose,
placeholder: "",
build: function() {
var self = this;
// Create list from select, update state
self.refresh();
self.select.attr( "tabindex", "-1" ).focus(function() {
$( this ).blur();
self.button.focus();
});
// Button events
self.button.bind( "vclick keydown" , function( event ) {
if ( event.type == "vclick" ||
event.keyCode && ( event.keyCode === $.mobile.keyCode.ENTER ||
event.keyCode === $.mobile.keyCode.SPACE ) ) {
self.open();
event.preventDefault();
}
});
// Events for list items
self.list.attr( "role", "listbox" )
.delegate( ".ui-li>a", "focusin", function() {
$( this ).attr( "tabindex", "0" );
})
.delegate( ".ui-li>a", "focusout", function() {
$( this ).attr( "tabindex", "-1" );
})
.delegate( "li:not(.ui-disabled, .ui-li-divider)", "vclick", function( event ) {
// index of option tag to be selected
var oldIndex = self.select[ 0 ].selectedIndex,
newIndex = self.list.find( "li:not(.ui-li-divider)" ).index( this ),
option = self.optionElems.eq( newIndex )[ 0 ];
// toggle selected status on the tag for multi selects
option.selected = self.isMultiple ? !option.selected : true;
// toggle checkbox class for multiple selects
if ( self.isMultiple ) {
$( this ).find( ".ui-icon" )
.toggleClass( "ui-icon-checkbox-on", option.selected )
.toggleClass( "ui-icon-checkbox-off", !option.selected );
}
// trigger change if value changed
if ( self.isMultiple || oldIndex !== newIndex ) {
self.select.trigger( "change" );
}
//hide custom select for single selects only
if ( !self.isMultiple ) {
self.close();
}
event.preventDefault();
})
.keydown(function( event ) { //keyboard events for menu items
var target = $( event.target ),
li = target.closest( "li" ),
prev, next;
// switch logic based on which key was pressed
switch ( event.keyCode ) {
// up or left arrow keys
case 38:
prev = li.prev();
// if there's a previous option, focus it
if ( prev.length ) {
target
.blur()
.attr( "tabindex", "-1" );
prev.find( "a" ).first().focus();
}
return false;
break;
// down or right arrow keys
case 40:
next = li.next();
// if there's a next option, focus it
if ( next.length ) {
target
.blur()
.attr( "tabindex", "-1" );
next.find( "a" ).first().focus();
}
return false;
break;
// If enter or space is pressed, trigger click
case 13:
case 32:
target.trigger( "vclick" );
return false;
break;
}
});
// button refocus ensures proper height calculation
// by removing the inline style and ensuring page inclusion
self.menuPage.bind( "pagehide", function() {
self.list.appendTo( self.listbox );
self._focusButton();
});
// Events on "screen" overlay
self.screen.bind( "vclick", function( event ) {
self.close();
});
// Close button on small overlays
self.headerClose.click( function() {
if ( self.menuType == "overlay" ) {
self.close();
return false;
}
});
},
refresh: function( forceRebuild ){
var self = this,
select = this.element,
isMultiple = this.isMultiple,
options = this.optionElems = select.find( "option" ),
selected = this.selected(),
// return an array of all selected index's
indicies = this.selectedIndices();
if ( forceRebuild || select[0].options.length != self.list.find( "li" ).length ) {
self._buildList();
}
self.setButtonText();
self.setButtonCount();
self.list.find( "li:not(.ui-li-divider)" )
.removeClass( $.mobile.activeBtnClass )
.attr( "aria-selected", false )
.each(function( i ) {
if ( $.inArray( i, indicies ) > -1 ) {
var item = $( this ).addClass( $.mobile.activeBtnClass );
// Aria selected attr
item.find( "a" ).attr( "aria-selected", true );
// Multiple selects: add the "on" checkbox state to the icon
if ( self.isMultiple ) {
item.find( ".ui-icon" ).removeClass( "ui-icon-checkbox-off" ).addClass( "ui-icon-checkbox-on" );
}
}
});
},
close: function() {
if ( this.options.disabled || !this.isOpen ) {
return;
}
var self = this;
if ( self.menuType == "page" ) {
// rebind the page remove that was unbound in the open function
// to allow for the parent page removal from actions other than the use
// of a dialog sized custom select
self.thisPage.bind( "pagehide.remove", function() {
$(this).remove();
});
// doesn't solve the possible issue with calling change page
// where the objects don't define data urls which prevents dialog key
// stripping - changePage has incoming refactor
window.history.back();
} else {
self.screen.addClass( "ui-screen-hidden" );
self.listbox.addClass( "ui-selectmenu-hidden" ).removeAttr( "style" ).removeClass( "in" );
self.list.appendTo( self.listbox );
self._focusButton();
}
// allow the dialog to be closed again
self.isOpen = false;
},
open: function() {
if ( this.options.disabled ) {
return;
}
var self = this,
menuHeight = self.list.parent().outerHeight(),
menuWidth = self.list.parent().outerWidth(),
scrollTop = $( window ).scrollTop(),
btnOffset = self.button.offset().top,
screenHeight = window.innerHeight,
screenWidth = window.innerWidth;
//add active class to button
self.button.addClass( $.mobile.activeBtnClass );
//remove after delay
setTimeout( function() {
self.button.removeClass( $.mobile.activeBtnClass );
}, 300);
function focusMenuItem() {
self.list.find( $.mobile.activeBtnClass ).focus();
}
if ( menuHeight > screenHeight - 80 || !$.support.scrollTop ) {
// prevent the parent page from being removed from the DOM,
// otherwise the results of selecting a list item in the dialog
// fall into a black hole
self.thisPage.unbind( "pagehide.remove" );
//for webos (set lastscroll using button offset)
if ( scrollTop == 0 && btnOffset > screenHeight ) {
self.thisPage.one( "pagehide", function() {
$( this ).jqmData( "lastScroll", btnOffset );
});
}
self.menuPage.one( "pageshow", function() {
// silentScroll() is called whenever a page is shown to restore
// any previous scroll position the page may have had. We need to
// wait for the "silentscroll" event before setting focus to avoid
// the browser"s "feature" which offsets rendering to make sure
// whatever has focus is in view.
$( window ).one( "silentscroll", function() {
focusMenuItem();
});
self.isOpen = true;
});
self.menuType = "page";
self.menuPageContent.append( self.list );
$.mobile.changePage( self.menuPage, {
transition: $.mobile.defaultDialogTransition
});
} else {
self.menuType = "overlay";
self.screen.height( $(document).height() )
.removeClass( "ui-screen-hidden" );
// Try and center the overlay over the button
var roomtop = btnOffset - scrollTop,
roombot = scrollTop + screenHeight - btnOffset,
halfheight = menuHeight / 2,
maxwidth = parseFloat( self.list.parent().css( "max-width" ) ),
newtop, newleft;
if ( roomtop > menuHeight / 2 && roombot > menuHeight / 2 ) {
newtop = btnOffset + ( self.button.outerHeight() / 2 ) - halfheight;
} else {
// 30px tolerance off the edges
newtop = roomtop > roombot ? scrollTop + screenHeight - menuHeight - 30 : scrollTop + 30;
}
// If the menuwidth is smaller than the screen center is
if ( menuWidth < maxwidth ) {
newleft = ( screenWidth - menuWidth ) / 2;
} else {
//otherwise insure a >= 30px offset from the left
newleft = self.button.offset().left + self.button.outerWidth() / 2 - menuWidth / 2;
// 30px tolerance off the edges
if ( newleft < 30 ) {
newleft = 30;
} else if ( (newleft + menuWidth) > screenWidth ) {
newleft = screenWidth - menuWidth - 30;
}
}
self.listbox.append( self.list )
.removeClass( "ui-selectmenu-hidden" )
.css({
top: newtop,
left: newleft
})
.addClass( "in" );
focusMenuItem();
// duplicate with value set in page show for dialog sized selects
self.isOpen = true;
}
},
_buildList: function() {
var self = this,
o = this.options,
placeholder = this.placeholder,
optgroups = [],
lis = [],
dataIcon = self.isMultiple ? "checkbox-off" : "false";
self.list.empty().filter( ".ui-listview" ).listview( "destroy" );
// Populate menu with options from select element
self.select.find( "option" ).each( function( i ) {
var $this = $( this ),
$parent = $this.parent(),
text = $this.text(),
anchor = "<a href='#'>"+ text +"</a>",
classes = [],
extraAttrs = [];
// Are we inside an optgroup?
if ( $parent.is( "optgroup" ) ) {
var optLabel = $parent.attr( "label" );
// has this optgroup already been built yet?
if ( $.inArray( optLabel, optgroups ) === -1 ) {
lis.push( "<li data-" + $.mobile.ns + "role='list-divider'>"+ optLabel +"</li>" );
optgroups.push( optLabel );
}
}
// Find placeholder text
// TODO: Are you sure you want to use getAttribute? ^RW
if ( !this.getAttribute( "value" ) || text.length == 0 || $this.jqmData( "placeholder" ) ) {
if ( o.hidePlaceholderMenuItems ) {
classes.push( "ui-selectmenu-placeholder" );
}
placeholder = self.placeholder = text;
}
// support disabled option tags
if ( this.disabled ) {
classes.push( "ui-disabled" );
extraAttrs.push( "aria-disabled='true'" );
}
lis.push( "<li data-" + $.mobile.ns + "option-index='" + i + "' data-" + $.mobile.ns + "icon='"+ dataIcon +"' class='"+ classes.join(" ") + "' " + extraAttrs.join(" ") +">"+ anchor +"</li>" );
});
self.list.html( lis.join(" ") );
self.list.find( "li" )
.attr({ "role": "option", "tabindex": "-1" })
.first().attr( "tabindex", "0" );
// Hide header close link for single selects
if ( !this.isMultiple ) {
this.headerClose.hide();
}
// Hide header if it's not a multiselect and there's no placeholder
if ( !this.isMultiple && !placeholder.length ) {
this.header.hide();
} else {
this.headerTitle.text( this.placeholder );
}
// Now populated, create listview
self.list.listview();
},
_button: function(){
return $( "<a>", {
"href": "#",
"role": "button",
// TODO value is undefined at creation
"id": this.buttonId,
"aria-haspopup": "true",
// TODO value is undefined at creation
"aria-owns": this.menuId
});
}
});
};
$( "select" ).live( "selectmenubeforecreate", function(){
var selectmenuWidget = $( this ).data( "selectmenu" );
if( !selectmenuWidget.options.nativeMenu ){
extendSelect( selectmenuWidget );
}
});
})( jQuery );

View file

@ -24,594 +24,182 @@ $.widget( "mobile.selectmenu", $.mobile.widget, {
nativeMenu: true,
initSelector: "select:not(:jqmData(role='slider'))"
},
_button: function(){
return $( "<div/>" );
},
_theme: function(){
var themedParent, theme;
// if no theme is defined, try to find closest theme container
// TODO move to core as something like findCurrentTheme
themedParent = this.select.closest( "[class*='ui-bar-'], [class*='ui-body-']" );
theme = themedParent.length ?
/ui-(bar|body)-([a-z])/.exec( themedParent.attr( "class" ) )[2] :
"c";
return theme;
},
_setDisabled: function( value ) {
this.element.attr( "disabled", value );
this.button.attr( "aria-disabled", value );
return this._setOption( "disabled", value );
},
_focusButton : function() {
var self = this;
setTimeout( function() {
self.button.focus();
}, 40);
},
// setup items that are generally necessary for select menu extension
_preExtension: function(){
this.select = this.element.wrap( "<div class='ui-select'>" );
this.selectID = this.select.attr( "id" );
this.label = $( "label[for='"+ this.selectID +"']" ).addClass( "ui-select" );
this.isMultiple = this.select[ 0 ].multiple;
this.options.theme = this._theme();
this.selectOptions = this.select.find( "option" );
},
_create: function() {
this._preExtension();
// Allows for extension of the native select for custom selects and other plugins
// see select.custom for example extension
// TODO explore plugin registration
this._trigger( "beforeCreate" );
this.button = this._button();
var self = this,
o = this.options,
select = this.element
.wrap( "<div class='ui-select'>" ),
selectID = select.attr( "id" ),
label = $( "label[for='"+ selectID +"']" ).addClass( "ui-select" ),
options = this.options,
// IE throws an exception at options.item() function when
// there is no selected item
// select first in this case
selectedIndex = select[ 0 ].selectedIndex == -1 ? 0 : select[ 0 ].selectedIndex,
selectedIndex = this.select[ 0 ].selectedIndex == -1 ? 0 : this.select[ 0 ].selectedIndex,
button = ( self.options.nativeMenu ? $( "<div/>" ) : $( "<a>", {
"href": "#",
"role": "button",
"id": buttonId,
"aria-haspopup": "true",
"aria-owns": menuId
}) )
.text( $( select[ 0 ].options.item( selectedIndex ) ).text() )
.insertBefore( select )
.buttonMarkup({
theme: o.theme,
icon: o.icon,
iconpos: o.iconpos,
inline: o.inline,
corners: o.corners,
shadow: o.shadow,
iconshadow: o.iconshadow
}),
// Multi select or not
isMultiple = self.isMultiple = select[ 0 ].multiple;
// TODO values buttonId and menuId are undefined here
button = this.button
.text( $( this.select[ 0 ].options.item( selectedIndex ) ).text() )
.insertBefore( this.select )
.buttonMarkup( {
theme: options.theme,
icon: options.icon,
iconpos: options.iconpos,
inline: options.inline,
corners: options.corners,
shadow: options.shadow,
iconshadow: options.iconshadow
});
// Opera does not properly support opacity on select elements
// In Mini, it hides the element, but not its text
// On the desktop,it seems to do the opposite
// for these reasons, using the nativeMenu option results in a full native select in Opera
if ( o.nativeMenu && window.opera && window.opera.version ) {
select.addClass( "ui-select-nativeonly" );
if ( options.nativeMenu && window.opera && window.opera.version ) {
this.select.addClass( "ui-select-nativeonly" );
}
//vars for non-native menus
if ( !o.nativeMenu ) {
var options = select.find("option"),
buttonId = selectID + "-button",
menuId = selectID + "-menu",
thisPage = select.closest( ".ui-page" ),
//button theme
theme = /ui-btn-up-([a-z])/.exec( button.attr( "class" ) )[1],
menuPage = $( "<div data-" + $.mobile.ns + "role='dialog' data-" +$.mobile.ns + "theme='"+ o.menuPageTheme +"'>" +
"<div data-" + $.mobile.ns + "role='header'>" +
"<div class='ui-title'>" + label.text() + "</div>"+
"</div>"+
"<div data-" + $.mobile.ns + "role='content'></div>"+
"</div>" )
.appendTo( $.mobile.pageContainer )
.page(),
menuPageContent = menuPage.find( ".ui-content" ),
menuPageClose = menuPage.find( ".ui-header a" ),
screen = $( "<div>", {"class": "ui-selectmenu-screen ui-screen-hidden"})
.appendTo( thisPage ),
listbox = $("<div>", { "class": "ui-selectmenu ui-selectmenu-hidden ui-overlay-shadow ui-corner-all ui-body-" + o.overlayTheme + " " + $.mobile.defaultDialogTransition })
.insertAfter(screen),
list = $( "<ul>", {
"class": "ui-selectmenu-list",
"id": menuId,
"role": "listbox",
"aria-labelledby": buttonId
})
.attr( "data-" + $.mobile.ns + "theme", theme )
.appendTo( listbox ),
header = $( "<div>", {
"class": "ui-header ui-bar-" + theme
})
.prependTo( listbox ),
headerTitle = $( "<h1>", {
"class": "ui-title"
})
.appendTo( header ),
headerClose = $( "<a>", {
"text": o.closeText,
"href": "#",
"class": "ui-btn-left"
})
.attr( "data-" + $.mobile.ns + "iconpos", "notext" )
.attr( "data-" + $.mobile.ns + "icon", "delete" )
.appendTo( header )
.buttonMarkup(),
menuType;
} // End non native vars
// Add counter for multi selects
if ( isMultiple ) {
self.buttonCount = $( "<span>" )
if ( this.isMultiple ) {
this.buttonCount = $( "<span>" )
.addClass( "ui-li-count ui-btn-up-c ui-btn-corner-all" )
.hide()
.appendTo( button );
}
// Disable if specified
if ( o.disabled ) {
if ( options.disabled ) {
this.disable();
}
// Events on native select
select.change(function() {
this.select.change( function() {
self.refresh();
});
// Expose to other methods
$.extend( self, {
select: select,
optionElems: options,
selectID: selectID,
label: label,
buttonId: buttonId,
menuId: menuId,
thisPage: thisPage,
button: button,
menuPage: menuPage,
menuPageContent: menuPageContent,
screen: screen,
listbox: listbox,
list: list,
menuType: menuType,
header: header,
headerClose: headerClose,
headerTitle: headerTitle,
placeholder: ""
});
this.build();
},
// Support for using the native select menu with a custom button
if ( o.nativeMenu ) {
build: function() {
var self = this;
select.appendTo( button )
.bind( "vmousedown", function() {
// Add active class to button
button.addClass( $.mobile.activeBtnClass );
})
.bind( "focus vmouseover", function() {
button.trigger( "vmouseover" );
})
.bind( "vmousemove", function() {
// Remove active class on scroll/touchmove
button.removeClass( $.mobile.activeBtnClass );
})
.bind( "change blur vmouseout", function() {
button.trigger( "vmouseout" )
.removeClass( $.mobile.activeBtnClass );
});
} else {
// Create list from select, update state
self.refresh();
select.attr( "tabindex", "-1" )
.focus(function() {
$(this).blur();
button.focus();
});
// Button events
button.bind( "vclick keydown" , function( event ) {
if ( event.type == "vclick" ||
event.keyCode && ( event.keyCode === $.mobile.keyCode.ENTER ||
event.keyCode === $.mobile.keyCode.SPACE ) ) {
self.open();
event.preventDefault();
}
});
// Events for list items
list.attr( "role", "listbox" )
.delegate( ".ui-li>a", "focusin", function() {
$( this ).attr( "tabindex", "0" );
})
.delegate( ".ui-li>a", "focusout", function() {
$( this ).attr( "tabindex", "-1" );
})
.delegate( "li:not(.ui-disabled, .ui-li-divider)", "vclick", function( event ) {
var $this = $( this ),
// index of option tag to be selected
oldIndex = select[ 0 ].selectedIndex,
newIndex = $this.jqmData( "option-index" ),
option = self.optionElems[ newIndex ];
// toggle selected status on the tag for multi selects
option.selected = isMultiple ? !option.selected : true;
// toggle checkbox class for multiple selects
if ( isMultiple ) {
$this.find( ".ui-icon" )
.toggleClass( "ui-icon-checkbox-on", option.selected )
.toggleClass( "ui-icon-checkbox-off", !option.selected );
}
// trigger change if value changed
if ( isMultiple || oldIndex !== newIndex ) {
select.trigger( "change" );
}
//hide custom select for single selects only
if ( !isMultiple ) {
self.close();
}
event.preventDefault();
this.select
.appendTo( self.button )
.bind( "vmousedown", function() {
// Add active class to button
self.button.addClass( $.mobile.activeBtnClass );
})
//keyboard events for menu items
.keydown(function( event ) {
var target = $( event.target ),
li = target.closest( "li" ),
prev, next;
// switch logic based on which key was pressed
switch ( event.keyCode ) {
// up or left arrow keys
case 38:
prev = li.prev();
// if there's a previous option, focus it
if ( prev.length ) {
target
.blur()
.attr( "tabindex", "-1" );
prev.find( "a" ).first().focus();
}
return false;
break;
// down or right arrow keys
case 40:
next = li.next();
// if there's a next option, focus it
if ( next.length ) {
target
.blur()
.attr( "tabindex", "-1" );
next.find( "a" ).first().focus();
}
return false;
break;
// If enter or space is pressed, trigger click
case 13:
case 32:
target.trigger( "vclick" );
return false;
break;
}
.bind( "focus vmouseover", function() {
self.button.trigger( "vmouseover" );
})
.bind( "vmousemove", function() {
// Remove active class on scroll/touchmove
self.button.removeClass( $.mobile.activeBtnClass );
})
.bind( "change blur vmouseout", function() {
self.button.trigger( "vmouseout" )
.removeClass( $.mobile.activeBtnClass );
})
.bind( "change blur", function() {
self.button.removeClass( "ui-btn-down-" + self.options.theme );
});
// button refocus ensures proper height calculation
// by removing the inline style and ensuring page inclusion
self.menuPage.bind( "pagehide", function(){
self.list.appendTo( self.listbox );
self._focusButton();
});
// Events on "screen" overlay
screen.bind( "vclick", function( event ) {
self.close();
});
// Close button on small overlays
self.headerClose.click(function() {
if ( self.menuType == "overlay" ) {
self.close();
return false;
}
});
}
},
_buildList: function() {
var self = this,
o = this.options,
placeholder = this.placeholder,
optgroups = [],
lis = [],
dataIcon = self.isMultiple ? "checkbox-off" : "false";
selected: function() {
return this.selectOptions.filter( ":selected" );
},
self.list.empty().filter( ".ui-listview" ).listview( "destroy" );
selectedIndices: function() {
var self = this;
// Populate menu with options from select element
self.select.find( "option" ).each(function( i ) {
var $this = $( this ),
$parent = $this.parent(),
text = $this.text(),
anchor = "<a href='#'>"+ text +"</a>",
classes = [],
extraAttrs = [];
return this.selected().map( function() {
return self.selectOptions.index( this );
}).get();
},
// Are we inside an optgroup?
if ( $parent.is( "optgroup" ) ) {
var optLabel = $parent.attr( "label" );
setButtonText: function() {
var self = this, selected = this.selected();
// has this optgroup already been built yet?
if ( $.inArray( optLabel, optgroups ) === -1 ) {
lis.push( "<li data-" + $.mobile.ns + "role='list-divider'>"+ optLabel +"</li>" );
optgroups.push( optLabel );
}
this.button.find( ".ui-btn-text" ).text( function() {
if ( !self.isMultiple ) {
return selected.text();
}
// Find placeholder text
// TODO: Are you sure you want to use getAttribute? ^RW
if ( !this.getAttribute( "value" ) || text.length == 0 || $this.jqmData( "placeholder" ) ) {
if ( o.hidePlaceholderMenuItems ) {
classes.push( "ui-selectmenu-placeholder" );
}
placeholder = self.placeholder = text;
}
// support disabled option tags
if ( this.disabled ) {
classes.push( "ui-disabled" );
extraAttrs.push( "aria-disabled='true'" );
}
lis.push( "<li data-" + $.mobile.ns + "option-index='" + i + "' data-" + $.mobile.ns + "icon='"+ dataIcon +"' class='"+ classes.join(" ") + "' " + extraAttrs.join(" ") +">"+ anchor +"</li>" )
return selected.length ? selected.map( function() {
return $( this ).text();
}).get().join( ", " ) : self.placeholder;
});
self.list.html( lis.join(" ") );
self.list.find( "li" )
.attr({ "role": "option", "tabindex": "-1" })
.first().attr( "tabindex", "0" );
// Hide header close link for single selects
if ( !this.isMultiple ) {
this.headerClose.hide();
}
// Hide header if it's not a multiselect and there's no placeholder
if ( !this.isMultiple && !placeholder.length ) {
this.header.hide();
} else {
this.headerTitle.text( this.placeholder );
}
// Now populated, create listview
self.list.listview();
},
refresh: function( forceRebuild ) {
var self = this,
select = this.element,
isMultiple = this.isMultiple,
options = this.optionElems = select.find( "option" ),
selected = options.filter( ":selected" ),
// return an array of all selected index's
indicies = selected.map(function() {
return options.index( this );
}).get();
if ( !self.options.nativeMenu &&
( forceRebuild || select[0].options.length != self.list.find( "li" ).length ) ) {
self._buildList();
}
self.button.find( ".ui-btn-text" )
.text(function() {
if ( !isMultiple ) {
return selected.text();
}
return selected.length ? selected.map(function() {
return $( this ).text();
}).get().join( ", " ) : self.placeholder;
});
setButtonCount: function() {
var selected = this.selected();
// multiple count inside button
if ( isMultiple ) {
self.buttonCount[ selected.length > 1 ? "show" : "hide" ]().text( selected.length );
}
if ( !self.options.nativeMenu ) {
self.list.find( "li:not(.ui-li-divider)" )
.removeClass( $.mobile.activeBtnClass )
.attr( "aria-selected", false )
.each(function( i ) {
if ( $.inArray( i, indicies ) > -1 ) {
var item = $( this ).addClass( $.mobile.activeBtnClass );
// Aria selected attr
item.find( "a" ).attr( "aria-selected", true );
// Multiple selects: add the "on" checkbox state to the icon
if ( isMultiple ) {
item.find( ".ui-icon" ).removeClass( "ui-icon-checkbox-off" ).addClass( "ui-icon-checkbox-on" );
}
}
});
if ( this.isMultiple ) {
this.buttonCount[ selected.length > 1 ? "show" : "hide" ]().text( selected.length );
}
},
open: function() {
if ( this.options.disabled || this.options.nativeMenu ) {
return;
}
var self = this,
menuHeight = self.list.parent().outerHeight(),
menuWidth = self.list.parent().outerWidth(),
scrollTop = $( window ).scrollTop(),
btnOffset = self.button.offset().top,
screenHeight = window.innerHeight,
screenWidth = window.innerWidth;
//add active class to button
self.button.addClass( $.mobile.activeBtnClass );
//remove after delay
setTimeout(function() {
self.button.removeClass( $.mobile.activeBtnClass );
}, 300);
function focusMenuItem() {
self.list.find( ".ui-btn-active" ).focus();
}
if ( menuHeight > screenHeight - 80 || !$.support.scrollTop ) {
// prevent the parent page from being removed from the DOM,
// otherwise the results of selecting a list item in the dialog
// fall into a black hole
self.thisPage.unbind( "pagehide.remove" );
//for webos (set lastscroll using button offset)
if ( scrollTop == 0 && btnOffset > screenHeight ) {
self.thisPage.one( "pagehide", function() {
$( this ).jqmData( "lastScroll", btnOffset );
});
}
self.menuPage.one( "pageshow", function() {
// silentScroll() is called whenever a page is shown to restore
// any previous scroll position the page may have had. We need to
// wait for the "silentscroll" event before setting focus to avoid
// the browser"s "feature" which offsets rendering to make sure
// whatever has focus is in view.
$( window ).one( "silentscroll", function() {
focusMenuItem();
});
self.isOpen = true;
});
self.menuType = "page";
self.menuPageContent.append( self.list );
$.mobile.changePage( self.menuPage, {
transition: $.mobile.defaultDialogTransition
});
} else {
self.menuType = "overlay";
self.screen.height( $(document).height() )
.removeClass( "ui-screen-hidden" );
// Try and center the overlay over the button
var roomtop = btnOffset - scrollTop,
roombot = scrollTop + screenHeight - btnOffset,
halfheight = menuHeight / 2,
maxwidth = parseFloat( self.list.parent().css( "max-width" ) ),
newtop, newleft;
if ( roomtop > menuHeight / 2 && roombot > menuHeight / 2 ) {
newtop = btnOffset + ( self.button.outerHeight() / 2 ) - halfheight;
} else {
// 30px tolerance off the edges
newtop = roomtop > roombot ? scrollTop + screenHeight - menuHeight - 30 : scrollTop + 30;
}
// If the menuwidth is smaller than the screen center is
if ( menuWidth < maxwidth ) {
newleft = ( screenWidth - menuWidth ) / 2;
} else {
//otherwise insure a >= 30px offset from the left
newleft = self.button.offset().left + self.button.outerWidth() / 2 - menuWidth / 2;
// 30px tolerance off the edges
if ( newleft < 30 ) {
newleft = 30;
} else if ( ( newleft + menuWidth ) > screenWidth ) {
newleft = screenWidth - menuWidth - 30;
}
}
self.listbox.append( self.list )
.removeClass( "ui-selectmenu-hidden" )
.css({
top: newtop,
left: newleft
})
.addClass( "in" );
focusMenuItem();
// duplicate with value set in page show for dialog sized selects
self.isOpen = true;
}
},
_focusButton : function(){
var self = this;
setTimeout(function() {
self.button.focus();
}, 40);
},
close: function() {
if ( this.options.disabled || !this.isOpen || this.options.nativeMenu ) {
return;
}
var self = this;
if ( self.menuType == "page" ) {
// rebind the page remove that was unbound in the open function
// to allow for the parent page removal from actions other than the use
// of a dialog sized custom select
self.thisPage.bind( "pagehide.remove", function(){
$(this).remove();
});
// doesn't solve the possible issue with calling change page
// where the objects don't define data urls which prevents dialog key
// stripping - changePage has incoming refactor
window.history.back();
} else{
self.screen.addClass( "ui-screen-hidden" );
self.listbox.addClass( "ui-selectmenu-hidden" ).removeAttr( "style" ).removeClass( "in" );
self.list.appendTo( self.listbox );
self._focusButton();
}
// allow the dialog to be closed again
this.isOpen = false;
refresh: function() {
this.setButtonText();
this.setButtonCount();
},
disable: function() {
this.element.attr( "disabled", true );
this.button.addClass( "ui-disabled" ).attr( "aria-disabled", true );
return this._setOption( "disabled", true );
this._setDisabled( true );
this.button.addClass( "ui-disabled" );
},
enable: function() {
this.element.attr( "disabled", false );
this.button.removeClass( "ui-disabled" ).attr( "aria-disabled", false );
return this._setOption( "disabled", false );
this._setDisabled( false );
this.button.removeClass( "ui-disabled" );
}
});
@ -621,6 +209,4 @@ $( document ).bind( "pagecreate create", function( e ){
.not( ":jqmData(role='none'), :jqmData(role='nojs')" )
.selectmenu();
});
})( jQuery );

View file

@ -12,47 +12,47 @@
$head = $( "head" ),
$window = $( window );
//trigger mobileinit event - useful hook for configuring $.mobile settings before they're used
// trigger mobileinit event - useful hook for configuring $.mobile settings before they're used
$( window.document ).trigger( "mobileinit" );
//support conditions
//if device support condition(s) aren't met, leave things as they are -> a basic, usable experience,
//otherwise, proceed with the enhancements
// support conditions
// if device support condition(s) aren't met, leave things as they are -> a basic, usable experience,
// otherwise, proceed with the enhancements
if ( !$.mobile.gradeA() ) {
return;
}
// override ajaxEnabled on platforms that have known conflicts with hash history updates
// override ajaxEnabled on platforms that have known conflicts with hash history updates
// or generally work better browsing in regular http for full page refreshes (BB5, Opera Mini)
if( $.mobile.ajaxBlacklist ){
if ( $.mobile.ajaxBlacklist ) {
$.mobile.ajaxEnabled = false;
}
//add mobile, initial load "rendering" classes to docEl
// add mobile, initial load "rendering" classes to docEl
$html.addClass( "ui-mobile ui-mobile-rendering" );
//loading div which appears during Ajax requests
//will not appear if $.mobile.loadingMessage is false
// loading div which appears during Ajax requests
// will not appear if $.mobile.loadingMessage is false
var $loader = $( "<div class='ui-loader ui-body-a ui-corner-all'><span class='ui-icon ui-icon-loading spin'></span><h1></h1></div>" );
$.extend($.mobile, {
// turn on/off page loading message.
showPageLoadingMsg: function() {
if( $.mobile.loadingMessage ){
if ( $.mobile.loadingMessage ) {
var activeBtn = $( "." + $.mobile.activeBtnClass ).first();
$loader
.find( "h1" )
.text( $.mobile.loadingMessage )
.end()
.appendTo( $.mobile.pageContainer )
//position at y center (if scrollTop supported), above the activeBtn (if defined), or just 100px from top
.css( {
top: $.support.scrollTop && $(window).scrollTop() + $(window).height() / 2 ||
// position at y center (if scrollTop supported), above the activeBtn (if defined), or just 100px from top
.css({
top: $.support.scrollTop && $window.scrollTop() + $window.height() / 2 ||
activeBtn.length && activeBtn.offset().top || 100
} );
});
}
$html.addClass( "ui-loading" );
},
@ -70,36 +70,36 @@
},
// find and enhance the pages in the dom and transition to the first page.
initializePage: function(){
//find present pages
initializePage: function() {
// find present pages
var $pages = $( ":jqmData(role='page')" );
//if no pages are found, create one with body's inner html
if( !$pages.length ){
// if no pages are found, create one with body's inner html
if ( !$pages.length ) {
$pages = $( "body" ).wrapInner( "<div data-" + $.mobile.ns + "role='page'></div>" ).children( 0 );
}
//add dialogs, set data-url attrs
$pages.add( ":jqmData(role='dialog')" ).each(function(){
// add dialogs, set data-url attrs
$pages.add( ":jqmData(role='dialog')" ).each(function() {
var $this = $(this);
// unless the data url is already set set it to the id
if( !$this.jqmData('url') ){
if ( !$this.jqmData("url") ) {
$this.attr( "data-" + $.mobile.ns + "url", $this.attr( "id" ) );
}
});
//define first page in dom case one backs out to the directory root (not always the first page visited, but defined as fallback)
// define first page in dom case one backs out to the directory root (not always the first page visited, but defined as fallback)
$.mobile.firstPage = $pages.first();
//define page container
// define page container
$.mobile.pageContainer = $pages.first().parent().addClass( "ui-mobile-viewport" );
//cue page loading message
// cue page loading message
$.mobile.showPageLoadingMsg();
// if hashchange listening is disabled or there's no hash deeplink, change to the first page in the DOM
if( !$.mobile.hashListeningEnabled || !$.mobile.path.stripHash( location.hash ) ){
if ( !$.mobile.hashListeningEnabled || !$.mobile.path.stripHash( location.hash ) ) {
$.mobile.changePage( $.mobile.firstPage, { transition: "none", reverse: true, changeHash: false, fromHashChange: true } );
}
// otherwise, trigger a hashchange to load a deeplink
@ -108,28 +108,28 @@
}
}
});
//initialize events now, after mobileinit has occurred
// initialize events now, after mobileinit has occurred
$.mobile._registerInternalEvents();
//check which scrollTop value should be used by scrolling to 1 immediately at domready
//then check what the scroll top is. Android will report 0... others 1
//note that this initial scroll won't hide the address bar. It's just for the check.
$(function(){
// check which scrollTop value should be used by scrolling to 1 immediately at domready
// then check what the scroll top is. Android will report 0... others 1
// note that this initial scroll won't hide the address bar. It's just for the check.
$(function() {
window.scrollTo( 0, 1 );
//if defaultHomeScroll hasn't been set yet, see if scrollTop is 1
//it should be 1 in most browsers, but android treats 1 as 0 (for hiding addr bar)
//so if it's 1, use 0 from now on
// if defaultHomeScroll hasn't been set yet, see if scrollTop is 1
// it should be 1 in most browsers, but android treats 1 as 0 (for hiding addr bar)
// so if it's 1, use 0 from now on
$.mobile.defaultHomeScroll = ( !$.support.scrollTop || $(window).scrollTop() === 1 ) ? 0 : 1;
//dom-ready inits
if( $.mobile.autoInitializePage ){
$( $.mobile.initializePage );
$.mobile.initializePage();
}
//window load event
//hide iOS browser chrome on load
// window load event
// hide iOS browser chrome on load
$window.load( $.mobile.silentScroll );
});
})( jQuery, this );

View file

@ -100,6 +100,7 @@ $( ":jqmData(role='listview')" ).live( "listviewcreate", function() {
//filtervalue is empty => show all
listItems.toggleClass( "ui-screen-hidden", false );
}
listview._refreshCorners();
})
.appendTo( wrapper )
.textinput();

View file

@ -36,10 +36,14 @@ $.widget( "mobile.listview", $.mobile.widget, {
},
_itemApply: function( $list, item ) {
var $countli = item.find( ".ui-li-count" );
if ( $countli.length ) {
item.addClass( "ui-li-has-count" );
}
$countli.addClass( "ui-btn-up-" + ( $list.jqmData( "counttheme" ) || this.options.countTheme ) + " ui-btn-corner-all" );
// TODO class has to be defined in markup
item.find( ".ui-li-count" )
.addClass( "ui-btn-up-" + ( $list.jqmData( "counttheme" ) || this.options.countTheme ) + " ui-btn-corner-all" ).end()
.find( "h1, h2, h3, h4, h5, h6" ).addClass( "ui-li-heading" ).end()
item.find( "h1, h2, h3, h4, h5, h6" ).addClass( "ui-li-heading" ).end()
.find( "p, dl" ).addClass( "ui-li-desc" ).end()
.find( ">img:eq(0), .ui-link-inherit>img:eq(0)" ).addClass( "ui-li-thumb" ).each(function() {
item.addClass( $(this).is( ".ui-li-icon" ) ? "ui-li-has-icon" : "ui-li-has-thumb" );
@ -65,6 +69,42 @@ $.widget( "mobile.listview", $.mobile.widget, {
}
},
_refreshCorners: function() {
var $li,
$visibleli,
$topli,
$bottomli;
if ( this.options.inset ) {
$li = this.element.children( "li" );
$visibleli = $li.not( ".ui-screen-hidden" );
this._removeCorners( $li );
// Select the first visible li element
$topli = $visibleli.first()
.addClass( "ui-corner-top" );
$topli.add( $topli.find( ".ui-btn-inner" ) )
.find( ".ui-li-link-alt" )
.addClass( "ui-corner-tr" )
.end()
.find( ".ui-li-thumb" )
.addClass( "ui-corner-tl" );
// Select the last visible li element
$bottomli = $visibleli.last()
.addClass( "ui-corner-bottom" );
$bottomli.add( $bottomli.find( ".ui-btn-inner" ) )
.find( ".ui-li-link-alt" )
.addClass( "ui-corner-br" )
.end()
.find( ".ui-li-thumb" )
.addClass( "ui-corner-bl" );
}
},
refresh: function( create ) {
this.parentPage = this.element.closest( ".ui-page" );
this._createSubPages();
@ -150,40 +190,6 @@ $.widget( "mobile.listview", $.mobile.widget, {
}
}
if ( o.inset ) {
if ( pos === 0 ) {
itemClass += " ui-corner-top";
item.add( item.find( ".ui-btn-inner" ) )
.find( ".ui-li-link-alt" )
.addClass( "ui-corner-tr" )
.end()
.find( ".ui-li-thumb" )
.addClass( "ui-corner-tl" );
if ( item.next().next().length ) {
self._removeCorners( item.next() );
}
}
if ( pos === li.length - 1 ) {
itemClass += " ui-corner-bottom";
item.add( item.find( ".ui-btn-inner" ) )
.find( ".ui-li-link-alt" )
.addClass( "ui-corner-br" )
.end()
.find( ".ui-li-thumb" )
.addClass( "ui-corner-bl" );
if ( item.prev().prev().length ) {
self._removeCorners( item.prev() );
} else if ( item.prev().length ) {
self._removeCorners( item.prev(), "bottom" );
}
}
}
if ( counter && itemClass.indexOf( "ui-li-divider" ) < 0 ) {
countParent = item.is( ".ui-li-static:first" ) ? item : item.find( ".ui-link-inherit" );
@ -197,6 +203,8 @@ $.widget( "mobile.listview", $.mobile.widget, {
self._itemApply( $list, item );
}
}
this._refreshCorners();
},
//create a string for ID/subpage url creation

View file

@ -18,7 +18,7 @@ function propExists( prop ) {
props = ( prop + " " + vendors.join( uc_prop + " " ) + uc_prop ).split( " " );
for ( var v in props ){
if ( fbCSS[ v ] !== undefined ) {
if ( fbCSS[ props[ v ] ] !== undefined ) {
return true;
}
}

View file

@ -11,13 +11,13 @@
<script>
$(function(){
$( "a" ).bind("tap",function( e ){
$( "a" ).bind("tap click",function( e ){
$("#log")
.prepend("<li>"+ e.type +" event; target: "+ e.target.nodeName +"</li>")
.listview("refresh");
return false;
})
.bind("click", false);
.bind("tap click", false);
});
</script>

View file

@ -10,19 +10,19 @@
<script>
$(function(){
$(".ui-grid-d a").bind("tap", function(e){
$(".ui-grid-d a").bind("tap click", function(e){
$(this).hide();
$("#log")
.prepend("<li>"+ e.type +" event; target: "+ e.target.nodeName +"; message: grid '"+$(this).text()+"' hidden</li>")
.listview("refresh");
return false;
}).bind("click", false);
$("#showbtn").bind("tap", function(e){
}).bind("tap click", false);
$("#showbtn").bind("tap click", function(e){
$(".ui-grid-d a").show();
$("#log")
.prepend("<li>"+ e.type +" event; target: "+ e.target.nodeName +"; message: show all buttons</li>")
.listview("refresh");
}).bind("click", false);
}).bind("tap click", false);
});
</script>
</head>

View file

@ -1,24 +1,38 @@
/*
* mobile dialog unit tests
*/
(function($){
module('jquery.mobile.dialog.js');
(function($) {
module( "jquery.mobile.dialog.js" );
asyncTest( "dialog hash is added when the dialog is opened and removed when closed", function(){
expect( 2 );
asyncTest( "dialog hash is added when the dialog is opened and removed when closed", function() {
expect( 6 );
//bring up the dialog
$("a[href='#foo-dialog']").click();
$.testHelper.pageSequence([
function() {
//bring up the dialog
$( "#foo-dialog-link" ).click();
},
setTimeout(function(){
ok(/&ui-state=dialog/.test(location.hash), "ui-state=dialog =~ location.hash");
// close the dialog
$(".ui-dialog").dialog("close");
}, 500);
function() {
var fooDialog = $( "#foo-dialog" );
setTimeout(function(){
ok(!/&ui-state=dialog/.test(location.hash), "ui-state=dialog !~ location.hash");
start();
}, 1000);
// make sure the dialog came up
ok( /&ui-state=dialog/.test(location.hash), "ui-state=dialog =~ location.hash", "dialog open" );
// Assert dialog theme inheritance (issue 1375):
ok( fooDialog.hasClass( "ui-body-b" ), "Expected explicit theme ui-body-b" );
ok( fooDialog.find( ":jqmData(role=header)" ).hasClass( "ui-bar-a" ), "Expected header to inherit from $.mobile.page.prototype.options.headerTheme" );
ok( fooDialog.find( ":jqmData(role=content)" ).hasClass( "ui-body-d" ), "Expect content to inherit from $.mobile.page.prototype.options.contentTheme" );
ok( fooDialog.find( ":jqmData(role=footer)" ).hasClass( "ui-bar-a" ), "Expected footer to inherit from $.mobile.page.prototype.options.footerTheme" );
// close the dialog
$( ".ui-dialog" ).dialog( "close" );
},
function() {
ok( !/&ui-state=dialog/.test(location.hash), "ui-state=dialog !~ location.hash" );
start();
}
]);
});
})(jQuery);
})( jQuery );

View file

@ -6,6 +6,12 @@
<title>jQuery Mobile Dialog Test Suite</title>
<script src="../../../js/jquery.js"></script>
<script>
$(document).bind('mobileinit',function(){
// Expect content to inherit this theme when not explicitly set
$.mobile.page.prototype.options.contentTheme = "d";
});
</script>
<script src="../jquery.setNameSpace.js"></script>
<script src="../../../js/"></script>
<script src="../../../tests/jquery.testHelper.js"></script>
@ -25,15 +31,20 @@
<ol id="qunit-tests">
</ol>
<div id="bar" data-nstest-role="page">
<a href="#foo-dialog" data-nstest-role="button" data-nstest-inline="true" data-nstest-rel="dialog"></a>
<div id="bar" data-nstest-role="page" data-nstest-theme="a">
<a href="#foo-dialog" id="foo-dialog-link" data-nstest-role="button" data-nstest-inline="true" data-nstest-rel="dialog"></a>
</div>
<div id="foo-dialog" data-nstest-role="dialog">
<div data-nstest-role="header" data-nstest-theme="d" data-nstest-position="inline">
<div id="foo-dialog" data-nstest-role="dialog" data-nstest-theme="b">
<div data-nstest-role="header" data-nstest-position="inline">
<h1>Dialog</h1>
</div>
<a href="#" id="internal-link">foo</a>
<div data-nstest-role="content" >
<a href="#" id="internal-link">foo</a>
</div>
<div data-nstest-role="footer">
footer
</div>
</div>
</body>

View file

@ -3,20 +3,38 @@
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="../../external/qunit.css"></link>
<script src="../../js/jquery.js"></script>
<script src="../../external/qunit.js"></script>
<script src="runner.js"></script>
<style type="text/css">
html, body {
width:100%;
height:100%;
margin:0px;
padding:0px;
}
#testFrame {
float: left;
border: 0px;
height: 100%;
width: 60%;
}
#results {
float: left;
width: 30%;
}
</style>
</head>
<body>
<div style="float: left; width: 500px;">
<div id="results">
<h1 id="qunit-header"><a href="#">jQuery Mobile Test Suite</a></h1>
<h2 id="qunit-banner"></h2>
<ol id="qunit-tests">
</ol>
</div>
<!-- under normal circumstances inline styles would be a poor choice, but in this case
I think an entire link and stylesheet is a waste -->
<iframe data-src="../../tests/unit/{{testdir}}" name="testFrame" id="testFrame" width="800px" height="100%" style="float: left; border: 0px; height: 100%;" scrolling="no">
<iframe data-src="../../tests/unit/{{testdir}}" name="testFrame" id="testFrame" scrolling="no">
</iframe>
<script src="../../js/jquery.js"></script>
<script src="../../external/qunit.js"></script>
<script src="runner.js"></script>
</body>
</html>

View file

@ -242,6 +242,21 @@
</div>
</div>
<!-- Search bar filter inset -->
<div data-nstest-role="page" id='search-filter-inset-test'>
<div data-nstest-role="header" data-nstest-position="inline">
<h1>Inset Filter List View</h1>
</div>
<div data-nstest-role="content">
<ul data-nstest-role="listview" data-nstest-filter="true" data-nstest-inset="true">
<li>a is for aquaman</li>
<li>b is for batman</li>
<li>c is for catwoman</li>
<li>d is for darkwing</li>
</ul>
</div>
</div>
<!-- Programmatically generated list items !-->
<div data-nstest-role="page" id="programmatically-generated-list">
<ul data-nstest-role="listview" data-nstest-inset="true" id="programmatically-generated-list-items"></ul>

View file

@ -296,32 +296,40 @@
asyncTest( "Filter downs results when the user enters information", function() {
var $searchPage = $(searchFilterId);
$.testHelper.openPage(searchFilterId);
$.testHelper.pageSequence([
function() {
$.testHelper.openPage(searchFilterId);
},
setTimeout(function(){
$searchPage.find('input').val('at');
$searchPage.find('input').trigger('change');
}, 500);
function() {
$searchPage.find('input').val('at');
$searchPage.find('input').trigger('change');
setTimeout(function() {
same($searchPage.find('li.ui-screen-hidden').length, 2);
start();
}, 1000);
setTimeout(function() {
same($searchPage.find('li.ui-screen-hidden').length, 2);
start();
}, 1000);
}
]);
});
asyncTest( "Redisplay results when user removes values", function() {
var $searchPage = $(searchFilterId);
$.testHelper.openPage(searchFilterId);
$.testHelper.pageSequence([
function() {
$.testHelper.openPage(searchFilterId);
},
setTimeout(function(){
$searchPage.find('input').val('a');
$searchPage.find('input').trigger('change');
}, 500);
function() {
$searchPage.find('input').val('a');
$searchPage.find('input').trigger('change');
setTimeout(function() {
same($searchPage.find("li[style^='display: none;']").length, 0);
start();
}, 1000);
setTimeout(function() {
same($searchPage.find("li[style^='display: none;']").length, 0);
start();
}, 1000);
}
]);
});
test( "Refresh applies thumb styling", function(){
@ -335,59 +343,115 @@
asyncTest( "Filter downs results and dividers when the user enters information", function() {
var $searchPage = $("#search-filter-with-dividers-test");
$.testHelper.openPage("#search-filter-with-dividers-test");
$.testHelper.pageSequence([
function() {
$.testHelper.openPage("#search-filter-with-dividers-test");
},
// wait for the page to become active/enhanced
setTimeout(function(){
$searchPage.find('input').val('at');
$searchPage.find('input').trigger('change');
}, 500);
// wait for the page to become active/enhanced
function(){
$searchPage.find('input').val('at');
$searchPage.find('input').trigger('change');
setTimeout(function() {
//there should be four hidden list entries
same($searchPage.find('li.ui-screen-hidden').length, 4);
setTimeout(function() {
//there should be four hidden list entries
same($searchPage.find('li.ui-screen-hidden').length, 4);
//there should be two list entries that are list dividers and hidden
same($searchPage.find('li.ui-screen-hidden:jqmData(role=list-divider)').length, 2);
//there should be two list entries that are list dividers and hidden
same($searchPage.find('li.ui-screen-hidden:jqmData(role=list-divider)').length, 2);
//there should be two list entries that are not list dividers and hidden
same($searchPage.find('li.ui-screen-hidden:not(:jqmData(role=list-divider))').length, 2);
start();
}, 1000);
//there should be two list entries that are not list dividers and hidden
same($searchPage.find('li.ui-screen-hidden:not(:jqmData(role=list-divider))').length, 2);
start();
}, 1000);
}
]);
});
asyncTest( "Redisplay results when user removes values", function() {
$.testHelper.openPage("#search-filter-with-dividers-test");
$.testHelper.pageSequence([
function() {
$.testHelper.openPage("#search-filter-with-dividers-test");
},
// wait for the page to become active/enhanced
setTimeout(function(){
$('.ui-page-active input').val('a');
$('.ui-page-active input').trigger('change');
}, 500);
function() {
$('.ui-page-active input').val('a');
$('.ui-page-active input').trigger('change');
setTimeout(function() {
same($('.ui-page-active input').val(), 'a');
same($('.ui-page-active li[style^="display: none;"]').length, 0);
start();
}, 1000);
setTimeout(function() {
same($('.ui-page-active input').val(), 'a');
same($('.ui-page-active li[style^="display: none;"]').length, 0);
start();
}, 1000);
}
]);
});
asyncTest( "Dividers are hidden when preceding hidden rows and shown when preceding shown rows", function () {
$.testHelper.openPage("#search-filter-with-dividers-test");
var $page = $('.ui-page-active');
$.testHelper.pageSequence([
function() {
$.testHelper.openPage("#search-filter-with-dividers-test");
},
// wait for the page to become active/enhanced
setTimeout(function(){
$page.find('input').val('at');
$page.find('input').trigger('change');
}, 500);
function() {
var $page = $('.ui-page-active');
setTimeout(function() {
same($page.find('li:jqmData(role=list-divider):hidden').length, 2);
same($page.find('li:jqmData(role=list-divider):hidden + li:not(:jqmData(role=list-divider)):hidden').length, 2);
same($page.find('li:jqmData(role=list-divider):not(:hidden) + li:not(:jqmData(role=list-divider)):not([:hidden)').length, 2);
start();
}, 1000);
$page.find('input').val('at');
$page.find('input').trigger('change');
setTimeout(function() {
same($page.find('li:jqmData(role=list-divider):hidden').length, 2);
same($page.find('li:jqmData(role=list-divider):hidden + li:not(:jqmData(role=list-divider)):hidden').length, 2);
same($page.find('li:jqmData(role=list-divider):not(:hidden) + li:not(:jqmData(role=list-divider)):not([:hidden)').length, 2);
start();
}, 1000);
}
]);
});
asyncTest( "Inset List View should refresh corner classes after filtering", 4 * 2, function () {
var checkClasses = function() {
var $page = $( ".ui-page-active" ),
$li = $page.find( "li:visible" );
ok($li.first().hasClass( "ui-corner-top" ), $li.length+" li elements: First visible element should have class ui-corner-top");
ok($li.last().hasClass( "ui-corner-bottom" ), $li.length+" li elements: Last visible element should have class ui-corner-bottom");
}
$.testHelper.pageSequence([
function() {
$.testHelper.openPage("#search-filter-inset-test");
},
function() {
var $page = $('.ui-page-active');
$.testHelper.sequence([
function() {
checkClasses();
$page.find('input').val('man');
$page.find('input').trigger('change');
},
function() {
checkClasses();
$page.find('input').val('at');
$page.find('input').trigger('change');
},
function() {
checkClasses();
$page.find('input').val('catwoman');
$page.find('input').trigger('change');
},
function() {
checkClasses();
start();
}
], 50);
}
]);
});
module( "Programmatically generated list items", {
@ -463,7 +527,7 @@
module("Rounded corners");
asyncTest("Top and bottom corners rounded in inset list", 10, function() {
asyncTest("Top and bottom corners rounded in inset list", 20, function() {
$.testHelper.pageSequence([
function() {
$.testHelper.openPage("#corner-rounded-test");
@ -475,8 +539,10 @@
for( var t = 0; t<5; t++){
ul.append("<li>Item " + t + "</li>");
ul.listview('refresh');
ok(ul.find("li").first().hasClass("ui-corner-top"), "First list item should have class ui-corner-top in list with " + ul.find("li").length + " item(s)");
ok(ul.find("li").last().hasClass("ui-corner-bottom"), "Last list item should have class ui-corner-bottom in list with " + ul.find("li").length + " item(s)");
equals(ul.find(".ui-corner-top").length, 1, "There should be only one element with class ui-corner-top");
equals(ul.find("li:visible").first()[0], ul.find(".ui-corner-top")[0], "First list item should have class ui-corner-top in list with " + ul.find("li").length + " item(s)");
equals(ul.find(".ui-corner-bottom").length, 1, "There should be only one element with class ui-corner-bottom");
equals(ul.find("li:visible").last()[0], ul.find(".ui-corner-bottom")[0], "Last list item should have class ui-corner-bottom in list with " + ul.find("li").length + " item(s)");
}
start();

View file

@ -13,6 +13,8 @@
closedir($handle);
}
sort($directories);
echo json_encode( array('directories' => $directories ));
$test_pages = array_merge($directories, glob("**/*-tests.html"));
sort($test_pages);
echo json_encode( array('testPages' => $test_pages));
?>

View file

@ -8,7 +8,7 @@
<a href="../app-base/base-page-1.html" class="bp1">Base Page 1</a>
<a href="../../#internal-page-1" class="ip1">Internal Page 1</a>
<a href="../../#internal-page-2" class="ip2">Internal Page 2</a>
<img src="images/content-page-2.png">
<img src="images/content-page-2.png">
</div>
</body>
</html>

View file

@ -122,7 +122,7 @@
report: "call changePage() with a relative path should resolve relative to current page"
});
// Try calling changePage() with an id
// test that an internal page works
$("a.ip2").click();
},

View file

@ -1,58 +1,82 @@
(function(){
var test = function(data){
var $frameElem = $("#testFrame"),
template = $frameElem.attr("data-src"),
updateFrame = function(dir){
return $frameElem.attr("src", template.replace("{{testdir}}", dir));
};
$(function() {
var Runner = function( ) {
var self = this;
$.each(data.directories, function(i, dir){
asyncTest( dir, function(){
var testTimeout = 3 * 60 * 1000, checkInterval = 2000;
$.extend( self, {
frame: window.frames[ "testFrame" ],
testTimeout: 3 * 60 * 1000,
$frameElem: $( "#testFrame" ),
assertionResultPrefix: "assertion result for test:",
onTimeout: QUnit.start,
onFrameLoad: function() {
// establish a timeout for a given suite in case of async tests hanging
var testTimer = setTimeout( function(){
// prevent any schedule checks for completion
clearTimeouts();
start();
}, testTimeout ),
self.testTimer = setTimeout( self.onTimeout, self.testTimeout );
checkTimer = setInterval( check, checkInterval ),
// when the QUnit object reports done in the iframe
// run the onFrameDone method
self.frame.QUnit.done = self.onFrameDone;
self.frame.QUnit.testDone = self.onTestDone;
},
clearTimeouts = function(){
// prevent the next interval of the check function and the test timeout
clearTimeout( checkTimer );
clearTimeout( testTimer );
};
onTestDone: function( name, bad, assertCount ) {
QUnit.ok( !bad, name );
self.recordAssertions( assertCount - 1, name );
},
// check the iframe for success or failure and respond accordingly
function check(){
// check for the frames jquery object each time
var framejQuery = window.frames["testFrame"].jQuery;
onFrameDone: function( failed, passed, total, runtime ){
// make sure we don't time out the tests
clearTimeout( self.testTimer );
// if the iframe hasn't loaded (ie loaded jQuery) check back again shortly
if( !framejQuery ) return;
// TODO decipher actual cause of multiple test results firing twice
// clear the done call to prevent early completion of other test cases
self.frame.QUnit.done = $.noop;
self.frame.QUnit.testDone = $.noop;
// grab the result of the iframe test suite
// TODO strip extra white space
var result = framejQuery( "#qunit-banner" ).attr( "class" );
// hide the extra assertions made to propogate the count
// to the suite level test
self.hideAssertionResults();
// if we have a result check it, otherwise check back shortly
if( result ){
ok( result === "qunit-pass" );
clearTimeouts();
start();
}
};
// continue on to the next suite
QUnit.start();
},
expect( 1 );
recordAssertions: function( count, parentTest ) {
for( var i = 0; i < count; i++ ) {
ok( true, self.assertionResultPrefix + parentTest );
}
},
// set the test suite page on the iframe
updateFrame( dir );
});
hideAssertionResults: function() {
$( "li:not([id]):contains('" + self.assertionResultPrefix + "')" ).hide();
},
exec: function( data ) {
var template = self.$frameElem.attr( "data-src" );
$.each( data.testPages, function(i, dir) {
QUnit.asyncTest( dir, function() {
self.dir = dir;
self.$frameElem.one( "load", self.onFrameLoad );
self.$frameElem.attr( "src", template.replace("{{testdir}}", dir) );
});
});
// having defined all suite level tests let QUnit run
QUnit.start();
}
});
};
// prevent qunit from starting the test suite until all tests are defined
QUnit.begin = function( ) {
this.config.autostart = false;
};
// get the test directories
$.get("ls.php", test);
})();
$.get( "ls.php", (new Runner()).exec );
});

View file

@ -1,65 +1,27 @@
<!DOCTYPE HTML>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
</head>
<body>
<div id="dialog-select-parent-cache-test" data-nstest-role="page">
<div data-nstest-role="fieldcontain" id="cached-page-select-container">
<label for="cached-page-select" class="select">Your state:</label>
<select name="cached-page-select" id="cached-page-select" data-nstest-native-menu="false">
<option value="AL">Alabama</option>
<option value="AK">Alaska</option>
<option value="AZ">Arizona</option>
<option value="AR">Arkansas</option>
<option value="CA">California</option>
<option value="CO">Colorado</option>
<option value="CT">Connecticut</option>
<option value="DE">Delaware</option>
<option value="FL">Florida</option>
<option value="GA">Georgia</option>
<option value="HI">Hawaii</option>
<option value="ID">Idaho</option>
<option value="IL">Illinois</option>
<option value="IN">Indiana</option>
<option value="IA">Iowa</option>
<option value="KS">Kansas</option>
<option value="KY">Kentucky</option>
<option value="LA">Louisiana</option>
<option value="ME">Maine</option>
<option value="MD">Maryland</option>
<option value="MA">Massachusetts</option>
<option value="MI">Michigan</option>
<option value="MN">Minnesota</option>
<option value="MS">Mississippi</option>
<option value="MO">Missouri</option>
<option value="MT">Montana</option>
<option value="NE">Nebraska</option>
<option value="NV">Nevada</option>
<option value="NH">New Hampshire</option>
<option value="NJ">New Jersey</option>
<option value="NM">New Mexico</option>
<option value="NY">New York</option>
<option value="NC">North Carolina</option>
<option value="ND">North Dakota</option>
<option value="OH">Ohio</option>
<option value="OK">Oklahoma</option>
<option value="OR">Oregon</option>
<option value="PA">Pennsylvania</option>
<option value="RI">Rhode Island</option>
<option value="SC">South Carolina</option>
<option value="SD">South Dakota</option>
<option value="TN">Tennessee</option>
<option value="TX">Texas</option>
<option value="UT">Utah</option>
<option value="VT">Vermont</option>
<option value="VA">Virginia</option>
<option value="WA">Washington</option>
<option value="WV">West Virginia</option>
<option value="WI">Wisconsin</option>
<option value="WY">Wyoming</option>
</select>
</div>
</div>
</body>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>jQuery Mobile Select Events Test Suite</title>
<script src="../../../js/jquery.js"></script>
<script src="../jquery.setNameSpace.js"></script>
<script src="../../../external/qunit.js"></script>
<script src="../../../tests/jquery.testHelper.js"></script>
<script src="../../../js"></script>
<script src="select_cached.js"></script>
<link rel="stylesheet" href="../../../themes/default/"/>
<link rel="stylesheet" href="../../../external/qunit.css"/>
</head>
<body>
<h1 id="qunit-header">jQuery Mobile Select Event Test Suite</h1>
<h2 id="qunit-banner"></h2>
<h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests">
</ol>
<div id="default" data-nstest-role="page"></div>
</body>
</html>

View file

@ -0,0 +1,65 @@
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="utf-8">
</head>
<body>
<div id="dialog-select-parent-cache-test" data-nstest-role="page">
<div data-nstest-role="fieldcontain" id="cached-page-select-container">
<label for="cached-page-select" class="select">Your state:</label>
<select name="cached-page-select" id="cached-page-select" data-nstest-native-menu="false">
<option value="AL">Alabama</option>
<option value="AK">Alaska</option>
<option value="AZ">Arizona</option>
<option value="AR">Arkansas</option>
<option value="CA">California</option>
<option value="CO">Colorado</option>
<option value="CT">Connecticut</option>
<option value="DE">Delaware</option>
<option value="FL">Florida</option>
<option value="GA">Georgia</option>
<option value="HI">Hawaii</option>
<option value="ID">Idaho</option>
<option value="IL">Illinois</option>
<option value="IN">Indiana</option>
<option value="IA">Iowa</option>
<option value="KS">Kansas</option>
<option value="KY">Kentucky</option>
<option value="LA">Louisiana</option>
<option value="ME">Maine</option>
<option value="MD">Maryland</option>
<option value="MA">Massachusetts</option>
<option value="MI">Michigan</option>
<option value="MN">Minnesota</option>
<option value="MS">Mississippi</option>
<option value="MO">Missouri</option>
<option value="MT">Montana</option>
<option value="NE">Nebraska</option>
<option value="NV">Nevada</option>
<option value="NH">New Hampshire</option>
<option value="NJ">New Jersey</option>
<option value="NM">New Mexico</option>
<option value="NY">New York</option>
<option value="NC">North Carolina</option>
<option value="ND">North Dakota</option>
<option value="OH">Ohio</option>
<option value="OK">Oklahoma</option>
<option value="OR">Oregon</option>
<option value="PA">Pennsylvania</option>
<option value="RI">Rhode Island</option>
<option value="SC">South Carolina</option>
<option value="SD">South Dakota</option>
<option value="TN">Tennessee</option>
<option value="TX">Texas</option>
<option value="UT">Utah</option>
<option value="VT">Vermont</option>
<option value="VA">Virginia</option>
<option value="WA">Washington</option>
<option value="WV">West Virginia</option>
<option value="WI">Wisconsin</option>
<option value="WY">Wyoming</option>
</select>
</div>
</div>
</body>
</html>

View file

@ -10,6 +10,7 @@
<script src="../../../external/qunit.js"></script>
<script src="../../../tests/jquery.testHelper.js"></script>
<script src="../../../js"></script>
<script src="select_native.js"></script>
<script src="select_events.js"></script>
<script src="select_core.js"></script>
@ -34,6 +35,16 @@
</select>
</div>
<div data-nstest-role="fieldcontain" id="native-select-choice-few-container">
<label for="native-select-choice-few" class="select">Choose shipping method:</label>
<select name="native-select-choice-few" id="native-select-choice-few" data-nstest-native-menu="true">
<option value="standard">Standard: 7 day</option>
<option value="rush">Rush: 3 days</option>
<option value="express">Express: next day</option>
<option value="overnight">Overnight</option>
</select>
</div>
<div data-nstest-role="fieldcontain" id="select-choice-native-container">
<select name="select-choice-native" id="select-choice-native" data-nstest-native-menu="true">
<option value="standard">Standard: 7 day</option>
@ -52,6 +63,15 @@
</select>
</div>
<div data-nstest-role="fieldcontain" id="select-choice-focus-test">
<select name="select-choice-focus-test" id="select-choice-focus-test" data-nstest-native-menu="false">
<option value="standard">Standard: 7 day</option>
<option value="rush">Rush: 3 days</option>
<option value="express">Express: next day</option>
<option value="overnight">Overnight</option>
</select>
</div>
<div data-nstest-role="fieldcontain" id="select-choice-many-container-1">
<label for="select-choice-many-1" class="select">Your state:</label>
<select name="select-choice-many-1" id="select-choice-many-1" data-nstest-native-menu="false">

View file

@ -0,0 +1,70 @@
/*
* mobile select unit tests
*/
(function($){
var resetHash;
resetHash = function(timeout){
$.testHelper.openPage( location.hash.indexOf("#default") >= 0 ? "#" : "#default" );
};
// https://github.com/jquery/jquery-mobile/issues/2181
asyncTest( "dialog sized select should alter the value of its parent select", function(){
var selectButton, value;
$.testHelper.pageSequence([
resetHash,
function(){
$.mobile.changePage( "cached.html" );
},
function(){
selectButton = $( "#cached-page-select" ).siblings( 'a' );
selectButton.click();
},
function(){
ok( $.mobile.activePage.hasClass('ui-dialog'), "the dialog came up" );
var option = $.mobile.activePage.find( "li a" ).not(":contains('" + selectButton.text() + "')").last();
value = option.text();
option.click();
},
function(){
same( value, selectButton.text(), "the selected value is propogated back to the button text" );
start();
}
]);
});
// https://github.com/jquery/jquery-mobile/issues/2181
asyncTest( "dialog sized select should prevent the removal of its parent page from the dom", function(){
var selectButton, parentPageId;
expect( 2 );
$.testHelper.pageSequence([
resetHash,
function(){
$.mobile.changePage( "cached.html" );
},
function(){
selectButton = $.mobile.activePage.find( "#cached-page-select" ).siblings( 'a' );
parentPageId = $.mobile.activePage.attr( 'id' );
same( $("#" + parentPageId).length, 1, "establish the parent page exists" );
selectButton.click();
},
function(){
same( $( "#" + parentPageId).length, 1, "make sure parent page is still there after opening the dialog" );
$.mobile.activePage.find( "li a" ).last().click();
},
start
]);
});
})(jQuery);

View file

@ -4,9 +4,9 @@
(function($){
var libName = "jquery.mobile.forms.select.js",
originalDefaultDialogTrans = $.mobile.defaultDialogTransition,
originalDefTransitionHandler = $.mobile.defaultTransitionHandler,
resetHash, closeDialog;
originalDefaultDialogTrans = $.mobile.defaultDialogTransition,
originalDefTransitionHandler = $.mobile.defaultTransitionHandler,
resetHash, closeDialog;
resetHash = function(timeout){
$.testHelper.openPage( location.hash.indexOf("#default") >= 0 ? "#" : "#default" );
@ -71,36 +71,6 @@
]);
});
asyncTest( "a large select menu should come up in a dialog many times", function(){
var menu, select;
$.testHelper.pageSequence([
resetHash,
function(){
select = $("#select-choice-many-container a");
// bring up the dialog
select.trigger("click");
},
function(){
menu = $("#select-choice-many-menu");
same(menu.closest('.ui-dialog').length, 1);
closeDialog();
},
function(){
//bring up the dialog again
select.trigger("click");
},
closeDialog,
start
]);
});
asyncTest( "custom select menu always renders screen from the left", function(){
var select;
@ -137,7 +107,7 @@
},
function(){
ok(location.hash.indexOf(dialogHashKey) == -1);
same(location.hash.indexOf(dialogHashKey), -1);
start();
}
]);
@ -164,6 +134,10 @@
$.testHelper.sequence(sequence, 1000);
});
test( "make sure the label for the select gets the ui-select class", function(){
ok( $( "#native-select-choice-few-container label" ).hasClass( "ui-select" ), "created label has ui-select class" );
});
module("Non native menus", {
setup: function() {
$.mobile.selectmenu.prototype.options.nativeMenu = false;
@ -195,66 +169,88 @@
], 500);
});
// https://github.com/jquery/jquery-mobile/issues/2181
asyncTest( "dialog sized select should alter the value of its parent select", function(){
var selectButton, value;
asyncTest( "using custom refocuses the button after close", function() {
var select, button, triggered = false;
$.testHelper.pageSequence([
expect( 1 );
$.testHelper.sequence([
resetHash,
function(){
$.mobile.changePage( "cached-tests.html" );
function() {
select = $("#select-choice-focus-test");
button = select.find( "a" );
button.trigger( "click" );
},
function() {
// NOTE this is called twice per triggered click
button.focus(function() {
triggered = true;
});
$(".ui-selectmenu-screen:not(.ui-screen-hidden)").trigger("click");
},
function(){
selectButton = $( "#cached-page-select" ).siblings( 'a' );
selectButton.click();
},
function(){
ok( $.mobile.activePage.hasClass('ui-dialog'), "the dialog came up" );
var option = $.mobile.activePage.find( "li a" ).not(":contains('" + selectButton.text() + "')").last();
value = option.text();
option.click();
},
function(){
same( value, selectButton.text(), "the selected value is propogated back to the button text" );
ok(triggered, "focus is triggered");
start();
}
]);
], 5000);
});
// https://github.com/jquery/jquery-mobile/issues/2181
asyncTest( "dialog sized select should prevent the removal of its parent page from the dom", function(){
var selectButton, parentPageId;
expect( 2 );
$.testHelper.pageSequence([
asyncTest( "selected items are highlighted", function(){
$.testHelper.sequence([
resetHash,
function(){
$.mobile.changePage( "cached-tests.html" );
// bring up the smaller choice menu
ok($("#select-choice-few-container a").length > 0, "there is in fact a button in the page");
$("#select-choice-few-container a").trigger("click");
},
function(){
selectButton = $.mobile.activePage.find( "#cached-page-select" ).siblings( 'a' ),
parentPageId = $.mobile.activePage.attr( 'id' );
same( $("#" + parentPageId).length, 1, "establish the parent page exists" );
selectButton.click();
var firstMenuChoice = $("#select-choice-few-menu li:first");
ok( firstMenuChoice.hasClass( $.mobile.activeBtnClass ),
"default menu choice has the active button class" );
$("#select-choice-few-menu a:last").click();
},
function(){
same( $( "#" + parentPageId).length, 1, "make sure parent page is still there after opening the dialog" );
$.mobile.activePage.find( "li a" ).last().click();
// bring up the menu again
$("#select-choice-few-container a").trigger("click");
},
function(){
window.history.back();
var lastMenuChoice = $("#select-choice-few-menu li:last");
ok( lastMenuChoice.hasClass( $.mobile.activeBtnClass ),
"previously slected item has the active button class" );
// close the dialog
lastMenuChoice.find( "a" ).click();
},
start
]);
], 1000);
});
test( "enabling and disabling", function(){
var select = $( "select" ).first(), button;
button = select.siblings( "a" ).first();
select.selectmenu( 'disable' );
same( select.attr('disabled'), "disabled", "select is disabled" );
ok( button.hasClass("ui-disabled"), "disabled class added" );
same( button.attr('aria-disabled'), "true", "select is disabled" );
same( select.selectmenu( 'option', 'disabled' ), true, "disbaled option set" );
select.selectmenu( 'enable' );
same( select.attr('disabled'), undefined, "select is disabled" );
ok( !button.hasClass("ui-disabled"), "disabled class added" );
same( button.attr('aria-disabled'), "false", "select is disabled" );
same( select.selectmenu( 'option', 'disabled' ), false, "disbaled option set" );
});
})(jQuery);

View file

@ -0,0 +1,47 @@
/*
* mobile select unit tests
*/
(function($){
module("jquery.mobile.forms.select native");
test( "native menu selections alter the button text", function(){
var select = $( "#native-select-choice-few" ), setAndCheck;
setAndCheck = function(key){
var text;
select.val( key ).selectmenu( 'refresh' );
text = select.find( "option[value='" + key + "']" ).text();
same( select.parent().find(".ui-btn-text").text(), text );
};
setAndCheck( 'rush' );
setAndCheck( 'standard' );
});
asyncTest( "selecting a value removes the related buttons down state", function(){
var select = $( "#native-select-choice-few" );
$.testHelper.sequence([
function() {
// click the native menu parent button
select.parent().trigger( 'vmousedown' );
},
function() {
ok( select.parent().hasClass("ui-btn-down-c"), "button down class added" );
},
function() {
// trigger a change on the select
select.trigger( "change" );
},
function() {
ok( !select.parent().hasClass("ui-btn-down-c"), "button down class removed" );
start();
}
], 300);
});
})(jQuery);

View file

@ -16,9 +16,10 @@ ol.ui-listview .ui-li-jsnumbering:before { content: "" !important; } /* to avoid
.ui-listview-inset .ui-li { border-right-width: 1px; border-left-width: 1px; }
.ui-li:last-child, .ui-li.ui-field-contain:last-child { border-bottom-width: 1px; }
.ui-li>.ui-btn-inner { display: block; position: relative; padding: 0; }
.ui-li .ui-btn-inner a.ui-link-inherit, .ui-li-static.ui-li { padding: .7em 75px .7em 15px; display: block; }
.ui-li .ui-btn-inner a.ui-link-inherit, .ui-li-static.ui-li { padding: .7em 25px .7em 15px; display: block; }
.ui-li-has-thumb .ui-btn-inner a.ui-link-inherit, .ui-li-static.ui-li-has-thumb { min-height: 60px; padding-left: 100px; }
.ui-li-has-icon .ui-btn-inner a.ui-link-inherit, .ui-li-static.ui-li-has-icon { min-height: 20px; padding-left: 40px; }
.ui-li-has-count .ui-btn-inner a.ui-link-inherit, .ui-li-static.ui-li-has-count { padding-right: 75px; }
.ui-li-heading { font-size: 16px; font-weight: bold; display: block; margin: .6em 0; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; }
.ui-li-desc { font-size: 12px; font-weight: normal; display: block; margin: -.5em 0 .6em; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; }
.ui-li-thumb, .ui-li-icon { position: absolute; left: 1px; top: 0; max-height: 80px; max-width: 80px; }

View file

@ -1 +1 @@
1.0b2
1.0b3pre