'),
/**
* @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: $(''),
right: $(''),
bottom: $(''),
left: $('')
},
/** @type {jQuery} Fill type chooser background */
background: $(''),
/** @type {jQuery} Fill type chooser */
list: $('
'+this.s.dt.i18n('autoFill.info', '')+'
'),
/** @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 {
// 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( $('')
.append(
'
'+
actions[ name ].option( dt, cells )+
'
'
)
.append( $('
' )
.append( $('')
.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 endCell = $(target);
var end = {
row: dt.rows( { page: 'current' } ).nodes().indexOf( endCell.parent()[0] ),
column: endCell.index()
};
var colIndx = dt.column.index( 'toData', end.column );
// 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 ).top;
left = this._getPosition( left ).left;
height = this._getPosition( bottom ).top + bottom.outerHeight() - top;
width = this._getPosition( right ).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=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;
// 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'
);
},
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'+cells[0][0].label+'' );
},
execute: function ( dt, cells, node ) {
var value = cells[0][0].data;
for ( var i=0, ien=cells.length ; i 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 1 && cells[0].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