Spaces:
Sleeping
Sleeping
/*! 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 ) { | |
; | |
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', '>')+'</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; | |
})); | |