/** * 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. /*jshint multistr:true */ template = '\
'; /*jshint multistr:false */ /** * ULS Public class definition */ ULS = function ( element, options ) { this.$element = $( element ); this.options = $.extend( {}, $.fn.uls.defaults, options ); this.$menu = $( template ); this.languages = this.options.languages; for ( var code in this.languages ) { if ( $.uls.data.languages[code] === undefined ) { if ( window.console && window.console.log ) { window.console.log( 'ULS: Unknown language ' + code + '.' ); } delete this.languages[code]; } } this.left = this.options.left; this.top = this.options.top; this.shown = false; this.initialized = false; this.$languageFilter = this.$menu.find( '#languagefilter' ); this.$regionFilters = this.$menu.find( '.uls-region' ); this.$resultsView = this.$menu.find( 'div.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. * @returns {Object} */ position: function () { var pos = $.extend( {}, this.$element.offset(), { height: this.$element[0].offsetHeight } ); return { top: this.top || pos.top + pos.height, left: this.left || '25%' }; }, /** * Show the ULS window */ show: function () { this.$menu.css( this.position() ); if ( this.options.compact ) { this.$menu.addClass( 'uls-compact' ); } if ( !this.initialized ) { $( 'body' ).prepend( this.$menu ); this.i18n(); // Initialize with a full search. // This happens on first time click of uls trigger. this.defaultSearch(); this.initialized = true; } // hide any other visible ULS $( '.uls-menu' ).hide(); 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' ) ); } }, defaultSearch: function () { this.$resultsView.lcd( 'empty' ); this.$regionFilters.regionselector( 'show' ); }, /** * Hide the ULS window */ hide: function () { this.$menu.hide(); this.shown = false; }, /** * Render the UI elements. * Does nothing by default. Can be used for customization. */ render: function () { // Rendering stuff here }, /** * Callback for no results found context. */ noresults: function () { this.$resultsView.lcd( 'noResults' ); }, /** * callback for results found context. */ success: function () { this.$resultsView.show(); }, /** * Bind the UI elements with their event listeners */ listen: function () { var lcd, uls = this; // Register all event listeners to the ULS here. uls.$element.on( 'click', $.proxy( uls.click, uls ) ); uls.$languageFilter.on( 'searchclear', $.proxy( uls.defaultSearch, uls ) ); // Close when clicking on the close button uls.$menu.find( '#uls-close' ).on( 'click', $.proxy( uls.cancel, uls ) ); // Don't do anything if pressing on empty space in the ULS uls.$menu.on( 'click', function ( e ) { e.stopPropagation(); } ); // Handle key press events on the menu uls.$menu.on( 'keypress', $.proxy( this.keypress, this ) ) .on( 'keyup', $.proxy( this.keyup, this ) ); if ( this.eventSupported( 'keydown' ) ) { this.$menu.on( 'keydown', $.proxy( this.keypress, this ) ); } lcd = uls.$resultsView.lcd( { languages: uls.languages, quickList: uls.options.quickList, clickhandler: $.proxy( uls.select, uls ), source: uls.$languageFilter, showRegions: uls.options.showRegions } ).data( 'lcd' ); uls.$languageFilter.languagefilter( { $target: lcd, languages: uls.languages, success: function () { $( '.regionselector' ).removeClass( 'active' ); uls.success(); }, noresults: function () { $( '.regionselector' ).removeClass( 'active' ); uls.noresults(); }, searchAPI: uls.options.searchAPI, onSelect: $.proxy( uls.select, uls ) } ); // Create region selectors, one per region this.$menu.find( '.uls-region, .uls-region-link' ).regionselector( { $target: lcd, languages: uls.languages, success: function ( regionfilter ) { // Deactivate search filtering uls.$languageFilter.languagefilter( 'deactivate' ); // If it is the WW region, show the quicklist if ( regionfilter.regionGroup === 1 ) { lcd.quicklist(); } // Show 'results view' if we are in no results mode uls.success(); }, noresults: function () { uls.$languageFilter.languagefilter( 'clear' ); } } ); $( 'html' ).click( $.proxy( this.hide, this ) ); }, /** * On select handler for search results * @param langCode */ select: function ( langCode ) { this.hide(); this.$languageFilter.trigger( 'searchclear' ); if ( this.options.onSelect ) { this.options.onSelect.call( this, langCode ); } }, /** * On cancel handler for the uls menu */ cancel: function () { this.hide(); if ( this.options.onCancel ) { this.options.onCancel.call( this ); } }, keyup: function ( e ) { if ( !this.shown ) { return; } if ( e.keyCode === 27 ) { // escape this.cancel(); e.preventDefault(); e.stopPropagation(); } }, keypress: function ( e ) { if ( !this.shown ) { return; } if ( e.keyCode === 27 ) { // escape this.cancel(); e.preventDefault(); e.stopPropagation(); } }, click: function ( e ) { e.stopPropagation(); e.preventDefault(); if ( this.shown ) { this.hide(); } else { this.show(); } }, eventSupported: function ( eventName ) { var isSupported = eventName in this.$menu; if ( !isSupported ) { this.$element.setAttribute( eventName, 'return;' ); isSupported = typeof this.$element[eventName] === 'function'; } return isSupported; }, 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 = { menu: template, onSelect: null, // Callback function to be called when a language is selected searchAPI: null, // Language search API languages: $.uls.data.getAutonyms(), // Languages to be used for ULS, default is all languages quickList: null, // Array of language codes or function that returns such compact: false, // Show ULS in compact mode showRegions: ['WW', 'AM', 'EU', 'ME', 'AF', 'AS', 'PA'] }; // Define a dummy i18n function, if jquery.i18n not integrated. if ( !$.fn.i18n ) { $.fn.i18n = function () { }; } /* * 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 ) );