1 //>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude);
2 //>>description: Shows list index and scroll to the index directly
4 //>>group: Tizen:Widgets
6 define( [ '../jquery.mobile.tizen.scrollview' ], function ( ) {
7 //>>excludeEnd("jqmBuildExclude");
10 * jQuery Mobile Widget @VERSION
12 * This software is licensed under the MIT licence (as defined by the OSI at
13 * http://www.opensource.org/licenses/mit-license.php)
15 * ***************************************************************************
16 * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
17 * Copyright (c) 2011 by Intel Corporation Ltd.
19 * Permission is hereby granted, free of charge, to any person obtaining a
20 * copy of this software and associated documentation files (the "Software"),
21 * to deal in the Software without restriction, including without limitation
22 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
23 * and/or sell copies of the Software, and to permit persons to whom the
24 * Software is furnished to do so, subject to the following conditions:
26 * The above copyright notice and this permission notice shall be included in
27 * all copies or substantial portions of the Software.
29 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
30 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
31 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
32 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
33 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
34 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
35 * DEALINGS IN THE SOFTWARE.
36 * ***************************************************************************
38 * Authors: Elliot Smith <elliot.smith@intel.com>
39 * Yonghwi Park <yonghwi0324.park@samsung.com>
42 // fastscroll is a scrollview controller, which binds
43 // a scrollview to a a list of short cuts; the shortcuts are built
44 // from the text on dividers in the list. Clicking on a shortcut
45 // instantaneously jumps the scrollview to the selected list divider;
46 // mouse movements on the shortcut column move the scrollview to the
47 // list divider matching the text currently under the touch; a popup
48 // with the text currently under the touch is also displayed.
50 // To apply, add the attribute data-fastscroll="true" to a listview
51 // (a <ul> or <ol> element inside a page). Alternatively, call
52 // fastscroll() on an element.
54 // The closest element with class ui-scrollview-clip is used as the
55 // scrollview to be controlled.
57 // If a listview has no dividers or a single divider, the widget won't
62 The shortcut scroll widget shows a shortcut list that is bound to its parent scroll bar and respective list view. This widget is displayed as a text pop-up representing shortcuts to different list dividers in the list view. If you select a shortcut text from the shortcut scroll, the parent list view is moved to the location representing the selected shortcut.
64 To add a shortcut scroll widget to the application, use the following code:
66 <div class="content" data-role="content" data-scroll="y">
67 <ul id="contacts" data-role="listview" data-fastscroll="true">
72 For the shortcut scroll widget to be visible, the parent list view must have multiple list dividers.
76 @property {Boolean} data-fastscroll
77 When set to true, creates a shortcut scroll using the HTML unordered list (<ul>) element.
81 The shortcut scroll is created for the closest list view with the ui-scrollview-clip class.
85 The indexString method is used to get (if no value is defined) or set the string to present the index.
87 <div class="content" data-role="content" data-scroll="y">
88 ul id="contacts" data-role="listview" data-fastscroll="true">
89 <li data-role="list-divider">A</li>
94 $(".selector").fastscroll( "indexString" [, indexAlphabet] );
96 (function ( $, undefined ) {
98 $.widget( "tizen.fastscroll", $.mobile.widget, {
100 initSelector: ":jqmData(fastscroll)"
103 _primaryLanguage: null,
104 _secondLanguage: null,
107 _defaultDuration: 500,
111 _create: function () {
112 var $el = this.element,
115 page = $el.closest( ':jqmData(role="page")' ),
118 this.scrollview = $el.addClass( 'ui-fastscroll-target' ).closest( '.ui-scrollview-clip' );
119 this.shortcutsContainer = $( '<div class="ui-fastscroll" aria-label="Fast scroll bar, double tap to fast scroll mode" tabindex="0"/>' );
120 this.shortcutsList = $( '<ul aria-hidden="true"></ul>' );
122 // popup for the hovering character
123 this.scrollview.append($( '<div class="ui-fastscroll-popup"></div>' ) );
124 $popup = this.scrollview.find( '.ui-fastscroll-popup' );
126 this.shortcutsContainer.append( this.shortcutsList );
127 this.scrollview.append( this.shortcutsContainer );
129 // find the bottom of the last item in the listview
130 this.lastListItem = $el.children().last();
132 // remove scrollbars from scrollview
133 this.scrollview.find( '.ui-scrollbar' ).hide();
135 this.jumpToDivider = function ( divider ) {
136 // get the vertical position of the divider (so we can scroll to it)
137 var dividerY = $( divider ).position().top,
138 // find the bottom of the last list item
139 bottomOffset = self.lastListItem.outerHeight( true ) + self.lastListItem.position().top,
140 scrollviewHeight = self.scrollview.height(),
142 // check that after the candidate scroll, the bottom of the
143 // last item will still be at the bottom of the scroll view
144 // and not some way up the page
145 maxScroll = bottomOffset - scrollviewHeight,
148 dividerY = ( dividerY > maxScroll ? maxScroll : dividerY );
150 // don't apply a negative scroll, as this means the
151 // divider should already be visible
152 dividerY = Math.max( dividerY, 0 );
155 self.scrollview.scrollview( 'scrollTo', 0, -dividerY );
157 dstOffset = self.scrollview.offset();
161 // bind mouse over so it moves the scroller to the divider
162 .bind( 'touchstart mousedown vmousedown touchmove vmousemove vmouseover', function ( e ) {
163 // Get coords relative to the element
164 var coords = $.mobile.tizen.targetRelativeCoordsFromEvent( e ),
165 shortcutsListOffset = self.shortcutsList.offset();
167 if ( self._isFadeOut === true ) {
171 // If the element is a list item, get coordinates relative to the shortcuts list
172 if ( e.target.tagName.toLowerCase() === "li" ) {
173 coords.x += $( e.target ).offset().left - shortcutsListOffset.left;
174 coords.y += $( e.target ).offset().top - shortcutsListOffset.top;
177 if ( e.target.tagName.toLowerCase() === "span" ) {
178 coords.x += $( e.target ).parent().offset().left - shortcutsListOffset.left;
179 coords.y += $( e.target ).parent().offset().top - shortcutsListOffset.top;
182 self.shortcutsList.find( 'li' ).each( function () {
183 var listItem = $( this );
185 .removeClass( "ui-fastscroll-hover" )
186 .removeClass( "ui-fastscroll-hover-up" )
187 .removeClass( "ui-fastscroll-hover-down" );
189 // Hit test each list item
190 self.shortcutsList.find( 'li' ).each( function () {
191 var listItem = $( this ),
192 l = listItem.offset().left - shortcutsListOffset.left,
193 t = listItem.offset().top - shortcutsListOffset.top,
194 r = l + Math.abs(listItem.outerWidth( true ) ),
195 b = t + Math.abs(listItem.outerHeight( true ) ),
202 if ( coords.x >= l && coords.x <= r && coords.y >= t && coords.y <= b ) {
203 if ( listItem.text() !== "." ) {
204 self._hitItem( listItem );
206 omitSet = listItem.data( "omitSet" );
207 unit = ( b - t ) / omitSet.length;
208 for ( i = 0; i < omitSet.length; i++ ) {
209 baseTop = t + ( i * unit );
210 baseBottom = baseTop + unit;
211 if ( coords.y >= baseTop && coords.y <= baseBottom ) {
212 self._hitOmitItem( listItem, omitSet.charAt( i ) );
221 self._setTimer( false );
226 // bind mouseout of the fastscroll container to remove popup
227 .bind( 'touchend mouseup vmouseup vmouseout', function () {
229 $( ".ui-fastscroll-hover" ).removeClass( "ui-fastscroll-hover" );
230 $( ".ui-fastscroll-hover-first-item" ).removeClass( "ui-fastscroll-hover-first-item" );
231 $( ".ui-fastscroll-hover-up" ).removeClass( "ui-fastscroll-hover-up" );
232 $( ".ui-fastscroll-hover-down" ).removeClass( "ui-fastscroll-hover-down" );
233 self._setTimer( true );
236 if ( page && !( page.is( ':visible' ) ) ) {
237 page.bind( 'pageshow', function () { self.refresh(); } );
242 // refresh the list when dividers are filtered out
243 $el.bind( 'updatelayout', function () {
247 self.scrollview.bind( "scrollstart", function ( e ) {
248 self._setTimer( false );
249 }).bind( "scrollstop", function ( e ) {
250 self._setTimer( true );
254 _hitOmitItem: function ( listItem, text ) {
256 $popup = self.scrollview.find( '.ui-fastscroll-popup' ),
257 divider = self._dividerMap[ text ];
259 if ( typeof divider !== "undefined" ) {
260 self.jumpToDivider( $( divider ) );
264 .css( { marginLeft: -( $popup.width() / 2 ),
265 marginTop: -( $popup.height() / 2 ),
266 padding: $popup.css( "paddingTop" ) } )
267 .width( $popup.height() )
270 $( listItem ).addClass( "ui-fastscroll-hover" );
271 if ( listItem.index() === 0 ) {
272 $( listItem ).addClass( "ui-fastscroll-hover-first-item" );
274 if ( listItem.index() > 0 ) {
275 $( listItem ).siblings().eq( listItem.index() - 1 ).addClass( "ui-fastscroll-hover-up" );
277 $( listItem ).siblings().eq( listItem.index() ).addClass( "ui-fastscroll-hover-down" );
280 _hitItem: function ( listItem ) {
282 $popup = self.scrollview.find( '.ui-fastscroll-popup' ),
283 text = listItem.text(),
286 if ( text === "#" ) {
287 divider = self._dividerMap.number;
289 divider = self._dividerMap[ text ];
292 if ( typeof divider !== "undefined" ) {
293 self.jumpToDivider( $( divider ) );
297 .css( { marginLeft: -( $popup.width() / 2 ),
298 marginTop: -( $popup.height() / 2 ),
299 padding: $popup.css( "paddingTop" ) } )
300 .width( $popup.height() )
303 $( listItem ).addClass( "ui-fastscroll-hover" );
304 if ( listItem.index() === 0 ) {
305 $( listItem ).addClass( "ui-fastscroll-hover-first-item" );
307 if ( listItem.index() > 0 ) {
308 $( listItem ).siblings().eq( listItem.index() - 1 ).addClass( "ui-fastscroll-hover-up" );
310 $( listItem ).siblings().eq( listItem.index() ).addClass( "ui-fastscroll-hover-down" );
313 _focusItem: function ( listItem ) {
315 $popup = self.scrollview.find( '.ui-fastscroll-popup' );
317 listItem.focusin( function ( e ) {
318 self.shortcutsList.attr( "aria-hidden", false );
319 self._hitItem( listItem );
320 self._setTimer( false );
321 }).focusout( function ( e ) {
322 self.shortcutsList.attr( "aria-hidden", true );
324 $( ".ui-fastscroll-hover" ).removeClass( "ui-fastscroll-hover" );
325 $( ".ui-fastscroll-hover-first-item" ).removeClass( "ui-fastscroll-hover-first-item" );
326 $( ".ui-fastscroll-hover-up" ).removeClass( "ui-fastscroll-hover-up" );
327 $( ".ui-fastscroll-hover-down" ).removeClass( "ui-fastscroll-hover-down" );
328 self._setTimer( true );
332 _contentHeight: function () {
334 $content = $( '.ui-scrollview-clip' ),
338 clipSize = $( window ).height();
340 if ( $content.hasClass( "ui-content" ) ) {
341 paddingValue = parseInt( $content.css( "padding-top" ), 10 );
342 clipSize = clipSize - ( paddingValue || 0 );
343 paddingValue = parseInt( $content.css( "padding-bottom" ), 10 );
344 clipSize = clipSize - ( paddingValue || 0 );
345 header = $content.siblings( ".ui-header:visible" );
346 footer = $content.siblings( ".ui-footer:visible" );
349 if ( header.outerHeight( true ) === null ) {
350 clipSize = clipSize - ( $( ".ui-header" ).outerHeight() || 0 );
352 clipSize = clipSize - header.outerHeight( true );
356 clipSize = clipSize - footer.outerHeight( true );
359 clipSize = $content.height();
364 _omit: function ( numOfItems, maxNumOfItems ) {
365 var maxGroupNum = parseInt( ( maxNumOfItems - 1 ) / 2, 10 ),
366 numOfExtraItems = numOfItems - maxNumOfItems,
374 if ( ( maxNumOfItems < 3 ) || ( numOfItems <= maxNumOfItems ) ) {
378 if ( numOfExtraItems >= maxGroupNum ) {
381 groupPosLength = maxGroupNum;
383 size = maxNumOfItems / ( numOfExtraItems + 1 );
385 groupPosLength = numOfExtraItems;
388 for ( i = 0; i < groupPosLength; i++ ) {
389 groupPos.push( parseInt( group, 10 ) );
393 for ( i = 0; i < maxNumOfItems; i++ ) {
397 for ( i = 0; i < numOfExtraItems; i++ ) {
398 omitInfo[ groupPos[ i % maxGroupNum ] ]++;
404 _createDividerMap: function () {
406 primaryCharacterSet = self._primaryLanguage ? self._primaryLanguage.replace( /,/g, "" ) : null,
407 secondCharacterSet = self._secondLanguage ? self._secondLanguage.replace( /,/g, "" ) : null,
408 numberSet = "0123456789",
409 dividers = self.element.find( '.ui-li-divider' ),
416 matchToDivider = function ( index, divider ) {
417 if ( $( divider ).text() === indexChar ) {
418 map[ indexChar ] = divider;
422 makeCharacterSet = function ( index, divider ) {
423 primaryCharacterSet += $( divider ).text();
426 if ( primaryCharacterSet === null ) {
427 primaryCharacterSet = "";
428 dividers.each( makeCharacterSet );
431 for ( i = 0; i < primaryCharacterSet.length; i++ ) {
432 indexChar = primaryCharacterSet.charAt( i );
433 dividers.each( matchToDivider );
436 if ( secondCharacterSet !== null ) {
437 for ( i = 0; i < secondCharacterSet.length; i++ ) {
438 indexChar = secondCharacterSet.charAt( i );
439 dividers.each( matchToDivider );
443 dividers.each( function ( index, divider ) {
444 if ( numberSet.search( $( divider ).text() ) !== -1 ) {
445 map.number = divider;
450 self._dividerMap = map;
453 _setTimer: function ( start ) {
456 if ( start === true ) {
457 self._timer = setTimeout( function () {
458 self._isFadeOut = true;
459 self.shortcutsContainer.fadeOut( self._defaultDuration, function () {
460 self._isFadeOut = false;
462 }, self._defaultTime );
464 if ( self._timer !== null ) {
465 clearTimeout( self._timer );
467 self.shortcutsContainer.show();
471 indexString: function ( indexAlphabet ) {
475 if ( typeof indexAlphabet === "undefined" ) {
476 return self._primaryLanguage + ":" + self._secondLanguage;
479 characterSet = indexAlphabet.split( ":" );
480 self._primaryLanguage = characterSet[ 0 ];
481 if ( characterSet.length === 2 ) {
482 self._secondLanguage = characterSet[ 1 ];
486 refresh: function () {
488 primaryCharacterSet = self._primaryLanguage ? self._primaryLanguage.replace( /,/g, "" ) : null,
489 secondCharacterSet = self._secondLanguage ? self._secondLanguage.replace( /,/g, "" ) : null,
490 contentHeight = self._contentHeight(),
491 shapItem = $( '<li tabindex="0" aria-label="double to move Number list"><span aria-hidden="true">#</span><span aria-label="Number"/></li>' ),
517 makeCharacterSet = function ( index, divider ) {
518 primaryCharacterSet += $( divider ).text();
521 makeOmitSet = function ( index, length ) {
525 for ( count = 0; count < length; count++ ) {
526 omitSet += primaryCharacterSet[ index + count ];
532 itemHandler = function ( e ) {
533 var text = $( this ).text(),
534 matchDivider = self._dividerMap[ text ];
536 if ( typeof matchDivider !== "undefined" ) {
537 $( matchDivider ).next().focus();
541 self._createDividerMap();
543 self.shortcutsList.find( 'li' ).remove();
545 // get all the dividers from the list and turn them into shortcuts
546 dividers = self.element.find( '.ui-li-divider' );
548 // get all the list items
549 listItems = self.element.find('li').not('.ui-li-divider');
551 // only use visible dividers
552 dividers = dividers.filter( ':visible' );
553 listItems = listItems.filter( ':visible' );
555 if ( dividers.length < 2 ) {
556 self.shortcutsList.hide();
560 self.shortcutsList.show();
561 self.lastListItem = listItems.last();
562 self.shortcutsList.append( shapItem );
563 self._focusItem( shapItem );
565 if ( primaryCharacterSet === null ) {
566 primaryCharacterSet = "";
567 dividers.each( makeCharacterSet );
570 padding = parseInt( shapItem.css( "padding" ), 10 );
571 minHeight = shapItem.height() + ( padding * 2 );
572 maxNumOfItems = parseInt( ( contentHeight / minHeight ) - 1, 10 );
573 numOfItems = primaryCharacterSet.length;
575 maxNumOfItems = secondCharacterSet ? maxNumOfItems - 2 : maxNumOfItems;
577 if ( maxNumOfItems < 3 ) {
582 omitInfo = self._omit( numOfItems, maxNumOfItems );
584 for ( i = 0; i < primaryCharacterSet.length; i++ ) {
585 indexChar = primaryCharacterSet.charAt( i );
586 shortcutItem = $( '<li tabindex="0" aria-label="double to move ' + indexChar + ' list">' + indexChar + '</li>' );
588 self._focusItem( shortcutItem );
590 if ( typeof omitInfo !== "undefined" && omitInfo[ omitIndex ] > 1 ) {
591 shortcutItem = $( '<li>.</li>' );
592 shortcutItem.data( "omitSet", makeOmitSet( i, omitInfo[ omitIndex ] ) );
593 i += omitInfo[ omitIndex ] - 1;
595 shortcutItem.bind( 'vclick', itemHandler );
598 shapItem.before( shortcutItem );
602 if ( secondCharacterSet !== null ) {
603 lastIndex = secondCharacterSet.length - 1;
606 seconds.push( secondCharacterSet.charAt( 0 ) );
607 seconds.push( secondCharacterSet.charAt( lastIndex ) );
609 for ( i = 0; i < seconds.length; i++ ) {
610 indexChar = seconds[ i ];
611 shortcutItem = $( '<li tabindex="0" aria-label="double to move ' + indexChar + ' list">' + indexChar + '</li>' );
613 self._focusItem( shortcutItem );
614 shortcutItem.bind( 'vclick', itemHandler );
615 shapItem.before( shortcutItem );
619 containerHeight = self.shortcutsContainer.outerHeight();
620 emptySize = contentHeight - containerHeight;
621 shortcutsItems = self.shortcutsList.children();
622 size = parseInt( emptySize / shortcutsItems.length, 10 );
623 correction = emptySize - ( shortcutsItems.length * size );
625 if ( emptySize > 0 ) {
626 shortcutsItems.each( function ( index, item ) {
627 height = $( item ).height() + size;
628 if ( correction !== 0 ) {
634 lineHeight: height + "px"
639 // position the shortcut flush with the top of the first list divider
640 shortcutsTop = dividers.first().position().top;
641 self.shortcutsContainer.css( 'top', shortcutsTop );
643 // make the scrollview clip tall enough to show the whole of the shortcutslist
644 minClipHeight = shortcutsTop + self.shortcutsContainer.outerHeight() + 'px';
645 self.scrollview.css( 'min-height', minClipHeight );
647 self._setTimer( false );
648 self._setTimer( true );
652 $( document ).bind( "pagecreate create", function ( e ) {
653 $( $.tizen.fastscroll.prototype.options.initSelector, e.target )
654 .not( ":jqmData(role='none'), :jqmData(role='nojs')" )
658 $( window ).bind( "resize orientationchange", function ( e ) {
659 $( ".ui-page-active .ui-fastscroll-target" ).fastscroll( "refresh" );
663 //>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude);
665 //>>excludeEnd("jqmBuildExclude");