'),
+
+ /** @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 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 endCell = $( dt.cell( ':eq('+end.row+')', 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 ).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'),select:{top:f(''),right:f(''),bottom:f(''),left:f('')},background:f(''),list:f('