Do not group search results by region

* Documentation updates
* Do not group search results by region
* Disable grouping by script when region grouping is disabled
* Add docs, clarify names per code review
* Fix bool -> boolean for consistency
This commit is contained in:
Niklas Laxström
2018-01-16 13:34:29 +02:00
committed by Santhosh Thottingal
parent fd41bbe5e1
commit d349937772
5 changed files with 157 additions and 133 deletions

View File

@@ -1,10 +1,8 @@
jQuery Universal Language Selector Universal Language Selector jQuery library
================================== ==================================
Universal Language Selector
[![Build Status](https://secure.travis-ci.org/wikimedia/jquery.uls.png)](http://travis-ci.org/wikimedia/jquery.uls) [![Build Status](https://secure.travis-ci.org/wikimedia/jquery.uls.png)](http://travis-ci.org/wikimedia/jquery.uls)
This is a [Wikimedia Foundation Language Engineering team project](https://www.mediawiki.org/wiki/Project_Milkshake). This is a [Wikimedia Foundation project](https://www.mediawiki.org/wiki/Project_Milkshake).
![Universal Language Selector](https://upload.wikimedia.org/wikipedia/commons/a/a1/UniversalLanguageSelector-Compact.png "Universal Language Selector") ![Universal Language Selector](https://upload.wikimedia.org/wikipedia/commons/a/a1/UniversalLanguageSelector-Compact.png "Universal Language Selector")
@@ -19,86 +17,69 @@ git clone https://github.com/wikimedia/jquery.uls.git
Documentation Documentation
------------- -------------
The quick and easy way to learn usage of jquery.uls is trying out the examples/index.html in some webserver. Try it from here: http://thottingal.in/projects/js/jquery.uls/examples/ The quick and easy way to learn usage of jquery.uls is trying out the examples/index.html in webserver. Try it online at http://thottingal.in/projects/js/jquery.uls/examples/
The jQuery.uls provides a jQuery extension ```$.fn.uls``` that can be attached to a trigger element. The expected behavior is, when you click on the trigger, the language selector opens up. The jquery.uls provides a jQuery extension ```$.fn.uls``` that can be attached to a trigger element. When you click on the trigger element, the language selector is shown.
The trigger can be a link, button or any valid jQuery element. The trigger can be a link, button or any valid jQuery element.
Example: Example:
```javascript ```javascript
$( '.uls-trigger' ).uls( ); $( '.uls-trigger' ).uls();
``` ```
To use the selected language, you need define a selection Handler as shown below To use the selected language, you need define a selection handler:
```javascript ```javascript
$( '.uls-trigger' ).uls( { $( '.uls-trigger' ).uls( {
onSelect : function( language ) { onSelect: function( language ) {
// language is a ISO 639 language code. eg: en, hi, fi, he etc // language is a IETF language tag in lowercase, for example: en, fi, ku-latn
// Your selection handler code goes here. // Your selection handler code goes here.
} }
} ); } );
``` ```
In some usecases, you may need to provide a quick list of languages to select before going through all languages. For eg, it can a list of recently selected languages, language suggestions based on Geo IP. You can provide a quick list of likely useful languages, for example based on Geo IP or recently selected languages:
That can be done as follows
```javascript ```javascript
$( '.uls-trigger' ).uls( { $( '.uls-trigger' ).uls( {
onSelect : function( language ) { onSelect: function( language ) { ... },
// language is a ISO 639 language code. eg: en, hi, fi, he etc quickList: [ 'en', 'ml', 'hi' ] // Can be a function returning an array too.
// Your selection handler code goes here.
},
quickList: [ 'en', 'ml', 'hi' ] // An array of language codes. Can be a function that returns this array too.
} ); } );
``` ```
If the search needs to be more complex(such as cross language search, spelling error tolerating etc), a search API option can be provided. jquery.uls knows about 500 languages. You can specify a subset of those languages:
```javascript ```javascript
$( '.uls-trigger' ).uls( { $( '.uls-trigger' ).uls( {
onSelect : function( language ) { onSelect: function( language ) { ... },
// language is a ISO 639 language code. eg: en, hi, fi, he etc languages: { languageCode1: languageName, languageCode2: languageName2, .... },
// Your selection handler code goes here.
},
searchAPI: apiURL,
quickList: [ 'en', 'ml', 'hi' ] // An array of language codes. Can be a function that returns this array too.
} ); } );
``` ```
Example for such an api is used in Wikipedia: http://en.wikipedia.org/w/api.php?action=languagesearch&search=Te *All options*
ULS knows about 500 languages. If you dont want to use that many languages for your usecase, use languages option.
```javascript
$( '.uls-trigger' ).uls( {
onSelect : function( language ) {
// language is a ISO 639 language code. eg: en, hi, fi, he etc
// Your selection handler code goes here.
},
languages: { languageCode1: languageName, languageCode2: languageName2 , .... },
searchAPI: apiURL,
quickList: [ 'en', 'ml', 'hi' ] // An array of language codes. Can be a function that returns this array too.
} );
```
*Other Options*
| Option | Description | | Option | Description |
|-------------|---------------------| |-------------|---------------------|
| left | left position of ULS. eg: 100px, 20%| | left | Left position of ULS dialog. E.g: 100px or 20% |
| top | top position of ULS. eg: 100px, 20%| | top | Top position of ULS dialog. E.g: 100px or 20% |
| onCancel | function to be handled when language selection is not done. ie. language selector is closed without selecting any | | onCancel | Callback function when the dialog is closed without selecting a language. |
| showRegions | Regions to be shown in the language selector. Default: ['WW', 'AM', 'EU', 'ME', 'AF', 'AS', 'PA'] | | onReady | Callback function when ULS has initialized. |
| onVisible | Callback function when ULS dialog is shown. |
| onSelect | Callback function when user selects a language. |
| languages | List of selectable languages. Defaults to all known languages. |
| quicklist | List of suggested languages. Defaults to empty list. |
| searchAPI | API URL. Parameter query with the user query is appened to it. |
| menuWidth | Override the automatic choice of menu width. One of narrow, medium, wide (1, 2, 4 columns respectively). |
| showRegions | Regions to be shown in the language selector. Defaults to [ WW, AM, EU, ME, AF, AS, PA ] |
| groupByRegion | Whether to group languages by the regions: true of false. Default value depends on the menu width. |
| itemsPerColumn | Number of languages per column. Default is 8 | | itemsPerColumn | Number of languages per column. Default is 8 |
| languageDecorator | Callback function to be called when a language link is prepared - for custom decoration. Arguments: (a) the $language - the language link jQuery object (b) languageCode. The function can do any styling, changing properties etc on the passed link. See examples/decorator.html for example usage.| | languageDecorator | Callback function to be called when a language link is prepared - for custom decoration. Arguments: (a) the $language - the language link jQuery object (b) languageCode. The function can do any styling, changing properties etc on the passed link. See examples/decorator.html for example usage.|
Features Features
-------- --------
jQuery.uls has an elaborative language information collection and it is based on https://github.com/wikimedia/language-data.git. It knows about jquery.uls has an elaborative language information collection and it is based on https://github.com/wikimedia/language-data.git. It knows about
1. The script in which a language is written. 1. The script in which a language is written.
2. The script code 2. The script code
@@ -107,14 +88,7 @@ jQuery.uls has an elaborative language information collection and it is based on
5. The autonym - language name written in its own script 5. The autonym - language name written in its own script
6. The directionality of the text 6. The directionality of the text
With all these information the search becomes very effective. Based on the spoken regions, the UI organize the languages. In side regions With all these information the search becomes very effective. An advanced usage of jquery.uls can be tried out from Wikimedia sites. For example, see the language icon at the top of https://mediawiki.org or the cog icon near to the languages list in Wikipedia in any language.
the language is again organized based on scripts.
A user can search for a language based on script name.
ULS can autocomplete a language name search.
An advanced usage of jQuery.uls can be tried out from wikimedia sites. For eg, see the language icon at the top of http://mediawiki.org or the cog icon near to the languages list in wikipedia in any language
More details More details
------------ ------------
@@ -122,7 +96,7 @@ More details
* UX Design https://www.mediawiki.org/wiki/Universal_Language_Selector/Design * UX Design https://www.mediawiki.org/wiki/Universal_Language_Selector/Design
How to build and test jQuery ULS How to build and test jquery.uls
---------------------------------- ----------------------------------
First, get a copy of the git repo by running: First, get a copy of the git repo by running:
@@ -150,4 +124,4 @@ You can also run the tests in a browser by navigating to the `test/` directory,
Coding style Coding style
------------- -------------
Please follow [jQuery coding guidelines](http://docs.jquery.com/JQuery_Core_Style_Guidelines) Please follow [MediaWiki coding conventions](https://www.mediawiki.org/wiki/Manual:Coding_conventions/JavaScript)

View File

@@ -17,6 +17,10 @@
padding: 0 16px; padding: 0 16px;
} }
.uls-lcd--no-quicklist [data-region="all"] .uls-lcd-region-title {
display: none;
}
.uls-lcd-region-section { .uls-lcd-region-section {
margin-top: 10px; margin-top: 10px;
} }

View File

@@ -232,14 +232,15 @@
quickList: languagesCount > 12 ? this.options.quickList : [], quickList: languagesCount > 12 ? this.options.quickList : [],
clickhandler: $.proxy( this.select, this ), clickhandler: $.proxy( this.select, this ),
source: this.$languageFilter,
showRegions: this.options.showRegions, showRegions: this.options.showRegions,
languageDecorator: this.options.languageDecorator, languageDecorator: this.options.languageDecorator,
noResultsTemplate: this.options.noResultsTemplate noResultsTemplate: this.options.noResultsTemplate,
itemsPerColumn: this.options.itemsPerColumn,
groupByRegion: this.options.groupByRegion
} ).data( 'lcd' ); } ).data( 'lcd' );
this.$languageFilter.languagefilter( { this.$languageFilter.languagefilter( {
$target: lcd, lcd: lcd,
languages: this.languages, languages: this.languages,
searchAPI: this.options.searchAPI, searchAPI: this.options.searchAPI,
onSelect: $.proxy( this.select, this ) onSelect: $.proxy( this.select, this )
@@ -366,15 +367,35 @@
}; };
$.fn.uls.defaults = { $.fn.uls.defaults = {
onSelect: null, // Callback function to be called when a language is selected // CSS top position for the dialog
searchAPI: null, // Language search API top: undefined,
languages: $.uls.data.getAutonyms(), // Languages to be used for ULS, default is all languages // CSS left position for the dialog
quickList: [], // Array of language codes or function that returns such 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). // The options are wide (4 columns), medium (2 columns), and narrow (1 column).
// If not specified, it will be set automatically. // If not specified, it will be set automatically.
menuWidth: null, menuWidth: undefined,
showRegions: [ 'WW', 'AM', 'EU', 'ME', 'AF', 'AS', 'PA' ], // Used by LCD
languageDecorator: null // Callback function to be called when a language link is prepared - for custom decoration. quickList: [],
// Used by LCD
showRegions: undefined,
// Used by LCD
languageDecorator: 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. // Define a dummy i18n function, if jquery.i18n not integrated.

View File

@@ -19,8 +19,7 @@
/** /**
* Usage: $( 'inputbox' ).languagefilter(); * Usage: $( 'inputbox' ).languagefilter();
* The values for autocompletion is from the options.languages. * The values for autocompletion is from the options.languages or options.searchAPI.
* The data is in the format of languagecode:languagename.
*/ */
( function ( $ ) { ( function ( $ ) {
'use strict'; 'use strict';
@@ -132,7 +131,7 @@
if ( !languageFilter.$element.val() ) { if ( !languageFilter.$element.val() ) {
languageFilter.clear(); languageFilter.clear();
} else { } else {
languageFilter.options.$target.empty(); languageFilter.options.lcd.empty();
languageFilter.search(); languageFilter.search();
} }
}, 300 ); }, 300 );
@@ -186,11 +185,13 @@
query = $.trim( this.$element.val() ).toLowerCase(); query = $.trim( this.$element.val() ).toLowerCase();
if ( query === '' ) { if ( query === '' ) {
this.options.lcd.setGroupByRegionOverride( null );
languages.map( this.render.bind( this ) ); languages.map( this.render.bind( this ) );
this.resultHandler( query, languages ); this.resultHandler( query, languages );
return; return;
} }
this.options.lcd.setGroupByRegionOverride( false );
// Local search results // Local search results
results = languages.filter( function ( langCode ) { results = languages.filter( function ( langCode ) {
return this.filter( langCode, query ); return this.filter( langCode, query );
@@ -287,14 +288,7 @@
}, },
render: function ( langCode ) { render: function ( langCode ) {
// This is actually instance of LanguageCategoryDisplay and not jQuery! return this.options.lcd.append( langCode );
var $target = this.options.$target;
if ( !$target ) {
return false;
}
return $target.append( langCode );
}, },
escapeRegex: function ( value ) { escapeRegex: function ( value ) {
@@ -352,10 +346,14 @@
}; };
$.fn.languagefilter.defaults = { $.fn.languagefilter.defaults = {
$target: null, // Where to append the results // LanguageCategoryDisplay
searchAPI: null, lcd: undefined,
languages: null, // Languages as code:name format. // URL to which we append query parameter with the query value
onSelect: null // Language select handler - like enter in filter textbox. searchAPI: undefined,
// Object of language tags to language names
languages: [],
// Callback function when language is selected
onSelect: undefined
}; };
$.fn.languagefilter.Constructor = LanguageFilter; $.fn.languagefilter.Constructor = LanguageFilter;

View File

@@ -36,23 +36,30 @@
* Language category display * Language category display
* @param {Element} element The container element to which the languages to be displayed * @param {Element} element The container element to which the languages to be displayed
* @param {Object} [options] Configuration object * @param {Object} [options] Configuration object
* @cfg {Object} [languages] Languages known to the ULS. Keyed by language code, values are autonyms. * @cfg {Object} [languages] Selectable languages. Keyed by language code, values are autonyms.
* @cfg {string[]} [showRegions] Array of region codes to show. Default is * @cfg {string[]} [showRegions] Array of region codes to show. Default is
* [ 'WW', 'AM', 'EU', 'ME', 'AF', 'AS', 'PA' ], * [ 'WW', 'AM', 'EU', 'ME', 'AF', 'AS', 'PA' ]
* @cfg {number} [itemsPerColumn] Number of languages per column. * @cfg {number} [itemsPerColumn] Number of languages per column.
* @cfg {number} [columns] Number of columns for languages. Default is 4. * @cfg {number} [columns] Number of columns for languages. Default is 4.
* @cfg {Function} [languageDecorator] Callback function to be called when a language * @cfg {Function} [languageDecorator] Callback function to be called when a language
* link is prepared - for custom decoration. * link is prepared - for custom decoration.
* @cfg {Function|string[]} [quickList] The languages to display as suggestions for quick selectoin. * @cfg {Function|string[]} [quickList] The languages to display as suggestions for quick selection.
* @cfg {Function} [clickhandler] Callback when language is selected.
* @cfg {jQuery|Function} [noResultsTemplate] * @cfg {jQuery|Function} [noResultsTemplate]
*/ */
function LanguageCategoryDisplay( element, options ) { function LanguageCategoryDisplay( element, options ) {
this.$element = $( element ); this.$element = $( element );
this.options = $.extend( {}, $.fn.lcd.defaults, options ); this.options = $.extend( {}, $.fn.lcd.defaults, options );
// Ensure the internal region 'all' is always present
if ( this.options.showRegions.indexOf( 'all' ) === -1 ) {
this.options.showRegions.push( 'all' );
}
this.$element.addClass( 'uls-lcd' ); this.$element.addClass( 'uls-lcd' );
this.regionLanguages = {}; this.regionLanguages = {};
this.renderTimeout = null; this.renderTimeout = null;
this.cachedQuicklist = null; this.cachedQuicklist = null;
this.groupByRegionOverride = null;
this.render(); this.render();
this.listen(); this.listen();
@@ -65,24 +72,21 @@
* Adds language to the language list. * Adds language to the language list.
* @param {string} langCode * @param {string} langCode
* @param {string} [regionCode] * @param {string} [regionCode]
* @return {boolean} Whether the language was added. * @return {boolean} Whether the language was known and accepted
*/ */
append: function ( langCode, regionCode ) { append: function ( langCode, regionCode ) {
var lcd = this, var i, regions;
i, regions;
if ( !$.uls.data.languages[ langCode ] ) { if ( !$.uls.data.languages[ langCode ] ) {
// Language is unknown or not in the list of languages for this context. // Language is unknown or not in the list of languages for this context.
return false; return false;
} }
// Show everything in one region when there is only one column if ( !this.isGroupingByRegionEnabled() ) {
if ( lcd.options.columns === 1 ) { regions = [ 'all' ];
regions = [ 'WW' ];
// Languages are expected to be repeated in this case, // Make sure we do not get duplicates
// and we only want to show them once if ( this.regionLanguages.all.indexOf( langCode ) > -1 ) {
if ( $.inArray( langCode, this.regionLanguages.WW ) > -1 ) {
return true; return true;
} }
} else { } else {
@@ -99,21 +103,45 @@
// Work around the bad interface, delay rendering until we have got // Work around the bad interface, delay rendering until we have got
// all the languages to speed up performance. // all the languages to speed up performance.
window.clearTimeout( this.renderTimeout ); clearTimeout( this.renderTimeout );
this.renderTimeout = window.setTimeout( function () { this.renderTimeout = setTimeout( function () {
lcd.renderRegions(); this.renderRegions();
}, 50 ); }.bind( this ), 50 );
return true; return true;
}, },
/**
* Whether we should render languages grouped to geographic regions.
* @return {boolean}
*/
isGroupingByRegionEnabled: function () {
if ( this.groupByRegionOverride !== null ) {
return this.groupByRegionOverride;
} else if ( this.options.groupByRegion !== 'auto' ) {
return this.options.groupByRegion;
} else {
return this.options.columns > 1;
}
},
/**
* Override the default region grouping setting.
* This is to allow LanguageFilter to disable grouping when displaying search results.
*
* @param {boolean|null} val True to force grouping, false to disable, null to undo override.
*/
setGroupByRegionOverride: function ( val ) {
this.groupByRegionOverride = val;
},
render: function () { render: function () {
var $section, $quicklist, var $section,
lcd = this, $quicklist = this.buildQuicklist(),
narrowMode = this.options.columns === 1,
regions = [], regions = [],
regionNames = { regionNames = {
// These are fallback text when i18n library not present // These are fallback text when i18n library not present
all: 'All languages', // Used if there is quicklist and no region grouping
WW: 'Worldwide', WW: 'Worldwide',
SP: 'Special', SP: 'Special',
AM: 'America', AM: 'America',
@@ -124,42 +152,30 @@
PA: 'Pacific' PA: 'Pacific'
}; };
$quicklist = this.buildQuicklist(); if ( $quicklist.length ) {
regions.push( $quicklist ); regions.push( $quicklist );
} else {
if ( narrowMode && $quicklist.length ) { // We use CSS to hide the header for 'all' when quicklist is NOT present
regions.push( $( '<h3>' ) this.$element.addClass( 'uls-lcd--no-quicklist' );
.attr( 'data-i18n', 'uls-region-all' )
.addClass( 'uls-lcd-region-title' )
.text( 'All languages' )
);
} }
$.each( $.uls.data.regiongroups, function ( regionCode ) { this.options.showRegions.forEach( function ( regionCode ) {
lcd.regionLanguages[ regionCode ] = []; this.regionLanguages[ regionCode ] = [];
// Don't show the region unless it was enabled
if ( $.inArray( regionCode, lcd.options.showRegions ) === -1 ) {
return;
}
$section = $( '<div>' ) $section = $( '<div>' )
.addClass( 'uls-lcd-region-section hide' ) .addClass( 'uls-lcd-region-section hide' )
.attr( 'data-region', regionCode ); .attr( 'data-region', regionCode );
// Show a region heading, unless we are using a narrow ULS $( '<h3>' )
if ( !narrowMode ) { .attr( 'data-i18n', 'uls-region-' + regionCode )
$section.append( $( '<h3>' ) .addClass( 'uls-lcd-region-title' )
.attr( 'data-i18n', 'uls-region-' + regionCode ) .text( regionNames[ regionCode ] )
.addClass( 'uls-lcd-region-title' ) .appendTo( $section );
.text( regionNames[ regionCode ] )
);
}
regions.push( $section ); regions.push( $section );
} ); }.bind( this ) );
lcd.$element.append( regions ); this.$element.append( regions );
this.i18n(); this.i18n();
}, },
@@ -239,8 +255,9 @@
nextScript = $.uls.data.getScriptGroupOfLanguage( languages[ i + 1 ] ); nextScript = $.uls.data.getScriptGroupOfLanguage( languages[ i + 1 ] );
lastItem = languagesCount - i === 1; lastItem = languagesCount - i === 1;
// Force column break if script changes and column has more than one row already // Force column break if script changes and column has more than one row already,
if ( i === 0 ) { // but only if grouping by region
if ( i === 0 || !this.isGroupingByRegionEnabled() ) {
currentScript = $.uls.data.getScriptGroupOfLanguage( languages[ i ] ); currentScript = $.uls.data.getScriptGroupOfLanguage( languages[ i ] );
} else if ( currentScript !== nextScript && items.length > 1 ) { } else if ( currentScript !== nextScript && items.length > 1 ) {
force = true; force = true;
@@ -356,6 +373,7 @@
* Called when a fresh search is started * Called when a fresh search is started
*/ */
empty: function () { empty: function () {
this.$element.addClass( 'uls-lcd--no-quicklist' );
this.$element.find( '.uls-lcd-quicklist' ).addClass( 'hide' ); this.$element.find( '.uls-lcd-quicklist' ).addClass( 'hide' );
}, },
@@ -416,14 +434,23 @@
}; };
$.fn.lcd.defaults = { $.fn.lcd.defaults = {
languages: null, // List of languages to show
languages: [],
// List of regions to show
showRegions: [ 'WW', 'AM', 'EU', 'ME', 'AF', 'AS', 'PA' ], showRegions: [ 'WW', 'AM', 'EU', 'ME', 'AF', 'AS', 'PA' ],
// Whether to group by region, defaults to true when columns > 1
groupByRegion: 'auto',
// How many items per column until new "row" starts
itemsPerColumn: 8, itemsPerColumn: 8,
// Other supported values are 1 and 2. // Number of columns, only 1, 2 and 4 are supported
// Other values will have rendering issues.
columns: 4, columns: 4,
languageDecorator: null, // Callback function for language item styling
languageDecorator: undefined,
// Likely candidates
quickList: [], quickList: [],
// Callback function for language selection
clickhandler: undefined,
// Callback function when no search results
noResultsTemplate: function () { noResultsTemplate: function () {
var $suggestionsContainer, $suggestions, var $suggestionsContainer, $suggestions,
$noResultsTemplate = $( noResultsTemplate ); $noResultsTemplate = $( noResultsTemplate );