diff options
Diffstat (limited to 'www/wiki/extensions/UniversalLanguageSelector/lib/jquery.uls/src/jquery.uls.core.js')
-rw-r--r-- | www/wiki/extensions/UniversalLanguageSelector/lib/jquery.uls/src/jquery.uls.core.js | 488 |
1 files changed, 488 insertions, 0 deletions
diff --git a/www/wiki/extensions/UniversalLanguageSelector/lib/jquery.uls/src/jquery.uls.core.js b/www/wiki/extensions/UniversalLanguageSelector/lib/jquery.uls/src/jquery.uls.core.js new file mode 100644 index 00000000..e4607878 --- /dev/null +++ b/www/wiki/extensions/UniversalLanguageSelector/lib/jquery.uls/src/jquery.uls.core.js @@ -0,0 +1,488 @@ +/** + * Universal Language Selector + * ULS core component. + * + * Copyright (C) 2012 Alolita Sharma, Amir Aharoni, Arun Ganesh, Brandon Harris, + * Niklas Laxström, Pau Giner, Santhosh Thottingal, Siebrand Mazeland and other + * contributors. See CREDITS for a list. + * + * UniversalLanguageSelector is dual licensed GPLv2 or later and MIT. You don't + * have to do anything special to choose one license or the other and you don't + * have to notify anyone which license you are using. You are free to use + * UniversalLanguageSelector in commercial projects as long as the copyright + * header is left intact. See files GPL-LICENSE and MIT-LICENSE for details. + * + * @file + * @ingroup Extensions + * @licence GNU General Public Licence 2.0 or later + * @licence MIT License + */ + +( function ( $ ) { + 'use strict'; + + var template, ULS; + + // Region numbers in id attributes also appear in the langdb. + // eslint-disable-next-line no-multi-str + template = '<div class="grid uls-menu"> \ + <div id="search" class="row uls-search"> \ + <div class="uls-search-wrapper"> \ + <label class="uls-search-label" for="uls-languagefilter"></label>\ + <div class="uls-search-input-wrapper">\ + <span class="uls-languagefilter-clear"></span>\ + <input type="text" class="uls-filterinput uls-filtersuggestion"\ + disabled="true" autocomplete="off">\ + <input type="text" class="uls-filterinput uls-languagefilter"\ + maxlength="40"\ + data-clear="uls-languagefilter-clear"\ + data-suggestion="uls-filtersuggestion"\ + placeholder="Search for a language" autocomplete="off">\ + </div>\ + </div>\ + </div>\ + <div class="row uls-language-list"></div>\ + <div class="row" id="uls-settings-block"></div>\ + </div>'; + + /** + * ULS Public class definition + * @param {Element} element + * @param {Object} options + */ + ULS = function ( element, options ) { + var code; + this.$element = $( element ); + this.options = $.extend( {}, $.fn.uls.defaults, options ); + this.$menu = $( template ); + this.languages = this.options.languages; + + for ( code in this.languages ) { + if ( $.uls.data.languages[ code ] === undefined ) { + // Language is unknown to ULS. + delete this.languages[ code ]; + } + } + + this.left = this.options.left; + this.top = this.options.top; + this.shown = false; + this.initialized = false; + this.shouldRecreate = false; + this.menuWidth = this.getMenuWidth(); + + this.$languageFilter = this.$menu.find( '.uls-languagefilter' ); + this.$resultsView = this.$menu.find( '.uls-language-list' ); + + this.render(); + this.listen(); + this.ready(); + }; + + ULS.prototype = { + constructor: ULS, + + /** + * A "hook" that runs after the ULS constructor. + * At this point it is not guaranteed that the ULS has its dimensions + * and that the languages lists are initialized. + * + * To use it, pass a function as the onReady parameter + * in the options when initializing ULS. + */ + ready: function () { + if ( this.options.onReady ) { + this.options.onReady.call( this ); + } + }, + + /** + * A "hook" that runs after the ULS panel becomes visible + * by using the show method. + * + * To use it, pass a function as the onVisible parameter + * in the options when initializing ULS. + */ + visible: function () { + if ( this.options.onVisible ) { + this.options.onVisible.call( this ); + } + }, + + /** + * Calculate the position of ULS + * Returns an object with top and left properties. + * @return {Object} + */ + position: function () { + var pos, + top = this.top, + left = this.left; + + if ( top === undefined ) { + pos = $.extend( {}, this.$element.offset(), { + height: this.$element[ 0 ].offsetHeight + } ); + top = pos.top + pos.height; + } + + if ( left === undefined ) { + left = $( window ).width() / 2 - this.$menu.outerWidth() / 2; + } + + return { + top: top, + left: left + }; + }, + + /** + * Show the ULS window + */ + show: function () { + var widthClasses = { + wide: 'uls-wide', + medium: 'uls-medium', + narrow: 'uls-narrow' + }; + + this.$menu.addClass( widthClasses[ this.menuWidth ] ); + + if ( !this.initialized ) { + $( 'body' ).prepend( this.$menu ); + this.i18n(); + this.initialized = true; + } + + this.$menu.css( this.position() ); + this.$menu.show(); + this.$menu.scrollIntoView(); + this.shown = true; + + if ( !this.isMobile() ) { + this.$languageFilter.focus(); + } + + this.visible(); + }, + + i18n: function () { + if ( $.i18n ) { + this.$menu.find( '[data-i18n]' ).i18n(); + this.$languageFilter.prop( 'placeholder', $.i18n( 'uls-search-placeholder' ) ); + } + }, + + /** + * Hide the ULS window + */ + hide: function () { + this.$menu.hide(); + this.shown = false; + + this.$menu.removeClass( 'uls-wide uls-medium uls-narrow' ); + + if ( this.shouldRecreate ) { + this.recreateLanguageFilter(); + } + + if ( this.options.onCancel ) { + this.options.onCancel.call( this ); + } + }, + + /** + * Render the UI elements. + * Does nothing by default. Can be used for customization. + */ + render: function () { + // Rendering stuff here + }, + + /** + * Callback for results found context. + */ + success: function () { + this.$resultsView.show(); + }, + + createLanguageFilter: function () { + var lcd, languagesCount, + columnsOptions = { + wide: 4, + medium: 2, + narrow: 1 + }; + + languagesCount = Object.keys( this.options.languages ).length; + lcd = this.$resultsView.lcd( { + languages: this.languages, + columns: columnsOptions[ this.menuWidth ], + + quickList: languagesCount > 12 ? this.options.quickList : [], + clickhandler: this.select.bind( this ), + showRegions: this.options.showRegions, + languageDecorator: this.options.languageDecorator, + noResultsTemplate: this.options.noResultsTemplate, + itemsPerColumn: this.options.itemsPerColumn, + groupByRegion: this.options.groupByRegion + } ).data( 'lcd' ); + + this.$languageFilter.languagefilter( { + lcd: lcd, + languages: this.languages, + ulsPurpose: this.options.ulsPurpose, + searchAPI: this.options.searchAPI, + onSelect: this.select.bind( this ) + } ); + + this.$languageFilter.on( 'noresults.uls', lcd.noResults.bind( lcd ) ); + }, + + recreateLanguageFilter: function () { + this.$resultsView.removeData( 'lcd' ); + this.$resultsView.empty(); + this.$languageFilter.removeData( 'languagefilter' ); + this.createLanguageFilter(); + + this.shouldRecreate = false; + }, + + /** + * Bind the UI elements with their event listeners + */ + listen: function () { + // Register all event listeners to the ULS here. + this.$element.on( 'click', this.click.bind( this ) ); + + // Don't do anything if pressing on empty space in the ULS + this.$menu.on( 'click', function ( e ) { + e.stopPropagation(); + } ); + + // Handle key press events on the menu + this.$menu.on( 'keydown', this.keypress.bind( this ) ); + + this.createLanguageFilter(); + + this.$languageFilter.on( 'resultsfound.uls', this.success.bind( this ) ); + + $( 'html' ).click( this.cancel.bind( this ) ); + $( window ).resize( $.fn.uls.debounce( this.resize.bind( this ), 250 ) ); + }, + + resize: function () { + var menuWidth = this.getMenuWidth(); + + if ( this.menuWidth === menuWidth ) { + return; + } + + this.menuWidth = menuWidth; + this.shouldRecreate = true; + if ( !this.shown ) { + this.recreateLanguageFilter(); + } + }, + + /** + * On select handler for search results + * @param {string} langCode + * @param {Object} event The jQuery click event + */ + select: function ( langCode, event ) { + this.hide(); + if ( this.options.onSelect ) { + this.options.onSelect.call( this, langCode, event ); + } + }, + + /** + * On cancel handler for the uls menu + * @param {Event} e + */ + cancel: function ( e ) { + if ( e && ( this.$element.is( e.target ) || + $.contains( this.$element[ 0 ], e.target ) ) ) { + return; + } + + this.hide(); + }, + + keypress: function ( e ) { + if ( !this.shown ) { + return; + } + + if ( e.keyCode === 27 ) { // escape + this.cancel(); + e.preventDefault(); + e.stopPropagation(); + } + }, + + click: function () { + if ( this.shown ) { + this.hide(); + } else { + this.show(); + } + }, + + /** + * Get the panel menu width parameter + * @return {string} + */ + getMenuWidth: function () { + var languagesCount, + screenWidth = document.documentElement.clientWidth; + + if ( this.options.menuWidth ) { + return this.options.menuWidth; + } + + languagesCount = Object.keys( this.options.languages ).length; + + if ( screenWidth > 900 && languagesCount >= 48 ) { + return 'wide'; + } + + if ( screenWidth > 500 && languagesCount >= 24 ) { + return 'medium'; + } + + return 'narrow'; + }, + + isMobile: function () { + return navigator.userAgent.match( /(iPhone|iPod|iPad|Android|BlackBerry)/ ); + } + }; + + /* ULS PLUGIN DEFINITION + * =========================== */ + + $.fn.uls = function ( option ) { + return this.each( function () { + var $this = $( this ), + data = $this.data( 'uls' ), + options = typeof option === 'object' && option; + + if ( !data ) { + $this.data( 'uls', ( data = new ULS( this, options ) ) ); + } + + if ( typeof option === 'string' ) { + data[ option ](); + } + } ); + }; + + $.fn.uls.defaults = { + // CSS top position for the dialog + top: undefined, + // CSS left position for the dialog + left: undefined, + // Callback function when user selects a language + onSelect: undefined, + // Callback function when the dialog is closed without selecting a language + onCancel: undefined, + // Callback function when ULS has initialized + onReady: undefined, + // Callback function when ULS dialog is shown + onVisible: undefined, + // Languages to be used for ULS, default is all languages + languages: $.uls.data.getAutonyms(), + // The options are wide (4 columns), medium (2 columns), and narrow (1 column). + // If not specified, it will be set automatically. + menuWidth: undefined, + // What is this ULS used for. + // Should be set for distinguishing between different instances of ULS + // in the same application. + ulsPurpose: '', + // Used by LCD + quickList: [], + // Used by LCD + showRegions: undefined, + // Used by LCD + languageDecorator: undefined, + // Used by LCD + noResultsTemplate: undefined, + // Used by LCD + itemsPerColumn: undefined, + // Used by LCD + groupByRegion: undefined, + // Used by LanguageFilter + searchAPI: undefined + }; + + // Define a dummy i18n function, if jquery.i18n not integrated. + if ( !$.fn.i18n ) { + $.fn.i18n = function () {}; + } + + /** + * Creates and returns a new debounced version of the passed function, + * which will postpone its execution, until after wait milliseconds have elapsed + * since the last time it was invoked. + * + * @param {Function} fn Function to be debounced. + * @param {number} wait Wait interval in milliseconds. + * @param {boolean} [immediate] Trigger the function on the leading edge of the wait interval, + * instead of the trailing edge. + * @return {Function} Debounced function. + */ + $.fn.uls.debounce = function ( fn, wait, immediate ) { + var timeout; + + return function () { + var callNow, self = this, + later = function () { + timeout = null; + if ( !immediate ) { + fn.apply( self, arguments ); + } + }; + + callNow = immediate && !timeout; + clearTimeout( timeout ); + timeout = setTimeout( later, wait || 100 ); + + if ( callNow ) { + fn.apply( self, arguments ); + } + }; + }; + + /* + * Simple scrollIntoView plugin. + * Scrolls the element to the viewport smoothly if it is not already. + */ + $.fn.scrollIntoView = function () { + return this.each( function () { + var scrollPosition, + $window = $( window ), + windowHeight = $window.height(), + windowTop = $window.scrollTop(), + windowBottom = windowTop + windowHeight, + $element = $( this ), + panelHeight = $element.height(), + panelTop = $element.offset().top, + panelBottom = panelTop + panelHeight; + + if ( ( panelTop < windowTop ) || ( panelBottom > windowBottom ) ) { + if ( windowTop > panelTop ) { + scrollPosition = panelTop; + } else { + scrollPosition = panelBottom - windowHeight; + } + $( 'html, body' ).stop().animate( { + scrollTop: scrollPosition + }, 500 ); + } + } ); + }; + + $.fn.uls.Constructor = ULS; +}( jQuery ) ); |