eb16f15314f0b574a351c24c2863c9cf6148a5d0
[platform/framework/web/web-ui-fw.git] / libs / js / jquery-mobile-1.0.1pre / js / jquery.mobile.forms.select.custom.js
1 /*
2 * custom "selectmenu" plugin
3 */
4
5 (function( $, undefined ) {
6         var extendSelect = function( widget ){
7
8                 var select = widget.select,
9                         selectID  = widget.selectID,
10                         label = widget.label,
11                         thisPage = widget.select.closest( ".ui-page" ),
12                         screen = $( "<div>", {"class": "ui-selectmenu-screen ui-screen-hidden"} ).appendTo( thisPage ),
13                         selectOptions = widget._selectOptions(),
14                         isMultiple = widget.isMultiple = widget.select[ 0 ].multiple,
15                         buttonId = selectID + "-button",
16                         menuId = selectID + "-menu",
17                         menuPage = $( "<div data-" + $.mobile.ns + "role='dialog' data-" +$.mobile.ns + "theme='"+ widget.options.theme +"' data-" +$.mobile.ns + "overlay-theme='"+ widget.options.overlayTheme +"'>" +
18                                 "<div data-" + $.mobile.ns + "role='header'>" +
19                                 "<div class='ui-title'>" + label.getEncodedText() + "</div>"+
20                                 "</div>"+
21                                 "<div data-" + $.mobile.ns + "role='content'></div>"+
22                                 "</div>" ).appendTo( $.mobile.pageContainer ).page(),
23
24                         listbox =  $("<div>", { "class": "ui-selectmenu ui-selectmenu-hidden ui-overlay-shadow ui-corner-all ui-body-" + widget.options.overlayTheme + " " + $.mobile.defaultDialogTransition } ).insertAfter(screen),
25
26                         list = $( "<ul>", {
27                                 "class": "ui-selectmenu-list",
28                                 "id": menuId,
29                                 "role": "listbox",
30                                 "aria-labelledby": buttonId
31                         }).attr( "data-" + $.mobile.ns + "theme", widget.options.theme ).appendTo( listbox ),
32
33                         header = $( "<div>", {
34                                 "class": "ui-header ui-bar-" + widget.options.theme
35                         }).prependTo( listbox ),
36
37                         headerTitle = $( "<h1>", {
38                                 "class": "ui-title"
39                         }).appendTo( header ),
40
41                         headerClose = $( "<a>", {
42                                 "text": widget.options.closeText,
43                                 "href": "#",
44                                 "class": "ui-btn-left"
45                         }).attr( "data-" + $.mobile.ns + "iconpos", "notext" ).attr( "data-" + $.mobile.ns + "icon", "delete" ).appendTo( header ).buttonMarkup(),
46
47                         menuPageContent = menuPage.find( ".ui-content" ),
48
49                         menuPageClose = menuPage.find( ".ui-header a" );
50
51
52                 $.extend( widget, {
53                         select: widget.select,
54                         selectID: selectID,
55                         buttonId: buttonId,
56                         menuId: menuId,
57                         thisPage: thisPage,
58                         menuPage: menuPage,
59                         label: label,
60                         screen: screen,
61                         selectOptions: selectOptions,
62                         isMultiple: isMultiple,
63                         theme: widget.options.theme,
64                         listbox: listbox,
65                         list: list,
66                         header: header,
67                         headerTitle: headerTitle,
68                         headerClose: headerClose,
69                         menuPageContent: menuPageContent,
70                         menuPageClose: menuPageClose,
71                         placeholder: "",
72
73                         build: function() {
74                                 var self = this;
75
76                                 // Create list from select, update state
77                                 self.refresh();
78
79                                 self.select.attr( "tabindex", "-1" ).focus(function() {
80                                         $( this ).blur();
81                                         self.button.focus();
82                                 });
83
84                                 // Button events
85                                 self.button.bind( "vclick keydown" , function( event ) {
86                                         if ( event.type == "vclick" ||
87                                                          event.keyCode && ( event.keyCode === $.mobile.keyCode.ENTER ||
88                                                                                                                                         event.keyCode === $.mobile.keyCode.SPACE ) ) {
89
90                                                 self.open();
91                                                 event.preventDefault();
92                                         }
93                                 });
94
95                                 // Events for list items
96                                 self.list.attr( "role", "listbox" )
97                                         .delegate( ".ui-li>a", "focusin", function() {
98                                                 $( this ).attr( "tabindex", "0" );
99                                         })
100                                         .delegate( ".ui-li>a", "focusout", function() {
101                                                 $( this ).attr( "tabindex", "-1" );
102                                         })
103                                         .delegate( "li:not(.ui-disabled, .ui-li-divider)", "click", function( event ) {
104
105                                                 // index of option tag to be selected
106                                                 var oldIndex = self.select[ 0 ].selectedIndex,
107                                                         newIndex = self.list.find( "li:not(.ui-li-divider)" ).index( this ),
108                                                         option = self._selectOptions().eq( newIndex )[ 0 ];
109
110                                                 // toggle selected status on the tag for multi selects
111                                                 option.selected = self.isMultiple ? !option.selected : true;
112
113                                                 // toggle checkbox class for multiple selects
114                                                 if ( self.isMultiple ) {
115                                                         $( this ).find( ".ui-icon" )
116                                                                 .toggleClass( "ui-icon-checkbox-on", option.selected )
117                                                                 .toggleClass( "ui-icon-checkbox-off", !option.selected );
118                                                 }
119
120                                                 // trigger change if value changed
121                                                 if ( self.isMultiple || oldIndex !== newIndex ) {
122                                                         self.select.trigger( "change" );
123                                                 }
124
125                                                 //hide custom select for single selects only
126                                                 if ( !self.isMultiple ) {
127                                                         self.close();
128                                                 }
129
130                                                 event.preventDefault();
131                                         })
132                                         .keydown(function( event ) {  //keyboard events for menu items
133                                                 var target = $( event.target ),
134                                                         li = target.closest( "li" ),
135                                                         prev, next;
136
137                                                 // switch logic based on which key was pressed
138                                                 switch ( event.keyCode ) {
139                                                         // up or left arrow keys
140                                                  case 38:
141                                                         prev = li.prev();
142
143                                                         // if there's a previous option, focus it
144                                                         if ( prev.length ) {
145                                                                 target
146                                                                         .blur()
147                                                                         .attr( "tabindex", "-1" );
148
149                                                                 prev.find( "a" ).first().focus();
150                                                         }
151
152                                                         return false;
153                                                         break;
154
155                                                         // down or right arrow keys
156                                                  case 40:
157                                                         next = li.next();
158
159                                                         // if there's a next option, focus it
160                                                         if ( next.length ) {
161                                                                 target
162                                                                         .blur()
163                                                                         .attr( "tabindex", "-1" );
164
165                                                                 next.find( "a" ).first().focus();
166                                                         }
167
168                                                         return false;
169                                                         break;
170
171                                                         // If enter or space is pressed, trigger click
172                                                  case 13:
173                                                  case 32:
174                                                         target.trigger( "click" );
175
176                                                         return false;
177                                                         break;
178                                                 }
179                                         });
180
181                                 // button refocus ensures proper height calculation
182                                 // by removing the inline style and ensuring page inclusion
183                                 self.menuPage.bind( "pagehide", function() {
184                                         self.list.appendTo( self.listbox );
185                                         self._focusButton();
186
187                                         // TODO centralize page removal binding / handling in the page plugin.
188                                         // Suggestion from @jblas to do refcounting
189                                         //
190                                         // TODO extremely confusing dependency on the open method where the pagehide.remove
191                                         // bindings are stripped to prevent the parent page from disappearing. The way
192                                         // we're keeping pages in the DOM right now sucks
193                                         //
194                                         // rebind the page remove that was unbound in the open function
195                                         // to allow for the parent page removal from actions other than the use
196                                         // of a dialog sized custom select
197                                         //
198                                         // doing this here provides for the back button on the custom select dialog
199                                         $.mobile._bindPageRemove.call( self.thisPage );
200                                 });
201
202                                 // Events on "screen" overlay
203                                 self.screen.bind( "vclick", function( event ) {
204                                         self.close();
205                                 });
206
207                                 // Close button on small overlays
208                                 self.headerClose.click( function() {
209                                         if ( self.menuType == "overlay" ) {
210                                                 self.close();
211                                                 return false;
212                                         }
213                                 });
214
215                                 // track this dependency so that when the parent page
216                                 // is removed on pagehide it will also remove the menupage
217                                 self.thisPage.addDependents( this.menuPage );
218                         },
219
220                         _isRebuildRequired: function() {
221                                 var list = this.list.find( "li" ),
222                                         options = this._selectOptions();
223
224                                 // TODO exceedingly naive method to determine difference
225                                 // ignores value changes etc in favor of a forcedRebuild
226                                 // from the user in the refresh method
227                                 return options.text() !== list.text();
228                         },
229
230                         refresh: function( forceRebuild , foo ){
231                                 var self = this,
232                                 select = this.element,
233                                 isMultiple = this.isMultiple,
234                                 options = this._selectOptions(),
235                                 selected = this.selected(),
236                                 // return an array of all selected index's
237                                 indicies = this.selectedIndices();
238
239                                 if (  forceRebuild || this._isRebuildRequired() ) {
240                                         self._buildList();
241                                 }
242
243                                 self.setButtonText();
244                                 self.setButtonCount();
245
246                                 self.list.find( "li:not(.ui-li-divider)" )
247                                         .removeClass( $.mobile.activeBtnClass )
248                                         .attr( "aria-selected", false )
249                                         .each(function( i ) {
250
251                                                 if ( $.inArray( i, indicies ) > -1 ) {
252                                                         var item = $( this );
253
254                                                         // Aria selected attr
255                                                         item.attr( "aria-selected", true );
256
257                                                         // Multiple selects: add the "on" checkbox state to the icon
258                                                         if ( self.isMultiple ) {
259                                                                 item.find( ".ui-icon" ).removeClass( "ui-icon-checkbox-off" ).addClass( "ui-icon-checkbox-on" );
260                                                         } else {
261                                                                 item.addClass( $.mobile.activeBtnClass );
262                                                         }
263                                                 }
264                                         });
265                         },
266
267                         close: function() {
268                                 if ( this.options.disabled || !this.isOpen ) {
269                                         return;
270                                 }
271
272                                 var self = this;
273
274                                 if ( self.menuType == "page" ) {
275                                         // doesn't solve the possible issue with calling change page
276                                         // where the objects don't define data urls which prevents dialog key
277                                         // stripping - changePage has incoming refactor
278                                         window.history.back();
279                                 } else {
280                                         self.screen.addClass( "ui-screen-hidden" );
281                                         self.listbox.addClass( "ui-selectmenu-hidden" ).removeAttr( "style" ).removeClass( "in" );
282                                         self.list.appendTo( self.listbox );
283                                         self._focusButton();
284                                 }
285
286                                 // allow the dialog to be closed again
287                                 self.isOpen = false;
288                         },
289
290                         open: function() {
291                                 if ( this.options.disabled ) {
292                                         return;
293                                 }
294
295                                 var self = this,
296                                         menuHeight = self.list.parent().outerHeight(),
297                                         menuWidth = self.list.parent().outerWidth(),
298                                         activePage = $( ".ui-page-active" ),
299                                         tOverflow = $.support.touchOverflow && $.mobile.touchOverflowEnabled,
300                                         tScrollElem = activePage.is( ".ui-native-fixed" ) ? activePage.find( ".ui-content" ) : activePage;
301                                         scrollTop = tOverflow ? tScrollElem.scrollTop() : $( window ).scrollTop(),
302                                         btnOffset = self.button.offset().top,
303                                         screenHeight = window.innerHeight,
304                                         screenWidth = window.innerWidth;
305
306                                 //add active class to button
307                                 self.button.addClass( $.mobile.activeBtnClass );
308
309                                 //remove after delay
310                                 setTimeout( function() {
311                                         self.button.removeClass( $.mobile.activeBtnClass );
312                                 }, 300);
313
314                                 function focusMenuItem() {
315                                         self.list.find( $.mobile.activeBtnClass ).focus();
316                                 }
317
318                                 if ( menuHeight > screenHeight - 80 || !$.support.scrollTop ) {
319                                         // prevent the parent page from being removed from the DOM,
320                                         // otherwise the results of selecting a list item in the dialog
321                                         // fall into a black hole
322                                         self.thisPage.unbind( "pagehide.remove" );
323
324                                         //for WebOS/Opera Mini (set lastscroll using button offset)
325                                         if ( scrollTop == 0 && btnOffset > screenHeight ) {
326                                                 self.thisPage.one( "pagehide", function() {
327                                                         $( this ).jqmData( "lastScroll", btnOffset );
328                                                 });
329                                         }
330
331                                         self.menuPage.one( "pageshow", function() {
332                                                 // silentScroll() is called whenever a page is shown to restore
333                                                 // any previous scroll position the page may have had. We need to
334                                                 // wait for the "silentscroll" event before setting focus to avoid
335                                                 // the browser"s "feature" which offsets rendering to make sure
336                                                 // whatever has focus is in view.
337                                                 $( window ).one( "silentscroll", function() {
338                                                         focusMenuItem();
339                                                 });
340
341                                                 self.isOpen = true;
342                                         });
343
344                                         self.menuType = "page";
345                                         self.menuPageContent.append( self.list );
346                                         self.menuPage.find("div .ui-title").text(self.label.text());
347                                         $.mobile.changePage( self.menuPage, {
348                                                 transition: $.mobile.defaultDialogTransition
349                                         });
350                                 } else {
351                                         self.menuType = "overlay";
352
353                                         self.screen.height( $(document).height() )
354                                                 .removeClass( "ui-screen-hidden" );
355
356                                         // Try and center the overlay over the button
357                                         var roomtop = btnOffset - scrollTop,
358                                                 roombot = scrollTop + screenHeight - btnOffset,
359                                                 halfheight = menuHeight / 2,
360                                                 maxwidth = parseFloat( self.list.parent().css( "max-width" ) ),
361                                                 newtop, newleft;
362
363                                         if ( roomtop > menuHeight / 2 && roombot > menuHeight / 2 ) {
364                                                 newtop = btnOffset + ( self.button.outerHeight() / 2 ) - halfheight;
365                                         } else {
366                                                 // 30px tolerance off the edges
367                                                 newtop = roomtop > roombot ? scrollTop + screenHeight - menuHeight - 30 : scrollTop + 30;
368                                         }
369
370                                         // If the menuwidth is smaller than the screen center is
371                                         if ( menuWidth < maxwidth ) {
372                                                 newleft = ( screenWidth - menuWidth ) / 2;
373                                         } else {
374
375                                                 //otherwise insure a >= 30px offset from the left
376                                                 newleft = self.button.offset().left + self.button.outerWidth() / 2 - menuWidth / 2;
377
378                                                 // 30px tolerance off the edges
379                                                 if ( newleft < 30 ) {
380                                                         newleft = 30;
381                                                 } else if ( (newleft + menuWidth) > screenWidth ) {
382                                                         newleft = screenWidth - menuWidth - 30;
383                                                 }
384                                         }
385
386                                         self.listbox.append( self.list )
387                                                 .removeClass( "ui-selectmenu-hidden" )
388                                                 .css({
389                                                         top: newtop,
390                                                         left: newleft
391                                                 })
392                                                 .addClass( "in" );
393
394                                         focusMenuItem();
395
396                                         // duplicate with value set in page show for dialog sized selects
397                                         self.isOpen = true;
398                                 }
399                         },
400
401                         _buildList: function() {
402                                 var self = this,
403                                         o = this.options,
404                                         placeholder = this.placeholder,
405                                         optgroups = [],
406                                         lis = [],
407                                         dataIcon = self.isMultiple ? "checkbox-off" : "false";
408
409                                 self.list.empty().filter( ".ui-listview" ).listview( "destroy" );
410
411                                 // Populate menu with options from select element
412                                 self.select.find( "option" ).each( function( i ) {
413                                         var $this = $( this ),
414                                                 $parent = $this.parent(),
415                                                 text = $this.getEncodedText(),
416                                                 anchor = "<a href='#'>"+ text +"</a>",
417                                                 classes = [],
418                                                 extraAttrs = [];
419
420                                         // Are we inside an optgroup?
421                                         if ( $parent.is( "optgroup" ) ) {
422                                                 var optLabel = $parent.attr( "label" );
423
424                                                 // has this optgroup already been built yet?
425                                                 if ( $.inArray( optLabel, optgroups ) === -1 ) {
426                                                         lis.push( "<li data-" + $.mobile.ns + "role='list-divider'>"+ optLabel +"</li>" );
427                                                         optgroups.push( optLabel );
428                                                 }
429                                         }
430
431                                         // Find placeholder text
432                                         // TODO: Are you sure you want to use getAttribute? ^RW
433                                         if ( !this.getAttribute( "value" ) || text.length == 0 || $this.jqmData( "placeholder" ) ) {
434                                                 if ( o.hidePlaceholderMenuItems ) {
435                                                         classes.push( "ui-selectmenu-placeholder" );
436                                                 }
437                                                 placeholder = self.placeholder = text;
438                                         }
439
440                                         // support disabled option tags
441                                         if ( this.disabled ) {
442                                                 classes.push( "ui-disabled" );
443                                                 extraAttrs.push( "aria-disabled='true'" );
444                                         }
445
446                                         lis.push( "<li data-" + $.mobile.ns + "option-index='" + i + "' data-" + $.mobile.ns + "icon='"+ dataIcon +"' class='"+ classes.join(" ") + "' " + extraAttrs.join(" ") +">"+ anchor +"</li>" );
447                                 });
448
449                                 self.list.html( lis.join(" ") );
450
451                                 self.list.find( "li" )
452                                         .attr({ "role": "option", "tabindex": "-1" })
453                                         .first().attr( "tabindex", "0" );
454
455                                 // Hide header close link for single selects
456                                 if ( !this.isMultiple ) {
457                                         this.headerClose.hide();
458                                 }
459
460                                 // Hide header if it's not a multiselect and there's no placeholder
461                                 if ( !this.isMultiple && !placeholder.length ) {
462                                         this.header.hide();
463                                 } else {
464                                         this.headerTitle.text( this.placeholder );
465                                 }
466
467                                 // Now populated, create listview
468                                 self.list.listview();
469                         },
470
471                         _button: function(){
472                                 return $( "<a>", {
473                                         "href": "#",
474                                         "role": "button",
475                                         // TODO value is undefined at creation
476                                         "id": this.buttonId,
477                                         "aria-haspopup": "true",
478
479                                         // TODO value is undefined at creation
480                                         "aria-owns": this.menuId
481                                 });
482                         }
483                 });
484         };
485
486         $( "select" ).live( "selectmenubeforecreate", function(){
487                 var selectmenuWidget = $( this ).data( "selectmenu" );
488
489                 if( !selectmenuWidget.options.nativeMenu ){
490                         extendSelect( selectmenuWidget );
491                 }
492         });
493 })( jQuery );