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