view/resources/jquery/wikibase/jquery.wikibase.entitytermsview.js
/**
* @license GPL-2.0-or-later
* @author H. Snater < mediawiki@snater.com >
*/
( function ( wb ) {
'use strict';
var PARENT = $.ui.EditableTemplatedWidget,
datamodel = require( 'wikibase.datamodel' );
require( '../../wikibase/view/termFallbackResolver.js' );
require( '../../wikibase/view/languageFallbackIndicator.js' );
const TOGGLER_OPTION_KEY = 'wikibase-entitytermsview-showEntitytermslistview';
/**
* Encapsulates a entitytermsforlanguagelistview widget.
*
* @extends jQuery.ui.EditableTemplatedWidget
*
* @option {Datamodel.fingerprint} value
*
* @option {string[]} userLanguages
* A list of languages for which terms should be displayed initially.
*
* @option {string} [helpMessage]
* Default: 'Edit label, description and aliases per language.'
*
* @event change
* - {jQuery.Event}
* - {string} Language code the change was made in.
*
* @event afterstartediting
* - {jQuery.Event}
*
* @event afterstopediting
* - {jQuery.Event}
* - {boolean} Whether to drop the value.
*
* @event toggleerror
* - {jQuery.Event}
* - {Error|null}
*/
$.widget( 'wikibase.entitytermsview', PARENT, {
options: {
template: 'wikibase-entitytermsview',
templateParams: [
function () {
return $( mw.wbTemplate(
'wikibase-entitytermsview-heading-part',
'description',
'',
''
) ).add( mw.wbTemplate(
'wikibase-entitytermsview-heading-part',
'aliases',
'',
''
) );
}, // header content
'', // entitytermsforlanguagelistview
'', // additional entitytermsforlanguagelistview container class(es)
'' // toolbar placeholder
],
templateShortCuts: {
$headingDescription: '.wikibase-entitytermsview-heading-description',
$entitytermsforlanguagelistviewContainer:
'.wikibase-entitytermsview-entitytermsforlanguagelistview'
},
value: null,
userLanguages: [],
helpMessage: 'Edit label, description and aliases per language.'
},
/**
* @type {jQuery}
*/
$entitytermsforlanguagelistview: null,
/**
* @type {jQuery}
*/
$entitytermsforlanguagelistviewToggler: null,
/**
* @type {jQuery|null}
*/
$entitytermsforlanguagelistviewHelp: null,
/**
* @type {Object} Has the termbox been hidden or shown via the button and has this click been tracked?
*/
_tracked: {},
/**
* @see jQuery.ui.TemplatedWidget._create
*/
_create: function () {
if ( !( this.options.value instanceof datamodel.Fingerprint )
|| !Array.isArray( this.options.userLanguages )
) {
throw new Error( 'Required option(s) missing' );
}
PARENT.prototype._create.call( this );
var self = this;
this.element
.on(
this.widgetEventPrefix + 'change.' + this.widgetName + ' ' +
this.widgetEventPrefix + 'afterstopediting.' + this.widgetName,
function ( event, lang ) {
var firstLanguage = self.options.userLanguages[ 0 ];
var fingerprint = self.value(),
description = wb.view.termFallbackResolver.getTerm( fingerprint.getDescriptions(), firstLanguage ),
aliases = fingerprint.getAliasesFor( firstLanguage ),
isDescriptionEmpty = !description || description.getText() === '',
isAliasesEmpty = !aliases || aliases.isEmpty();
if ( isDescriptionEmpty ) {
self.$headingDescription
.toggleClass( 'wb-empty', true )
.text( mw.msg( 'wikibase-description-empty' ) );
} else {
var indicator = wb.view.languageFallbackIndicator.getHtml( description, firstLanguage );
self.$headingDescription
.toggleClass( 'wb-empty', false )
.html( mw.html.escape( description.getText() ) + indicator );
}
var $ul = self.element.find( '.wikibase-entitytermsview-heading-aliases' )
.toggleClass( 'wb-empty', isAliasesEmpty )
.children( 'ul' );
if ( isAliasesEmpty ) {
$ul.remove();
} else {
if ( $ul.length === 0 ) {
$ul = mw.wbTemplate( 'wikibase-entitytermsview-aliases', '' );
self.element.find( '.wikibase-entitytermsview-heading-aliases' ).append( $ul );
}
$ul.empty();
aliases.getTexts().forEach( function ( text ) {
$ul.append( mw.wbTemplate(
'wikibase-entitytermsview-aliases-alias',
text,
mw.msg( 'wikibase-aliases-separator' )
) );
} );
}
}
);
this.draw();
},
/**
* @see jQuery.ui.TemplatedWidget.destroy
*/
destroy: function () {
// When destroying a widget not initialized properly, entitytermsforlanguagelistview will
// not have been created.
if ( this.$entitytermsforlanguagelistview ) {
var entitytermsforlanguagelistview = this._getEntitytermsforlanguagelistview();
if ( entitytermsforlanguagelistview ) {
entitytermsforlanguagelistview.destroy();
}
this.$entitytermsforlanguagelistview.remove();
}
if ( this.$entitytermsforlanguagelistviewToggler ) {
this.$entitytermsforlanguagelistviewToggler.remove();
}
if ( this.$entitytermsforlanguagelistviewHelp ) {
this.$entitytermsforlanguagelistviewHelp.remove();
}
this.element.off( '.' + this.widgetName );
this.element.removeClass( 'wikibase-entitytermsview' );
PARENT.prototype.destroy.call( this );
},
/**
* @inheritdoc
*/
draw: function () {
var deferred = $.Deferred();
this.$entitytermsforlanguagelistview
= this.element.find( '.wikibase-entitytermsforlanguagelistview' );
if ( !this.$entitytermsforlanguagelistview.length ) {
this.$entitytermsforlanguagelistview = $( '<div>' )
.appendTo( this.$entitytermsforlanguagelistviewContainer );
}
if ( !this._getEntitytermsforlanguagelistview() ) {
this._createEntitytermsforlanguagelistview();
}
if ( !this.element
.find( '.wikibase-entitytermsview-entitytermsforlanguagelistview-toggler' )
.length
) {
// TODO: Remove as soon as drop-down edit buttons are implemented. The language list may
// then be shown (without directly switching to edit mode) using the drop down menu.
this._createEntitytermsforlanguagelistviewToggler();
}
return deferred.resolve().promise();
},
_trackToggling: function ( isVisible ) {
var currentActionTracking = isVisible ? 'hide' : 'show';
if ( this._tracked[ currentActionTracking ] ) {
return;
}
mw.track(
'event.WikibaseTermboxInteraction', {
actionType: currentActionTracking
}
);
this._tracked[ currentActionTracking ] = true;
},
/**
* Creates the dedicated toggler for showing/hiding the list of entity terms. This function is
* supposed to be removed as soon as drop-down edit buttons are implemented with the mechanism
* toggling the list's visibility while not starting edit mode will be part of the drop-down
* menu.
*
* @private
*/
_createEntitytermsforlanguagelistviewToggler: function () {
this._registerTogglerForLanguagelistviewDiv( TOGGLER_OPTION_KEY );
this.$entitytermsforlanguagelistviewContainer.before(
this.$entitytermsforlanguagelistviewToggler
);
// Inject link to page providing help about how to configure languages:
// TODO: Remove as soon as soon as some user-friendly mechanism is implemented to define
// user languages.
if ( mw.config.get( 'wbUserSpecifiedLanguages' )
&& mw.config.get( 'wbUserSpecifiedLanguages' ).length > 1
) {
// User applied custom configuration, no need to show link to help page.
return;
}
var toggler = this.$entitytermsforlanguagelistviewToggler.data( 'toggler' );
this.$entitytermsforlanguagelistviewHelp =
$( '<span>' )
.addClass( 'wikibase-entitytermsview-entitytermsforlanguagelistview-configure' )
.append(
$( '<a>' )
.attr(
'href',
mw.msg(
'wikibase-entitytermsview-entitytermsforlanguagelistview-configure-link'
)
)
.text( mw.msg(
'wikibase-entitytermsview-entitytermsforlanguagelistview-configure-link-label'
) )
)
.insertAfter( this.$entitytermsforlanguagelistviewToggler );
if ( !toggler.option( '$subject' ).is( ':visible' ) ) {
this.$entitytermsforlanguagelistviewHelp
.addClass(
'wikibase-entitytermsview-entitytermsforlanguagelistview-configure-hidden'
);
}
},
_languageListViewInitialStateIsVisible: function ( optionKey ) {
if ( mw.cookie.get( optionKey ) === null &&
mw.user.options.get( optionKey ) === null ) {
return true;
}
if ( mw.user.options.get( optionKey ) !== null ) {
return mw.user.options.get( optionKey ) === '1';
}
return mw.cookie.get( optionKey ) === 'true';
},
/**
* Registers the dedicated toggler itself on the element
*
* @see _createEntitytermsforlanguagelistviewToggler
* @private
*/
_registerTogglerForLanguagelistviewDiv: function ( optionKey ) {
var self = this,
api = new mw.Api();
this.$entitytermsforlanguagelistviewToggler = $( '<div>' )
.addClass( 'wikibase-entitytermsview-entitytermsforlanguagelistview-toggler' )
.text( mw.msg( 'wikibase-entitytermsview-entitytermsforlanguagelistview-toggler' ) )
.toggler( {
$subject: this.$entitytermsforlanguagelistviewContainer,
duration: 'fast',
visible: this._languageListViewInitialStateIsVisible( optionKey )
} )
.on( 'toggleranimation.' + this.widgetName, function ( event, params ) {
if ( !mw.user.isNamed() ) {
mw.cookie.set(
optionKey,
params.visible,
{ expires: 365 * 24 * 60 * 60, path: '/' }
);
} else {
api.saveOption(
optionKey,
params.visible ? '1' : '0'
)
.done( function () {
mw.user.options.set(
optionKey,
params.visible ? '1' : '0'
);
} );
}
// Show "help" link only if the toggler content is visible (decided by Product
// Management):
if ( self.$entitytermsforlanguagelistviewHelp ) {
self.$entitytermsforlanguagelistviewHelp.toggleClass(
'wikibase-entitytermsview-entitytermsforlanguagelistview-configure-hidden',
!params.visible
);
}
self._trackToggling( params.visible );
} );
},
/**
* @return {jQuery.wikibase.entitytermsforlanguagelistview}
* @private
*/
_getEntitytermsforlanguagelistview: function () {
return this.$entitytermsforlanguagelistview.data( 'entitytermsforlanguagelistview' );
},
/**
* Creates and initializes the entitytermsforlanguagelistview widget.
*/
_createEntitytermsforlanguagelistview: function () {
var self = this,
prefix = $.wikibase.entitytermsforlanguagelistview.prototype.widgetEventPrefix;
this.$entitytermsforlanguagelistview
.on( prefix + 'change.' + this.widgetName, function ( event, lang ) {
event.stopPropagation();
// Event handlers for this are in the entitytermsview toolbar controller (for enabling
// the save button), in entityViewInit (for updating the title) and in this file (for
// updating description and aliases).
self._trigger( 'change', null, [ lang ] );
} )
.on(
[
prefix + 'create.' + this.widgetName,
prefix + 'afterstartediting.' + this.widgetName,
prefix + 'afterstopediting.' + this.widgetName,
prefix + 'disable.' + this.widgetName
].join( ' ' ),
function ( event ) {
event.stopPropagation();
}
)
.entitytermsforlanguagelistview( {
value: this.options.value,
userLanguages: this.options.userLanguages
} );
if ( !this._languageListViewInitialStateIsVisible( TOGGLER_OPTION_KEY ) ) {
this.$entitytermsforlanguagelistviewContainer.hide();
}
},
_startEditing: function () {
this._getEntitytermsforlanguagelistview().startEditing();
return this.draw();
},
/**
* @param {boolean} dropValue
*/
_stopEditing: function ( dropValue ) {
this.draw();
var self = this;
return this._getEntitytermsforlanguagelistview().stopEditing( dropValue ).done( function () {
self.notification();
} );
},
/**
* @inheritdoc
*/
focus: function () {
this._getEntitytermsforlanguagelistview().focus();
},
/**
* @inheritdoc
*/
removeError: function () {
this.element.removeClass( 'wb-error' );
this._getEntitytermsforlanguagelistview().removeError();
},
/**
* @inheritdoc
*
* @param {Datamodel.fingerprint} [value]
* @return {Fingerprint|*}
*/
value: function ( value ) {
if ( value !== undefined ) {
return this.option( 'value', value );
}
return this._getEntitytermsforlanguagelistview().value();
},
/**
* @inheritdoc
*/
_setOption: function ( key, value ) {
if ( key === 'value' && !( value instanceof datamodel.Fingerprint ) ) {
throw new Error( 'value must be a Fingerprint' );
}
var response = PARENT.prototype._setOption.apply( this, arguments );
if ( key === 'disabled' || key === 'value' ) {
this._getEntitytermsforlanguagelistview().option( key, value );
}
return response;
},
/**
* @inheritdoc
*/
notification: function ( $content, additionalCssClasses ) {
if ( !this._$notification ) {
var $closeable = $( '<div>' ).closeable();
this._$notification = $( '<tr>' ).append( $( '<td>' ).append( $closeable ) );
this._$notification.data( 'closeable', $closeable.data( 'closeable' ) );
this._$notification
.appendTo( this._getEntitytermsforlanguagelistview().$header );
var $headerTr = this._getEntitytermsforlanguagelistview().$header.children( 'tr' ).first();
this._$notification.children( 'td' ).attr( 'colspan', $headerTr.children().length );
}
this._$notification.data( 'closeable' ).setContent( $content, additionalCssClasses );
return this._$notification;
},
/**
* @inheritdoc
*/
setError: function ( error ) {
if ( error && error.context ) {
var context = error.context;
var viewType = 'wikibase-' + context.type + 'view';
this.element.find( '.wikibase-entitytermsforlanguageview-' + context.value.getLanguageCode() )
.find( '.' + viewType ).data( viewType ).setError( error );
}
return PARENT.prototype.setError.apply( this, arguments );
}
} );
}( wikibase ) ); // TODO should this really use wikibase?