| //>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude); |
| //>>description: Shows list index and scroll to the index directly |
| //>>label: Fastscroll |
| //>>group: Tizen:Widgets |
| |
| define( [ |
| 'jquery', |
| '../jquery.mobile.tizen.scrollview' |
| ], function ( jQuery ) { |
| |
| //>>excludeEnd("jqmBuildExclude"); |
| |
| /* |
| * jQuery Mobile Widget @VERSION |
| * |
| * This software is licensed under the MIT licence (as defined by the OSI at |
| * http://www.opensource.org/licenses/mit-license.php) |
| * |
| * *************************************************************************** |
| * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd. |
| * Copyright (c) 2011 by Intel Corporation Ltd. |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a |
| * copy of this software and associated documentation files (the "Software"), |
| * to deal in the Software without restriction, including without limitation |
| * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
| * and/or sell copies of the Software, and to permit persons to whom the |
| * Software is furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be included in |
| * all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
| * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
| * DEALINGS IN THE SOFTWARE. |
| * *************************************************************************** |
| * |
| * Authors: Elliot Smith <elliot.smith@intel.com> |
| * Yonghwi Park <yonghwi0324.park@samsung.com> |
| */ |
| |
| // fastscroll is a scrollview controller, which binds |
| // a scrollview to a a list of short cuts; the shortcuts are built |
| // from the text on dividers in the list. Clicking on a shortcut |
| // instantaneously jumps the scrollview to the selected list divider; |
| // mouse movements on the shortcut column move the scrollview to the |
| // list divider matching the text currently under the touch; a popup |
| // with the text currently under the touch is also displayed. |
| // |
| // To apply, add the attribute data-fastscroll="true" to a listview |
| // (a <ul> or <ol> element inside a page). Alternatively, call |
| // fastscroll() on an element. |
| // |
| // The closest element with class ui-scrollview-clip is used as the |
| // scrollview to be controlled. |
| // |
| // If a listview has no dividers or a single divider, the widget won't |
| // display. |
| |
| /** |
| @class fastscroll |
| 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. |
| |
| To add a shortcut scroll widget to the application, use the following code: |
| |
| <div class="content" data-role="content" data-scroll="y"> |
| <ul id="contacts" data-role="listview" data-fastscroll="true"> |
| <li>Anton</li> |
| </ul> |
| </div> |
| |
| For the shortcut scroll widget to be visible, the parent list view must have multiple list dividers. |
| */ |
| |
| /** |
| @property {Boolean} data-fastscroll |
| When set to true, creates a shortcut scroll using the HTML unordered list (<ul>) element. |
| */ |
| /** |
| @method fastscroll |
| The shortcut scroll is created for the closest list view with the ui-scrollview-clip class. |
| */ |
| /** |
| @method indexString |
| The indexString method is used to get (if no value is defined) or set the string to present the index. |
| |
| <div class="content" data-role="content" data-scroll="y"> |
| ul id="contacts" data-role="listview" data-fastscroll="true"> |
| <li data-role="list-divider">A</li> |
| <li>Anton</li> |
| </ul> |
| </div> |
| |
| $(".selector").fastscroll( "indexString" [, indexAlphabet] ); |
| */ |
| (function ( $, undefined ) { |
| |
| $.widget( "tizen.fastscroll", $.mobile.widget, { |
| options: { |
| initSelector: ":jqmData(fastscroll)" |
| }, |
| |
| _primaryLanguage: null, |
| _secondLanguage: null, |
| _dividerMap: {}, |
| _charSet: null, |
| |
| _create: function () { |
| var $el = this.element, |
| self = this, |
| $popup, |
| page = $el.closest( ':jqmData(role="page")' ), |
| jumpToDivider; |
| |
| this.scrollview = $el.addClass( 'ui-fastscroll-target' ).closest( '.ui-scrollview-clip' ); |
| this.shortcutsContainer = $( '<div class="ui-fastscroll" aria-label="Fast scroll bar, double tap to fast scroll mode" tabindex="0"/>' ); |
| this.shortcutsList = $( '<ul aria-hidden="true"></ul>' ); |
| |
| // popup for the hovering character |
| this.scrollview.append($( '<div class="ui-fastscroll-popup"></div>' ) ); |
| $popup = this.scrollview.find( '.ui-fastscroll-popup' ); |
| |
| this.shortcutsContainer.append( this.shortcutsList ); |
| this.scrollview.append( this.shortcutsContainer ); |
| |
| // find the bottom of the last item in the listview |
| this.lastListItem = $el.children().last(); |
| |
| // remove scrollbars from scrollview |
| this.scrollview.find( '.ui-scrollbar' ).hide(); |
| |
| this.jumpToDivider = function ( divider ) { |
| // get the vertical position of the divider (so we can scroll to it) |
| var dividerY = $( divider ).position().top, |
| // find the bottom of the last list item |
| bottomOffset = self.lastListItem.outerHeight( true ) + self.lastListItem.position().top, |
| scrollviewHeight = self.scrollview.height(), |
| |
| // check that after the candidate scroll, the bottom of the |
| // last item will still be at the bottom of the scroll view |
| // and not some way up the page |
| maxScroll = bottomOffset - scrollviewHeight, |
| dstOffset; |
| |
| dividerY = ( dividerY > maxScroll ? maxScroll : dividerY ); |
| |
| // don't apply a negative scroll, as this means the |
| // divider should already be visible |
| dividerY = Math.max( dividerY, 0 ); |
| |
| // apply the scroll |
| self.scrollview.scrollview( 'scrollTo', 0, -dividerY ); |
| |
| dstOffset = self.scrollview.offset(); |
| }; |
| |
| this.shortcutsList |
| // bind mouse over so it moves the scroller to the divider |
| .bind( 'touchstart mousedown vmousedown touchmove vmousemove vmouseover', function ( e ) { |
| // Get coords relative to the element |
| var coords = $.mobile.tizen.targetRelativeCoordsFromEvent( e ), |
| shortcutsListOffset = self.shortcutsList.offset(); |
| |
| self.shortcutsContainer.addClass( "ui-fastscroll-hover" ); |
| |
| // If the element is a list item, get coordinates relative to the shortcuts list |
| if ( e.target.tagName.toLowerCase() === "li" ) { |
| coords.x += $( e.target ).offset().left - shortcutsListOffset.left; |
| coords.y += $( e.target ).offset().top - shortcutsListOffset.top; |
| } |
| |
| if ( e.target.tagName.toLowerCase() === "span" ) { |
| coords.x += $( e.target ).parent().offset().left - shortcutsListOffset.left; |
| coords.y += $( e.target ).parent().offset().top - shortcutsListOffset.top; |
| } |
| |
| self.shortcutsList.find( 'li' ).each( function () { |
| var listItem = $( this ); |
| $( listItem ) |
| .removeClass( "ui-fastscroll-hover" ) |
| .removeClass( "ui-fastscroll-hover-down" ); |
| }); |
| // Hit test each list item |
| self.shortcutsList.find( 'li' ).each( function () { |
| var listItem = $( this ), |
| l = listItem.offset().left - shortcutsListOffset.left, |
| t = listItem.offset().top - shortcutsListOffset.top, |
| r = l + Math.abs(listItem.outerWidth( true ) ), |
| b = t + Math.abs(listItem.outerHeight( true ) ), |
| unit, |
| baseTop, |
| baseBottom, |
| omitSet, |
| i; |
| |
| if ( coords.x >= l && coords.x <= r && coords.y >= t && coords.y <= b ) { |
| if ( listItem.text() !== "." ) { |
| self._hitItem( listItem ); |
| } else { |
| omitSet = listItem.data( "omitSet" ); |
| unit = ( b - t ) / omitSet.length; |
| for ( i = 0; i < omitSet.length; i++ ) { |
| baseTop = t + ( i * unit ); |
| baseBottom = baseTop + unit; |
| if ( coords.y >= baseTop && coords.y <= baseBottom ) { |
| self._hitOmitItem( listItem, omitSet.charAt( i ) ); |
| } |
| } |
| } |
| return false; |
| } |
| return true; |
| } ); |
| |
| e.preventDefault(); |
| e.stopPropagation(); |
| } ) |
| // bind mouseout of the fastscroll container to remove popup |
| .bind( 'touchend mouseup vmouseup vmouseout', function () { |
| $popup.hide(); |
| $( ".ui-fastscroll-hover" ).removeClass( "ui-fastscroll-hover" ); |
| $( ".ui-fastscroll-hover-first-item" ).removeClass( "ui-fastscroll-hover-first-item" ); |
| $( ".ui-fastscroll-hover-down" ).removeClass( "ui-fastscroll-hover-down" ); |
| self.shortcutsContainer.removeClass( "ui-fastscroll-hover" ); |
| } ); |
| |
| if ( page && !( page.is( ':visible' ) ) ) { |
| page.bind( 'pageshow', function () { self.refresh(); } ); |
| } else { |
| self.refresh(); |
| } |
| |
| // refresh the list when dividers are filtered out |
| $el.bind( 'updatelayout', function () { |
| self.refresh(); |
| } ); |
| }, |
| |
| _findClosestDivider: function ( targetChar ) { |
| var i, |
| dividerMap = this._dividerMap, |
| charSet = this._charSet, |
| charSetLen = charSet.length, |
| targetIdx = charSet.indexOf( targetChar ), |
| lastDivider, |
| subDivider = null; |
| |
| for ( i = 0; i < targetIdx; ++i ) { |
| lastDivider = dividerMap[ charSet.charAt( i ) ]; |
| if ( lastDivider !== undefined ) { |
| subDivider = lastDivider; |
| } |
| } |
| if ( !subDivider ) { |
| for ( ++i; i < charSetLen; ++i ) { |
| lastDivider = dividerMap[ charSet.charAt( i ) ]; |
| if ( lastDivider !== undefined ) { |
| subDivider = lastDivider; |
| break; |
| } |
| } |
| } |
| return subDivider; |
| }, |
| |
| _hitOmitItem: function ( listItem, text ) { |
| var $popup = this.scrollview.find( '.ui-fastscroll-popup' ), |
| divider; |
| |
| divider = this._dividerMap[ text ] || this._findClosestDivider( text ); |
| if ( typeof divider !== "undefined" ) { |
| this.jumpToDivider( $( divider ) ); |
| } |
| |
| $popup.text( text ).show(); |
| |
| $( listItem ).addClass( "ui-fastscroll-hover" ); |
| if ( listItem.index() === 0 ) { |
| $( listItem ).addClass( "ui-fastscroll-hover-first-item" ); |
| } |
| $( listItem ).siblings().eq( listItem.index() ).addClass( "ui-fastscroll-hover-down" ); |
| }, |
| |
| _hitItem: function ( listItem ) { |
| var $popup = this.scrollview.find( '.ui-fastscroll-popup' ), |
| text = listItem.text(), |
| divider; |
| |
| if ( text === "#" ) { |
| divider = this._dividerMap.number; |
| } else { |
| divider = this._dividerMap[ text ] || this._findClosestDivider( text ); |
| } |
| |
| if ( typeof divider !== "undefined" ) { |
| this.jumpToDivider( $( divider ) ); |
| } |
| |
| $popup.text( text ).show(); |
| |
| $( listItem ).addClass( "ui-fastscroll-hover" ); |
| if ( listItem.index() === 0 ) { |
| $( listItem ).addClass( "ui-fastscroll-hover-first-item" ); |
| } |
| $( listItem ).siblings().eq( listItem.index() ).addClass( "ui-fastscroll-hover-down" ); |
| }, |
| |
| _focusItem: function ( listItem ) { |
| var self = this, |
| $popup = self.scrollview.find( '.ui-fastscroll-popup' ); |
| |
| listItem.focusin( function ( e ) { |
| self.shortcutsList.attr( "aria-hidden", false ); |
| self._hitItem( listItem ); |
| }).focusout( function ( e ) { |
| self.shortcutsList.attr( "aria-hidden", true ); |
| $popup.hide(); |
| $( ".ui-fastscroll-hover" ).removeClass( "ui-fastscroll-hover" ); |
| $( ".ui-fastscroll-hover-first-item" ).removeClass( "ui-fastscroll-hover-first-item" ); |
| $( ".ui-fastscroll-hover-down" ).removeClass( "ui-fastscroll-hover-down" ); |
| }); |
| }, |
| |
| _contentHeight: function () { |
| var self = this, |
| $content = $( '.ui-scrollview-clip' ), |
| header = null, |
| footer = null, |
| paddingValue = 0, |
| clipSize = $( window ).height(); |
| |
| if ( $content.hasClass( "ui-content" ) ) { |
| paddingValue = parseInt( $content.css( "padding-top" ), 10 ); |
| clipSize = clipSize - ( paddingValue || 0 ); |
| paddingValue = parseInt( $content.css( "padding-bottom" ), 10 ); |
| clipSize = clipSize - ( paddingValue || 0 ); |
| header = $content.siblings( ".ui-header:visible" ); |
| footer = $content.siblings( ".ui-footer:visible" ); |
| |
| if ( header ) { |
| if ( header.outerHeight( true ) === null ) { |
| clipSize = clipSize - ( $( ".ui-header" ).outerHeight() || 0 ); |
| } else { |
| clipSize = clipSize - header.outerHeight( true ); |
| } |
| } |
| if ( footer ) { |
| clipSize = clipSize - footer.outerHeight( true ); |
| } |
| } else { |
| clipSize = $content.height(); |
| } |
| return clipSize; |
| }, |
| |
| _omit: function ( numOfItems, maxNumOfItems ) { |
| var maxGroupNum = parseInt( ( maxNumOfItems - 1 ) / 2, 10 ), |
| numOfExtraItems = numOfItems - maxNumOfItems, |
| groupPos = [], |
| omitInfo = [], |
| groupPosLength, |
| group, |
| size, |
| i; |
| |
| if ( ( maxNumOfItems < 3 ) || ( numOfItems <= maxNumOfItems ) ) { |
| return; |
| } |
| |
| if ( numOfExtraItems >= maxGroupNum ) { |
| size = 2; |
| group = 1; |
| groupPosLength = maxGroupNum; |
| } else { |
| size = maxNumOfItems / ( numOfExtraItems + 1 ); |
| group = size; |
| groupPosLength = numOfExtraItems; |
| } |
| |
| for ( i = 0; i < groupPosLength; i++ ) { |
| groupPos.push( parseInt( group, 10 ) ); |
| group += size; |
| } |
| |
| for ( i = 0; i < maxNumOfItems; i++ ) { |
| omitInfo.push( 1 ); |
| } |
| |
| for ( i = 0; i < numOfExtraItems; i++ ) { |
| omitInfo[ groupPos[ i % maxGroupNum ] ]++; |
| } |
| |
| return omitInfo; |
| }, |
| |
| _createDividerMap: function () { |
| var primaryCharacterSet = this._primaryLanguage ? this._primaryLanguage.replace( /,/g, "" ) : null, |
| secondCharacterSet = this._secondLanguage ? this._secondLanguage.replace( /,/g, "" ) : null, |
| numberSet = "0123456789", |
| dividers = this.element.find( '.ui-li-divider' ), |
| map = {}, |
| matchToDivider, |
| makeCharacterSet, |
| indexChar, |
| i; |
| |
| matchToDivider = function ( index, divider ) { |
| if ( $( divider ).text() === indexChar ) { |
| map[ indexChar ] = divider; |
| } |
| }; |
| |
| makeCharacterSet = function ( index, divider ) { |
| primaryCharacterSet += $( divider ).text(); |
| }; |
| |
| if ( primaryCharacterSet === null ) { |
| primaryCharacterSet = ""; |
| dividers.each( makeCharacterSet ); |
| } |
| |
| for ( i = 0; i < primaryCharacterSet.length; i++ ) { |
| indexChar = primaryCharacterSet.charAt( i ); |
| dividers.each( matchToDivider ); |
| } |
| |
| if ( secondCharacterSet !== null ) { |
| for ( i = 0; i < secondCharacterSet.length; i++ ) { |
| indexChar = secondCharacterSet.charAt( i ); |
| dividers.each( matchToDivider ); |
| } |
| } |
| |
| dividers.each( function ( index, divider ) { |
| if ( numberSet.search( $( divider ).text() ) !== -1 ) { |
| map.number = divider; |
| return false; |
| } |
| }); |
| |
| this._dividerMap = map; |
| this._charSet = primaryCharacterSet + secondCharacterSet; |
| }, |
| |
| indexString: function ( indexAlphabet ) { |
| var self = this, |
| characterSet = []; |
| |
| if ( typeof indexAlphabet === "undefined" ) { |
| return self._primaryLanguage + ":" + self._secondLanguage; |
| } |
| |
| characterSet = indexAlphabet.split( ":" ); |
| self._primaryLanguage = characterSet[ 0 ]; |
| if ( characterSet.length === 2 ) { |
| self._secondLanguage = characterSet[ 1 ]; |
| } |
| }, |
| |
| refresh: function () { |
| var self = this, |
| primaryCharacterSet = self._primaryLanguage ? self._primaryLanguage.replace( /,/g, "" ) : null, |
| secondCharacterSet = self._secondLanguage ? self._secondLanguage.replace( /,/g, "" ) : null, |
| contentHeight = self._contentHeight(), |
| shapItem = $( '<li tabindex="0" aria-label="double to move Number list"><span aria-hidden="true">#</span><span aria-label="Number"/></li>' ), |
| $popup = this.scrollview.find( '.ui-fastscroll-popup' ), |
| omitIndex = 0, |
| makeCharacterSet, |
| makeOmitSet, |
| itemHandler, |
| containerHeight, |
| shortcutsItems, |
| shortcutItem, |
| shortcutsTop, |
| minClipHeight, |
| maxNumOfItems, |
| numOfItems, |
| minHeight, |
| omitInfo, |
| dividers, |
| listItems, |
| emptySize, |
| correction, |
| indexChar, |
| lastIndex, |
| seconds, |
| height, |
| size, |
| i; |
| |
| makeCharacterSet = function ( index, divider ) { |
| primaryCharacterSet += $( divider ).text(); |
| }; |
| |
| makeOmitSet = function ( index, length ) { |
| var count, |
| omitSet = ""; |
| |
| for ( count = 0; count < length; count++ ) { |
| omitSet += primaryCharacterSet[ index + count ]; |
| } |
| |
| return omitSet; |
| }; |
| |
| self._createDividerMap(); |
| |
| self.shortcutsList.find( 'li' ).remove(); |
| |
| // get all the dividers from the list and turn them into shortcuts |
| dividers = self.element.find( '.ui-li-divider' ); |
| |
| // get all the list items |
| listItems = self.element.find('li').not('.ui-li-divider'); |
| |
| // only use visible dividers |
| dividers = dividers.filter( ':visible' ); |
| listItems = listItems.filter( ':visible' ); |
| |
| if ( dividers.length < 2 ) { |
| self.shortcutsList.hide(); |
| return; |
| } |
| |
| self.shortcutsList.show(); |
| self.lastListItem = listItems.last(); |
| self.shortcutsList.append( shapItem ); |
| self._focusItem( shapItem ); |
| |
| if ( primaryCharacterSet === null ) { |
| primaryCharacterSet = ""; |
| dividers.each( makeCharacterSet ); |
| } |
| |
| minHeight = shapItem.outerHeight( true ); |
| maxNumOfItems = parseInt( contentHeight / minHeight - 1, 10 ); |
| numOfItems = primaryCharacterSet.length; |
| |
| maxNumOfItems = secondCharacterSet ? maxNumOfItems - 2 : maxNumOfItems; |
| |
| if ( maxNumOfItems < 3 ) { |
| shapItem.remove(); |
| return; |
| } |
| |
| omitInfo = self._omit( numOfItems, maxNumOfItems ); |
| |
| for ( i = 0; i < primaryCharacterSet.length; i++ ) { |
| indexChar = primaryCharacterSet.charAt( i ); |
| shortcutItem = $( '<li tabindex="0" aria-label="double to move ' + indexChar + ' list">' + indexChar + '</li>' ); |
| |
| self._focusItem( shortcutItem ); |
| |
| if ( typeof omitInfo !== "undefined" && omitInfo[ omitIndex ] > 1 ) { |
| shortcutItem = $( '<li>.</li>' ); |
| shortcutItem.data( "omitSet", makeOmitSet( i, omitInfo[ omitIndex ] ) ); |
| i += omitInfo[ omitIndex ] - 1; |
| } |
| |
| self.shortcutsList.append( shortcutItem ); |
| omitIndex++; |
| } |
| |
| if ( secondCharacterSet !== null ) { |
| lastIndex = secondCharacterSet.length - 1; |
| seconds = []; |
| |
| seconds.push( secondCharacterSet.charAt( 0 ) ); |
| seconds.push( secondCharacterSet.charAt( lastIndex ) ); |
| |
| for ( i = 0; i < seconds.length; i++ ) { |
| indexChar = seconds[ i ]; |
| shortcutItem = $( '<li tabindex="0" aria-label="double to move ' + indexChar + ' list">' + indexChar + '</li>' ); |
| |
| self._focusItem( shortcutItem ); |
| shortcutItem.bind( 'vclick', itemHandler ); |
| self.shortcutsList.append( shortcutItem ); |
| } |
| } |
| |
| containerHeight = self.shortcutsContainer.outerHeight( true ); |
| emptySize = contentHeight - containerHeight; |
| shortcutsItems = self.shortcutsList.children(); |
| size = parseInt( emptySize / shortcutsItems.length, 10 ); |
| correction = emptySize - ( shortcutsItems.length * size ); |
| |
| if ( emptySize > 0 ) { |
| shortcutsItems.each( function ( index, item ) { |
| height = $( item ).height() + size; |
| if ( correction !== 0 ) { |
| height += 1; |
| correction -= 1; |
| } |
| $( item ).css( { |
| height: height, |
| lineHeight: height + "px" |
| } ); |
| } ); |
| } |
| |
| // position the shortcut flush with the top of the first list divider |
| shortcutsTop = dividers.first().position().top; |
| self.shortcutsContainer.css( 'top', shortcutsTop ); |
| |
| // make the scrollview clip tall enough to show the whole of the shortcutslist |
| minClipHeight = shortcutsTop + self.shortcutsContainer.outerHeight() + 'px'; |
| self.scrollview.css( 'min-height', minClipHeight ); |
| |
| $popup.text( "M" ) // Popup size is determined based on "M". |
| .width( $popup.height() ) |
| .css( { marginLeft: -( $popup.outerWidth() / 2 ), |
| marginTop: -( $popup.outerHeight() / 2 ) } ); |
| } |
| } ); |
| |
| $( document ).bind( "pagecreate create", function ( e ) { |
| $( $.tizen.fastscroll.prototype.options.initSelector, e.target ) |
| .not( ":jqmData(role='none'), :jqmData(role='nojs')" ) |
| .fastscroll(); |
| } ); |
| |
| $( window ).bind( "resize orientationchange", function ( e ) { |
| $( ".ui-page-active .ui-fastscroll-target" ).fastscroll( "refresh" ); |
| } ); |
| } ( jQuery ) ); |
| |
| //>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude); |
| } ); |
| //>>excludeEnd("jqmBuildExclude"); |