364 lines
9.2 KiB
JavaScript
364 lines
9.2 KiB
JavaScript
/*!
|
|
* @license GNU GPL v2+
|
|
* @since 3.0
|
|
*
|
|
* @author mwjames
|
|
* @author thomas-topway-it
|
|
*/
|
|
|
|
/* global jQuery, mediaWiki, mw */
|
|
( function ( $, mw ) {
|
|
|
|
'use strict';
|
|
|
|
var dataTable = {
|
|
|
|
/**
|
|
* Adds the initial sort/order from the #ask request that is available as
|
|
* `data-column-sort` attribute with something like:
|
|
*
|
|
* {
|
|
* "list":["","Foo","Bar"]
|
|
* "sort":["Foo"],
|
|
* "order":["asc"]
|
|
* }
|
|
*
|
|
* on
|
|
*
|
|
* {{#ask: ...
|
|
* |?Foo
|
|
* |?Bar
|
|
* |sort=Foo
|
|
* |order=asc
|
|
* ...
|
|
* }}
|
|
*
|
|
* @since 3.0
|
|
*
|
|
* @private
|
|
* @static
|
|
*
|
|
* @param {Object} context
|
|
*/
|
|
initColumnSort: function ( context ) {
|
|
|
|
var column = context.data( 'column-sort' );
|
|
var order = [];
|
|
|
|
// SMW allows descending and ascending but those are unknown to DataTables
|
|
var orderMap = {
|
|
'descending' : 'desc',
|
|
'ascending': 'asc',
|
|
'asc': 'asc',
|
|
'desc': 'desc'
|
|
};
|
|
|
|
// In case of a transposed table, don't try to match a column or its order
|
|
if ( column === undefined || !column.hasOwnProperty( 'sort' ) || column.sort.length === 0 || context.attr( 'data-transpose' ) ) {
|
|
return;
|
|
};
|
|
|
|
// https://datatables.net/reference/api/order()
|
|
// [1, 'asc'], [2, 'desc']
|
|
$.map( column.sort, function( val, i ) {
|
|
|
|
if ( val === '' ) {
|
|
i = 0;
|
|
};
|
|
|
|
if ( $.inArray( val, column.list ) < 0 ) {
|
|
return
|
|
};
|
|
|
|
order.push( [
|
|
$.inArray( val, column.list ), // Find matchable index from the list
|
|
column.order[i] === undefined ? 'asc' : orderMap[column.order[i]]
|
|
] );
|
|
} );
|
|
|
|
if ( order.length > 0 ) {
|
|
context.data( 'order', order );
|
|
};
|
|
},
|
|
|
|
/**
|
|
* @since 3.0
|
|
*
|
|
* @private
|
|
* @static
|
|
*
|
|
* @param {Object} context
|
|
*/
|
|
addHeader: function ( context ) {
|
|
|
|
// Copy the thead to a position the DataTable plug-in can transform
|
|
// and display
|
|
if ( context.find( 'thead' ).length === 0 ) {
|
|
var head = context.find( 'tbody tr' );
|
|
context.prepend( '<thead>' + head.html() + '</thead>' );
|
|
head.eq(0).remove();
|
|
|
|
// In case of a transposed, turn any td into a th
|
|
context.find( 'thead td' ).wrapInner( '<th />' ).contents().unwrap();
|
|
}
|
|
|
|
// Ensure that any link in the header stops the propagation of the
|
|
// click sorting event
|
|
context.find( 'thead tr a' ).on( 'click.sorting', function ( event ) {
|
|
event.stopPropagation();
|
|
} );
|
|
},
|
|
|
|
/**
|
|
* @since 3.0
|
|
*
|
|
* @private
|
|
* @static
|
|
*
|
|
* @param {Object} context
|
|
*/
|
|
addFooter: function ( context ) {
|
|
|
|
// As a transposed table, move the footer column to the bottom
|
|
// and remove any footer-cell from the table matrix to
|
|
// ensure a proper formatted table
|
|
if ( context.data( 'transpose' ) === 1 && context.find( 'tbody .sortbottom' ).length === 1 ) {
|
|
var footer = context.find( 'tbody .sortbottom' );
|
|
context.append( '<tfoot><tr><td colspan=' + footer.index() + '>' + footer.html() + '</td></tr></tfoot>' );
|
|
footer.eq(0).remove();
|
|
|
|
// Remove remaining footer cells to avoid an uneven table
|
|
context.find( 'tbody .footer-cell' ).each( function() {
|
|
$( this ).remove();
|
|
} );
|
|
};
|
|
|
|
// Copy the tbody to a position the DataTable plug-in can transform
|
|
// and display
|
|
if ( context.find( 'tbody .smwfooter' ).length == 1 ) {
|
|
var footer = context.find( 'tbody .smwfooter' );
|
|
context.append( '<tfoot>' + footer.html() + '</tfoot>' );
|
|
footer.eq(0).remove();
|
|
}
|
|
|
|
context.find( '.sortbottom' ).addClass( 'plainlinks' );
|
|
},
|
|
|
|
/**
|
|
* @since 3.0
|
|
*
|
|
* @param {Object} context
|
|
*/
|
|
addToolbarExportLinks: function ( context ) {
|
|
|
|
// @see https://datatables.net/examples/layout/custom-nodes.html
|
|
var toolbar = $('<div class="smw-datatable-toolbar">'),
|
|
query = context.data( 'query' ),
|
|
exportFormats = {
|
|
'JSON': {
|
|
'format': 'json',
|
|
'searchlabel': 'JSON',
|
|
'type': 'simple',
|
|
'prettyprint': true,
|
|
'unescape': true
|
|
},
|
|
'CSV': {
|
|
'format': 'csv',
|
|
'searchlabel': 'CSV'
|
|
},
|
|
'RSS': {
|
|
'format': 'rss',
|
|
'searchlabel': 'RSS'
|
|
},
|
|
'RDF': {
|
|
'format': 'rdf',
|
|
'searchlabel': 'RDF'
|
|
}
|
|
}
|
|
|
|
if ( !query instanceof Object || query === undefined ) {
|
|
return;
|
|
}
|
|
|
|
var items = '';
|
|
|
|
Object.keys( exportFormats ).forEach( function( key ) {
|
|
|
|
// https://stackoverflow.com/questions/122102/what-is-the-most-efficient-way-to-deep-clone-an-object-in-javascript
|
|
var conf = exportFormats[key],
|
|
parameters = $.extend( {}, query.parameters );
|
|
|
|
// Modify the default query with that of the configuration
|
|
Object.keys( conf ).forEach(function( k ) {
|
|
parameters[k] = conf[k];
|
|
} );
|
|
|
|
var q = new smw.query(
|
|
query.printouts,
|
|
parameters,
|
|
query.conditions
|
|
);
|
|
|
|
q.setLinkAttributes( {
|
|
'title': key
|
|
} );
|
|
|
|
if ( key === 'RDF' ) {
|
|
items += '<li class="divider"></li>';
|
|
};
|
|
|
|
items += '<li class="action">' + q.getLink( key ) + '</li>';
|
|
} );
|
|
|
|
toolbar.append(
|
|
'<span class="smw-dropdown">' +
|
|
'<button>' + mw.msg( 'smw-format-datatable-toolbar-export' ) + '</button>' +
|
|
'<label><input type="checkbox">' +
|
|
'<ul class="smw-dropdown-menu">' +
|
|
items +
|
|
'</ul></label></span>'
|
|
);
|
|
|
|
toolbar.find( '.action a' ).on( 'click', function( e ) {
|
|
toolbar.find( '.smw-dropdown input' ).prop( 'checked', false );
|
|
} );
|
|
|
|
return toolbar;
|
|
},
|
|
|
|
/**
|
|
* @since 3.0
|
|
*
|
|
* @param {Object} context
|
|
*/
|
|
attach: function ( context ) {
|
|
|
|
var self = this;
|
|
context.show();
|
|
|
|
// Remove any class that may interfere due to some external JS or CSS
|
|
context.removeClass( 'jquery-tablesorter' );
|
|
context.removeClass( 'sortable' );
|
|
context.removeClass( 'is-disabled' );
|
|
context.removeClass( 'wikitable' );
|
|
|
|
// DataTables default display class
|
|
context.addClass( 'display' );
|
|
|
|
mw.loader.using( 'onoi.dataTables' ).done( function () {
|
|
|
|
self.initColumnSort( context );
|
|
|
|
// MediaWiki table output is missing some standard formatting hence
|
|
// add a footer and header
|
|
self.addFooter( context );
|
|
self.addHeader( context );
|
|
|
|
// https://datatables.net/manual/tech-notes/3
|
|
// Ensure the object initialization only happens once
|
|
if ( $.fn.dataTable.isDataTable( context ) ) {
|
|
return;
|
|
}
|
|
|
|
var conf = {
|
|
// *** use the new layout feature
|
|
// dom: 'l<"smw-datatable-toolbar float-right">frtip',
|
|
searchHighlight: true,
|
|
language: {
|
|
"sProcessing": mw.msg( 'smw-format-datatable-processing' ),
|
|
"sLengthMenu": mw.msg( 'smw-format-datatable-lengthmenu' ),
|
|
"sZeroRecords": mw.msg( 'smw-format-datatable-zerorecords' ),
|
|
"sEmptyTable": mw.msg( 'smw-format-datatable-emptytable' ),
|
|
"sInfo": mw.msg( 'smw-format-datatable-info' ),
|
|
"sInfoEmpty": mw.msg( 'smw-format-datatable-infoempty' ),
|
|
"sInfoFiltered": mw.msg( 'smw-format-datatable-infofiltered' ),
|
|
"sSearch": mw.msg( 'smw-format-datatable-search' ),
|
|
"sInfoThousands": mw.msg( 'smw-format-datatable-infothousands' ),
|
|
"sLoadingRecords": mw.msg( 'smw-format-datatable-loadingrecords' ),
|
|
"oPaginate": {
|
|
"sFirst": mw.msg( 'smw-format-datatable-first' ),
|
|
"sLast": mw.msg( 'smw-format-datatable-last' ),
|
|
"sNext": mw.msg( 'smw-format-datatable-next' ),
|
|
"sPrevious": mw.msg( 'smw-format-datatable-previous' )
|
|
},
|
|
"oAria": {
|
|
"sSortAscending": mw.msg( 'smw-format-datatable-sortascending' ),
|
|
"sSortDescending": mw.msg( 'smw-format-datatable-sortdescending' )
|
|
}
|
|
}
|
|
};
|
|
|
|
var toolbar = self.addToolbarExportLinks( context );
|
|
|
|
// @see https://datatables.net/reference/option/layout
|
|
// @see https://datatables.net/examples/layout/ids-and-classes.html
|
|
conf.layout = {
|
|
topStart: 'pageLength',
|
|
topEnd: 'search',
|
|
bottomStart: 'info',
|
|
bottomEnd: 'paging',
|
|
top1Start: {
|
|
features: [{
|
|
div: {}
|
|
}]
|
|
},
|
|
top1End: toolbar
|
|
};
|
|
|
|
var table = context.DataTable( conf );
|
|
|
|
// Remove accented characters from the search input
|
|
context.parent().find( 'input' ).on( "keyup", function () {
|
|
table.search(
|
|
$.fn.DataTable.ext.type.search.string( $.trim( this.value ) )
|
|
).draw();
|
|
} );
|
|
|
|
// Fire to instantiate the tooltip after the DT has been generated
|
|
mw.hook( 'smw.tooltip' ).fire( context );
|
|
} );
|
|
},
|
|
|
|
/**
|
|
* @since 3.0
|
|
*
|
|
* @private
|
|
* @static
|
|
*
|
|
* @param {Object} context
|
|
*/
|
|
init: function ( context ) {
|
|
|
|
context.removeClass( 'is-disabled' );
|
|
context.removeClass( 'smw-flex-center' );
|
|
|
|
context.css( "background-color", "transparent" );
|
|
context.css( "height", "" );
|
|
context.find( '.smw-overlay-spinner' ).hide();
|
|
|
|
context.find( '.datatable' ).css( "opacity", "1" );
|
|
context.removeClass( 'smw-extra-margin' );
|
|
|
|
context.find( '.smw-datatable' ).removeClass( 'smw-extra-margin' );
|
|
this.attach( context.find( '.datatable' ) );
|
|
}
|
|
};
|
|
|
|
$( document ).ready( function() {
|
|
|
|
$( '.smw-datatable' ).each( function() {
|
|
dataTable.init( $( this ) );
|
|
} );
|
|
|
|
// Listen to the smw.deferred.query event
|
|
mw.hook( 'smw.deferred.query' ).add( function( context ) {
|
|
if ( context.find( '.smw-datatable .datatable' ).length ) {
|
|
dataTable.init( context.find( '.smw-datatable' ) );
|
|
} else {
|
|
dataTable.init( context );
|
|
}
|
|
} );
|
|
} );
|
|
|
|
}( jQuery, mediaWiki ) );
|