Skip to content

Commit 1ddce18

Browse files
author
Gabriel "_|Nix|_" Schulhof
committed
Merge pull request jquery-archive#6108 from jquery/flatten-custom-select
Flatten custom select
2 parents 3da7657 + e7691a7 commit 1ddce18

File tree

2 files changed

+136
-127
lines changed

2 files changed

+136
-127
lines changed

js/widgets/forms/select.custom.js

Lines changed: 133 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -48,55 +48,135 @@ $.widget( "mobile.selectmenu", $.mobile.selectmenu, {
4848
return this._super();
4949
},
5050

51+
_handleSelectFocus: function() {
52+
this.element.blur();
53+
this.button.focus();
54+
},
55+
56+
_handleButtonVclickKeydown: function( event ) {
57+
if ( this.options.disabled || this.isOpen ) {
58+
return;
59+
}
60+
61+
if (event.type === "vclick" ||
62+
event.keyCode && (event.keyCode === $.mobile.keyCode.ENTER || event.keyCode === $.mobile.keyCode.SPACE)) {
63+
64+
this._decideFormat();
65+
if ( this.menuType === "overlay" ) {
66+
this.button.attr( "href", "#" + this.popupId ).attr( "data-" + ( $.mobile.ns || "" ) + "rel", "popup" );
67+
} else {
68+
this.button.attr( "href", "#" + this.dialogId ).attr( "data-" + ( $.mobile.ns || "" ) + "rel", "dialog" );
69+
}
70+
this.isOpen = true;
71+
// Do not prevent default, so the navigation may have a chance to actually open the chosen format
72+
}
73+
},
74+
75+
_handleListFocus: function( e ) {
76+
var params = ( e.type === "focusin" ) ?
77+
{ tabindex: "0", event: "vmouseover" }:
78+
{ tabindex: "-1", event: "vmouseout" };
79+
80+
$( e.target )
81+
.attr( "tabindex", params.tabindex )
82+
.trigger( params.event );
83+
},
84+
85+
_handleListKeydown: function( event ) {
86+
var target = $( event.target ),
87+
li = target.closest( "li" );
88+
89+
// switch logic based on which key was pressed
90+
switch ( event.keyCode ) {
91+
// up or left arrow keys
92+
case 38:
93+
goToAdjacentItem( li, target, "prev" );
94+
return false;
95+
// down or right arrow keys
96+
case 40:
97+
goToAdjacentItem( li, target, "next" );
98+
return false;
99+
// If enter or space is pressed, trigger click
100+
case 13:
101+
case 32:
102+
target.trigger( "click" );
103+
return false;
104+
}
105+
},
106+
107+
_handleMenuPageHide: function() {
108+
// TODO centralize page removal binding / handling in the page plugin.
109+
// Suggestion from @jblas to do refcounting
110+
//
111+
// TODO extremely confusing dependency on the open method where the pagehide.remove
112+
// bindings are stripped to prevent the parent page from disappearing. The way
113+
// we're keeping pages in the DOM right now sucks
114+
//
115+
// rebind the page remove that was unbound in the open function
116+
// to allow for the parent page removal from actions other than the use
117+
// of a dialog sized custom select
118+
//
119+
// doing this here provides for the back button on the custom select dialog
120+
$.mobile._bindPageRemove.call( this.thisPage );
121+
},
122+
123+
_handleHeaderCloseClick: function() {
124+
if ( this.menuType === "overlay" ) {
125+
this.close();
126+
return false;
127+
}
128+
},
129+
51130
build: function() {
52-
var selectID, prefix, popupID, dialogID, label, thisPage, isMultiple, menuId, themeAttr, overlayThemeAttr,
53-
dividerThemeAttr, menuPage, listbox, list, header, headerTitle, menuPageContent, menuPageClose, headerClose, self;
131+
var selectId, prefix, popupId, dialogId, label, thisPage, isMultiple, menuId, themeAttr, overlayThemeAttr,
132+
dividerThemeAttr, menuPage, listbox, list, header, headerTitle, menuPageContent, menuPageClose, headerClose, self,
133+
o = this.options;
54134

55-
if ( this.options.nativeMenu ) {
135+
if ( o.nativeMenu ) {
56136
return this._super();
57137
}
58138

59139
self = this;
60-
selectID = this.selectID;
61-
prefix = ( selectID ? selectID : ( ( $.mobile.ns || "" ) + "uuid-" + this.uuid ) );
62-
popupID = prefix + "-listbox";
63-
dialogID = prefix + "-dialog";
140+
selectId = this.selectId;
141+
prefix = ( selectId ? selectId : ( ( $.mobile.ns || "" ) + "uuid-" + this.uuid ) );
142+
popupId = prefix + "-listbox";
143+
dialogId = prefix + "-dialog";
64144
label = this.label;
65145
thisPage = this.element.closest( ".ui-page" );
66146
isMultiple = this.element[ 0 ].multiple;
67-
menuId = selectID + "-menu";
68-
themeAttr = this.options.theme ? ( " data-" + $.mobile.ns + "theme='" + this.options.theme + "'" ) : "";
69-
overlayThemeAttr = this.options.overlayTheme ? ( " data-" + $.mobile.ns + "theme='" + this.options.overlayTheme + "'" ) : "";
70-
dividerThemeAttr = ( this.options.dividerTheme && isMultiple ) ? ( " data-" + $.mobile.ns + "divider-theme='" + this.options.dividerTheme + "'" ) : "";
71-
menuPage = $( "<div data-" + $.mobile.ns + "role='dialog' class='ui-selectmenu' id='" + dialogID + "'" + themeAttr + overlayThemeAttr + ">" +
147+
menuId = selectId + "-menu";
148+
themeAttr = o.theme ? ( " data-" + $.mobile.ns + "theme='" + o.theme + "'" ) : "";
149+
overlayThemeAttr = o.overlayTheme ? ( " data-" + $.mobile.ns + "theme='" + o.overlayTheme + "'" ) : "";
150+
dividerThemeAttr = ( o.dividerTheme && isMultiple ) ? ( " data-" + $.mobile.ns + "divider-theme='" + o.dividerTheme + "'" ) : "";
151+
menuPage = $( "<div data-" + $.mobile.ns + "role='dialog' class='ui-selectmenu' id='" + dialogId + "'" + themeAttr + overlayThemeAttr + ">" +
72152
"<div data-" + $.mobile.ns + "role='header'>" +
73153
"<div class='ui-title'>" + label.getEncodedText() + "</div>"+
74154
"</div>"+
75155
"<div data-" + $.mobile.ns + "role='content'></div>"+
76156
"</div>" );
77-
listbox = $( "<div id='" + popupID + "' class='ui-selectmenu'>" ).insertAfter( this.select ).popup({ theme: this.options.overlayTheme });
157+
listbox = $( "<div id='" + popupId + "' class='ui-selectmenu'>" ).insertAfter( this.select ).popup({ theme: o.overlayTheme });
78158
list = $( "<ul class='ui-selectmenu-list' id='" + menuId + "' role='listbox' aria-labelledby='" + this.buttonId + "'" + themeAttr + dividerThemeAttr + ">" ).appendTo( listbox );
79-
header = $( "<div class='ui-header ui-bar-" + ( this.options.theme ? this.options.theme : "inherit" ) + "'>" ).prependTo( listbox );
159+
header = $( "<div class='ui-header ui-bar-" + ( o.theme ? o.theme : "inherit" ) + "'>" ).prependTo( listbox );
80160
headerTitle = $( "<h1 class='ui-title'>" ).appendTo( header );
81161

82162
if ( this.isMultiple ) {
83163
headerClose = $( "<a>", {
84-
"text": this.options.closeText,
164+
"text": o.closeText,
85165
"href": "#",
86166
"class": "ui-btn ui-corner-all ui-btn-left ui-btn-icon-notext ui-icon-delete"
87167
}).appendTo( header );
88168
}
89169

90170
$.extend( this, {
91-
selectID: selectID,
171+
selectId: selectId,
92172
menuId: menuId,
93-
popupID: popupID,
94-
dialogID: dialogID,
173+
popupId: popupId,
174+
dialogId: dialogId,
95175
thisPage: thisPage,
96176
menuPage: menuPage,
97177
label: label,
98178
isMultiple: isMultiple,
99-
theme: this.options.theme,
179+
theme: o.theme,
100180
listbox: listbox,
101181
list: list,
102182
header: header,
@@ -108,59 +188,38 @@ $.widget( "mobile.selectmenu", $.mobile.selectmenu, {
108188
});
109189

110190
// Create list from select, update state
111-
self.refresh();
191+
this.refresh();
112192

113-
if ( self._origTabIndex === undefined ) {
114-
// Map undefined to false, because self._origTabIndex === undefined
193+
if ( this._origTabIndex === undefined ) {
194+
// Map undefined to false, because this._origTabIndex === undefined
115195
// indicates that we have not yet checked whether the select has
116196
// originally had a tabindex attribute, whereas false indicates that
117197
// we have checked the select for such an attribute, and have found
118198
// none present.
119-
self._origTabIndex = ( self.select[ 0 ].getAttribute( "tabindex" ) === null ) ? false : self.select.attr( "tabindex" );
199+
this._origTabIndex = ( this.select[ 0 ].getAttribute( "tabindex" ) === null ) ? false : this.select.attr( "tabindex" );
120200
}
121-
self.select.attr( "tabindex", "-1" ).focus(function() {
122-
$( this ).blur();
123-
self.button.focus();
124-
});
201+
this.select.attr( "tabindex", "-1" );
202+
this._on( this.select, { focus : "_handleSelectFocus" } );
125203

126204
// Button events
127-
self.button.bind( "vclick keydown" , function( event ) {
128-
if ( self.options.disabled || self.isOpen ) {
129-
return;
130-
}
131-
132-
if (event.type === "vclick" ||
133-
event.keyCode && (event.keyCode === $.mobile.keyCode.ENTER || event.keyCode === $.mobile.keyCode.SPACE)) {
134-
135-
self._decideFormat();
136-
if ( self.menuType === "overlay" ) {
137-
self.button.attr( "href", "#" + self.popupID ).attr( "data-" + ( $.mobile.ns || "" ) + "rel", "popup" );
138-
} else {
139-
self.button.attr( "href", "#" + self.dialogID ).attr( "data-" + ( $.mobile.ns || "" ) + "rel", "dialog" );
140-
}
141-
self.isOpen = true;
142-
// Do not prevent default, so the navigation may have a chance to actually open the chosen format
143-
}
205+
this._on( this.button, {
206+
vclick : "_handleButtonVclickKeydown",
207+
keydown : "_handleButtonVclickKeydown"
144208
});
145209

146210
// Events for list items
147-
self.list.attr( "role", "listbox" )
148-
.bind( "focusin", function( e ) {
149-
$( e.target )
150-
.attr( "tabindex", "0" )
151-
.trigger( "vmouseover" );
152-
153-
})
154-
.bind( "focusout", function( e ) {
155-
$( e.target )
156-
.attr( "tabindex", "-1" )
157-
.trigger( "vmouseout" );
158-
})
211+
this.list.attr( "role", "listbox" );
212+
this._on( this.list, {
213+
focusin : "_handleListFocus",
214+
focusout : "_handleListFocus",
215+
keydown: "_handleListKeydown"
216+
});
217+
this.list
159218
.delegate( "li:not(.ui-disabled, .ui-li-divider)", "click", function( event ) {
160219

161220
// index of option tag to be selected
162221
var oldIndex = self.select[ 0 ].selectedIndex,
163-
newIndex = self.list.find( "li:not(.ui-li-divider)" ).index( this ),
222+
newIndex = $.mobile.getAttribute( this, "option-index", true ),
164223
option = self._selectOptions().eq( newIndex )[ 0 ];
165224

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

191250
event.preventDefault();
192-
})
193-
.keydown(function( event ) { //keyboard events for menu items
194-
var target = $( event.target ),
195-
li = target.closest( "li" );
196-
197-
// switch logic based on which key was pressed
198-
switch ( event.keyCode ) {
199-
// up or left arrow keys
200-
case 38:
201-
goToAdjacentItem( li, target, "prev" );
202-
return false;
203-
// down or right arrow keys
204-
case 40:
205-
goToAdjacentItem( li, target, "next" );
206-
return false;
207-
// If enter or space is pressed, trigger click
208-
case 13:
209-
case 32:
210-
target.trigger( "click" );
211-
212-
return false;
213-
}
214251
});
215252

216253
// button refocus ensures proper height calculation
217254
// by removing the inline style and ensuring page inclusion
218-
self.menuPage.bind( "pagehide", function() {
219-
// TODO centralize page removal binding / handling in the page plugin.
220-
// Suggestion from @jblas to do refcounting
221-
//
222-
// TODO extremely confusing dependency on the open method where the pagehide.remove
223-
// bindings are stripped to prevent the parent page from disappearing. The way
224-
// we're keeping pages in the DOM right now sucks
225-
//
226-
// rebind the page remove that was unbound in the open function
227-
// to allow for the parent page removal from actions other than the use
228-
// of a dialog sized custom select
229-
//
230-
// doing this here provides for the back button on the custom select dialog
231-
$.mobile._bindPageRemove.call( self.thisPage );
232-
});
255+
this._on( this.menuPage, { pagehide: "_handleMenuPageHide" } );
233256

234257
// Events on the popup
235-
self.listbox.bind( "popupafterclose", function() {
236-
self.close();
237-
});
258+
this._on( this.listbox, { popupafterclose: "close" } );
238259

239260
// Close button on small overlays
240-
if ( self.isMultiple ) {
241-
self.headerClose.click(function() {
242-
if ( self.menuType === "overlay" ) {
243-
self.close();
244-
return false;
245-
}
246-
});
261+
if ( this.isMultiple ) {
262+
this._on( this.headerClose, { click: "_handleHeaderCloseClick" } );
247263
}
248264

249-
// track this dependency so that when the parent page
250-
// is removed on pagehide it will also remove the menupage
251-
self.thisPage.addDependents( this.menuPage );
252-
253265
return this;
254266
},
255267

@@ -332,6 +344,14 @@ $.widget( "mobile.selectmenu", $.mobile.selectmenu, {
332344
this.button.click();
333345
},
334346

347+
_focusMenuItem: function() {
348+
var selector = this.list.find( "a." + $.mobile.activeBtnClass );
349+
if ( selector.length === 0 ) {
350+
selector = this.list.find( "li:not(" + unfocusableItemSelector + ") a.ui-btn" );
351+
}
352+
selector.first().focus();
353+
},
354+
335355
_decideFormat: function() {
336356
var self = this,
337357
$window = $.mobile.window,
@@ -341,14 +361,6 @@ $.widget( "mobile.selectmenu", $.mobile.selectmenu, {
341361
btnOffset = self.button.offset().top,
342362
screenHeight = $window.height();
343363

344-
function focusMenuItem() {
345-
var selector = self.list.find( "a." + $.mobile.activeBtnClass );
346-
if ( selector.length === 0 ) {
347-
selector = self.list.find( "li:not(" + unfocusableItemSelector + ") a.ui-btn" );
348-
}
349-
selector.first().focus();
350-
}
351-
352364
if ( menuHeight > screenHeight - 80 || !$.support.scrollTop ) {
353365

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

370-
self.menuPage
371-
.one( "pageshow", function() {
372-
focusMenuItem();
373-
})
374-
.one( "pagehide", function() {
375-
self.close();
376-
});
382+
self.menuPage.one( {
383+
pageshow: $.proxy( this, "_focusMenuItem" ),
384+
pagehide: $.proxy( this, "close" )
385+
});
377386

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

384-
self.listbox.one( "popupafteropen", focusMenuItem );
393+
self.listbox.one( { popupafteropen: $.proxy( this, "_focusMenuItem" ) } );
385394
}
386395
},
387396

js/widgets/forms/select.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,9 @@ $.widget( "mobile.selectmenu", $.mobile.widget, $.extend( {
7676
}
7777

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

0 commit comments

Comments
 (0)