Source: JSEngine/libs/datatable.js

/* --------------------------------------------------------------
 datatable.js 2016-07-11
 Gambio GmbH
 http://www.gambio.de
 Copyright (c) 2016 Gambio GmbH
 Released under the GNU General Public License (Version 2)
 [http://www.gnu.org/licenses/gpl-2.0.html]
 --------------------------------------------------------------
 */

jse.libs.datatable = jse.libs.datatable || {};

/**
 * ## DataTable Library
 *
 * This is a wrapper library for the manipulation of jQuery DataTables. Use the "create" method with DataTable 
 * configuration to initialize a table on your page. All you need when using this library is an empty `<table>` 
 * element. Visit the official website of DataTables to check examples and other information about the plugin.
 * 
 * {@link http://www.datatables.net Official DataTables Website}
 *
 * Notice: Make sure that you load the DataTables vendor files before using this module. 
 *
 * ### Examples
 * 
 * **Example - Create A New Instance**
 * ```javascript
 * var tableApi = jse.libs.datatable.create($('#my-table'), {
 *      ajax: 'http://shop.de/table-data.php',
 *      columns: [
 *          { title: 'Name', data: 'name' defaultContent: '...' },
 *          { title: 'Email', data: 'email' },
 *          { title: 'Actions', data: null, orderable: false, defaultContent: 'Add | Edit | Delete' },
 *      ]
 * });
 * ```
 *
 * **Example - Add Error Handler**
 * ```javascript
 * jse.libs.datatable.error($('#my-table'), function(event, settings, techNote, message) {
 *      // Log error in the JavaScript console.
 *      console.log('DataTable Error:', message);
 * });
 * ```
 *
 * @module JSE/Libs/datatable
 * @exports jse.libs.datatable
 * @requires jQuery-DataTables-Plugin
 */
(function(exports) {
	
	'use strict';
	
	// ------------------------------------------------------------------------
	// VARIABLES
	// ------------------------------------------------------------------------
	
	let languages = {
		de: {
			'sEmptyTable': 'Keine Daten in der Tabelle vorhanden',
			'sInfo': '_START_ bis _END_ (von _TOTAL_)',
			'sInfoEmpty': '0 bis 0 von 0 Einträgen',
			'sInfoFiltered': '(gefiltert von _MAX_ Einträgen)',
			'sInfoPostFix': '',
			'sInfoThousands': '.',
			'sLengthMenu': '_MENU_ Einträge anzeigen',
			'sLoadingRecords': 'Wird geladen...',
			'sProcessing': 'Bitte warten...',
			'sSearch': 'Suchen',
			'sZeroRecords': 'Keine Einträge vorhanden.',
			'oPaginate': {
				'sFirst': 'Erste',
				'sPrevious': 'Zurück',
				'sNext': 'Nächste',
				'sLast': 'Letzte'
			},
			'oAria': {
				'sSortAscending': ': aktivieren, um Spalte aufsteigend zu sortieren',
				'sSortDescending': ': aktivieren, um Spalte absteigend zu sortieren'
			}
		},
		en: {
			'sEmptyTable': 'No data available in table',
			'sInfo': '_START_ to _END_ (of _TOTAL_)',
			'sInfoEmpty': 'Showing 0 to 0 of 0 entries',
			'sInfoFiltered': '(filtered from _MAX_ total entries)',
			'sInfoPostFix': '',
			'sInfoThousands': ',',
			'sLengthMenu': 'Show _MENU_ entries',
			'sLoadingRecords': 'Loading...',
			'sProcessing': 'Processing...',
			'sSearch': 'Search:',
			'sZeroRecords': 'No matching records found',
			'oPaginate': {
				'sFirst': 'First',
				'sLast': 'Last',
				'sNext': 'Next',
				'sPrevious': 'Previous'
			},
			'oAria': {
				'sSortAscending': ': activate to sort column ascending',
				'sSortDescending': ': activate to sort column descending'
			}
		}
	};
	
	// ------------------------------------------------------------------------
	// FUNCTIONALITY
	// ------------------------------------------------------------------------
	
	/**
	 * Reorder the table columns as defined in the active columns array.
	 *
	 * @param {jQuery} $target Table jQuery selector object.
	 * @param {Object} columnDefinitions Array containing the DataTable column definitions.
	 * @param {Array} activeColumnNames Array containing the slug-names of the active columns.
	 *
	 * @return {Array} Returns array with the active column definitions ready to use in DataTable.columns option.
	 *
	 * @private
	 */
	function _reorderColumns($target, columnDefinitions, activeColumnNames) {
		activeColumnNames.unshift('checkbox');
		activeColumnNames.push('actions');
		
		// Hide the table header cells that are not active.
		$.each(columnDefinitions, (index, columnDefinition) => {
			$target.find('thead tr').each(function() {
				let $headerCell = $(this).find(`[data-column-name="${columnDefinition.name}"]`);
				
				if (columnDefinition.data !== null && activeColumnNames.indexOf(columnDefinition.name) === -1) {
					$headerCell.hide();
				}
			});
		});
		
		// Prepare the active column definitions.
		let finalColumnDefinitions = [],
			columnIndexes = [];
		
		$.each(activeColumnNames, (index, name) => {
			$.each(columnDefinitions, (index, columnDefinition) => {
				if (columnDefinition.name === name) {
					// Add the active column definition in the "finalColumnDefinitions" array.
					finalColumnDefinitions.push(columnDefinition);
					const headerCellIndex = $target
						.find(`thead:first tr:first [data-column-name="${columnDefinition.name}"]`)
						.index();
					columnIndexes.push(headerCellIndex);
					return true; // continue
				}
			});
		});
		
		finalColumnDefinitions.sort((a, b) => {
			const aIndex = activeColumnNames.indexOf(a.name);
			const bIndex = activeColumnNames.indexOf(b.name);
			
			if (aIndex < bIndex) {
				return -1;
			} else if (aIndex > bIndex) {
				return 1;
			} else {
				return 0;
			}
		});
		
		// Reorder the table header elements depending the activeColumnNames order.
		$target.find('thead tr').each(function() {
			let activeColumnSelections = [$(this).find('th:first')];
			
			// Sort the columns in the correct order.
			columnIndexes.forEach((index) => {
				let $headerCell = $(this).find('th').eq(index);
				activeColumnSelections.push($headerCell);
			});
			
			// Move the columns to their final position.
			activeColumnSelections.forEach(function($headerCell, index) {
				if (index === 0) {
					return true;
				}
				
				$headerCell.insertAfter(activeColumnSelections[index - 1]);
			});
		});
		
		return finalColumnDefinitions;
	}
	
	/**
	 * Creates a DataTable Instance
	 *
	 * This method will create a new instance of datatable into a `<table>` element. It enables
	 * developers to easily pass the configuration needed for different and more special situations.
	 *
	 * @param {jQuery} $target jQuery object for the target table.
	 * @param {Object} configuration DataTables configuration applied on the new instance.
	 *
	 * @return {DataTable} Returns the DataTable API instance (different from the jQuery object).
	 */
	exports.create = function($target, configuration) {
		return $target.DataTable(configuration);
	};
	
	/**
	 * Sets the error handler for specific DataTable.
	 *
	 * DataTables provide a useful mechanism that enables developers to control errors during data parsing.
	 * If there is an error in the AJAX response or some data are invalid in the JavaScript code you can use 
	 * this method to control the behavior of the app and show or log the error messages.
	 *
	 * {@link http://datatables.net/reference/event/error}
	 *
	 * @param {jQuery} $target jQuery object for the target table.
	 * @param {Object} callback Provide a callback method called with the "event", "settings", "techNote", 
	 * "message" arguments (see provided link).
	 */
	exports.error = function($target, callback) {
		$.fn.dataTable.ext.errMode = 'none';
		$target
			.on('error.dt', callback)
			.on('xhr.dt', (event, settings, json, xhr) => {
				if (json.exception === true) {
					callback(event, settings, null, json.message); 
				}
			}); 
	};
	
	/**
	 * Sets the callback method when ajax load of data is complete.
	 *
	 * This method is useful for checking PHP errors or modifying the data before
	 * they are displayed to the server.
	 *
	 * {@link http://datatables.net/reference/event/xhr}
	 *
	 * @param {jQuery} $target jQuery object for the target table.
	 * @param {Function} callback Provide a callback method called with the "event", "settings", "techNote", 
	 * "message" arguments (see provided link).
	 */
	exports.ajaxComplete = function($target, callback) {
		$target.on('xhr.dt', callback);
	};
	
	/**
	 * Sets the table column to be displayed as an index.
	 *
	 * This method will easily enable you to set a column as an index column, used
	 * for numbering the table rows regardless of the search, sorting and row count.
	 *
	 * {@link http://www.datatables.net/examples/api/counter_columns.html}
	 *
	 * @param {jQuery} $target jQuery object for the target table.
	 * @param {Number} columnIndex Zero based index of the column to be indexed.
	 */
	exports.indexColumn = function($target, columnIndex) {
		$target.on('order.dt search.dt', function() {
			$target.DataTable().column(columnIndex, {
				search: 'applied',
				order: 'applied'
			}).nodes().each(function(cell, index) {
				cell.innerHTML = index + 1;
			});
		});
	};
	
	/**
	 * Returns the german translation of the DataTables
	 *
	 * This method provides a quick way to get the language JSON without having to perform
	 * and AJAX request to the server. If you setup your DataTable manually you can set the
	 * "language" attribute with this method.
	 *
	 * @deprecated Since v1.4, use the "getTranslations" method instead.
	 *
	 * @return {Object} Returns the german translation, must be the same as the "german.lang.json" file.
	 */
	exports.getGermanTranslation = function() {
		jse.core.debug.warn('DataTables Library: the getGermanTranslation method is deprecated and will be removed '
			+ 'in JSE v1.5, please use the "getTranslations" method instead.');
		return languages.de;
	};
	
	/**
	 * Get the DataTables translation depending the language code parameter.
	 *
	 * @param {String} languageCode Provide 'de' or 'en' (you can also use the jse.core.config.get('languageCode') to
	 * get the current language code).
	 *
	 * @return {Object} Returns the translation strings in an object literal as described by the official DataTables
	 * documentation.
	 *
	 * {@link https://www.datatables.net/plug-ins/i18n}
	 */
	exports.getTranslations = function(languageCode) {
		if (languages[languageCode] === undefined) {
			jse.core.debug.warn('DataTables Library: The requested DataTables translation was not found:', languageCode);
			languageCode = 'en';
		}
		
		return languages[languageCode];
	};
	
	/**
	 * Prepare table columns.
	 *
	 * This method will convert the column definitions to a DataTable compatible format and also reorder
	 * the table header cells of the "thead" element.
	 *
	 * @param {jQuery} $target Table jQuery selector object.
	 * @param {Object} columnDefinitions Array containing the DataTable column definitions.
	 * @param {String[]} activeColumnNames Array containing the slug-names of the active columns.
	 *
	 * @return {Object[]} Returns array with the active column definitions ready to use in DataTable.columns option.
	 */
	exports.prepareColumns = function($target, columnDefinitions, activeColumnNames) {
		let convertedColumnDefinitions = [];
		
		for (let columnName in columnDefinitions) {
			let columnDefinition = columnDefinitions[columnName];
			columnDefinition.name = columnName;
			convertedColumnDefinitions.push(columnDefinition);
		}
		
		return _reorderColumns($target, convertedColumnDefinitions, activeColumnNames);
	};
	
}(jse.libs.datatable));