MediaWiki
Difference between revisions of "Common.js"
Line 25: | Line 25: | ||
} ); | } ); | ||
} | } | ||
+ | } ); | ||
+ | |||
+ | |||
+ | |||
+ | // Fetch and display DataViz results | ||
+ | const sparqlEndpoint = "https://v2.lingualibre.fr/bigdata/namespace/wdq/sparql"; | ||
+ | |||
+ | var DataViz = function( node ) { | ||
+ | this.node = node; | ||
+ | this.baseQuery = node.children( '.dataviz-query' ).text().replace(/\u00A0/g, ' '); | ||
+ | this.resultNode = node.children( '.dataviz-result' ); | ||
+ | this.inputsNode = node.children( '.dataviz-inputs' ); | ||
+ | |||
+ | // Add inputs | ||
+ | |||
+ | this.addInputs(); | ||
+ | this.refresh(); | ||
+ | }; | ||
+ | |||
+ | DataViz.prototype.addInputs = function() { | ||
+ | var regex = /#extra:({.+?})? (.+)/g, | ||
+ | process = new OO.ui.Process(), | ||
+ | query = this.baseQuery, | ||
+ | m; | ||
+ | |||
+ | this.inputs = []; | ||
+ | this.labels = {}; | ||
+ | this.filters = {}; | ||
+ | while ( ( m = regex.exec( query ) ) !== null ) { | ||
+ | console.log( m ); | ||
+ | // This is necessary to avoid infinite loops with zero-width matches | ||
+ | if (m.index === regex.lastIndex) { | ||
+ | regex.lastIndex++; | ||
+ | } | ||
+ | |||
+ | var input = JSON.parse( m[ 1 ] ); | ||
+ | input[ 'query' ] = m[ 2 ]; | ||
+ | |||
+ | if ( input[ 'label' ] !== undefined ) { | ||
+ | this.labels[ input[ 'label' ] ] = input[ 'label' ]; | ||
+ | } | ||
+ | |||
+ | if ( input[ 'filter' ] !== undefined ) { | ||
+ | this.filters[ input[ 'filter' ] ] = []; | ||
+ | } | ||
+ | |||
+ | var i = this.inputs.push( input ); | ||
+ | this.baseQuery = this.baseQuery.replace( m[ 0 ], '#$' + i ); | ||
+ | } | ||
+ | |||
+ | // Fetch labels | ||
+ | if ( Object.keys( this.labels ).length ) { | ||
+ | process.next( this.getLabels.bind( this ) ); | ||
+ | } | ||
+ | |||
+ | // Preload values for limited inputs | ||
+ | for ( var item in this.filters ) { | ||
+ | process.next( this.loadFilterValues.bind( this, item ) ); | ||
+ | } | ||
+ | |||
+ | // Once all data has been collected, we can create the fields | ||
+ | for ( var i=0; i < this.inputs.length; i++ ) { | ||
+ | process.next( this.createField.bind( this, this.inputs[ i ] ) ); | ||
+ | } | ||
+ | |||
+ | var button = new OO.ui.ButtonWidget( { | ||
+ | label: 'Filtrer!', | ||
+ | flags: [ 'primary', 'progressive' ] | ||
+ | } ); | ||
+ | |||
+ | button.on( 'click', this.refresh.bind( this ) ); | ||
+ | this.inputsNode.append( button.$element ); | ||
+ | |||
+ | process.execute(); | ||
+ | }; | ||
+ | |||
+ | DataViz.prototype.loadFilterValues = function( item ) { | ||
+ | var query = 'select ?id ?idLabel where {?id prop:P2 entity:' + item + '. SERVICE wikibase:label{bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en".}}'; | ||
+ | |||
+ | return this.postQuery( query ).then( function( data ) { | ||
+ | var rows = data.results.bindings; | ||
+ | for ( var i=0; i < rows.length; i++ ) { | ||
+ | dataviz.filters[ item ].push( new OO.ui.MenuOptionWidget( { | ||
+ | data: rows[ i ][ 'id' ].value.split( '/' ).pop(), | ||
+ | label: rows[ i ][ 'idLabel' ].value | ||
+ | } ) ); | ||
+ | } | ||
+ | } ); | ||
+ | }; | ||
+ | |||
+ | DataViz.prototype.getLabels = function( query ) { | ||
+ | var dataviz = this, | ||
+ | lang = mw.config.get( 'wgUserLanguage' ), | ||
+ | api = new mw.Api(); | ||
+ | return api.get( { | ||
+ | action: 'wbgetentities', | ||
+ | format: 'json', | ||
+ | ids: Object.keys( this.labels ).join( '|' ), | ||
+ | props: 'labels', | ||
+ | languages: lang, | ||
+ | languagefallback: 1 | ||
+ | } ).then( function( data ) { | ||
+ | for ( entity in data.entities ) { | ||
+ | dataviz.labels[ entity ] = data.entities[ entity ].labels[ lang ].value; | ||
+ | } | ||
+ | } ); | ||
+ | }; | ||
+ | |||
+ | DataViz.prototype.createField = function( infos ) { | ||
+ | var field; | ||
+ | |||
+ | switch( infos.type ) { | ||
+ | case 'wikibase-item': | ||
+ | field = new OO.ui.CapsuleMultiselectWidget( { | ||
+ | menu: { items: this.filters[ infos[ 'filter' ] ] } | ||
+ | } ); | ||
+ | break; | ||
+ | default: | ||
+ | field = new OO.ui.TextInputWidget(); | ||
+ | } | ||
+ | this.inputsNode.append( new OO.ui.FieldLayout( field, { | ||
+ | align: 'left', | ||
+ | label: this.labels[ infos[ 'label' ] ], | ||
+ | } ).$element ); | ||
+ | |||
+ | infos[ 'field' ] = field; | ||
+ | }; | ||
+ | |||
+ | DataViz.prototype.preProcessQuery = function() { | ||
+ | var query = this.baseQuery; | ||
+ | |||
+ | for ( var i=0; i < this.inputs.length; i++ ) { | ||
+ | if ( this.inputs[ i ].field !== undefined ) { | ||
+ | switch( this.inputs[ i ].type ) { | ||
+ | case 'wikibase-item': | ||
+ | var values = this.inputs[ i ].field.getItemsData(); | ||
+ | for ( var j=0; j < values.length; j++ ) { | ||
+ | values[ j ] = '{' + this.inputs[ i ].query.replace( '[EXTRA]', values[ j ] ) + '}'; | ||
+ | } | ||
+ | query = query.replace( '#$' + (i+1), values.join( ' UNION ' ) ); | ||
+ | break; | ||
+ | default: | ||
+ | var value = this.inputs[ i ].field.getValue(); | ||
+ | query = query.replace( '#$' + (i+1), this.inputs[ i ].query.replace( '[EXTRA]', value ) ); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | return query; | ||
+ | }; | ||
+ | |||
+ | DataViz.prototype.postQuery = function( query ) { | ||
+ | query = query.replace(/\u00A0/g, ' ').replace( '[AUTO_LANGUAGE]', mw.config.get( 'wgUserLanguage' ) ); | ||
+ | return $.post( sparqlEndpoint, { format: 'json', query: query } ); | ||
+ | }; | ||
+ | |||
+ | DataViz.prototype.refresh = function() { | ||
+ | var dataviz = this, | ||
+ | query = this.preProcessQuery(); | ||
+ | |||
+ | this.postQuery( query ).then( function( data ) { | ||
+ | var table = dataviz.dataToTable( data.head.vars, data.results.bindings ); | ||
+ | dataviz.resultNode.html( table ); | ||
+ | } ).fail( function( data ) { | ||
+ | console.log( data.responseText ); | ||
+ | //TODO: manage errors | ||
+ | } ); | ||
+ | }; | ||
+ | |||
+ | DataViz.prototype.dataToTable = function( headList, bodyList ) { | ||
+ | var order = [], | ||
+ | theadTr = $( '<tr>' ), | ||
+ | thead = $( '<thead>' ).append( theadTr ), | ||
+ | tbody = $( '<tbody>' ), | ||
+ | table = $( '<table>' ) | ||
+ | .addClass( 'wikitable sortable' ) | ||
+ | .append( thead ).append( tbody ); | ||
+ | |||
+ | for ( var i = 0; i < headList.length; i++ ) { | ||
+ | theadTr.append( $( '<th>' ).text( headList[ i ] ) ); | ||
+ | order.push( headList[ i ] ); | ||
+ | } | ||
+ | |||
+ | for ( var i=0; i < bodyList.length; i++ ) { | ||
+ | var tr = $( '<tr>' ).appendTo( tbody ); | ||
+ | |||
+ | for ( var j=0; j < order.length; j++ ) { | ||
+ | var cell = bodyList[ i ][ order[ j ] ]; | ||
+ | if ( cell.type === 'uri' ) { | ||
+ | cell.value = $( '<a>' ) | ||
+ | .attr( 'href', cell.value ) | ||
+ | .text( cell.value.split( '/' ).pop() ); | ||
+ | } | ||
+ | tr.append( $( '<td>' ).html( cell.value ) ); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | table.tablesorter(); | ||
+ | |||
+ | return table; | ||
+ | } | ||
+ | |||
+ | |||
+ | |||
+ | mw.loader.using( [ 'jquery.tablesorter', 'oojs-ui' ], function() { | ||
+ | $( '.dataviz' ).each( function() { | ||
+ | window.dataviz = new DataViz( $( this ) ); | ||
+ | } ); | ||
} ); | } ); |
Revision as of 14:09, 6 April 2018
// Replace Wikidata IDs with their [label, description]
$( '.wb-external-id' ).each( function() {
if ( $( this ).attr( 'href' ).lastIndexOf( 'https://www.wikidata.org', 0 ) === 0 ) {
var wikidataApi = new mw.ForeignApi( 'https://www.wikidata.org/w/api.php', {
anonymous: true,
parameters: { 'origin': '*' },
ajax: { timeout: 10000 }
} ),
lang = mw.config.get( 'wgUserLanguage' ),
node = $( this );
wikidataApi.get( {
'action': 'wbgetentities',
'format': 'json',
'ids': node.text(),
'props': 'labels|descriptions',
'languages': lang,
'languagefallback': 1,
'origin': '*'
} ).then( function( data ) {
var entity = data.entities[ node.text() ],
label = entity.labels[ lang ].value,
description = entity.descriptions[ lang ].value;
node.html( label + ' <i>(' + node.text() + ')</i><br><small>' + description + '</small>' )
} );
}
} );
// Fetch and display DataViz results
const sparqlEndpoint = "https://v2.lingualibre.fr/bigdata/namespace/wdq/sparql";
var DataViz = function( node ) {
this.node = node;
this.baseQuery = node.children( '.dataviz-query' ).text().replace(/\u00A0/g, ' ');
this.resultNode = node.children( '.dataviz-result' );
this.inputsNode = node.children( '.dataviz-inputs' );
// Add inputs
this.addInputs();
this.refresh();
};
DataViz.prototype.addInputs = function() {
var regex = /#extra:({.+?})? (.+)/g,
process = new OO.ui.Process(),
query = this.baseQuery,
m;
this.inputs = [];
this.labels = {};
this.filters = {};
while ( ( m = regex.exec( query ) ) !== null ) {
console.log( m );
// This is necessary to avoid infinite loops with zero-width matches
if (m.index === regex.lastIndex) {
regex.lastIndex++;
}
var input = JSON.parse( m[ 1 ] );
input[ 'query' ] = m[ 2 ];
if ( input[ 'label' ] !== undefined ) {
this.labels[ input[ 'label' ] ] = input[ 'label' ];
}
if ( input[ 'filter' ] !== undefined ) {
this.filters[ input[ 'filter' ] ] = [];
}
var i = this.inputs.push( input );
this.baseQuery = this.baseQuery.replace( m[ 0 ], '#$' + i );
}
// Fetch labels
if ( Object.keys( this.labels ).length ) {
process.next( this.getLabels.bind( this ) );
}
// Preload values for limited inputs
for ( var item in this.filters ) {
process.next( this.loadFilterValues.bind( this, item ) );
}
// Once all data has been collected, we can create the fields
for ( var i=0; i < this.inputs.length; i++ ) {
process.next( this.createField.bind( this, this.inputs[ i ] ) );
}
var button = new OO.ui.ButtonWidget( {
label: 'Filtrer!',
flags: [ 'primary', 'progressive' ]
} );
button.on( 'click', this.refresh.bind( this ) );
this.inputsNode.append( button.$element );
process.execute();
};
DataViz.prototype.loadFilterValues = function( item ) {
var query = 'select ?id ?idLabel where {?id prop:P2 entity:' + item + '. SERVICE wikibase:label{bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en".}}';
return this.postQuery( query ).then( function( data ) {
var rows = data.results.bindings;
for ( var i=0; i < rows.length; i++ ) {
dataviz.filters[ item ].push( new OO.ui.MenuOptionWidget( {
data: rows[ i ][ 'id' ].value.split( '/' ).pop(),
label: rows[ i ][ 'idLabel' ].value
} ) );
}
} );
};
DataViz.prototype.getLabels = function( query ) {
var dataviz = this,
lang = mw.config.get( 'wgUserLanguage' ),
api = new mw.Api();
return api.get( {
action: 'wbgetentities',
format: 'json',
ids: Object.keys( this.labels ).join( '|' ),
props: 'labels',
languages: lang,
languagefallback: 1
} ).then( function( data ) {
for ( entity in data.entities ) {
dataviz.labels[ entity ] = data.entities[ entity ].labels[ lang ].value;
}
} );
};
DataViz.prototype.createField = function( infos ) {
var field;
switch( infos.type ) {
case 'wikibase-item':
field = new OO.ui.CapsuleMultiselectWidget( {
menu: { items: this.filters[ infos[ 'filter' ] ] }
} );
break;
default:
field = new OO.ui.TextInputWidget();
}
this.inputsNode.append( new OO.ui.FieldLayout( field, {
align: 'left',
label: this.labels[ infos[ 'label' ] ],
} ).$element );
infos[ 'field' ] = field;
};
DataViz.prototype.preProcessQuery = function() {
var query = this.baseQuery;
for ( var i=0; i < this.inputs.length; i++ ) {
if ( this.inputs[ i ].field !== undefined ) {
switch( this.inputs[ i ].type ) {
case 'wikibase-item':
var values = this.inputs[ i ].field.getItemsData();
for ( var j=0; j < values.length; j++ ) {
values[ j ] = '{' + this.inputs[ i ].query.replace( '[EXTRA]', values[ j ] ) + '}';
}
query = query.replace( '#$' + (i+1), values.join( ' UNION ' ) );
break;
default:
var value = this.inputs[ i ].field.getValue();
query = query.replace( '#$' + (i+1), this.inputs[ i ].query.replace( '[EXTRA]', value ) );
}
}
}
return query;
};
DataViz.prototype.postQuery = function( query ) {
query = query.replace(/\u00A0/g, ' ').replace( '[AUTO_LANGUAGE]', mw.config.get( 'wgUserLanguage' ) );
return $.post( sparqlEndpoint, { format: 'json', query: query } );
};
DataViz.prototype.refresh = function() {
var dataviz = this,
query = this.preProcessQuery();
this.postQuery( query ).then( function( data ) {
var table = dataviz.dataToTable( data.head.vars, data.results.bindings );
dataviz.resultNode.html( table );
} ).fail( function( data ) {
console.log( data.responseText );
//TODO: manage errors
} );
};
DataViz.prototype.dataToTable = function( headList, bodyList ) {
var order = [],
theadTr = $( '<tr>' ),
thead = $( '<thead>' ).append( theadTr ),
tbody = $( '<tbody>' ),
table = $( '<table>' )
.addClass( 'wikitable sortable' )
.append( thead ).append( tbody );
for ( var i = 0; i < headList.length; i++ ) {
theadTr.append( $( '<th>' ).text( headList[ i ] ) );
order.push( headList[ i ] );
}
for ( var i=0; i < bodyList.length; i++ ) {
var tr = $( '<tr>' ).appendTo( tbody );
for ( var j=0; j < order.length; j++ ) {
var cell = bodyList[ i ][ order[ j ] ];
if ( cell.type === 'uri' ) {
cell.value = $( '<a>' )
.attr( 'href', cell.value )
.text( cell.value.split( '/' ).pop() );
}
tr.append( $( '<td>' ).html( cell.value ) );
}
}
table.tablesorter();
return table;
}
mw.loader.using( [ 'jquery.tablesorter', 'oojs-ui' ], function() {
$( '.dataviz' ).each( function() {
window.dataviz = new DataViz( $( this ) );
} );
} );