Make ULS language selector appear faster after clicking trigger
* Removed unused class end. * Class uls-lcd-region-section was used in two different elements, retained it on the outer one. * Build a list of languages per region, once done render the regions at once. * Use direct dom manipulation in hot spots. * Use shorter selectors like children instead of find and dropping the tag name. * Fixed mismatch when there were no results and when the message was shown. * Less flickering by not hiding or showing regions unnecessary * Cache the common languages "quicklist" * Added itemsPerColumn as an option Reduces time used to show language selector after clicking the trigger on my machine from about 1.8s to about 380 ms (JS execution times, actual times are about the same but little bigger. The actual rendering is now less than 100ms, the rest is other setup overheard and $.fn.scrollIntoView which can take many hundreds of milliseconds.
This commit is contained in:
@@ -26,12 +26,12 @@
|
||||
|
||||
/*jshint multistr:true */
|
||||
noResultsTemplate = '\
|
||||
<div class="twelve columns uls-no-results-view">\
|
||||
<h2 data-i18n="uls-no-results-found" class="eleven columns end offset-by-one uls-no-results-found-title">\
|
||||
<div class="twelve columns uls-no-results-view hide">\
|
||||
<h2 data-i18n="uls-no-results-found" class="eleven columns offset-by-one uls-no-results-found-title">\
|
||||
No results found\
|
||||
</h2>\
|
||||
<div id="uls-no-found-more" class="uls-no-found-more">\
|
||||
<div class="ten columns end offset-by-one">\
|
||||
<div class="ten columns offset-by-one">\
|
||||
<p>\
|
||||
<span data-i18n="uls-search-help">You can search by language name, \
|
||||
script name, ISO code of language or \
|
||||
@@ -53,9 +53,13 @@
|
||||
this.$element = $( element );
|
||||
this.options = $.extend( {}, $.fn.lcd.defaults, options );
|
||||
this.$element.addClass( 'lcd' );
|
||||
this.regionDivs = {};
|
||||
this.regionLanguages = {};
|
||||
this.renderTimeout = null;
|
||||
this.cachedQuicklist = null;
|
||||
|
||||
this.$element.append( $( noResultsTemplate ) );
|
||||
this.$noResults = this.$element.find( 'div.uls-no-results-view' );
|
||||
this.$noResults = this.$element.children( '.uls-no-results-view' );
|
||||
|
||||
this.render();
|
||||
this.listen();
|
||||
};
|
||||
@@ -63,109 +67,50 @@
|
||||
LanguageCategoryDisplay.prototype = {
|
||||
constructor: LanguageCategoryDisplay,
|
||||
|
||||
/**
|
||||
* Adds language to the language list.
|
||||
* @param {string} langCode
|
||||
* @param {string} [regionCode]
|
||||
* @return {bool} Whether the language was added.
|
||||
*/
|
||||
append: function ( langCode, regionCode ) {
|
||||
var lcd = this, i, regions;
|
||||
|
||||
if ( !this.options.languages[langCode] ) {
|
||||
// Language is unknown or not in the list of languages for this context.
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
this.addToRegion( langCode, regionCode );
|
||||
this.$noResults.hide();
|
||||
},
|
||||
|
||||
/**
|
||||
* Add the language to a region.
|
||||
* If the region parameter is given, add to that region alone
|
||||
* Otherwise to all regions that this language belongs.
|
||||
* @param langCode
|
||||
* @param region Optional region
|
||||
*/
|
||||
addToRegion: function ( langCode, region ) {
|
||||
var i, regionCode, $li, $column, lastLanguage, lastScriptGroup, currentScriptGroup,
|
||||
language = this.options.languages[langCode],
|
||||
langName = $.uls.data.getAutonym( langCode ) || language || langCode,
|
||||
regions = [];
|
||||
|
||||
if ( region ) {
|
||||
regions.push( region );
|
||||
if ( regionCode ) {
|
||||
regions = [regionCode];
|
||||
} else {
|
||||
regions = $.uls.data.getRegions( langCode );
|
||||
}
|
||||
|
||||
// World wide languages need not be repeated in all regions.
|
||||
// Worldwides only displayed once
|
||||
if ( $.inArray( 'WW', regions ) > -1 ) {
|
||||
regions = [ 'WW' ];
|
||||
regions = ['WW'];
|
||||
}
|
||||
|
||||
for ( i = 0; i < regions.length; i++ ) {
|
||||
regionCode = regions[i];
|
||||
|
||||
$li = $( '<li>' )
|
||||
.data( 'code', langCode )
|
||||
.attr( {
|
||||
lang: langCode,
|
||||
dir: $.uls.data.getDir( langCode )
|
||||
} )
|
||||
.append(
|
||||
$( '<a>' ).attr( 'title', language ).text( langName )
|
||||
);
|
||||
|
||||
// Append the element to the column in the list
|
||||
$column = this.getColumn( regionCode );
|
||||
lastLanguage = $column.find( 'li:last' ).data( 'code' );
|
||||
|
||||
if ( lastLanguage ) {
|
||||
lastScriptGroup = $.uls.data.getScriptGroupOfLanguage( lastLanguage );
|
||||
currentScriptGroup = $.uls.data.getScriptGroupOfLanguage( langCode );
|
||||
|
||||
if ( lastScriptGroup !== currentScriptGroup ) {
|
||||
if ( $column.find( 'li' ).length > 2 ) {
|
||||
// If column already has 2 or more languages, add a new column
|
||||
$column = this.getColumn( regionCode, true );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$column.append( $li );
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a column to add language.
|
||||
* @param regionCode string The region code
|
||||
* @param forceNew bool whether a new column must be created or not
|
||||
*/
|
||||
getColumn: function ( regionCode, forceNew ) {
|
||||
var $divRegionCode, $rowDiv, $ul;
|
||||
|
||||
forceNew = forceNew || false;
|
||||
$divRegionCode = this.regionDivs[regionCode];
|
||||
$rowDiv = $divRegionCode.find( 'div.row:last' );
|
||||
$ul = $rowDiv.find( 'ul:last' );
|
||||
|
||||
// Each column can have maximum 8 languages.
|
||||
if ( $ul.length === 0 || $ul.find( 'li' ).length >= 8 || forceNew ) {
|
||||
$ul = $( '<ul>' ).addClass( 'three columns end autonym' );
|
||||
|
||||
if ( $rowDiv.length === 0 || $rowDiv.find( 'ul' ).length >= 4 ) {
|
||||
$rowDiv = $( '<div>' ).addClass( 'row uls-language-block' );
|
||||
$divRegionCode.append( $rowDiv );
|
||||
}
|
||||
|
||||
$rowDiv.append( $ul );
|
||||
this.regionLanguages[regions[i]].push( langCode );
|
||||
}
|
||||
|
||||
// Don't show the region unless it was enabled
|
||||
if ( $.inArray( regionCode, this.options.showRegions ) > -1 ) {
|
||||
$divRegionCode.show();
|
||||
}
|
||||
// Work around the bad interface, delay rendering until we have got
|
||||
// all the languages to speed up performance.
|
||||
window.clearTimeout( this.renderTimeout );
|
||||
this.renderTimeout = window.setTimeout( function () {
|
||||
lcd.renderRegions();
|
||||
}, 50 );
|
||||
|
||||
return $ul;
|
||||
return true;
|
||||
},
|
||||
|
||||
render: function () {
|
||||
var $section, $sectionTitle,
|
||||
var $section,
|
||||
lcd = this,
|
||||
regions = {
|
||||
regions = [],
|
||||
regionNames = {
|
||||
// These are fallback text when i18n library not present
|
||||
WW: 'Worldwide',
|
||||
SP: 'Special',
|
||||
@@ -177,38 +122,160 @@
|
||||
PA: 'Pacific'
|
||||
};
|
||||
|
||||
regions.push( this.buildQuicklist() );
|
||||
|
||||
$.each( $.uls.data.regiongroups, function ( regionCode ) {
|
||||
$section = $( '<div>' ).addClass( 'eleven columns offset-by-one uls-lcd-region-section' ).attr( 'id', regionCode );
|
||||
lcd.regionLanguages[regionCode] = [];
|
||||
// Don't show the region unless it was enabled
|
||||
if ( $.inArray( regionCode, lcd.options.showRegions ) === -1 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sectionTitle = $( '<h3>' )
|
||||
.attr( 'data-i18n', 'uls-region-' + regionCode )
|
||||
.addClass( 'eleven columns uls-lcd-region-section uls-lcd-region-title' )
|
||||
.text( regions[regionCode] );
|
||||
$section = $( '<div>' )
|
||||
.addClass( 'eleven columns offset-by-one uls-lcd-region-section hide' )
|
||||
.attr( 'id', regionCode )
|
||||
.append(
|
||||
$( '<h3>' )
|
||||
.attr( 'data-i18n', 'uls-region-' + regionCode )
|
||||
.addClass( 'eleven columns uls-lcd-region-title' )
|
||||
.text( regionNames[regionCode] )
|
||||
);
|
||||
|
||||
$section.append( $sectionTitle );
|
||||
lcd.$element.append( $section );
|
||||
$section.hide();
|
||||
lcd.regionDivs[regionCode] = $section;
|
||||
regions.push( $section );
|
||||
} );
|
||||
|
||||
this.$noResults.hide();
|
||||
lcd.$element.append( regions );
|
||||
|
||||
this.i18n();
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders a region and displays it if it has content.
|
||||
*/
|
||||
renderRegions: function () {
|
||||
var lcd = this, languages,
|
||||
items = lcd.options.itemsPerColumn,
|
||||
columns = 4;
|
||||
|
||||
this.$noResults.addClass( 'hide' );
|
||||
this.$element.find( '.uls-lcd-region-section' ).each( function () {
|
||||
var $region = $( this ),
|
||||
regionCode = $region.attr( 'id' );
|
||||
|
||||
if ( $region.is( '#uls-lcd-quicklist' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$region.children( '.uls-language-block' ).remove();
|
||||
|
||||
languages = lcd.regionLanguages[regionCode];
|
||||
if ( !languages || languages.length === 0 ) {
|
||||
$region.addClass( 'hide' );
|
||||
return;
|
||||
}
|
||||
|
||||
lcd.renderRegion( $region, languages, items, columns );
|
||||
$region.removeClass( 'hide' );
|
||||
|
||||
lcd.regionLanguages[regionCode] = [];
|
||||
} );
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds given languages sorted into rows and columns into given element.
|
||||
* @param {jQuery} $region Element to add language list.
|
||||
* @param {array} languages List of language codes.
|
||||
* @param {number} itemsPerColumn How many languages fit in a column.
|
||||
* @param {number} columnsPerRow How many columns fit in a row.
|
||||
*/
|
||||
renderRegion: function( $region, languages, itemsPerColumn, columnsPerRow ) {
|
||||
var i, lastItem, currentScript, nextScript, force,
|
||||
len = languages.length,
|
||||
items = [],
|
||||
columns = [],
|
||||
rows = [];
|
||||
|
||||
for ( i = 0; i < len; i++ ) {
|
||||
force = false;
|
||||
nextScript = $.uls.data.getScriptGroupOfLanguage( languages[i+1] );
|
||||
|
||||
lastItem = len - i === 1;
|
||||
// Force column break if script changes and column has more than one row already
|
||||
if ( i === 0 ) {
|
||||
currentScript = $.uls.data.getScriptGroupOfLanguage( languages[i] );
|
||||
} else if ( currentScript !== nextScript && items.length > 1 ) {
|
||||
force = true;
|
||||
}
|
||||
currentScript = nextScript;
|
||||
|
||||
items.push( this.renderItem( languages[i] ) );
|
||||
|
||||
if ( items.length >= itemsPerColumn || lastItem || force ) {
|
||||
columns.push( $( '<ul>' ).addClass( 'three columns' ).append( items ) );
|
||||
items = [];
|
||||
if ( columns.length >= columnsPerRow || lastItem ) {
|
||||
rows.push( $( '<div>' ).addClass( 'row uls-language-block' ).append( columns ) );
|
||||
columns = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$region.append( rows );
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates dom node representing one item in language list.
|
||||
* @param {string} code Language code
|
||||
* @return {Element}
|
||||
*/
|
||||
renderItem: function( code ) {
|
||||
var a, name, autonym, li;
|
||||
|
||||
name = this.options.languages[code];
|
||||
autonym = $.uls.data.getAutonym( code ) || name || code;
|
||||
|
||||
// Not using jQuery as this is performance hotspot
|
||||
li = document.createElement( 'li' );
|
||||
li.title = name;
|
||||
li.lang = code;
|
||||
li.dir = $.uls.data.getDir( code );
|
||||
li.setAttribute( 'data-code', code );
|
||||
|
||||
a = document.createElement( 'a' );
|
||||
a.appendChild( document.createTextNode( autonym ) );
|
||||
a.className = 'autonym';
|
||||
|
||||
li.appendChild( a );
|
||||
|
||||
return li;
|
||||
},
|
||||
|
||||
i18n: function ( ) {
|
||||
this.$element.find( '[data-i18n]' ).i18n();
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds quicklist as a region.
|
||||
*/
|
||||
quicklist: function () {
|
||||
var quickList, $quickListSection, $quickListSectionTitle, i,
|
||||
$column, langCode, language, langName, $li;
|
||||
this.$element.find( '#uls-lcd-quicklist' ).removeClass( 'hide' );
|
||||
},
|
||||
|
||||
buildQuicklist: function () {
|
||||
var quickList, $quickListSection, $quickListSectionTitle;
|
||||
|
||||
if ( this.cachedQuicklist !== null ) {
|
||||
return this.cachedQuicklist;
|
||||
}
|
||||
|
||||
if ( $.isFunction( this.options.quickList ) ) {
|
||||
this.options.quickList = this.options.quickList();
|
||||
}
|
||||
|
||||
if ( !this.options.quickList ) {
|
||||
return $( [] );
|
||||
this.cachedQuicklist = $( [] );
|
||||
return this.cachedQuicklist;
|
||||
}
|
||||
|
||||
// Pick only the first elements, because we don't have room for more
|
||||
@@ -216,36 +283,22 @@
|
||||
quickList = quickList.slice( 0, 16 );
|
||||
quickList.sort( $.uls.data.sortByAutonym );
|
||||
|
||||
$quickListSection = $( '<div>' ).addClass( 'eleven columns offset-by-one uls-lcd-region-section' ).attr( 'id', 'uls-lcd-quicklist' );
|
||||
$quickListSection = $( '<div>' )
|
||||
.addClass( 'eleven columns offset-by-one uls-lcd-region-section' )
|
||||
.attr( 'id', 'uls-lcd-quicklist' );
|
||||
|
||||
$quickListSectionTitle = $( '<h3>' )
|
||||
.attr( 'data-i18n', 'uls-common-languages' )
|
||||
.addClass( 'eleven columns uls-lcd-region-section uls-lcd-region-title' )
|
||||
.addClass( 'eleven columns uls-lcd-region-title' )
|
||||
.text( 'Common languages' ); // This is placeholder text if jquery.i18n not present
|
||||
$quickListSection.append( $quickListSectionTitle );
|
||||
this.$element.prepend( $quickListSection );
|
||||
this.regionDivs.quick = $quickListSection;
|
||||
|
||||
for ( i = 0; i < quickList.length; i++) {
|
||||
$column = this.getColumn( 'quick', i % 4 === 0 );
|
||||
langCode = quickList[i];
|
||||
language = this.options.languages[langCode];
|
||||
langName = $.uls.data.getAutonym( langCode ) || language || langCode;
|
||||
$li = $( '<li>' )
|
||||
.data( 'code', langCode )
|
||||
.attr( {
|
||||
lang: langCode,
|
||||
dir: $.uls.data.getDir( langCode )
|
||||
} )
|
||||
.append(
|
||||
$( '<a>' ).attr( 'title', language ).text( langName )
|
||||
);
|
||||
$column.append( $li );
|
||||
}
|
||||
this.renderRegion( $quickListSection, quickList, 4, 4 );
|
||||
|
||||
$quickListSection.show();
|
||||
$quickListSectionTitle.i18n();
|
||||
|
||||
return $quickListSection;
|
||||
this.cachedQuicklist = $quickListSection;
|
||||
return this.cachedQuicklist;
|
||||
},
|
||||
|
||||
show: function () {
|
||||
@@ -255,8 +308,7 @@
|
||||
},
|
||||
|
||||
empty: function () {
|
||||
this.$element.find( 'div.uls-language-block' ).remove();
|
||||
this.$element.find( 'div.uls-lcd-region-section' ).hide();
|
||||
this.$element.find( '#uls-lcd-quicklist' ).addClass( 'hide' );
|
||||
},
|
||||
|
||||
focus: function () {
|
||||
@@ -264,8 +316,12 @@
|
||||
},
|
||||
|
||||
noResults: function () {
|
||||
this.$noResults.show();
|
||||
var $suggestions = this.quicklist();
|
||||
this.$noResults.removeClass( 'hide' );
|
||||
if ( this.$noResults.find( '.uls-lcd-region-title' ).length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
var $suggestions = this.buildQuicklist().clone();
|
||||
$suggestions.find( 'h3' )
|
||||
.data( 'i18n', 'uls-no-results-suggestion-title' )
|
||||
.text( 'You may be interested in:' )
|
||||
@@ -277,7 +333,7 @@
|
||||
var lcd = this;
|
||||
|
||||
if ( this.options.clickhandler ) {
|
||||
this.$element.on( 'click', 'div.row li', function () {
|
||||
this.$element.on( 'click', '.row li', function () {
|
||||
lcd.options.clickhandler.call( this, $( this ).data( 'code' ) );
|
||||
} );
|
||||
}
|
||||
@@ -291,7 +347,7 @@
|
||||
|
||||
// The region section need to be in sync with the map filter.
|
||||
inviewRegion = 'WW';
|
||||
lcd.$element.find( 'div.uls-lcd-region-section' ).each( function () {
|
||||
lcd.$element.find( '.uls-lcd-region-section' ).each( function () {
|
||||
var $lcdRegionSection = $( this ),
|
||||
top = $lcdRegionSection.position().top,
|
||||
height = $lcdRegionSection.height(),
|
||||
@@ -299,8 +355,6 @@
|
||||
|
||||
if ( top - padding <= scrollTop && height > scrollBottom ) {
|
||||
inviewRegion = $lcdRegionSection.attr( 'id' );
|
||||
|
||||
return true;
|
||||
}
|
||||
} );
|
||||
|
||||
@@ -332,7 +386,8 @@
|
||||
|
||||
$.fn.lcd.defaults = {
|
||||
languages: null,
|
||||
showRegions: ['WW', 'AM', 'EU', 'ME', 'AF', 'AS', 'PA']
|
||||
showRegions: ['WW', 'AM', 'EU', 'ME', 'AF', 'AS', 'PA'],
|
||||
itemsPerColumn: 8
|
||||
};
|
||||
|
||||
$.fn.lcd.Constructor = LanguageCategoryDisplay;
|
||||
|
||||
Reference in New Issue
Block a user