summaryrefslogtreecommitdiff
path: root/src/main/resources/static/plugins/datatables-autofill/js/dataTables.autoFill.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/resources/static/plugins/datatables-autofill/js/dataTables.autoFill.js')
-rw-r--r--src/main/resources/static/plugins/datatables-autofill/js/dataTables.autoFill.js1212
1 files changed, 1212 insertions, 0 deletions
diff --git a/src/main/resources/static/plugins/datatables-autofill/js/dataTables.autoFill.js b/src/main/resources/static/plugins/datatables-autofill/js/dataTables.autoFill.js
new file mode 100644
index 0000000..bc13b7d
--- /dev/null
+++ b/src/main/resources/static/plugins/datatables-autofill/js/dataTables.autoFill.js
@@ -0,0 +1,1212 @@
+/*! AutoFill 2.3.9
+ * ©2008-2021 SpryMedia Ltd - datatables.net/license
+ */
+
+/**
+ * @summary AutoFill
+ * @description Add Excel like click and drag auto-fill options to DataTables
+ * @version 2.3.9
+ * @file dataTables.autoFill.js
+ * @author SpryMedia Ltd (www.sprymedia.co.uk)
+ * @contact www.sprymedia.co.uk/contact
+ * @copyright Copyright 2010-2021 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;
+
+
+var _instance = 0;
+
+/**
+ * AutoFill provides Excel like auto-fill features for a DataTable
+ *
+ * @class AutoFill
+ * @constructor
+ * @param {object} oTD DataTables settings object
+ * @param {object} oConfig Configuration object for AutoFill
+ */
+var AutoFill = function( dt, opts )
+{
+ if ( ! DataTable.versionCheck || ! DataTable.versionCheck( '1.10.8' ) ) {
+ throw( "Warning: AutoFill requires DataTables 1.10.8 or greater");
+ }
+
+ // User and defaults configuration object
+ this.c = $.extend( true, {},
+ DataTable.defaults.autoFill,
+ AutoFill.defaults,
+ opts
+ );
+
+ /**
+ * @namespace Settings object which contains customisable information for AutoFill instance
+ */
+ this.s = {
+ /** @type {DataTable.Api} DataTables' API instance */
+ dt: new DataTable.Api( dt ),
+
+ /** @type {String} Unique namespace for events attached to the document */
+ namespace: '.autoFill'+(_instance++),
+
+ /** @type {Object} Cached dimension information for use in the mouse move event handler */
+ scroll: {},
+
+ /** @type {integer} Interval object used for smooth scrolling */
+ scrollInterval: null,
+
+ handle: {
+ height: 0,
+ width: 0
+ },
+
+ /**
+ * Enabled setting
+ * @type {Boolean}
+ */
+ enabled: false
+ };
+
+
+ /**
+ * @namespace Common and useful DOM elements for the class instance
+ */
+ this.dom = {
+ /** @type {jQuery} AutoFill handle */
+ handle: $('<div class="dt-autofill-handle"/>'),
+
+ /**
+ * @type {Object} Selected cells outline - Need to use 4 elements,
+ * otherwise the mouse over if you back into the selected rectangle
+ * will be over that element, rather than the cells!
+ */
+ select: {
+ top: $('<div class="dt-autofill-select top"/>'),
+ right: $('<div class="dt-autofill-select right"/>'),
+ bottom: $('<div class="dt-autofill-select bottom"/>'),
+ left: $('<div class="dt-autofill-select left"/>')
+ },
+
+ /** @type {jQuery} Fill type chooser background */
+ background: $('<div class="dt-autofill-background"/>'),
+
+ /** @type {jQuery} Fill type chooser */
+ list: $('<div class="dt-autofill-list">'+this.s.dt.i18n('autoFill.info', '')+'<ul/></div>'),
+
+ /** @type {jQuery} DataTables scrolling container */
+ dtScroll: null,
+
+ /** @type {jQuery} Offset parent element */
+ offsetParent: null
+ };
+
+
+ /* Constructor logic */
+ this._constructor();
+};
+
+
+
+$.extend( AutoFill.prototype, {
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Public methods (exposed via the DataTables API below)
+ */
+ enabled: function ()
+ {
+ return this.s.enabled;
+ },
+
+
+ enable: function ( flag )
+ {
+ var that = this;
+
+ if ( flag === false ) {
+ return this.disable();
+ }
+
+ this.s.enabled = true;
+
+ this._focusListener();
+
+ this.dom.handle.on( 'mousedown', function (e) {
+ that._mousedown( e );
+ return false;
+ } );
+
+ return this;
+ },
+
+ disable: function ()
+ {
+ this.s.enabled = false;
+
+ this._focusListenerRemove();
+
+ return this;
+ },
+
+
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Constructor
+ */
+
+ /**
+ * Initialise the RowReorder instance
+ *
+ * @private
+ */
+ _constructor: function ()
+ {
+ var that = this;
+ var dt = this.s.dt;
+ var dtScroll = $('div.dataTables_scrollBody', this.s.dt.table().container());
+
+ // Make the instance accessible to the API
+ dt.settings()[0].autoFill = this;
+
+ if ( dtScroll.length ) {
+ this.dom.dtScroll = dtScroll;
+
+ // Need to scroll container to be the offset parent
+ if ( dtScroll.css('position') === 'static' ) {
+ dtScroll.css( 'position', 'relative' );
+ }
+ }
+
+ if ( this.c.enable !== false ) {
+ this.enable();
+ }
+
+ dt.on( 'destroy.autoFill', function () {
+ that._focusListenerRemove();
+ } );
+ },
+
+
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Private methods
+ */
+
+ /**
+ * Display the AutoFill drag handle by appending it to a table cell. This
+ * is the opposite of the _detach method.
+ *
+ * @param {node} node TD/TH cell to insert the handle into
+ * @private
+ */
+ _attach: function ( node )
+ {
+ var dt = this.s.dt;
+ var idx = dt.cell( node ).index();
+ var handle = this.dom.handle;
+ var handleDim = this.s.handle;
+
+ if ( ! idx || dt.columns( this.c.columns ).indexes().indexOf( idx.column ) === -1 ) {
+ this._detach();
+ return;
+ }
+
+ if ( ! this.dom.offsetParent ) {
+ // We attach to the table's offset parent
+ this.dom.offsetParent = $( dt.table().node() ).offsetParent();
+ }
+
+ if ( ! handleDim.height || ! handleDim.width ) {
+ // Append to document so we can get its size. Not expecting it to
+ // change during the life time of the page
+ handle.appendTo( 'body' );
+ handleDim.height = handle.outerHeight();
+ handleDim.width = handle.outerWidth();
+ }
+
+ // Might need to go through multiple offset parents
+ var offset = this._getPosition( node, this.dom.offsetParent );
+
+ this.dom.attachedTo = node;
+ handle
+ .css( {
+ top: offset.top + node.offsetHeight - handleDim.height,
+ left: offset.left + node.offsetWidth - handleDim.width
+ } )
+ .appendTo( this.dom.offsetParent );
+ },
+
+
+ /**
+ * Determine can the fill type should be. This can be automatic, or ask the
+ * end user.
+ *
+ * @param {array} cells Information about the selected cells from the key
+ * up function
+ * @private
+ */
+ _actionSelector: function ( cells )
+ {
+ var that = this;
+ var dt = this.s.dt;
+ var actions = AutoFill.actions;
+ var available = [];
+
+ // "Ask" each plug-in if it wants to handle this data
+ $.each( actions, function ( key, action ) {
+ if ( action.available( dt, cells ) ) {
+ available.push( key );
+ }
+ } );
+
+ if ( available.length === 1 && this.c.alwaysAsk === false ) {
+ // Only one action available - enact it immediately
+ var result = actions[ available[0] ].execute( dt, cells );
+ this._update( result, cells );
+ }
+ else if ( available.length > 1 ) {
+ // Multiple actions available - ask the end user what they want to do
+ var list = this.dom.list.children('ul').empty();
+
+ // Add a cancel option
+ available.push( 'cancel' );
+
+ $.each( available, function ( i, name ) {
+ list.append( $('<li/>')
+ .append(
+ '<div class="dt-autofill-question">'+
+ actions[ name ].option( dt, cells )+
+ '<div>'
+ )
+ .append( $('<div class="dt-autofill-button">' )
+ .append( $('<button class="'+AutoFill.classes.btn+'">'+dt.i18n('autoFill.button', '&gt;')+'</button>')
+ .on( 'click', function () {
+ var result = actions[ name ].execute(
+ dt, cells, $(this).closest('li')
+ );
+ that._update( result, cells );
+
+ that.dom.background.remove();
+ that.dom.list.remove();
+ } )
+ )
+ )
+ );
+ } );
+
+ this.dom.background.appendTo( 'body' );
+ this.dom.list.appendTo( 'body' );
+
+ this.dom.list.css( 'margin-top', this.dom.list.outerHeight()/2 * -1 );
+ }
+ },
+
+
+ /**
+ * Remove the AutoFill handle from the document
+ *
+ * @private
+ */
+ _detach: function ()
+ {
+ this.dom.attachedTo = null;
+ this.dom.handle.detach();
+ },
+
+
+ /**
+ * Draw the selection outline by calculating the range between the start
+ * and end cells, then placing the highlighting elements to draw a rectangle
+ *
+ * @param {node} target End cell
+ * @param {object} e Originating event
+ * @private
+ */
+ _drawSelection: function ( target, e )
+ {
+ // Calculate boundary for start cell to this one
+ var dt = this.s.dt;
+ var start = this.s.start;
+ var startCell = $(this.dom.start);
+ var end = {
+ row: this.c.vertical ?
+ dt.rows( { page: 'current' } ).nodes().indexOf( target.parentNode ) :
+ start.row,
+ column: this.c.horizontal ?
+ $(target).index() :
+ start.column
+ };
+ var colIndx = dt.column.index( 'toData', end.column );
+ var endRow = dt.row( ':eq('+end.row+')', { page: 'current' } ); // Workaround for M581
+ var endCell = $( dt.cell( endRow.index(), colIndx ).node() );
+
+ // Be sure that is a DataTables controlled cell
+ if ( ! dt.cell( endCell ).any() ) {
+ return;
+ }
+
+ // if target is not in the columns available - do nothing
+ if ( dt.columns( this.c.columns ).indexes().indexOf( colIndx ) === -1 ) {
+ return;
+ }
+
+ this.s.end = end;
+
+ var top, bottom, left, right, height, width;
+
+ top = start.row < end.row ? startCell : endCell;
+ bottom = start.row < end.row ? endCell : startCell;
+ left = start.column < end.column ? startCell : endCell;
+ right = start.column < end.column ? endCell : startCell;
+
+ top = this._getPosition( top.get(0) ).top;
+ left = this._getPosition( left.get(0) ).left;
+ height = this._getPosition( bottom.get(0) ).top + bottom.outerHeight() - top;
+ width = this._getPosition( right.get(0) ).left + right.outerWidth() - left;
+
+ var select = this.dom.select;
+ select.top.css( {
+ top: top,
+ left: left,
+ width: width
+ } );
+
+ select.left.css( {
+ top: top,
+ left: left,
+ height: height
+ } );
+
+ select.bottom.css( {
+ top: top + height,
+ left: left,
+ width: width
+ } );
+
+ select.right.css( {
+ top: top,
+ left: left + width,
+ height: height
+ } );
+ },
+
+
+ /**
+ * Use the Editor API to perform an update based on the new data for the
+ * cells
+ *
+ * @param {array} cells Information about the selected cells from the key
+ * up function
+ * @private
+ */
+ _editor: function ( cells )
+ {
+ var dt = this.s.dt;
+ var editor = this.c.editor;
+
+ if ( ! editor ) {
+ return;
+ }
+
+ // Build the object structure for Editor's multi-row editing
+ var idValues = {};
+ var nodes = [];
+ var fields = editor.fields();
+
+ for ( var i=0, ien=cells.length ; i<ien ; i++ ) {
+ for ( var j=0, jen=cells[i].length ; j<jen ; j++ ) {
+ var cell = cells[i][j];
+
+ // Determine the field name for the cell being edited
+ var col = dt.settings()[0].aoColumns[ cell.index.column ];
+ var fieldName = col.editField;
+
+ if ( fieldName === undefined ) {
+ var dataSrc = col.mData;
+
+ // dataSrc is the `field.data` property, but we need to set
+ // using the field name, so we need to translate from the
+ // data to the name
+ for ( var k=0, ken=fields.length ; k<ken ; k++ ) {
+ var field = editor.field( fields[k] );
+
+ if ( field.dataSrc() === dataSrc ) {
+ fieldName = field.name();
+ break;
+ }
+ }
+ }
+
+ if ( ! fieldName ) {
+ throw 'Could not automatically determine field data. '+
+ 'Please see https://datatables.net/tn/11';
+ }
+
+ if ( ! idValues[ fieldName ] ) {
+ idValues[ fieldName ] = {};
+ }
+
+ var id = dt.row( cell.index.row ).id();
+ idValues[ fieldName ][ id ] = cell.set;
+
+ // Keep a list of cells so we can activate the bubble editing
+ // with them
+ nodes.push( cell.index );
+ }
+ }
+
+ // Perform the edit using bubble editing as it allows us to specify
+ // the cells to be edited, rather than using full rows
+ editor
+ .bubble( nodes, false )
+ .multiSet( idValues )
+ .submit();
+ },
+
+
+ /**
+ * Emit an event on the DataTable for listeners
+ *
+ * @param {string} name Event name
+ * @param {array} args Event arguments
+ * @private
+ */
+ _emitEvent: function ( name, args )
+ {
+ this.s.dt.iterator( 'table', function ( ctx, i ) {
+ $(ctx.nTable).triggerHandler( name+'.dt', args );
+ } );
+ },
+
+
+ /**
+ * Attach suitable listeners (based on the configuration) that will attach
+ * and detach the AutoFill handle in the document.
+ *
+ * @private
+ */
+ _focusListener: function ()
+ {
+ var that = this;
+ var dt = this.s.dt;
+ var namespace = this.s.namespace;
+ var focus = this.c.focus !== null ?
+ this.c.focus :
+ dt.init().keys || dt.settings()[0].keytable ?
+ 'focus' :
+ 'hover';
+
+ // All event listeners attached here are removed in the `destroy`
+ // callback in the constructor
+ if ( focus === 'focus' ) {
+ dt
+ .on( 'key-focus.autoFill', function ( e, dt, cell ) {
+ that._attach( cell.node() );
+ } )
+ .on( 'key-blur.autoFill', function ( e, dt, cell ) {
+ that._detach();
+ } );
+ }
+ else if ( focus === 'click' ) {
+ $(dt.table().body()).on( 'click'+namespace, 'td, th', function (e) {
+ that._attach( this );
+ } );
+
+ $(document.body).on( 'click'+namespace, function (e) {
+ if ( ! $(e.target).parents().filter( dt.table().body() ).length ) {
+ that._detach();
+ }
+ } );
+ }
+ else {
+ $(dt.table().body())
+ .on( 'mouseenter'+namespace, 'td, th', function (e) {
+ that._attach( this );
+ } )
+ .on( 'mouseleave'+namespace, function (e) {
+ if ( $(e.relatedTarget).hasClass('dt-autofill-handle') ) {
+ return;
+ }
+
+ that._detach();
+ } );
+ }
+ },
+
+
+ _focusListenerRemove: function ()
+ {
+ var dt = this.s.dt;
+
+ dt.off( '.autoFill' );
+ $(dt.table().body()).off( this.s.namespace );
+ $(document.body).off( this.s.namespace );
+ },
+
+
+ /**
+ * Get the position of a node, relative to another, including any scrolling
+ * offsets.
+ * @param {Node} node Node to get the position of
+ * @param {jQuery} targetParent Node to use as the parent
+ * @return {object} Offset calculation
+ * @private
+ */
+ _getPosition: function ( node, targetParent )
+ {
+ var
+ currNode = node,
+ currOffsetParent,
+ top = 0,
+ left = 0;
+
+ if ( ! targetParent ) {
+ targetParent = $( $( this.s.dt.table().node() )[0].offsetParent );
+ }
+
+ do {
+ // Don't use jQuery().position() the behaviour changes between 1.x and 3.x for
+ // tables
+ var positionTop = currNode.offsetTop;
+ var positionLeft = currNode.offsetLeft;
+
+ // jQuery doesn't give a `table` as the offset parent oddly, so use DOM directly
+ currOffsetParent = $( currNode.offsetParent );
+
+ top += positionTop + parseInt( currOffsetParent.css('border-top-width') || 0 ) * 1;
+ left += positionLeft + parseInt( currOffsetParent.css('border-left-width') || 0 ) * 1;
+
+ // Emergency fall back. Shouldn't happen, but just in case!
+ if ( currNode.nodeName.toLowerCase() === 'body' ) {
+ break;
+ }
+
+ currNode = currOffsetParent.get(0); // for next loop
+ }
+ while ( currOffsetParent.get(0) !== targetParent.get(0) )
+
+ return {
+ top: top,
+ left: left
+ };
+ },
+
+
+ /**
+ * Start mouse drag - selects the start cell
+ *
+ * @param {object} e Mouse down event
+ * @private
+ */
+ _mousedown: function ( e )
+ {
+ var that = this;
+ var dt = this.s.dt;
+
+ this.dom.start = this.dom.attachedTo;
+ this.s.start = {
+ row: dt.rows( { page: 'current' } ).nodes().indexOf( $(this.dom.start).parent()[0] ),
+ column: $(this.dom.start).index()
+ };
+
+ $(document.body)
+ .on( 'mousemove.autoFill', function (e) {
+ that._mousemove( e );
+ } )
+ .on( 'mouseup.autoFill', function (e) {
+ that._mouseup( e );
+ } );
+
+ var select = this.dom.select;
+ var offsetParent = $( dt.table().node() ).offsetParent();
+ select.top.appendTo( offsetParent );
+ select.left.appendTo( offsetParent );
+ select.right.appendTo( offsetParent );
+ select.bottom.appendTo( offsetParent );
+
+ this._drawSelection( this.dom.start, e );
+
+ this.dom.handle.css( 'display', 'none' );
+
+ // Cache scrolling information so mouse move doesn't need to read.
+ // This assumes that the window and DT scroller will not change size
+ // during an AutoFill drag, which I think is a fair assumption
+ var scrollWrapper = this.dom.dtScroll;
+ this.s.scroll = {
+ windowHeight: $(window).height(),
+ windowWidth: $(window).width(),
+ dtTop: scrollWrapper ? scrollWrapper.offset().top : null,
+ dtLeft: scrollWrapper ? scrollWrapper.offset().left : null,
+ dtHeight: scrollWrapper ? scrollWrapper.outerHeight() : null,
+ dtWidth: scrollWrapper ? scrollWrapper.outerWidth() : null
+ };
+ },
+
+
+ /**
+ * Mouse drag - selects the end cell and update the selection display for
+ * the end user
+ *
+ * @param {object} e Mouse move event
+ * @private
+ */
+ _mousemove: function ( e )
+ {
+ var that = this;
+ var dt = this.s.dt;
+ var name = e.target.nodeName.toLowerCase();
+ if ( name !== 'td' && name !== 'th' ) {
+ return;
+ }
+
+ this._drawSelection( e.target, e );
+ this._shiftScroll( e );
+ },
+
+
+ /**
+ * End mouse drag - perform the update actions
+ *
+ * @param {object} e Mouse up event
+ * @private
+ */
+ _mouseup: function ( e )
+ {
+ $(document.body).off( '.autoFill' );
+
+ var that = this;
+ var dt = this.s.dt;
+ var select = this.dom.select;
+ select.top.remove();
+ select.left.remove();
+ select.right.remove();
+ select.bottom.remove();
+
+ this.dom.handle.css( 'display', 'block' );
+
+ // Display complete - now do something useful with the selection!
+ var start = this.s.start;
+ var end = this.s.end;
+
+ // Haven't selected multiple cells, so nothing to do
+ if ( start.row === end.row && start.column === end.column ) {
+ return;
+ }
+
+ var startDt = dt.cell( ':eq('+start.row+')', start.column+':visible', {page:'current'} );
+
+ // If Editor is active inside this cell (inline editing) we need to wait for Editor to
+ // submit and then we can loop back and trigger the fill.
+ if ( $('div.DTE', startDt.node()).length ) {
+ var editor = dt.editor();
+
+ editor
+ .on( 'submitSuccess.dtaf close.dtaf', function () {
+ editor.off( '.dtaf');
+
+ setTimeout( function () {
+ that._mouseup( e );
+ }, 100 );
+ } )
+ .on( 'submitComplete.dtaf preSubmitCancelled.dtaf close.dtaf', function () {
+ editor.off( '.dtaf');
+ } );
+
+ // Make the current input submit
+ editor.submit();
+
+ return;
+ }
+
+ // Build a matrix representation of the selected rows
+ var rows = this._range( start.row, end.row );
+ var columns = this._range( start.column, end.column );
+ var selected = [];
+ var dtSettings = dt.settings()[0];
+ var dtColumns = dtSettings.aoColumns;
+ var enabledColumns = dt.columns( this.c.columns ).indexes();
+
+ // Can't use Array.prototype.map as IE8 doesn't support it
+ // Can't use $.map as jQuery flattens 2D arrays
+ // Need to use a good old fashioned for loop
+ for ( var rowIdx=0 ; rowIdx<rows.length ; rowIdx++ ) {
+ selected.push(
+ $.map( columns, function (column) {
+ var row = dt.row( ':eq('+rows[rowIdx]+')', {page:'current'} ); // Workaround for M581
+ var cell = dt.cell( row.index(), column+':visible' );
+ var data = cell.data();
+ var cellIndex = cell.index();
+ var editField = dtColumns[ cellIndex.column ].editField;
+
+ if ( editField !== undefined ) {
+ data = dtSettings.oApi._fnGetObjectDataFn( editField )( dt.row( cellIndex.row ).data() );
+ }
+
+ if ( enabledColumns.indexOf(cellIndex.column) === -1 ) {
+ return;
+ }
+
+ return {
+ cell: cell,
+ data: data,
+ label: cell.data(),
+ index: cellIndex
+ };
+ } )
+ );
+ }
+
+ this._actionSelector( selected );
+
+ // Stop shiftScroll
+ clearInterval( this.s.scrollInterval );
+ this.s.scrollInterval = null;
+ },
+
+
+ /**
+ * Create an array with a range of numbers defined by the start and end
+ * parameters passed in (inclusive!).
+ *
+ * @param {integer} start Start
+ * @param {integer} end End
+ * @private
+ */
+ _range: function ( start, end )
+ {
+ var out = [];
+ var i;
+
+ if ( start <= end ) {
+ for ( i=start ; i<=end ; i++ ) {
+ out.push( i );
+ }
+ }
+ else {
+ for ( i=start ; i>=end ; i-- ) {
+ out.push( i );
+ }
+ }
+
+ return out;
+ },
+
+
+ /**
+ * Move the window and DataTables scrolling during a drag to scroll new
+ * content into view. This is done by proximity to the edge of the scrolling
+ * container of the mouse - for example near the top edge of the window
+ * should scroll up. This is a little complicated as there are two elements
+ * that can be scrolled - the window and the DataTables scrolling view port
+ * (if scrollX and / or scrollY is enabled).
+ *
+ * @param {object} e Mouse move event object
+ * @private
+ */
+ _shiftScroll: function ( e )
+ {
+ var that = this;
+ var dt = this.s.dt;
+ var scroll = this.s.scroll;
+ var runInterval = false;
+ var scrollSpeed = 5;
+ var buffer = 65;
+ var
+ windowY = e.pageY - document.body.scrollTop,
+ windowX = e.pageX - document.body.scrollLeft,
+ windowVert, windowHoriz,
+ dtVert, dtHoriz;
+
+ // Window calculations - based on the mouse position in the window,
+ // regardless of scrolling
+ if ( windowY < buffer ) {
+ windowVert = scrollSpeed * -1;
+ }
+ else if ( windowY > scroll.windowHeight - buffer ) {
+ windowVert = scrollSpeed;
+ }
+
+ if ( windowX < buffer ) {
+ windowHoriz = scrollSpeed * -1;
+ }
+ else if ( windowX > scroll.windowWidth - buffer ) {
+ windowHoriz = scrollSpeed;
+ }
+
+ // DataTables scrolling calculations - based on the table's position in
+ // the document and the mouse position on the page
+ if ( scroll.dtTop !== null && e.pageY < scroll.dtTop + buffer ) {
+ dtVert = scrollSpeed * -1;
+ }
+ else if ( scroll.dtTop !== null && e.pageY > scroll.dtTop + scroll.dtHeight - buffer ) {
+ dtVert = scrollSpeed;
+ }
+
+ if ( scroll.dtLeft !== null && e.pageX < scroll.dtLeft + buffer ) {
+ dtHoriz = scrollSpeed * -1;
+ }
+ else if ( scroll.dtLeft !== null && e.pageX > scroll.dtLeft + scroll.dtWidth - buffer ) {
+ dtHoriz = scrollSpeed;
+ }
+
+ // This is where it gets interesting. We want to continue scrolling
+ // without requiring a mouse move, so we need an interval to be
+ // triggered. The interval should continue until it is no longer needed,
+ // but it must also use the latest scroll commands (for example consider
+ // that the mouse might move from scrolling up to scrolling left, all
+ // with the same interval running. We use the `scroll` object to "pass"
+ // this information to the interval. Can't use local variables as they
+ // wouldn't be the ones that are used by an already existing interval!
+ if ( windowVert || windowHoriz || dtVert || dtHoriz ) {
+ scroll.windowVert = windowVert;
+ scroll.windowHoriz = windowHoriz;
+ scroll.dtVert = dtVert;
+ scroll.dtHoriz = dtHoriz;
+ runInterval = true;
+ }
+ else if ( this.s.scrollInterval ) {
+ // Don't need to scroll - remove any existing timer
+ clearInterval( this.s.scrollInterval );
+ this.s.scrollInterval = null;
+ }
+
+ // If we need to run the interval to scroll and there is no existing
+ // interval (if there is an existing one, it will continue to run)
+ if ( ! this.s.scrollInterval && runInterval ) {
+ this.s.scrollInterval = setInterval( function () {
+ // Don't need to worry about setting scroll <0 or beyond the
+ // scroll bound as the browser will just reject that.
+ if ( scroll.windowVert ) {
+ document.body.scrollTop += scroll.windowVert;
+ }
+ if ( scroll.windowHoriz ) {
+ document.body.scrollLeft += scroll.windowHoriz;
+ }
+
+ // DataTables scrolling
+ if ( scroll.dtVert || scroll.dtHoriz ) {
+ var scroller = that.dom.dtScroll[0];
+
+ if ( scroll.dtVert ) {
+ scroller.scrollTop += scroll.dtVert;
+ }
+ if ( scroll.dtHoriz ) {
+ scroller.scrollLeft += scroll.dtHoriz;
+ }
+ }
+ }, 20 );
+ }
+ },
+
+
+ /**
+ * Update the DataTable after the user has selected what they want to do
+ *
+ * @param {false|undefined} result Return from the `execute` method - can
+ * be false internally to do nothing. This is not documented for plug-ins
+ * and is used only by the cancel option.
+ * @param {array} cells Information about the selected cells from the key
+ * up function, argumented with the set values
+ * @private
+ */
+ _update: function ( result, cells )
+ {
+ // Do nothing on `false` return from an execute function
+ if ( result === false ) {
+ return;
+ }
+
+ var dt = this.s.dt;
+ var cell;
+ var columns = dt.columns( this.c.columns ).indexes();
+
+ // Potentially allow modifications to the cells matrix
+ this._emitEvent( 'preAutoFill', [ dt, cells ] );
+
+ this._editor( cells );
+
+ // Automatic updates are not performed if `update` is null and the
+ // `editor` parameter is passed in - the reason being that Editor will
+ // update the data once submitted
+ var update = this.c.update !== null ?
+ this.c.update :
+ this.c.editor ?
+ false :
+ true;
+
+ if ( update ) {
+ for ( var i=0, ien=cells.length ; i<ien ; i++ ) {
+ for ( var j=0, jen=cells[i].length ; j<jen ; j++ ) {
+ cell = cells[i][j];
+
+ if ( columns.indexOf(cell.index.column) !== -1 ) {
+ cell.cell.data( cell.set );
+ }
+ }
+ }
+
+ dt.draw(false);
+ }
+
+ this._emitEvent( 'autoFill', [ dt, cells ] );
+ }
+} );
+
+
+/**
+ * AutoFill actions. The options here determine how AutoFill will fill the data
+ * in the table when the user has selected a range of cells. Please see the
+ * documentation on the DataTables site for full details on how to create plug-
+ * ins.
+ *
+ * @type {Object}
+ */
+AutoFill.actions = {
+ increment: {
+ available: function ( dt, cells ) {
+ var d = cells[0][0].label;
+
+ // is numeric test based on jQuery's old `isNumeric` function
+ return !isNaN( d - parseFloat( d ) );
+ },
+
+ option: function ( dt, cells ) {
+ return dt.i18n(
+ 'autoFill.increment',
+ 'Increment / decrement each cell by: <input type="number" value="1">'
+ );
+ },
+
+ execute: function ( dt, cells, node ) {
+ var value = cells[0][0].data * 1;
+ var increment = $('input', node).val() * 1;
+
+ for ( var i=0, ien=cells.length ; i<ien ; i++ ) {
+ for ( var j=0, jen=cells[i].length ; j<jen ; j++ ) {
+ cells[i][j].set = value;
+
+ value += increment;
+ }
+ }
+ }
+ },
+
+ fill: {
+ available: function ( dt, cells ) {
+ return true;
+ },
+
+ option: function ( dt, cells ) {
+ return dt.i18n('autoFill.fill', 'Fill all cells with <i>%d</i>', cells[0][0].label );
+ },
+
+ execute: function ( dt, cells, node ) {
+ var value = cells[0][0].data;
+
+ for ( var i=0, ien=cells.length ; i<ien ; i++ ) {
+ for ( var j=0, jen=cells[i].length ; j<jen ; j++ ) {
+ cells[i][j].set = value;
+ }
+ }
+ }
+ },
+
+ fillHorizontal: {
+ available: function ( dt, cells ) {
+ return cells.length > 1 && cells[0].length > 1;
+ },
+
+ option: function ( dt, cells ) {
+ return dt.i18n('autoFill.fillHorizontal', 'Fill cells horizontally' );
+ },
+
+ execute: function ( dt, cells, node ) {
+ for ( var i=0, ien=cells.length ; i<ien ; i++ ) {
+ for ( var j=0, jen=cells[i].length ; j<jen ; j++ ) {
+ cells[i][j].set = cells[i][0].data;
+ }
+ }
+ }
+ },
+
+ fillVertical: {
+ available: function ( dt, cells ) {
+ return cells.length > 1;
+ },
+
+ option: function ( dt, cells ) {
+ return dt.i18n('autoFill.fillVertical', 'Fill cells vertically' );
+ },
+
+ execute: function ( dt, cells, node ) {
+ for ( var i=0, ien=cells.length ; i<ien ; i++ ) {
+ for ( var j=0, jen=cells[i].length ; j<jen ; j++ ) {
+ cells[i][j].set = cells[0][j].data;
+ }
+ }
+ }
+ },
+
+ // Special type that does not make itself available, but is added
+ // automatically by AutoFill if a multi-choice list is shown. This allows
+ // sensible code reuse
+ cancel: {
+ available: function () {
+ return false;
+ },
+
+ option: function ( dt ) {
+ return dt.i18n('autoFill.cancel', 'Cancel' );
+ },
+
+ execute: function () {
+ return false;
+ }
+ }
+};
+
+
+/**
+ * AutoFill version
+ *
+ * @static
+ * @type String
+ */
+AutoFill.version = '2.3.9';
+
+
+/**
+ * AutoFill defaults
+ *
+ * @namespace
+ */
+AutoFill.defaults = {
+ /** @type {Boolean} Ask user what they want to do, even for a single option */
+ alwaysAsk: false,
+
+ /** @type {string|null} What will trigger a focus */
+ focus: null, // focus, click, hover
+
+ /** @type {column-selector} Columns to provide auto fill for */
+ columns: '', // all
+
+ /** @type {Boolean} Enable AutoFill on load */
+ enable: true,
+
+ /** @type {boolean|null} Update the cells after a drag */
+ update: null, // false is editor given, true otherwise
+
+ /** @type {DataTable.Editor} Editor instance for automatic submission */
+ editor: null,
+
+ /** @type {boolean} Enable vertical fill */
+ vertical: true,
+
+ /** @type {boolean} Enable horizontal fill */
+ horizontal: true
+};
+
+
+/**
+ * Classes used by AutoFill that are configurable
+ *
+ * @namespace
+ */
+AutoFill.classes = {
+ /** @type {String} Class used by the selection button */
+ btn: 'btn'
+};
+
+
+/*
+ * API
+ */
+var Api = $.fn.dataTable.Api;
+
+// Doesn't do anything - Not documented
+Api.register( 'autoFill()', function () {
+ return this;
+} );
+
+Api.register( 'autoFill().enabled()', function () {
+ var ctx = this.context[0];
+
+ return ctx.autoFill ?
+ ctx.autoFill.enabled() :
+ false;
+} );
+
+Api.register( 'autoFill().enable()', function ( flag ) {
+ return this.iterator( 'table', function ( ctx ) {
+ if ( ctx.autoFill ) {
+ ctx.autoFill.enable( flag );
+ }
+ } );
+} );
+
+Api.register( 'autoFill().disable()', function () {
+ return this.iterator( 'table', function ( ctx ) {
+ if ( ctx.autoFill ) {
+ ctx.autoFill.disable();
+ }
+ } );
+} );
+
+
+// Attach a listener to the document which listens for DataTables initialisation
+// events so we can automatically initialise
+$(document).on( 'preInit.dt.autofill', function (e, settings, json) {
+ if ( e.namespace !== 'dt' ) {
+ return;
+ }
+
+ var init = settings.oInit.autoFill;
+ var defaults = DataTable.defaults.autoFill;
+
+ if ( init || defaults ) {
+ var opts = $.extend( {}, init, defaults );
+
+ if ( init !== false ) {
+ new AutoFill( settings, opts );
+ }
+ }
+} );
+
+
+// Alias for access
+DataTable.AutoFill = AutoFill;
+DataTable.AutoFill = AutoFill;
+
+
+return AutoFill;
+}));