From 7a9b801a79c5e342e12dd44aa77f07cfd746a025 Mon Sep 17 00:00:00 2001 From: Santhosh Thottingal Date: Wed, 27 Jun 2012 19:01:08 +0530 Subject: [PATCH] Introduce Language Category Display (LCD) Introduce $.fn.lcd - displaying languages by categories. Added the jquery.viewport.js module by Mika Tuupola (MIT license). More tests for language utils. Change-Id: I804b6da8ab0a2d518e17ae8bbd35e1c1da4bab3f --- UniversalLanguageSelector.hooks.php | 2 - UniversalLanguageSelector.php | 17 +++++ resources/css/ext.uls.css | 8 -- resources/css/ext.uls.lcd.css | 16 ++++ resources/ext.uls.core.js | 35 +++++---- resources/ext.uls.data.utils.js | 60 +++++++++++---- resources/ext.uls.languagefilter.js | 68 +++++++---------- resources/ext.uls.lcd.js | 111 ++++++++++++++++++++++++++++ resources/jquery.viewport.js | 58 +++++++++++++++ tests/qunit/ext.uls.tests.js | 17 +++-- 10 files changed, 302 insertions(+), 90 deletions(-) create mode 100644 resources/css/ext.uls.lcd.css create mode 100644 resources/ext.uls.lcd.js create mode 100644 resources/jquery.viewport.js diff --git a/UniversalLanguageSelector.hooks.php b/UniversalLanguageSelector.hooks.php index ed6aba4c..6d620097 100644 --- a/UniversalLanguageSelector.hooks.php +++ b/UniversalLanguageSelector.hooks.php @@ -87,8 +87,6 @@ class UniversalLanguageSelectorHooks {
-
"; diff --git a/UniversalLanguageSelector.php b/UniversalLanguageSelector.php index a3fc68bf..39ceb817 100644 --- a/UniversalLanguageSelector.php +++ b/UniversalLanguageSelector.php @@ -51,6 +51,22 @@ $wgResourceModules['ext.uls.data'] = array( 'remoteExtPath' => 'UniversalLanguageSelector', ); +$wgResourceModules['ext.uls.lcd'] = array( + 'scripts' => 'resources/ext.uls.lcd.js', + 'localBasePath' => $dir, + 'remoteExtPath' => 'UniversalLanguageSelector', + 'styles' => 'resources/css/ext.uls.lcd.css', + 'dependencies' => array( + 'jquery.viewport', + ), +); + +$wgResourceModules['jquery.viewport'] = array( + 'scripts' => 'resources/jquery.viewport.js', + 'localBasePath' => $dir, + 'remoteExtPath' => 'UniversalLanguageSelector', +); + $wgResourceModules['ext.uls.core'] = array( 'scripts' => array( 'resources/ext.uls.core.js', @@ -63,6 +79,7 @@ $wgResourceModules['ext.uls.core'] = array( 'dependencies' => array( 'mediawiki.Uri', 'ext.uls.data', + 'ext.uls.lcd', ), 'position' => 'top', ); diff --git a/resources/css/ext.uls.css b/resources/css/ext.uls.css index da1e8541..d678770e 100644 --- a/resources/css/ext.uls.css +++ b/resources/css/ext.uls.css @@ -100,14 +100,6 @@ margin-top: 2%; } -.uls-language-list ul { - list-style: none; - margin-left: 0; - -moz-column-count: 3; - -webkit-column-count: 3; - column-count: 3; -} - .uls-language-list ul li { font-weight: bold; } diff --git a/resources/css/ext.uls.lcd.css b/resources/css/ext.uls.lcd.css new file mode 100644 index 00000000..91bd1d0d --- /dev/null +++ b/resources/css/ext.uls.lcd.css @@ -0,0 +1,16 @@ +.uls-lcd-region-section h3 { + color: #999999; +} + +.uls-lcd-region-section ul { + list-style: none; + margin-left: 0; + float: left; + width: 150px; + padding: 10px; + clear: right; +} + +.uls-lcd-region-section { + clear: both; +} \ No newline at end of file diff --git a/resources/ext.uls.core.js b/resources/ext.uls.core.js index a2355f24..022a7153 100644 --- a/resources/ext.uls.core.js +++ b/resources/ext.uls.core.js @@ -49,24 +49,22 @@ that.$element.on( 'click', $.proxy( that.click, that ) ); $( ".icon-close" ).on( 'click', $.proxy( that.click, that ) ); - // The search input box - $( "#languagefilter" ).languagefilter( { - $target: $( 'ul.uls-language-filter-result' ), + var $lcd = $( "div.uls-language-list" ).lcd( { + languages: that.languages, clickhandler: function( langCode ) { that.setLang( langCode ); - }, + } + }).data( "lcd" ); + $( "#languagefilter" ).languagefilter( { + $target: $lcd, //$( 'ul.uls-language-filter-result' ), languages: that.languages } ); // Create region selectors, one per region $( '.uls-region' ).regionselector( { - $target: $( 'ul.uls-language-filter-result' ), - clickhandler: function( langCode ) { - that.setLang( langCode ); - }, - //FIXME This is confusing: languages and source are acturally data for ULS. + $target: $lcd, + //FIXME This is confusing: languages and source are actually data for ULS. languages: that.languages, - source: $.uls.data, callback: function () { // clear the search field. $( "#languagefilter" ).val( "" ); @@ -75,10 +73,9 @@ // trigger a search for all languages. $( "#languagefilter" ).languagefilter( "search" ); }, - keyup : function(e) { - switch(e.keyCode) { - case 27: - // escape + keyup: function( e ) { + switch( e.keyCode ) { + case 27: // escape if (!this.shown ) { return this.hide(); } @@ -93,8 +90,7 @@ } switch( e.keyCode ) { - case 27: - // escape + case 27: // escape e.preventDefault(); break; } @@ -117,7 +113,10 @@ $.fn.uls = function( option ) { return this.each( function() { - var $this = $( this ), data = $this.data( 'uls' ), options = typeof option == 'object' && option; + var $this = $( this ), + data = $this.data( 'uls' ), + options = typeof option === 'object' && option; + if ( !data ) { $this.data( 'uls', ( data = new ULS( this, options ) ) ); } @@ -128,7 +127,7 @@ }; $.fn.uls.defaults = { - menu : '.uls-menu', + menu: '.uls-menu' }; $.fn.uls.Constructor = ULS; diff --git a/resources/ext.uls.data.utils.js b/resources/ext.uls.data.utils.js index e9781fda..6988b07c 100644 --- a/resources/ext.uls.data.utils.js +++ b/resources/ext.uls.data.utils.js @@ -5,9 +5,32 @@ (function ( $ ) { "use strict"; - // Constants - var scriptIndex = 0, - regionsIndex = 1; + /* + * Returns the script of the language. + * @param string language code + * @return string + */ + $.uls.data.script = function( language ) { + return $.uls.data.languages[language][0]; + }; + + /* + * Returns the regions in which a language is spoken. + * @param string language code + * @return array of strings + */ + $.uls.data.regions = function( language ) { + return $.uls.data.languages[language][1]; + }; + + /* + * Returns the autonym of the language. + * @param string language code + * @return string + */ + $.uls.data.autonym = function( language ) { + return $.uls.data.autonyms[language]; + }; /* * Returns all languages written in script. @@ -16,7 +39,7 @@ */ $.uls.data.languagesInScript = function( script ) { return $.uls.data.languagesInScripts( [ script ] ); - } + }; /* * Returns all languages written in the given scripts. @@ -28,7 +51,7 @@ for ( var language in $.uls.data.languages ) { for ( var i = 0; i < scripts.length; i++ ) { - if ( scripts[i] === $.uls.data.languages[language][scriptIndex] ) { + if ( scripts[i] === $.uls.data.script(language) ) { languagesInScripts.push( language ); break; } @@ -36,7 +59,7 @@ } return languagesInScripts; - } + }; /* * Returns all languages in a given region. @@ -45,7 +68,7 @@ */ $.uls.data.languagesInRegion = function( region ) { return $.uls.data.languagesInRegions( [ region ] ); - } + }; /* * Returns all languages in given regions. @@ -57,7 +80,7 @@ for ( var language in $.uls.data.languages ) { for ( var i = 0; i < regions.length; i++ ) { - if ( $.inArray( regions[i], $.uls.data.languages[language][regionsIndex] ) != -1 ) { + if ( $.inArray( regions[i], $.uls.data.regions( language ) ) !== -1 ) { languagesInRegions.push( language ); break; } @@ -65,7 +88,7 @@ } return languagesInRegions; - } + }; /* * Returns an associative array of languages in a region, @@ -77,8 +100,8 @@ var languagesByScriptInRegion = {}; for ( var language in $.uls.data.languages ) { - if ( $.inArray( region, $.uls.data.languages[language][regionsIndex] ) != -1 ) { - var script = $.uls.data.languages[language][scriptIndex]; + if ( $.inArray( region, $.uls.data.regions( language ) ) !== -1 ) { + var script = $.uls.data.script( language ); if ( languagesByScriptInRegion[script] === undefined ) { languagesByScriptInRegion[script] = []; } @@ -87,7 +110,7 @@ } return languagesByScriptInRegion; - } + }; /* * Returns all regions in a region group. @@ -104,20 +127,27 @@ } return regionsInGroup; - } + }; /* * Returns the script group of a script or 'Other' if it doesn't * belong to any group. + * @param string script code + * @return string script group name */ $.uls.data.groupOfScript = function( script ) { for ( var group in $.uls.data.scriptgroups ) { - if ( $.inArray( script, $.uls.data.scriptgroups[group] ) != -1 ) { + if ( $.inArray( script, $.uls.data.scriptgroups[group] ) !== -1 ) { return group; } } return 'Other'; - } + }; + + $.uls.data.sortByScriptGroup = function( languages ) { + // FIXME sort it. + return languages; + }; } )( jQuery ); diff --git a/resources/ext.uls.languagefilter.js b/resources/ext.uls.languagefilter.js index 9f208233..d3623039 100644 --- a/resources/ext.uls.languagefilter.js +++ b/resources/ext.uls.languagefilter.js @@ -1,9 +1,10 @@ /** * @author Santhosh Thottingal * jQuery language filter plugin - * Usage: $('inputbox').languagefilter(); + * + * Usage: $( 'inputbox' ).languagefilter(); * The values for autocompletion is from the options.languages. - * the data is in the format of languagecode:languagename format. + * The data is in the format of languagecode:languagename. */ (function ( $ ) { "use strict"; @@ -33,7 +34,7 @@ var that = this; var languages = this.options.languages; var query = this.$element.val(); - $.each( languages, function ( name, code ) { + $.each( languages, function ( code, name ) { if ( query === "" ) { that.render(code); } @@ -49,16 +50,7 @@ if ( !$target ) { return; } - var $li = $( "
  • " ) - .data( "code", code ) - .append( $( "" ).prop( 'href', '#' ). html( this.options.languages[code] || code ) ) - .appendTo( $target ); - - if ( this.options.clickhandler ) { - $li.click( function() { - that.options.clickhandler.call( this, code ); - } ); - } + $target.append(code); }, escapeRegex: function( value ) { @@ -74,13 +66,12 @@ * d) ISO 15924 code for the script match the search string. */ filter: function( code, searchTerm ) { - var languages = this.options.languages; - var langName = languages[code]; - var autonym = $.uls.data.autonyms[code]; - var script = $.uls.data.languages[code]? $.uls.data.languages[code][0]: "unknown"; // FIXME script is ISO 15924 code. We might need actual name of script. var matcher = new RegExp( this.escapeRegex( searchTerm ), 'i' ); - return matcher.test( langName ) || matcher.test( autonym ) || matcher.test( code ) || matcher.test( script ); + return matcher.test( this.options.languages[code] ) || + matcher.test( $.uls.data.autonym( code ) ) || + matcher.test( code ) || + matcher.test( $.uls.data.script( code ) ); } }; @@ -112,7 +103,7 @@ /* * Region selector is a language selector based on regions. - * Usage: $( 'jqueryselector' ).regionselector(options); + * Usage: $( 'jqueryselector' ).regionselector( options ); * The attached element should have data-region attribute * that defines the region for the selector. */ @@ -128,8 +119,8 @@ constructor: RegionSelector, test: function( langCode ) { var that = this; - var languages = that.options.source.languages; - var regionGroups = that.options.source.regiongroups; + var languages = $.uls.data.languages; + var regionGroups = $.uls.data.regiongroups; var regions = languages[langCode][1]; // 1. loop over all regions - like {EU: 2, AF: 2, AS: 3 ...} // 2. check that the region matches the active region group @@ -137,18 +128,21 @@ // 4. if none of the conditions match, the language is not shown $.each( regionGroups, function( regionGroup, groupId ) { if ( groupId === that.region && $.inArray( regionGroup, regions ) >= 0 ) { - that.render( langCode ); - return true; + that.render( langCode, regionGroup ); + return; } } ); - return false; }, show: function() { - var that = this; - var languages = that.options.source.languages; + var that = this, + languages = $.uls.data.languages; + + languages = $.uls.data.sortByScriptGroup( languages ); + // Make the selected region (and it only) active $( '.regionselector' ).removeClass( 'active' ); that.$element.addClass( 'active' ); + // Repopulate the list of languages that.options.$target.empty(); $.each( languages, function( langCode, langDef ) { @@ -158,19 +152,12 @@ that.options.callback.call(); } }, - render: function( langCode ) { - var that = this; - var langName = that.options.languages[langCode] || langCode; - var $li = $( "
  • " ) - .data( "code", langCode ) - .append( $( "" ).prop( 'href', '#' ).html( langName ) ) - .appendTo( this.options.$target ); - - if ( that.options.clickhandler ) { - $li.click( function() { - that.options.clickhandler.call( this, langCode ); - } ); + render: function( langCode, regionGroup) { + var $target = this.options.$target; + if ( !$target ) { + return; } + $target.append( langCode, regionGroup ); }, listen: function(){ this.$element.on( 'click', $.proxy( this.click, this ) ); @@ -187,7 +174,7 @@ return this.each( function() { var $this = $( this ), data = $this.data( 'regionselector' ), - options = typeof option == 'object' && option; + options = typeof option === 'object' && option; if ( !data ) { $this.data( 'regionselector', ( data = new RegionSelector( this, options ) ) ); } @@ -199,9 +186,6 @@ $.fn.regionselector.defaults = { $target: null, // Where to render the results. Must be a ul element - clickhandler: null, // Click handler to handle click events on results - source: null, // The language database - languages:null, // Language names for the current UI language callback: null // Callback - will be called after results are displayed. }; diff --git a/resources/ext.uls.lcd.js b/resources/ext.uls.lcd.js new file mode 100644 index 00000000..ed1a67c9 --- /dev/null +++ b/resources/ext.uls.lcd.js @@ -0,0 +1,111 @@ +(function( $ ) { + "use strict"; + + var LanguageCategoryDisplay = function( element, options ) { + this.$element = $( element ); + this.options = $.extend( {}, $.fn.lcd.defaults, options ); + this.$element.addClass( 'lcd' ); + this.show(); + this.listen(); + }; + + LanguageCategoryDisplay.prototype = { + constructor: LanguageCategoryDisplay, + + append: function( langCode, regionGroup ) { + var that = this; + this.addToRegion( langCode, regionGroup ); + }, + + addToRegion: function( langCode, regionGroup ) { + var that = this, + language = $.uls.data.languages[langCode]; + + if ( !language ) { + console.log( "Definition for " + langCode + " was not found in the language database." ); + return; + } + + var langName = that.options.languages[langCode]; + + var regions = $.uls.data.regions( langCode ); + for ( var i = 0; i < regions.length; i++ ) { + var regionCode = regions[i]; + + if ( regionGroup && regionCode !== regionGroup ) { + continue; + } + + var $li = $( '
  • ' ) + .data( 'code', langCode ) + .append( + $( '' ).prop( 'href', '#' ).html( langName ) + ); + that.getColumn( regionCode ).append( $li ); + if ( that.options.clickhandler ) { + $li.click( function() { + that.options.clickhandler.call( this, langCode ); + } ); + } + } + }, + + getColumn: function( regionCode ) { + var $ul = $( "div#" + regionCode ).find( 'ul:last' ); + if ( $ul.length === 0 || $ul.find( 'li' ).length >= 10) { + $ul = $( '
      ' ); + $( 'div#' + regionCode ).append( $ul ); + } + $( 'div#' + regionCode ).show(); + return $ul; + }, + + show: function() { + var that = this; + $.each( $.uls.data.regiongroups, function( regionCode, regionIndex ) { + var $section = $( '
      ' ).addClass( 'uls-lcd-region-section' ).prop( 'id', regionCode ); + $section.append( $( '

      ' ).html( regionCode ) ); + // FIXME this is regioncode(NA, EU etc). Should be Proper localized region name. + $section.append( $( '
        ' ) ); + that.$element.append( $section ); + } ); + }, + + empty: function() { + this.$element.find( 'div ul' ).remove(); + this.$element.find( 'div' ).hide(); + }, + + listen: function(){ + this.$element.scroll( function() { + var inviewRegion = $( 'div.uls-lcd-region-section:in-viewport:first' ).attr( 'id' ); + var inview = $.uls.data.regiongroups[inviewRegion]; + $( 'div.uls-region' ).removeClass( 'active' ); + $( 'div#uls-region-' + inview).addClass( 'active' ); + }); + } + + }; + + $.fn.lcd = function( option ) { + return this.each( function() { + var $this = $( this ), + data = $this.data( 'lcd' ), + options = typeof option === 'object' && option; + + if ( !data ) { + $this.data( 'lcd', ( data = new LanguageCategoryDisplay( this, options ) ) ); + } + if ( typeof option === 'string') { + data[option](); + } + } ); + }; + + $.fn.lcd.defaults = { + languages: null + }; + + $.fn.lcd.Constructor = LanguageCategoryDisplay; + +} )( jQuery ); diff --git a/resources/jquery.viewport.js b/resources/jquery.viewport.js new file mode 100644 index 00000000..7826000f --- /dev/null +++ b/resources/jquery.viewport.js @@ -0,0 +1,58 @@ +/* + * Viewport - jQuery selectors for finding elements in viewport + * + * Copyright (c) 2008-2009 Mika Tuupola + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + * + * Project home: + * http://www.appelsiini.net/projects/viewport + * + */ +(function($) { + + $.belowthefold = function(element, settings) { + var fold = $(window).height() + $(window).scrollTop(); + return fold <= $(element).offset().top - settings.threshold; + }; + + $.abovethetop = function(element, settings) { + var top = $(window).scrollTop(); + return top >= $(element).offset().top + $(element).height() - settings.threshold; + }; + + $.rightofscreen = function(element, settings) { + var fold = $(window).width() + $(window).scrollLeft(); + return fold <= $(element).offset().left - settings.threshold; + }; + + $.leftofscreen = function(element, settings) { + var left = $(window).scrollLeft(); + return left >= $(element).offset().left + $(element).width() - settings.threshold; + }; + + $.inviewport = function(element, settings) { + return !$.rightofscreen(element, settings) && !$.leftofscreen(element, settings) && !$.belowthefold(element, settings) && !$.abovethetop(element, settings); + }; + + $.extend($.expr[':'], { + "below-the-fold": function(a, i, m) { + return $.belowthefold(a, {threshold : 0}); + }, + "above-the-top": function(a, i, m) { + return $.abovethetop(a, {threshold : 0}); + }, + "left-of-screen": function(a, i, m) { + return $.leftofscreen(a, {threshold : 0}); + }, + "right-of-screen": function(a, i, m) { + return $.rightofscreen(a, {threshold : 0}); + }, + "in-viewport": function(a, i, m) { + return $.inviewport(a, {threshold : 0}); + } + }); + + +})(jQuery); diff --git a/tests/qunit/ext.uls.tests.js b/tests/qunit/ext.uls.tests.js index c803938f..811fd511 100644 --- a/tests/qunit/ext.uls.tests.js +++ b/tests/qunit/ext.uls.tests.js @@ -23,7 +23,7 @@ var orphanScript = function () { } return ''; -} +}; test( "-- Initial check", function() { expect( 1 ); @@ -31,10 +31,15 @@ test( "-- Initial check", function() { } ); test( "-- $.uls.data testing", function() { - expect( 8 ); + expect( 12 ); - // Unless we actually want some scripts to be in the 'Other' group. + // This test assumes that we don't want any scripts to be in the 'Other' + // group. Actually, this may become wrong some day. strictEqual( orphanScript(), '', 'No orphan scripts found.' ); + strictEqual( $.uls.data.groupOfScript( 'Beng' ), 'SouthAsian', 'Bengali script belongs to the SouthAsian group.' ); + + strictEqual( $.uls.data.script( 'ii' ), 'Yiii', 'Correct script of the Yi language was selected' ); + deepEqual( $.uls.data.regions( 'lzz' ), [ 'EU', 'ME' ], 'Correct regions of the Laz language were selected' ); deepEqual( $.uls.data.languagesInRegion( 'AU' ), ["en", "en-gb", "hif", "hif-latn", "mi", "na"], "languages of region AU are selected correctly" ); deepEqual( $.uls.data.languagesInRegions( ['NA', 'WW'] ), @@ -42,8 +47,8 @@ test( "-- $.uls.data testing", function() { "avk", "cho", "chr", "chy", "cr", "en", "en-ca", "eo", "es", "haw", "ht", "ia", "ie", "ik", "ike-cans", "ike-latn", "io", "iu", "jam", "jbo", "kl", "lfn", "mus", "nah", "nov", "nv", - "pdc", "pdt", "rue", "sei", "simple", "srn", "tokipona", - "uk", "vo", "yi" + "pdc", "pdt", "sei", "simple", "srn", "tokipona", + "vo", "yi" ], "languages of regions NA and WW are selected correctly" ); @@ -59,6 +64,8 @@ test( "-- $.uls.data testing", function() { languagesByScriptInNA = $.uls.data.languagesByScriptInRegion( 'NA' ); deepEqual( languagesByScriptInNA['Cans'], ["cr", "ike-cans", "iu"], "correct languages in Cans in NA selected" ); + + strictEqual( $.uls.data.autonym( 'pa' ), 'ਪੰਜਾਬੀ', 'Correct autonym of the Punjabi language was selected' ); } ); }() );