MediaWiki:Gadget-tagautocomplete.js
MediaWiki interface page
More actions
Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (β-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (β-Shift-R on a Mac)
- Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/* =============================================================================
Gadget-tagautocomplete.js
Tokenized tag input with autocomplete on Submit Project / Tutorial forms.
Pulls existing tags from the Projects and Tutorials Cargo tables.
============================================================================= */
( function () {
// Only run on Special:FormEdit/* pages
if ( mw.config.get( 'wgCanonicalSpecialPageName' ) !== 'FormEdit' ) { return; }
$( function () {
// ββ Find the tags input by name attribute ββββββββββββββββββββββββββββ
// Page Forms names inputs as TemplateName[fieldname] β target [tags]
// specifically. We use .filter() because jQuery CSS attribute selectors
// require the attribute to be explicitly present in the HTML; Page Forms
// does not write type="text" on its inputs, so [type="text"] never matches.
var $raw = $( 'input' ).filter( function () {
var name = $( this ).attr( 'name' ) || '';
return name === 'ProjectMeta[tags]' || name === 'TutorialMeta[tags]';
} );
if ( !$raw.length ) { return; }
// ββ Load all existing tags from Cargo ββββββββββββββββββββββββββββββββ
var allTags = [];
function fetchTags( table ) {
return $.getJSON( mw.util.wikiScript( 'api' ), {
action : 'cargoquery',
tables : table,
fields : 'tags',
limit : 500,
format : 'json'
} ).then( function ( data ) {
( data.cargoquery || [] ).forEach( function ( r ) {
( r.title.tags || '' ).split( ',' ).forEach( function ( t ) {
t = t.trim().toLowerCase();
if ( t && allTags.indexOf( t ) === -1 ) {
allTags.push( t );
}
} );
} );
} );
}
$.when(
fetchTags( 'Projects' ),
fetchTags( 'Tutorials' )
).then( function () {
allTags.sort();
$raw.each( function () {
initTokenizer( $( this ) );
} );
} );
// ββ Tokenizer ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
function initTokenizer( $input ) {
$input.hide();
var $wrap = $( '<div>' ).addClass( 'tag-tokenizer' ).insertAfter( $input );
var $box = $( '<div>' ).addClass( 'tag-tokenizer-box' ).appendTo( $wrap );
var $entry = $( '<input>' ).attr( {
type : 'text',
placeholder : 'Type a tag and press Enter or comma\u2026',
autocomplete: 'off'
} ).addClass( 'tag-tokenizer-entry' ).appendTo( $box );
var $dropdown = $( '<ul>' ).addClass( 'tag-tokenizer-dropdown' ).hide().appendTo( $wrap );
// Seed from existing hidden input value (edit mode)
var existing = ( $input.val() || '' ).split( ',' )
.map( function ( t ) { return t.trim(); } )
.filter( Boolean );
existing.forEach( addToken );
syncHidden();
function addToken( tag ) {
tag = tag.trim().toLowerCase().replace( /\s+/g, '-' );
if ( !tag ) { return; }
if ( $box.find( '.tag-token' ).filter( function () {
return $( this ).data( 'tag' ) === tag;
} ).length ) { return; }
var $token = $( '<span>' ).addClass( 'tag-token' ).text( tag ).attr( 'data-tag', tag );
var $x = $( '<button>' )
.attr( 'type', 'button' )
.addClass( 'tag-token-remove' )
.text( '\u00d7' )
.on( 'click', function () {
$token.remove();
syncHidden();
} );
$token.append( $x ).insertBefore( $entry );
syncHidden();
}
function syncHidden() {
var tags = [];
$box.find( '.tag-token' ).each( function () {
tags.push( $( this ).data( 'tag' ) );
} );
$input.val( tags.join( ', ' ) );
}
function showDropdown( query ) {
var active = [];
$box.find( '.tag-token' ).each( function () {
active.push( $( this ).data( 'tag' ) );
} );
var matches = allTags.filter( function ( t ) {
return t.indexOf( query ) === 0 && active.indexOf( t ) === -1;
} ).slice( 0, 8 );
$dropdown.empty();
if ( !matches.length ) { $dropdown.hide(); return; }
matches.forEach( function ( tag ) {
$( '<li>' ).text( tag ).on( 'mousedown', function ( e ) {
e.preventDefault();
addToken( tag );
$entry.val( '' );
$dropdown.hide();
} ).appendTo( $dropdown );
} );
$dropdown.show();
}
$entry.on( 'input', function () {
var val = $entry.val();
if ( /,$/.test( val ) ) {
addToken( val.replace( /,$/, '' ) );
$entry.val( '' );
$dropdown.hide();
return;
}
var q = val.trim().toLowerCase();
if ( q.length >= 1 ) { showDropdown( q ); } else { $dropdown.hide(); }
} );
$entry.on( 'keydown', function ( e ) {
if ( e.key === 'Enter' ) {
e.preventDefault();
var first = $dropdown.find( 'li:first' ).text();
addToken( first || $entry.val() );
$entry.val( '' );
$dropdown.hide();
}
if ( e.key === 'Backspace' && $entry.val() === '' ) {
$box.find( '.tag-token' ).last().remove();
syncHidden();
}
} );
$entry.on( 'blur', function () {
setTimeout( function () { $dropdown.hide(); }, 150 );
var val = $entry.val().trim();
if ( val ) { addToken( val ); $entry.val( '' ); }
} );
$box.on( 'click', function () { $entry.trigger( 'focus' ); } );
}
} );
}() );