diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..933bc44 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +.git* export-ignore +README* export-ignore +LICENSE* export-ignore +mk* export-ignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b25c15b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*~ diff --git a/README.md b/README.md index 806508d..57c1c96 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,32 @@ -# tablepress-datatables-colrejigger +## tablepress-datatables-colrejigger + +##### Abstract + +allow columns of a Tablepress table to be reordered/resized on the fly in the front end + +##### Description + +This project is an extension to the TablePress plug-in (see https://tablepress.org/) for WordPress (https://wordpress.org/). It is not useful without the TablePress plug-in. This project is not part of TablePress, but designed to work with it and extend its capabilities. + +Based on the ColReorder and ColResize (see https://github.com/smasala/ColResize) extensions for the DataTables JavaScript library (see https://datatables.net), the tablepress-datatables-colrejigger extension gives you the ability to allow columns of a TablePress table in your WordPress site to be reordered or resized by viewers of the table. This feature may be particularly useful when you want to present more information than can comfortably fit in your page's width and want to allow viewers to arrange it in the way most useful or readable for them. + +##### Installation + +Download a release from this site (as a zip file) and install and activate in your WordPress site like any other extension in zip format. + +##### Usage + +This extension operates by adding additional parameters to the [table id=NN /] shortcode. For a list of the provided parameters and their meanings, see the source file tablepress-datatables-colrejigger.php. But one quick example should show how simple this is: + +`[table id=2 datatables_colreorder=true datatables_colresize_resizetable=true /]` + +This will present table 2 in the default fashion, except that the page viewer will be able to reorder its columns (by dragging headers) and resize its columns (by dragging the boundaries between headers, indicated by the cursor changing shape). The table will change its total width to accommodate the new column sizes after a resize operation. + +##### Included software + +Note that DataTables ColReorder and DataTables ColResize are **not** part of this project, but are redistributed with it for convenience, under the respective license terms of each of those packages. + +##### Building + +To produce an installable zip file, change directories into a git clone of this repository, and simply execute the command in mkplugin.sh (for example, via `bash mkplugin.sh`). Note that currently you must update the version number in the mkplugin.sh command. -allow columns of a Tablepress table to be reordered/resized on the fly in the front end \ No newline at end of file diff --git a/js/dataTables.colReorder.js b/js/dataTables.colReorder.js new file mode 100644 index 0000000..294e529 --- /dev/null +++ b/js/dataTables.colReorder.js @@ -0,0 +1,1422 @@ +/*! ColReorder 1.5.0 + * ©2010-2018 SpryMedia Ltd - datatables.net/license + */ + +/** + * @summary ColReorder + * @description Provide the ability to reorder columns in a DataTable + * @version 1.5.0 + * @file dataTables.colReorder.js + * @author SpryMedia Ltd (www.sprymedia.co.uk) + * @contact www.sprymedia.co.uk/contact + * @copyright Copyright 2010-2018 SpryMedia Ltd. + * + * This source file is free software, available under the following license: + * MIT license - http://datatables.net/license/mit + * + * This source file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. + * + * For details please refer to: http://www.datatables.net + */ +(function( factory ){ + if ( typeof define === 'function' && define.amd ) { + // AMD + define( ['jquery', 'datatables.net'], function ( $ ) { + return factory( $, window, document ); + } ); + } + else if ( typeof exports === 'object' ) { + // CommonJS + module.exports = function (root, $) { + if ( ! root ) { + root = window; + } + + if ( ! $ || ! $.fn.dataTable ) { + $ = require('datatables.net')(root, $).$; + } + + return factory( $, root, root.document ); + }; + } + else { + // Browser + factory( jQuery, window, document ); + } +}(function( $, window, document, undefined ) { +'use strict'; +var DataTable = $.fn.dataTable; + + +/** + * Switch the key value pairing of an index array to be value key (i.e. the old value is now the + * key). For example consider [ 2, 0, 1 ] this would be returned as [ 1, 2, 0 ]. + * @method fnInvertKeyValues + * @param array aIn Array to switch around + * @returns array + */ +function fnInvertKeyValues( aIn ) +{ + var aRet=[]; + for ( var i=0, iLen=aIn.length ; i= iCols ) + { + this.oApi._fnLog( oSettings, 1, "ColReorder 'from' index is out of bounds: "+iFrom ); + return; + } + + if ( iTo < 0 || iTo >= iCols ) + { + this.oApi._fnLog( oSettings, 1, "ColReorder 'to' index is out of bounds: "+iTo ); + return; + } + + /* + * Calculate the new column array index, so we have a mapping between the old and new + */ + var aiMapping = []; + for ( i=0, iLen=iCols ; i this.s.fixed-1 && i < iLen - this.s.fixedRight ) + { + this._fnMouseListener( i, this.s.dt.aoColumns[i].nTh ); + } + + /* Mark the original column order for later reference */ + this.s.dt.aoColumns[i]._ColReorder_iOrigCol = i; + } + + /* State saving */ + this.s.dt.oApi._fnCallbackReg( this.s.dt, 'aoStateSaveParams', function (oS, oData) { + that._fnStateSave.call( that, oData ); + }, "ColReorder_State" ); + + /* An initial column order has been specified */ + var aiOrder = null; + if ( this.s.init.aiOrder ) + { + aiOrder = this.s.init.aiOrder.slice(); + } + + /* State loading, overrides the column order given */ + if ( this.s.dt.oLoadedState && typeof this.s.dt.oLoadedState.ColReorder != 'undefined' && + this.s.dt.oLoadedState.ColReorder.length == this.s.dt.aoColumns.length ) + { + aiOrder = this.s.dt.oLoadedState.ColReorder; + } + + /* If we have an order to apply - do so */ + if ( aiOrder ) + { + /* We might be called during or after the DataTables initialisation. If before, then we need + * to wait until the draw is done, if after, then do what we need to do right away + */ + if ( !that.s.dt._bInitComplete ) + { + var bDone = false; + $(table).on( 'draw.dt.colReorder', function () { + if ( !that.s.dt._bInitComplete && !bDone ) + { + bDone = true; + var resort = fnInvertKeyValues( aiOrder ); + that._fnOrderColumns.call( that, resort ); + } + } ); + } + else + { + var resort = fnInvertKeyValues( aiOrder ); + that._fnOrderColumns.call( that, resort ); + } + } + else { + this._fnSetColumnIndexes(); + } + + // Destroy clean up + $(table).on( 'destroy.dt.colReorder', function () { + $(table).off( 'destroy.dt.colReorder draw.dt.colReorder' ); + + $.each( that.s.dt.aoColumns, function (i, column) { + $(column.nTh).off('.ColReorder'); + $(column.nTh).removeAttr('data-column-index'); + } ); + + that.s.dt._colReorder = null; + that.s = null; + } ); + }, + + + /** + * Set the column order from an array + * @method _fnOrderColumns + * @param array a An array of integers which dictate the column order that should be applied + * @returns void + * @private + */ + "_fnOrderColumns": function ( a ) + { + var changed = false; + + if ( a.length != this.s.dt.aoColumns.length ) + { + this.s.dt.oInstance.oApi._fnLog( this.s.dt, 1, "ColReorder - array reorder does not "+ + "match known number of columns. Skipping." ); + return; + } + + for ( var i=0, iLen=a.length ; i') + .addClass( 'DTCR_pointer' ) + .css( { + position: 'absolute', + top: scrolling ? + $('div.dataTables_scroll', this.s.dt.nTableWrapper).offset().top : + $(this.s.dt.nTable).offset().top, + height : scrolling ? + $('div.dataTables_scroll', this.s.dt.nTableWrapper).height() : + $(this.s.dt.nTable).height() + } ) + .appendTo( 'body' ); + }, + + + /** + * Add a data attribute to the column headers, so we know the index of + * the row to be reordered. This allows fast detection of the index, and + * for this plug-in to work with FixedHeader which clones the nodes. + * @private + */ + "_fnSetColumnIndexes": function () + { + $.each( this.s.dt.aoColumns, function (i, column) { + $(column.nTh).attr('data-column-index', i); + } ); + }, + + + /** + * Get cursor position regardless of mouse or touch input + * @param {Event} e jQuery Event + * @param {string} prop Property to get + * @return {number} Value + */ + _fnCursorPosition: function ( e, prop ) { + if ( e.type.indexOf('touch') !== -1 ) { + return e.originalEvent.touches[0][ prop ]; + } + return e[ prop ]; + } +} ); + + + + + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Static parameters + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + +/** + * ColReorder default settings for initialisation + * @namespace + * @static + */ +ColReorder.defaults = { + /** + * Predefined ordering for the columns that will be applied automatically + * on initialisation. If not specified then the order that the columns are + * found to be in the HTML is the order used. + * @type array + * @default null + * @static + */ + aiOrder: null, + + /** + * ColReorder enable on initialisation + * @type boolean + * @default true + * @static + */ + bEnable: true, + + /** + * Redraw the table's column ordering as the end user draws the column + * (`true`) or wait until the mouse is released (`false` - default). Note + * that this will perform a redraw on each reordering, which involves an + * Ajax request each time if you are using server-side processing in + * DataTables. + * @type boolean + * @default false + * @static + */ + bRealtime: true, + + /** + * Indicate how many columns should be fixed in position (counting from the + * left). This will typically be 1 if used, but can be as high as you like. + * @type int + * @default 0 + * @static + */ + iFixedColumnsLeft: 0, + + /** + * As `iFixedColumnsRight` but counting from the right. + * @type int + * @default 0 + * @static + */ + iFixedColumnsRight: 0, + + /** + * Callback function that is fired when columns are reordered. The `column- + * reorder` event is preferred over this callback + * @type function():void + * @default null + * @static + */ + fnReorderCallback: null +}; + + + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Constants + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/** + * ColReorder version + * @constant version + * @type String + * @default As code + */ +ColReorder.version = "1.5.0"; + + + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * DataTables interfaces + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +// Expose +$.fn.dataTable.ColReorder = ColReorder; +$.fn.DataTable.ColReorder = ColReorder; + + +// Register a new feature with DataTables +if ( typeof $.fn.dataTable == "function" && + typeof $.fn.dataTableExt.fnVersionCheck == "function" && + $.fn.dataTableExt.fnVersionCheck('1.10.8') ) +{ + $.fn.dataTableExt.aoFeatures.push( { + "fnInit": function( settings ) { + var table = settings.oInstance; + + if ( ! settings._colReorder ) { + var dtInit = settings.oInit; + var opts = dtInit.colReorder || dtInit.oColReorder || {}; + + new ColReorder( settings, opts ); + } + else { + table.oApi._fnLog( settings, 1, "ColReorder attempted to initialise twice. Ignoring second" ); + } + + return null; /* No node for DataTables to insert */ + }, + "cFeature": "R", + "sFeature": "ColReorder" + } ); +} +else { + alert( "Warning: ColReorder requires DataTables 1.10.8 or greater - www.datatables.net/download"); +} + + +// Attach a listener to the document which listens for DataTables initialisation +// events so we can automatically initialise +$(document).on( 'preInit.dt.colReorder', function (e, settings) { + if ( e.namespace !== 'dt' ) { + return; + } + + var init = settings.oInit.colReorder; + var defaults = DataTable.defaults.colReorder; + + if ( init || defaults ) { + var opts = $.extend( {}, init, defaults ); + + if ( init !== false ) { + new ColReorder( settings, opts ); + } + } +} ); + + +// API augmentation +$.fn.dataTable.Api.register( 'colReorder.reset()', function () { + return this.iterator( 'table', function ( ctx ) { + ctx._colReorder.fnReset(); + } ); +} ); + +$.fn.dataTable.Api.register( 'colReorder.order()', function ( set, original ) { + if ( set ) { + return this.iterator( 'table', function ( ctx ) { + ctx._colReorder.fnOrder( set, original ); + } ); + } + + return this.context.length ? + this.context[0]._colReorder.fnOrder() : + null; +} ); + +$.fn.dataTable.Api.register( 'colReorder.transpose()', function ( idx, dir ) { + return this.context.length && this.context[0]._colReorder ? + this.context[0]._colReorder.fnTranspose( idx, dir ) : + idx; +} ); + +$.fn.dataTable.Api.register( 'colReorder.move()', function( from, to, drop, invalidateRows ) { + if (this.context.length) { + this.context[0]._colReorder.s.dt.oInstance.fnColReorder( from, to, drop, invalidateRows ); + } + return this; +} ); + +$.fn.dataTable.Api.register( 'colReorder.enable()', function( flag ) { + return this.iterator( 'table', function ( ctx ) { + if ( ctx._colReorder ) { + ctx._colReorder.fnEnable( flag ); + } + } ); +} ); + +$.fn.dataTable.Api.register( 'colReorder.disable()', function() { + return this.iterator( 'table', function ( ctx ) { + if ( ctx._colReorder ) { + ctx._colReorder.fnDisable(); + } + } ); +} ); + + +return ColReorder; +})); diff --git a/js/dataTables.colResize.js b/js/dataTables.colResize.js new file mode 100644 index 0000000..f82c19f --- /dev/null +++ b/js/dataTables.colResize.js @@ -0,0 +1,896 @@ +/**! ColResize 2.6.0 + * ©2017 Steven Masala + */ + +/** + * @summary ColResize + * @description Provide the ability to resize the columns in a DataTable + * @version 2.6.0 + * @file dataTables.colResize.js + * @author Steven Masala + * @copyright Copyright 2017 Steven Masala + * + * This source file is free software, available under the following license: + * MIT license - https://github.com/smasala/ColResize/blob/master/LICENSE + * + * This source file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. + * + * For details please refer to: http://www.datatables.net + */ +(function( factory ) { + if ( typeof define === "function" && define.amd ) { + // amd + define( ["jquery", "datatables.net"], function($) { + return factory($, window, document); + }); + } else if (typeof exports === "object") { + // CommonJs + module.exports = function( root, $ ) { + if ( !root ) { + root = window; + } + if ( !$ || !$.fn.dataTable ) { + $ = require( "datatables.net" )( root, $ ).$; + } + return factory( $, root, root.document ); + }; + } else { + factory(jQuery, window, document); + } +} (function( $, window, document ) { + "use strict"; + var DataTable = $.fn.dataTable; + + // Sanity check that we are using DataTables 1.10 or newer + if ( ! DataTable.versionCheck || ! DataTable.versionCheck( '1.10.15' ) ) { + throw 'DataTables ColResize requires DataTables 1.10.15 or newer'; + } + + var ColResize = function( dt, userOptions) { + var dtInstance = new DataTable.Api( dt ), + settings = dtInstance.settings()[0]; + + if (settings.colResize) { + // already exists on this instance + return; + } + this.options = $.extend(true, {}, this._defaults, userOptions); + this.init(dtInstance); + settings.colResize = this; + }; + + $.extend( ColResize.prototype, { + /** + * Extension version number + * @static + * @property version + * @type {string} semVer + */ + version: "2.6.0", + /** + * Default options for extension + * @property _defaults + * @type {object} + * @private + */ + _defaults: { + /** + * Determines the minimum width size a column is allowed to be + * @property minColumnWidth + * @public + * @type {integer} + * @default 20 + */ + minColumnWidth: 20, + /** + * Height of the table body + * @property scrollY + * @public + * @type {integer|string|false} + * @default false + */ + scrollY: false, + /** + * Resizes the table instead of shrinking the next column + * when a specific column is resized + * @property resizeTable + * @public + * @type {boolean} + * @default false + */ + resizeTable: false + }, + /** + * Created during extension init + * {} <= defaults <= userOptions + * @property options + * @type {object} + * @public + * @default {null} + */ + options: null, + /** + * Wrapper which is created around the + * @property _wrapper + * @private + * @type {jQuery} + * @default null + */ + _wrapper: null, + /** + * Current jquery table element + * @property _table + * @type {jQuery} + * @private + * @default {null} + */ + _table: null, + /** + * Current jquery table body element + * @property _tableBody + * @type {jQuery} + * @private + * @default {null} + */ + _tableBody: null, + /** + * Current jquery "th" elements + * @property _tableHeaders + * @type {jQuery} + * @private + * @default null + */ + _tableHeaders: null, + /** + * Datatables instance + * @property _dtInstance + * @type {DataTables.Api} + * @private + * @default {null} + */ + _dtInstance: null, + /** + * Is column currently being dragged? + * internal flag + * @property _isDragging + * @type {boolean} + * @private + * @default {false} + */ + _isDragging: false, + /** + * Element cache variable. + * @property _container + * @type {jQuery} + * @private + * @default null + */ + _container: null, + /** + * Scroller Wrapper div used to show y scroll bar + * @property _scrollWrapper + * @type {jQuery} + * @private + * @default null + */ + _scrollWrapper: null, + /** + * Mimics the tbody height + * @property _scrollContent + * @type {jQuery} + * @private + * @default null + */ + _scrollContent: null, + /** + * Array of all generated draggable columns. + * @property _columns + * @type {jQuery[]} + * @private + * @default null + */ + _columns: null, + /** + * Array of index numbers of the columns which have been updated + * this is an internal cache which is used to only update the widths + * of the
element in columns that were changed by the user, + * for example on page change. + * @property _updatedColumns + * @type {interger[]} + * @private + * @default [] + */ + _updatedColumns: [], + /** + * Events cache object to store registered events and listeners + * @example + * { + * "click": [ [$el1, callbackFunction], [$el2, callbackFunction2] ] + * } + * @private + * @property _events + * @type {object} + * @default + */ + _events: {}, + /** + * Interval value used to check if the table height has changed. + * @property _tableHeight + * @type {integer} + * @private + * @default 0 + */ + _tableHeight: 0, + /** + * css class for container which the table is wrapped in. + * @private + * @property CLASS_TABLE_WRAPPER + * @type {string} + * @private + * @default "dt-colresizable-table-wrapper" + */ + CLASS_TABLE_WRAPPER: "dt-colresizable-table-wrapper", + /** + * css class for draggable container + * @private + * @property CLASS_WRAPPER + * @type {string} + * @private + * @default "dt-colresizable" + */ + CLASS_WRAPPER: "dt-colresizable", + /** + * css class for draggable column + * @private + * @property CLASS_COLUMN + * @type {string} + * @private + * @default "dt-colresizable-col" + */ + CLASS_COLUMN: "dt-colresizable-col", + /** + * css class for css conditional scroller identifier + * @private + * @property CLASS_SCROLLER_HASWRAPPER + * @type {string} + * @private + * @default "dt-colresizable-with-scroller" + */ + CLASS_SCROLLER_HASWRAPPER: "dt-colresizable-with-scroller", + /** + * css class for scroller wrapper div + * @private + * @property CLASS_SCROLLER_WRAPPER + * @type {string} + * @private + * @default "dt-colresizable-scroller-wrapper" + */ + CLASS_SCROLLER_WRAPPER: "dt-colresizable-scroller-wrapper", + /** + * css class for scroller content mimic div + * @private + * @property CLASS_SCROLLER_CONTENT_WRAPPER + * @type {string} + * @private + * @default "dt-colresizable-scroller-content-wrapper" + */ + CLASS_SCROLLER_CONTENT_WRAPPER: "dt-colresizable-scroller-content-wrapper", + /** + * data tag name to save the column width + * saved on the draggable col
+ * @private + * @property DATA_TAG_WIDTH + * @type {string} + * @private + * @default "dt-colresizable-width" + */ + DATA_TAG_WIDTH: "dt-colresizable-width", + /** + * data tag name to save the column item
+ * saved on the draggable col
+ * @private + * @property DATA_TAG_ITEM + * @type {string} + * @private + * @default "dt-colresizable-item" + */ + DATA_TAG_ITEM: "dt-colresizable-item", + /** + * data tag name to save the previous draggable column + * element + * @private + * @property DATA_TAG_PREV_COL + * @type {string} + * @private + * @default "dt-prev-col-item" + */ + DATA_TAG_PREV_COL: "dt-prev-col-item", + /** + * @method init + * @param {object} dtInstance + */ + init: function(dtInstance) { + var that = this; + that._dtInstance = dtInstance; + that._table = $(that._dtInstance.table().node()); + that._tableBody = that._table.find("tbody"); + that._tableHeaders = that._table.find("thead > tr:first-child > th"); + that._addEvent(that._table, "stateSaveParams.dt", function(_event, _settings, data) { + that._storeWidthData(data); + }); + that.buildDom(); + that._addEvent(that._table, "destroy.dt", function() { + that.destroy(); + }, true); + }, + /** + * Builds the draggable components together and places + * them in the DOM (above the actual table) + * @method buildDom + * @returns null + */ + buildDom: function() { + var that = this, + redraw = !!(that._wrapper), + defaultWidths; + // wrap the table so that the overflow can be controlled when + // resizing a big table on a small screen + if (!redraw) { + // wrapper check is needed for "redraw()", since this is left over and + // the table shouldn't be "re-wrapped" + that._table.wrap("
"); + that._wrapper = that._table.parent(); + } + + // build the column resize container and draggable bars + that._container = $("
"); + + if (!redraw && that.options.scrollY) { + that.initScroller(); + } + //get any default widths from the store + defaultWidths = that.getStoreWidths(); + // set the table dimensions correctly + that.calcTableDimensions(defaultWidths); + // build and insert columns into container + that._container.append(that.buildColDoms()); + // resave the state, they get lost the second time. + if (defaultWidths && defaultWidths.length) { + that._dtInstance.state.save(); + } + // cache jQuery columns + that._columns = $("." + that.CLASS_COLUMN, that._container); + that._table.before(that._container); + that.checkTableHeight(); + that.initEvents(); + }, + /** + * Get all the widths for columns out of the datatable storage when state saving is enabled + * @returns {Array|null} + */ + getStoreWidths: function() { + var that = this, + state = that._dtInstance.state.loaded(); + if (state && state.colResize && state.colResize.widths) { + return state.colResize.widths; + } + }, + /** + * Register all events needed on ColResize init + * @method initEvents + * @returns null + */ + initEvents: function() { + var that = this; + that._addEvent(that._table, "draw.dt", function() { + // set timeout so that table dom manipulation can be done first + setTimeout(function() { + that.checkTableHeight(); + }, 0); + }); + that._addEvent(that._dtInstance, "column-reorder", function() { + that.redraw(); + }, true); + that._addEvent(that._table, "column-visibility.dt", function() { + that.redraw(); + }, true); + if (that.options.scrollY) { + that._addEvent(that._wrapper, "scroll", function() { + that._scrollWrapper.css("right", 0 - that._wrapper.scrollLeft()); + }); + } + }, + /** + * Initialises the table width and height + * @method calcTableDimensions + * @param widths {Array|null} [default=null] if passed then sets the + * default width state of the columns + * @returns null + */ + calcTableDimensions: function(widths) { + var that = this, + $th, + thWidth = 0, + $ths = that._tableHeaders, // get all table headers + totalWidth = 0; + for (var i = 0, l = $ths.length; i < l; i++) { + $th = $ths.eq(i); // get individual
+ thWidth = widths ? widths[i] : that._getWidth($th); // get current/correct width + $th.css("width", thWidth); + totalWidth += thWidth; + } + // set the table width correctly + that._table.css("width", totalWidth); + // and it's container + if (that.options.scrollY) { + totalWidth = totalWidth + 20; + } + that._container.width(totalWidth); + }, + /** + * Creates the draggable columns, add the necessary drag events + * @method buildColDoms + * @return jQuery[] actual draggable columns as jquery objects + */ + buildColDoms: function() { + // replicate table header widths + var that = this, + $ths = that._tableHeaders, // get all table headers + $th, + $cols = [], + $col, + thWidth = 0; + + for (var i = 0, l = $ths.length; i < l; i++) { + $th = $ths.eq(i); // get individual + thWidth = $th.outerWidth(); // get current width + $col = $("
"); // create drag column item
+ // place the drag column at the end of the
and as tall as the table itself + $col.css({ + left: Math.ceil($th.position().left + thWidth) + }); + // save the prev col item for speed rather than using the .prev() function + $col.data(that.DATA_TAG_PREV_COL, $cols[ i - 1 ]); + // save the current width + $col.data(that.DATA_TAG_WIDTH, thWidth); + // save the element reference for easy access later + $col.data(that.DATA_TAG_ITEM, $th); + // register necessary events + that.registerEvents($col); + // push created drag column element in array + $cols.push($col); + } + return $cols; + }, + + _storeWidthData: function(data) { + var that = this, + $ths = that._tableHeaders, + $th, + widths = []; + for (var i = 0, l = $ths.length; i < l; i++) { + $th = $ths.eq(i); // get individual + widths.push(that._getWidth($th)); // get current/correct width + } + data.colResize = { + widths: widths + } + }, + /** + * Get the current or correct th element width. + * If the th element has an inline width set "style='width: 100px'" then this + * the return value. If it doesn't, then the calculated "outerWidth()" is returned. + * + * minColumnWidth value always wins if greater than the calculated width. + * @method _getWidth + * @param $th {jQuery} th jquery element + * @returns {integer|float} + */ + _getWidth: function($th) { + var that = this, + width; + if (typeof $th[0].style.width === "string" && $th[0].style.width.indexOf("px") >= 1) { + width = parseFloat($th[0].style.width); + } else { + width = $th.outerWidth(); + } + return width < that.options.minColumnWidth ? that.options.minColumnWidth : width; + }, + /** + * Registers the required drag events on the specified column. + * @method registerEvents + * @param $col {jQuery} + * @returns {null} + */ + registerEvents: function($col) { + var that = this; + $col.mousedown(that.onMouseDown()); + }, + /** + * Returns the mousedown event function to be used by jQuery.mousedown() + * @method onMouseDown + * @returns {function} + */ + onMouseDown: function() { + var that = this; + return function() { + var $col = $(this), + mouseMoveFunc = function(event) { + that.onMouseMove(event, $col); + }; + that._isDragging = true; + // check if the use has "let go of dragging" + // mouse up + $(document).one("mouseup", function() { + // no longer dragging the column + that._isDragging = false; + // remove the drag (mousemove) event listener + $(document).off("mousemove", mouseMoveFunc); + $(that._table).trigger($.Event( "column-resized.dt" ), [$col.index(), $col.data(that.DATA_TAG_ITEM).outerWidth()] ); + that._dtInstance.state.save(); + }).on("mousemove", mouseMoveFunc); //on mousemove + return false; // stop text highlighting + }; + }, + /** + * Used by jQuery.mousemove to determine the new column widths when a drag action is undertaken + * @method onMouseMove + * @param {MouseEvent} event + * @param {jQuery} $col + * @returns {null} + */ + onMouseMove: function(event, $col) { + var that = this, + diff = 0, + $nextCol, + posPlusDiff; + if (that._isDragging) { + // caculate the difference between where the mouse has moved to + // and the left position of the column that is being dragged + diff = Math.ceil(event.clientX - $col.offset().left); + $nextCol = $col.next(); + posPlusDiff = Math.ceil($col.position().left + diff); + if ($nextCol.length) { + // check whether neighbouring is still bigger than 10px if a resize + // takes place. + if (posPlusDiff < ($nextCol.position().left - that.options.minColumnWidth)) { + if (that.updateColumn($col, diff)) { + if (!that.options.resizeTable) { + // col was resized so resize the neighbouring col too. + that.updateColumn($nextCol, diff < 0 ? Math.abs(diff) : -Math.abs(diff), true); + } else { + that._recalcPositions(); + } + } + } + } else { + // if we are expanding the last column + // or when shrinking: don't allow it to shrink smaller than the minColumnWidth + if(diff > 0 || (posPlusDiff > $col.prev().position().left + that.options.minColumnWidth) ) { + if(that.updateColumn($col, diff)) { + // very last col drag bar is being dragged here (expanded) + that.updateTableOnLastColumnMove($col, diff); + } + } + } + that.checkTableHeight(); + } + }, + updateTableOnLastColumnMove: function($col, diff) { + var that = this; + // update the table width with the next size to prevent the other columns + // going crazy + $col.css({ + left: Math.ceil($col.position().left + diff) + }); + that.calcTableDimensions(); + }, + /** + * Update the column width by a given number + * @method updateColumn + * @param {jQuery} $col - column that needs a size adjustment + * @param {integer} by - width to change the column size by + * @param {boolean} nextColumn [default=false] - set to true if the column being resized is not the original but + * it's sibling. + * @return {boolean} {true} if resize was possible, {false} if not; + */ + updateColumn: function($col, by, nextColumn) { + var that = this, + // calculate the new width of the column + newWidth = Math.ceil(by + $col.data(that.DATA_TAG_WIDTH)); + //only resize to a min of 10px + if (newWidth > that.options.minColumnWidth) { + // get the actual column of the table and set the new width + $col.data(that.DATA_TAG_ITEM).css({ + width: newWidth + }); + // save the new width for the next mouse drag call + $col.data(that.DATA_TAG_WIDTH, newWidth); + if(nextColumn) { + // set the new let position of the dragged column (div) + $col.data(that.DATA_TAG_PREV_COL).css({ + left: Math.ceil($col.data(that.DATA_TAG_ITEM).position().left) + }); + } + if (that.options.scrollY) { + that.updateCells($col.index(), newWidth); + } + return true; + } + return false; + }, + /** + * Repositions all draggable columns and recalculates + * all table dimensions + * @method _recalcPositions + * @returns null + */ + _recalcPositions: function() { + var that = this, + pos = that._table.position().left, + $th; + for (var i = 0, l = that._tableHeaders.length; i < l; i++) { + $th = that._tableHeaders.eq(i); + pos = pos + $th.outerWidth(); + that._columns.eq(i).css("left", pos); + } + that.calcTableDimensions(); + }, + /** + * Checks whether the height of the table has changed, + * if it has, then it set the draggable column items with + * the new height values. + * @method checkTableHeight + * @returns null + */ + checkTableHeight: function() { + var that = this, + topMarg = 0, + newHeight = that._table.outerHeight(); + if (newHeight !== that._tableHeight) { + that._tableHeight = newHeight; + // convert the "px" value to just a number + topMarg = parseFloat(that._table.css("margin-top")); + // if the table is empty, then don't show the column bars inside the tbody + // as they overlap the empty text which has colspan across the table. + if (that._tableBody.find("td.dataTables_empty").length) { + newHeight = (newHeight - (newHeight - that._tableBody.position().top)) - topMarg ; + } + //set the the position and height of all the draggable columns + that._columns.css({ + height: newHeight, + top: topMarg + }); + } + + }, + /** + * Initialise the vertical scrolling feature (scrollY) + * @method initScroller + * @returns null + */ + initScroller: function() { + var that = this; + // Build required DOM elements. + that.buildScrollerDom(); + // register when a scroll is performed inside the wrapping div + // this then forces the tbody to scroll in-sync. + that._scrollWrapper.on("scroll", that.onScroll()); + that._addEvent(that._table, "draw.dt", function() { + // set timeout so that table dom manipulation can be done first + setTimeout(function() { + that.syncRows(); + that.syncHeight(); + }, 0); + }); + }, + /** + * Builds the required dom elements together for vertical scrolling + * @method buildScrollerDom + * @returns null + */ + buildScrollerDom: function() { + var that = this; + // add class to wrapper for better css conditional selection + that._wrapper.addClass(that.CLASS_SCROLLER_HASWRAPPER); + // scroll wrapper container - where the scroll-y bar appears + that._scrollWrapper = $("
"); + // move it over the tbody content + that._scrollWrapper.css("margin-top", that._tableBody.position().top); + // create an inner div to mimic the height of the tbody content + that._scrollContent = $("
"); + // shrink the wrapper to the defined height so that the scroll bar appears + that._scrollWrapper.height(that.options.scrollY); + // fix the content to the tbody original height + that._scrollContent.height(that._tableBody.height()); + // resize the tbody to the desired height - same as the overlapping wrapper div + that._tableBody.height(that.options.scrollY); + + // just wide enough to show the scrollbar + that._scrollWrapper.width(20); + + // hide the overflowing (y) tbody content + that._tableBody.css("overflow-y", "hidden"); + // add all the new scroll controlling divs + that._scrollWrapper.append(that._scrollContent); + that._wrapper.prepend(that._scrollWrapper); + }, + /** + * Event listener function for the scroll wrapper scrolling events + * @method onScroll + * @returns {function} + */ + onScroll: function() { + var that = this, + scrollWrapper = that._scrollWrapper; + return function() { + // scroll the tbody accordingly + // i.e. keep the tbody scroll in-sync with the scrolling wrapper + that._tableBody.scrollTop(scrollWrapper.scrollTop()); + }; + }, + /** + * Update the cells within this column to align with the header on column resize. + * @method updateCells + * @param {integer} index column index which was changed + * @param {interger} width the new width of the column in pixels + */ + updateCells: function(index, width) { + var that = this, + $trs = that._tableBody.find("tr"); + for (var i = 0, l = $trs.length; i < l; i++) { + $trs.eq(i).find("td").eq(index).css("width", width); + } + if(that._updatedColumns.indexOf(index) < 0) { + that._updatedColumns.push(index); + } + }, + /** + * After a pagination DataTables draws the cells new, + * this function adjusts the cells back to the correct width + * in sync with the headers + * @method syncRows + * @returns null + */ + syncRows: function() { + var that = this, + $ths, + $trs, + index; + if (that._updatedColumns.length) { + $ths = that._table.find("thead th"); + $trs = that._tableBody.find("tr"); + for(var i = 0, l = $trs.length; i < l; i++) { + for(var ii = 0, ll = that._updatedColumns.length; ii < ll; ii++) { + index = that._updatedColumns[ii]; + $trs.eq(i).find("td").eq(index).css("width", $ths.eq(index).css("width")); + } + } + } + }, + /** + * After a draw (like page change), the content height might not be longer + * than the set scrollY value. Therefore the Y scroll bar might not be needed. + * Update the content height accordingly to show or hide the scroll bar. + * @method syncHeight + * @returns null + */ + syncHeight: function() { + var that = this, + // make the tbody full height (auto) again to get the new content height + height = that._tableBody.css("height", "auto").height(); + // reset the tbody height back after calculation + that._tableBody.height(that.options.scrollY); + // adjust scroll content div to the new table height + // this is needed if the content of the next page for example doesn't fill the entire height + // but the scroll bar is still visible + that._scrollContent.height(height); + }, + /** + * @method destroy + * @returns null + */ + destroy: function() { + var that = this; + that._container.remove(); + that._columns = []; + that._updatedColumns = []; + that._tableHeight = null; + // remove al the events that were registered using the _addEvent(...) method + for (var key in that._events) { + if (that._events.hasOwnProperty(key)) { + var arr = that._events[key]; + for(var i = 0, l = arr.length; i 0 + || $js_options['datatables_colreorder_fixright'] > 0 + ) { + // Register the JS files. + $url = plugins_url( "js/dataTables.colReorder.js", __FILE__ ); + wp_enqueue_script( 'tablepress-datatables-colreorder', $url, array( 'tablepress-datatables' ), self::$version, true ); + + $needJS = TRUE; + } + if ( $js_options['datatables_colresize'] != '' + || $js_options['datatables_colresize_minwidth'] > 0 + || $js_options['datatables_colresize_scrolly'] > 0 + || $js_options['datatables_colresize_resizetable'] != '' + ) { + // Register the JS files. + $url = plugins_url( "js/dataTables.colResize.js", __FILE__ ); + wp_enqueue_script( 'tablepress-datatables-colresize', $url, array( 'tablepress-datatables' ), self::$version, true ); + + $needJS = TRUE; + } + // Add the common filter that adds JS for all calls on the page. + if ( $needJS && ! has_filter( 'tablepress_all_datatables_commands', array( __CLASS__, 'all_datatables_commands' ) ) + ) { + add_filter( 'tablepress_all_datatables_commands', array( __CLASS__, 'all_datatables_commands' ) ); + } + + return $js_options; + } + + /** + * Evaluate JS parameters and convert them to DataTables parameters. + * + * @since 0.1 + * + * @param array $parameters DataTables parameters. + * @param string $table_id Table ID. + * @param string $html_id HTML ID of the table. + * @param array $js_options JS options for DataTables. + * @return array Extended DataTables parameters. + */ + public static function datatables_parameters( $parameters, $table_id, $html_id, $js_options ) { + // Construct the DataTables ColReorder config parameter. + $parameters['colReorder'] = array(); + if ($js_options['datatables_colreorder_realtime'] != '') { + $parameters['colReorder'][] = "\"realtime\":{$js_options['datatables_colreorder_realtime']}"; + } + if ($js_options['datatables_colreorder_fixleft'] > 0) { + $parameters['colReorder'][] = "\"fixedColumnsLeft\":{$js_options['datatables_colreorder_fixleft']}"; + } + if ($js_options['datatables_colreorder_fixright'] > 0) { + $parameters['colReorder'][] = "\"fixedColumnsRight\":{$js_options['datatables_colreorder_fixright']}"; + } + if (empty($parameters['colReorder'])) { + if ($js_options['datatables_colreorder'] != '' + && $js_options['datatables_colreorder'] != 'false') + { + $parameters['colReorder'] = '"colReorder":true'; + } else { + unset($parameters['colReorder']); + } + } else { + $parameters['colReorder'] = '"colReorder":{' . implode(',', $parameters['colReorder']) . '}'; + } + // Construct the DataTables ColResize config parameter. + $parameters['colResize'] = array(); + if ($js_options['datatables_colresize_minwidth'] > 0) { + $parameters['colResize'][] = "\"minColumnWidth\":{$js_options['datatables_colresize_minwidth']}"; + } + if ($js_options['datatables_colresize_scrolly'] > 0) { + $parameters['colResize'][] = "\"scrollY\":{$js_options['datatables_colresize_scrolly']}"; + } + if ($js_options['datatables_colresize_resizetable'] != '') { + if ($js_options['datatables_colresize_resizetable'] != 'false') { + $parameters['colResize'][] = "\"resizeTable\":true"; + } else { + $parameters['colResize'][] = "\"resizeTable\":false"; + } + } + if (empty($parameters['colResize'])) { + if ($js_options['datatables_colresize'] != '' + && ($js_options['datatables_colresize'] != 'false')) + { + $parameters['colResize'] = '"colResize":true'; + } else { + unset($parameters['colResize']); + } + } else { + $parameters['colResize'] = '"colResize":{' . implode(',', $parameters['colResize']) . '}'; + } + if (isset($parameters['colResize'])) { + $parameters['autoWidth'] = '"autoWidth":false'; + $parameters['scrollX'] = '"scrollX":false'; + } + return $parameters; + } + + /** + * Add jQuery code that adds the necessary CSS for the Extension, instead of loading that CSS from a file on all pages. + * + * @since 0.1 + * + * @param array $commands The JS commands for the DataTables JS library. + * @return array Modified JS commands for the DataTables JS library. + */ + public static function all_datatables_commands( $commands ) { + // First the ColReorder CSS + $commands = "$('head').append('');\n" . $commands; + + // Now the ColResize CSS + $commands = "$('head').append('