Skip to content
This repository was archived by the owner on Oct 8, 2021. It is now read-only.

Flatten custom select #6108

Merged
merged 12 commits into from
Jun 21, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
257 changes: 133 additions & 124 deletions js/widgets/forms/select.custom.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,55 +48,135 @@ $.widget( "mobile.selectmenu", $.mobile.selectmenu, {
return this._super();
},

_handleSelectFocus: function() {
this.element.blur();
this.button.focus();
},

_handleButtonVclickKeydown: function( event ) {
if ( this.options.disabled || this.isOpen ) {
return;
}

if (event.type === "vclick" ||
event.keyCode && (event.keyCode === $.mobile.keyCode.ENTER || event.keyCode === $.mobile.keyCode.SPACE)) {

this._decideFormat();
if ( this.menuType === "overlay" ) {
this.button.attr( "href", "#" + this.popupId ).attr( "data-" + ( $.mobile.ns || "" ) + "rel", "popup" );
} else {
this.button.attr( "href", "#" + this.dialogId ).attr( "data-" + ( $.mobile.ns || "" ) + "rel", "dialog" );
}
this.isOpen = true;
// Do not prevent default, so the navigation may have a chance to actually open the chosen format
}
},

_handleListFocus: function( e ) {
var params = ( e.type === "focusin" ) ?
{ tabindex: "0", event: "vmouseover" }:
{ tabindex: "-1", event: "vmouseout" };

$( e.target )
.attr( "tabindex", params.tabindex )
.trigger( params.event );
},

_handleListKeydown: function( event ) {
var target = $( event.target ),
li = target.closest( "li" );

// switch logic based on which key was pressed
switch ( event.keyCode ) {
// up or left arrow keys
case 38:
goToAdjacentItem( li, target, "prev" );
return false;
// down or right arrow keys
case 40:
goToAdjacentItem( li, target, "next" );
return false;
// If enter or space is pressed, trigger click
case 13:
case 32:
target.trigger( "click" );
return false;
}
},

_handleMenuPageHide: function() {
// TODO centralize page removal binding / handling in the page plugin.
// Suggestion from @jblas to do refcounting
//
// TODO extremely confusing dependency on the open method where the pagehide.remove
// bindings are stripped to prevent the parent page from disappearing. The way
// we're keeping pages in the DOM right now sucks
//
// 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
//
// doing this here provides for the back button on the custom select dialog
$.mobile._bindPageRemove.call( this.thisPage );
},

_handleHeaderCloseClick: function() {
if ( this.menuType === "overlay" ) {
this.close();
return false;
}
},

build: function() {
var selectID, prefix, popupID, dialogID, label, thisPage, isMultiple, menuId, themeAttr, overlayThemeAttr,
dividerThemeAttr, menuPage, listbox, list, header, headerTitle, menuPageContent, menuPageClose, headerClose, self;
var selectId, prefix, popupId, dialogId, label, thisPage, isMultiple, menuId, themeAttr, overlayThemeAttr,
dividerThemeAttr, menuPage, listbox, list, header, headerTitle, menuPageContent, menuPageClose, headerClose, self,
o = this.options;

if ( this.options.nativeMenu ) {
if ( o.nativeMenu ) {
return this._super();
}

self = this;
selectID = this.selectID;
prefix = ( selectID ? selectID : ( ( $.mobile.ns || "" ) + "uuid-" + this.uuid ) );
popupID = prefix + "-listbox";
dialogID = prefix + "-dialog";
selectId = this.selectId;
prefix = ( selectId ? selectId : ( ( $.mobile.ns || "" ) + "uuid-" + this.uuid ) );
popupId = prefix + "-listbox";
dialogId = prefix + "-dialog";
label = this.label;
thisPage = this.element.closest( ".ui-page" );
isMultiple = this.element[ 0 ].multiple;
menuId = selectID + "-menu";
themeAttr = this.options.theme ? ( " data-" + $.mobile.ns + "theme='" + this.options.theme + "'" ) : "";
overlayThemeAttr = this.options.overlayTheme ? ( " data-" + $.mobile.ns + "theme='" + this.options.overlayTheme + "'" ) : "";
dividerThemeAttr = ( this.options.dividerTheme && isMultiple ) ? ( " data-" + $.mobile.ns + "divider-theme='" + this.options.dividerTheme + "'" ) : "";
menuPage = $( "<div data-" + $.mobile.ns + "role='dialog' class='ui-selectmenu' id='" + dialogID + "'" + themeAttr + overlayThemeAttr + ">" +
menuId = selectId + "-menu";
themeAttr = o.theme ? ( " data-" + $.mobile.ns + "theme='" + o.theme + "'" ) : "";
overlayThemeAttr = o.overlayTheme ? ( " data-" + $.mobile.ns + "theme='" + o.overlayTheme + "'" ) : "";
dividerThemeAttr = ( o.dividerTheme && isMultiple ) ? ( " data-" + $.mobile.ns + "divider-theme='" + o.dividerTheme + "'" ) : "";
menuPage = $( "<div data-" + $.mobile.ns + "role='dialog' class='ui-selectmenu' id='" + dialogId + "'" + themeAttr + overlayThemeAttr + ">" +
"<div data-" + $.mobile.ns + "role='header'>" +
"<div class='ui-title'>" + label.getEncodedText() + "</div>"+
"</div>"+
"<div data-" + $.mobile.ns + "role='content'></div>"+
"</div>" );
listbox = $( "<div id='" + popupID + "' class='ui-selectmenu'>" ).insertAfter( this.select ).popup({ theme: this.options.overlayTheme });
listbox = $( "<div id='" + popupId + "' class='ui-selectmenu'>" ).insertAfter( this.select ).popup({ theme: o.overlayTheme });
list = $( "<ul class='ui-selectmenu-list' id='" + menuId + "' role='listbox' aria-labelledby='" + this.buttonId + "'" + themeAttr + dividerThemeAttr + ">" ).appendTo( listbox );
header = $( "<div class='ui-header ui-bar-" + ( this.options.theme ? this.options.theme : "inherit" ) + "'>" ).prependTo( listbox );
header = $( "<div class='ui-header ui-bar-" + ( o.theme ? o.theme : "inherit" ) + "'>" ).prependTo( listbox );
headerTitle = $( "<h1 class='ui-title'>" ).appendTo( header );

if ( this.isMultiple ) {
headerClose = $( "<a>", {
"text": this.options.closeText,
"text": o.closeText,
"href": "#",
"class": "ui-btn ui-corner-all ui-btn-left ui-btn-icon-notext ui-icon-delete"
}).appendTo( header );
}

$.extend( this, {
selectID: selectID,
selectId: selectId,
menuId: menuId,
popupID: popupID,
dialogID: dialogID,
popupId: popupId,
dialogId: dialogId,
thisPage: thisPage,
menuPage: menuPage,
label: label,
isMultiple: isMultiple,
theme: this.options.theme,
theme: o.theme,
listbox: listbox,
list: list,
header: header,
Expand All @@ -108,59 +188,38 @@ $.widget( "mobile.selectmenu", $.mobile.selectmenu, {
});

// Create list from select, update state
self.refresh();
this.refresh();

if ( self._origTabIndex === undefined ) {
// Map undefined to false, because self._origTabIndex === undefined
if ( this._origTabIndex === undefined ) {
// Map undefined to false, because this._origTabIndex === undefined
// indicates that we have not yet checked whether the select has
// originally had a tabindex attribute, whereas false indicates that
// we have checked the select for such an attribute, and have found
// none present.
self._origTabIndex = ( self.select[ 0 ].getAttribute( "tabindex" ) === null ) ? false : self.select.attr( "tabindex" );
this._origTabIndex = ( this.select[ 0 ].getAttribute( "tabindex" ) === null ) ? false : this.select.attr( "tabindex" );
}
self.select.attr( "tabindex", "-1" ).focus(function() {
$( this ).blur();
self.button.focus();
});
this.select.attr( "tabindex", "-1" );
this._on( this.select, { focus : "_handleSelectFocus" } );

// Button events
self.button.bind( "vclick keydown" , function( event ) {
if ( self.options.disabled || self.isOpen ) {
return;
}

if (event.type === "vclick" ||
event.keyCode && (event.keyCode === $.mobile.keyCode.ENTER || event.keyCode === $.mobile.keyCode.SPACE)) {

self._decideFormat();
if ( self.menuType === "overlay" ) {
self.button.attr( "href", "#" + self.popupID ).attr( "data-" + ( $.mobile.ns || "" ) + "rel", "popup" );
} else {
self.button.attr( "href", "#" + self.dialogID ).attr( "data-" + ( $.mobile.ns || "" ) + "rel", "dialog" );
}
self.isOpen = true;
// Do not prevent default, so the navigation may have a chance to actually open the chosen format
}
this._on( this.button, {
vclick : "_handleButtonVclickKeydown",
keydown : "_handleButtonVclickKeydown"
});

// Events for list items
self.list.attr( "role", "listbox" )
.bind( "focusin", function( e ) {
$( e.target )
.attr( "tabindex", "0" )
.trigger( "vmouseover" );

})
.bind( "focusout", function( e ) {
$( e.target )
.attr( "tabindex", "-1" )
.trigger( "vmouseout" );
})
this.list.attr( "role", "listbox" );
this._on( this.list, {
focusin : "_handleListFocus",
focusout : "_handleListFocus",
keydown: "_handleListKeydown"
});
this.list
.delegate( "li:not(.ui-disabled, .ui-li-divider)", "click", 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 ),
newIndex = $.mobile.getAttribute( this, "option-index", true ),
option = self._selectOptions().eq( newIndex )[ 0 ];

// toggle selected status on the tag for multi selects
Expand Down Expand Up @@ -189,67 +248,20 @@ $.widget( "mobile.selectmenu", $.mobile.selectmenu, {
}

event.preventDefault();
})
.keydown(function( event ) { //keyboard events for menu items
var target = $( event.target ),
li = target.closest( "li" );

// switch logic based on which key was pressed
switch ( event.keyCode ) {
// up or left arrow keys
case 38:
goToAdjacentItem( li, target, "prev" );
return false;
// down or right arrow keys
case 40:
goToAdjacentItem( li, target, "next" );
return false;
// If enter or space is pressed, trigger click
case 13:
case 32:
target.trigger( "click" );

return false;
}
});

// button refocus ensures proper height calculation
// by removing the inline style and ensuring page inclusion
self.menuPage.bind( "pagehide", function() {
// TODO centralize page removal binding / handling in the page plugin.
// Suggestion from @jblas to do refcounting
//
// TODO extremely confusing dependency on the open method where the pagehide.remove
// bindings are stripped to prevent the parent page from disappearing. The way
// we're keeping pages in the DOM right now sucks
//
// 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
//
// doing this here provides for the back button on the custom select dialog
$.mobile._bindPageRemove.call( self.thisPage );
});
this._on( this.menuPage, { pagehide: "_handleMenuPageHide" } );

// Events on the popup
self.listbox.bind( "popupafterclose", function() {
self.close();
});
this._on( this.listbox, { popupafterclose: "close" } );

// Close button on small overlays
if ( self.isMultiple ) {
self.headerClose.click(function() {
if ( self.menuType === "overlay" ) {
self.close();
return false;
}
});
if ( this.isMultiple ) {
this._on( this.headerClose, { click: "_handleHeaderCloseClick" } );
}

// track this dependency so that when the parent page
// is removed on pagehide it will also remove the menupage
self.thisPage.addDependents( this.menuPage );

return this;
},

Expand Down Expand Up @@ -332,6 +344,14 @@ $.widget( "mobile.selectmenu", $.mobile.selectmenu, {
this.button.click();
},

_focusMenuItem: function() {
var selector = this.list.find( "a." + $.mobile.activeBtnClass );
if ( selector.length === 0 ) {
selector = this.list.find( "li:not(" + unfocusableItemSelector + ") a.ui-btn" );
}
selector.first().focus();
},

_decideFormat: function() {
var self = this,
$window = $.mobile.window,
Expand All @@ -341,14 +361,6 @@ $.widget( "mobile.selectmenu", $.mobile.selectmenu, {
btnOffset = self.button.offset().top,
screenHeight = $window.height();

function focusMenuItem() {
var selector = self.list.find( "a." + $.mobile.activeBtnClass );
if ( selector.length === 0 ) {
selector = self.list.find( "li:not(" + unfocusableItemSelector + ") a.ui-btn" );
}
selector.first().focus();
}

if ( menuHeight > screenHeight - 80 || !$.support.scrollTop ) {

self.menuPage.appendTo( $.mobile.pageContainer ).page();
Expand All @@ -367,21 +379,18 @@ $.widget( "mobile.selectmenu", $.mobile.selectmenu, {
});
}

self.menuPage
.one( "pageshow", function() {
focusMenuItem();
})
.one( "pagehide", function() {
self.close();
});
self.menuPage.one( {
pageshow: $.proxy( this, "_focusMenuItem" ),
pagehide: $.proxy( this, "close" )
});

self.menuType = "page";
self.menuPageContent.append( self.list );
self.menuPage.find( "div .ui-title" ).text( self.label.text() );
} else {
self.menuType = "overlay";

self.listbox.one( "popupafteropen", focusMenuItem );
self.listbox.one( { popupafteropen: $.proxy( this, "_focusMenuItem" ) } );
}
},

Expand Down
6 changes: 3 additions & 3 deletions js/widgets/forms/select.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,9 @@ $.widget( "mobile.selectmenu", $.mobile.widget, $.extend( {
}

this.select = this.element.removeClass( "ui-btn-left ui-btn-right" ).wrap( "<div class='ui-select" + classes + "'>" );
this.selectID = this.select.attr( "id" );
this.buttonId = this.selectID + "-button";
this.label = $( "label[for='"+ this.selectID +"']" );
this.selectId = this.select.attr( "id" );
this.buttonId = this.selectId + "-button";
this.label = $( "label[for='"+ this.selectId +"']" );
this.isMultiple = this.select[ 0 ].multiple;
},

Expand Down