Blame | Letzte Änderung | Log anzeigen | RSS feed
/*! AutoFill 1.2.1* ©2008-2014 SpryMedia Ltd - datatables.net/license*//*** @summary AutoFill* @description Add Excel like click and drag auto-fill options to DataTables* @version 1.2.1* @file dataTables.autoFill.js* @author SpryMedia Ltd (www.sprymedia.co.uk)* @contact www.sprymedia.co.uk/contact* @copyright Copyright 2010-2014 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( window, document, undefined ) {var factory = function( $, DataTable ) {"use strict";/*** 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( oDT, oConfig ){/* Sanity check that we are a new instance */if ( ! (this instanceof AutoFill) ) {throw( "Warning: AutoFill must be initialised with the keyword 'new'" );}if ( ! $.fn.dataTableExt.fnVersionCheck('1.7.0') ) {throw( "Warning: AutoFill requires DataTables 1.7 or greater");}/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ** Public class variables* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */this.c = {};/*** @namespace Settings object which contains customisable information for AutoFill instance*/this.s = {/*** @namespace Cached information about the little dragging icon (the filler)*/"filler": {"height": 0,"width": 0},/*** @namespace Cached information about the border display*/"border": {"width": 2},/*** @namespace Store for live information for the current drag*/"drag": {"startX": -1,"startY": -1,"startTd": null,"endTd": null,"dragging": false},/*** @namespace Data cache for information that we need for scrolling the screen when we near* the edges*/"screen": {"interval": null,"y": 0,"height": 0,"scrollTop": 0},/*** @namespace Data cache for the position of the DataTables scrolling element (when scrolling* is enabled)*/"scroller": {"top": 0,"bottom": 0},/*** @namespace Information stored for each column. An array of objects*/"columns": []};/*** @namespace Common and useful DOM elements for the class instance*/this.dom = {"table": null,"filler": null,"borderTop": null,"borderRight": null,"borderBottom": null,"borderLeft": null,"currentTarget": null};/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ** Public class methods* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *//*** Retreieve the settings object from an instance* @method fnSettings* @returns {object} AutoFill settings object*/this.fnSettings = function () {return this.s;};/* Constructor logic */this._fnInit( oDT, oConfig );return this;};AutoFill.prototype = {/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ** Private methods (they are of course public in JS, but recommended as private)* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *//*** Initialisation* @method _fnInit* @param {object} dt DataTables settings object* @param {object} config Configuration object for AutoFill* @returns void*/"_fnInit": function ( dt, config ){varthat = this,i, iLen;// Use DataTables API to get the settings allowing selectors, instances// etc to be used, or for backwards compatibility get from the old// fnSettings methodthis.s.dt = DataTable.Api ?new DataTable.Api( dt ).settings()[0] :dt.fnSettings();this.s.init = config || {};this.dom.table = this.s.dt.nTable;$.extend( true, this.c, AutoFill.defaults, config );/* Add and configure the columns */this._initColumns();/* Auto Fill click and drag icon */var filler = $('<div/>', {'class': 'AutoFill_filler'} ).appendTo( 'body' );this.dom.filler = filler[0];// Get the height / width of the click elementthis.s.filler.height = filler.height();this.s.filler.width = filler.width();filler[0].style.display = "none";/* Border display - one div for each side. We can't just use a single* one with a border, as we want the events to effectively pass through* the transparent bit of the box*/var border;var appender = document.body;if ( that.s.dt.oScroll.sY !== "" ) {that.s.dt.nTable.parentNode.style.position = "relative";appender = that.s.dt.nTable.parentNode;}border = $('<div/>', {"class": "AutoFill_border"} );this.dom.borderTop = border.clone().appendTo( appender )[0];this.dom.borderRight = border.clone().appendTo( appender )[0];this.dom.borderBottom = border.clone().appendTo( appender )[0];this.dom.borderLeft = border.clone().appendTo( appender )[0];/* Events */filler.on( 'mousedown.DTAF', function (e) {this.onselectstart = function() { return false; };that._fnFillerDragStart.call( that, e );return false;} );$('tbody', this.dom.table).on('mouseover.DTAF mouseout.DTAF','>tr>td, >tr>th',function (e) {that._fnFillerDisplay.call( that, e );});$(this.dom.table).on( 'destroy.dt.DTAF', function () {filler.off( 'mousedown.DTAF' ).remove();$('tbody', this.dom.table).off( 'mouseover.DTAF mouseout.DTAF' );} );},_initColumns: function ( ){var that = this;var i, ien;var dt = this.s.dt;var config = this.s.init;for ( i=0, ien=dt.aoColumns.length ; i<ien ; i++ ) {this.s.columns[i] = $.extend( true, {}, AutoFill.defaults.column );}dt.oApi._fnApplyColumnDefs(dt,config.aoColumnDefs || config.columnDefs,config.aoColumns || config.columns,function (colIdx, def) {that._fnColumnOptions( colIdx, def );});// For columns which don't have read, write, step functions defined,// use the default onesfor ( i=0, ien=dt.aoColumns.length ; i<ien ; i++ ) {var column = this.s.columns[i];if ( ! column.read ) {column.read = this._fnReadCell;}if ( ! column.write ) {column.read = this._fnWriteCell;}if ( ! column.step ) {column.read = this._fnStep;}}},"_fnColumnOptions": function ( i, opts ){var column = this.s.columns[ i ];var set = function ( outProp, inProp ) {if ( opts[ inProp[0] ] !== undefined ) {column[ outProp ] = opts[ inProp[0] ];}if ( opts[ inProp[1] ] !== undefined ) {column[ outProp ] = opts[ inProp[1] ];}};// Compatibility with the old Hungarian style of notationset( 'enable', ['bEnable', 'enable'] );set( 'read', ['fnRead', 'read'] );set( 'write', ['fnWrite', 'write'] );set( 'step', ['fnStep', 'step'] );set( 'increment', ['bIncrement', 'increment'] );},/*** Find out the coordinates of a given TD cell in a table* @method _fnTargetCoords* @param {Node} nTd* @returns {Object} x and y properties, for the position of the cell in the tables DOM*/"_fnTargetCoords": function ( nTd ){var nTr = $(nTd).parents('tr')[0];var position = this.s.dt.oInstance.fnGetPosition( nTd );return {"x": $('td', nTr).index(nTd),"y": $('tr', nTr.parentNode).index(nTr),"row": position[0],"column": position[2]};},/*** Display the border around one or more cells (from start to end)* @method _fnUpdateBorder* @param {Node} nStart Starting cell* @param {Node} nEnd Ending cell* @returns void*/"_fnUpdateBorder": function ( nStart, nEnd ){varborder = this.s.border.width,offsetStart = $(nStart).offset(),offsetEnd = $(nEnd).offset(),x1 = offsetStart.left - border,x2 = offsetEnd.left + $(nEnd).outerWidth(),y1 = offsetStart.top - border,y2 = offsetEnd.top + $(nEnd).outerHeight(),width = offsetEnd.left + $(nEnd).outerWidth() - offsetStart.left + (2*border),height = offsetEnd.top + $(nEnd).outerHeight() - offsetStart.top + (2*border),oStyle;// Recalculate start and end (when dragging "backwards")if( offsetStart.left > offsetEnd.left) {x1 = offsetEnd.left - border;x2 = offsetStart.left + $(nStart).outerWidth();width = offsetStart.left + $(nStart).outerWidth() - offsetEnd.left + (2*border);}if ( this.s.dt.oScroll.sY !== "" ){/* The border elements are inside the DT scroller - so position relative to that */varoffsetScroll = $(this.s.dt.nTable.parentNode).offset(),scrollTop = $(this.s.dt.nTable.parentNode).scrollTop(),scrollLeft = $(this.s.dt.nTable.parentNode).scrollLeft();x1 -= offsetScroll.left - scrollLeft;x2 -= offsetScroll.left - scrollLeft;y1 -= offsetScroll.top - scrollTop;y2 -= offsetScroll.top - scrollTop;}/* Top */oStyle = this.dom.borderTop.style;oStyle.top = y1+"px";oStyle.left = x1+"px";oStyle.height = this.s.border.width+"px";oStyle.width = width+"px";/* Bottom */oStyle = this.dom.borderBottom.style;oStyle.top = y2+"px";oStyle.left = x1+"px";oStyle.height = this.s.border.width+"px";oStyle.width = width+"px";/* Left */oStyle = this.dom.borderLeft.style;oStyle.top = y1+"px";oStyle.left = x1+"px";oStyle.height = height+"px";oStyle.width = this.s.border.width+"px";/* Right */oStyle = this.dom.borderRight.style;oStyle.top = y1+"px";oStyle.left = x2+"px";oStyle.height = height+"px";oStyle.width = this.s.border.width+"px";},/*** Mouse down event handler for starting a drag* @method _fnFillerDragStart* @param {Object} e Event object* @returns void*/"_fnFillerDragStart": function (e){var that = this;var startingTd = this.dom.currentTarget;this.s.drag.dragging = true;that.dom.borderTop.style.display = "block";that.dom.borderRight.style.display = "block";that.dom.borderBottom.style.display = "block";that.dom.borderLeft.style.display = "block";var coords = this._fnTargetCoords( startingTd );this.s.drag.startX = coords.x;this.s.drag.startY = coords.y;this.s.drag.startTd = startingTd;this.s.drag.endTd = startingTd;this._fnUpdateBorder( startingTd, startingTd );$(document).bind('mousemove.AutoFill', function (e) {that._fnFillerDragMove.call( that, e );} );$(document).bind('mouseup.AutoFill', function (e) {that._fnFillerFinish.call( that, e );} );/* Scrolling information cache */this.s.screen.y = e.pageY;this.s.screen.height = $(window).height();this.s.screen.scrollTop = $(document).scrollTop();if ( this.s.dt.oScroll.sY !== "" ){this.s.scroller.top = $(this.s.dt.nTable.parentNode).offset().top;this.s.scroller.bottom = this.s.scroller.top + $(this.s.dt.nTable.parentNode).height();}/* Scrolling handler - we set an interval (which is cancelled on mouse up) which will fire* regularly and see if we need to do any scrolling*/this.s.screen.interval = setInterval( function () {var iScrollTop = $(document).scrollTop();var iScrollDelta = iScrollTop - that.s.screen.scrollTop;that.s.screen.y += iScrollDelta;if ( that.s.screen.height - that.s.screen.y + iScrollTop < 50 ){$('html, body').animate( {"scrollTop": iScrollTop + 50}, 240, 'linear' );}else if ( that.s.screen.y - iScrollTop < 50 ){$('html, body').animate( {"scrollTop": iScrollTop - 50}, 240, 'linear' );}if ( that.s.dt.oScroll.sY !== "" ){if ( that.s.screen.y > that.s.scroller.bottom - 50 ){$(that.s.dt.nTable.parentNode).animate( {"scrollTop": $(that.s.dt.nTable.parentNode).scrollTop() + 50}, 240, 'linear' );}else if ( that.s.screen.y < that.s.scroller.top + 50 ){$(that.s.dt.nTable.parentNode).animate( {"scrollTop": $(that.s.dt.nTable.parentNode).scrollTop() - 50}, 240, 'linear' );}}}, 250 );},/*** Mouse move event handler for during a move. See if we want to update the display based on the* new cursor position* @method _fnFillerDragMove* @param {Object} e Event object* @returns void*/"_fnFillerDragMove": function (e){if ( e.target && e.target.nodeName.toUpperCase() == "TD" &&e.target != this.s.drag.endTd ){var coords = this._fnTargetCoords( e.target );if ( this.c.mode == "y" && coords.x != this.s.drag.startX ){e.target = $('tbody>tr:eq('+coords.y+')>td:eq('+this.s.drag.startX+')', this.dom.table)[0];}if ( this.c.mode == "x" && coords.y != this.s.drag.startY ){e.target = $('tbody>tr:eq('+this.s.drag.startY+')>td:eq('+coords.x+')', this.dom.table)[0];}if ( this.c.mode == "either"){if(coords.x != this.s.drag.startX ){e.target = $('tbody>tr:eq('+this.s.drag.startY+')>td:eq('+coords.x+')', this.dom.table)[0];}else if ( coords.y != this.s.drag.startY ) {e.target = $('tbody>tr:eq('+coords.y+')>td:eq('+this.s.drag.startX+')', this.dom.table)[0];}}// update coordsif ( this.c.mode !== "both" ) {coords = this._fnTargetCoords( e.target );}var drag = this.s.drag;drag.endTd = e.target;if ( coords.y >= this.s.drag.startY ) {this._fnUpdateBorder( drag.startTd, drag.endTd );}else {this._fnUpdateBorder( drag.endTd, drag.startTd );}this._fnFillerPosition( e.target );}/* Update the screen information so we can perform scrolling */this.s.screen.y = e.pageY;this.s.screen.scrollTop = $(document).scrollTop();if ( this.s.dt.oScroll.sY !== "" ){this.s.scroller.scrollTop = $(this.s.dt.nTable.parentNode).scrollTop();this.s.scroller.top = $(this.s.dt.nTable.parentNode).offset().top;this.s.scroller.bottom = this.s.scroller.top + $(this.s.dt.nTable.parentNode).height();}},/*** Mouse release handler - end the drag and take action to update the cells with the needed values* @method _fnFillerFinish* @param {Object} e Event object* @returns void*/"_fnFillerFinish": function (e){var that = this, i, iLen, j;$(document).unbind('mousemove.AutoFill mouseup.AutoFill');this.dom.borderTop.style.display = "none";this.dom.borderRight.style.display = "none";this.dom.borderBottom.style.display = "none";this.dom.borderLeft.style.display = "none";this.s.drag.dragging = false;clearInterval( this.s.screen.interval );var cells = [];var table = this.dom.table;var coordsStart = this._fnTargetCoords( this.s.drag.startTd );var coordsEnd = this._fnTargetCoords( this.s.drag.endTd );var columnIndex = function ( visIdx ) {return that.s.dt.oApi._fnVisibleToColumnIndex( that.s.dt, visIdx );};// xxx - urgh - there must be a way of reducing this...if ( coordsStart.y <= coordsEnd.y ) {for ( i=coordsStart.y ; i<=coordsEnd.y ; i++ ) {if ( coordsStart.x <= coordsEnd.x ) {for ( j=coordsStart.x ; j<=coordsEnd.x ; j++ ) {cells.push( {node: $('tbody>tr:eq('+i+')>td:eq('+j+')', table)[0],x: j - coordsStart.x,y: i - coordsStart.y,colIdx: columnIndex( j )} );}}else {for ( j=coordsStart.x ; j>=coordsEnd.x ; j-- ) {cells.push( {node: $('tbody>tr:eq('+i+')>td:eq('+j+')', table)[0],x: j - coordsStart.x,y: i - coordsStart.y,colIdx: columnIndex( j )} );}}}}else {for ( i=coordsStart.y ; i>=coordsEnd.y ; i-- ) {if ( coordsStart.x <= coordsEnd.x ) {for ( j=coordsStart.x ; j<=coordsEnd.x ; j++ ) {cells.push( {node: $('tbody>tr:eq('+i+')>td:eq('+j+')', table)[0],x: j - coordsStart.x,y: i - coordsStart.y,colIdx: columnIndex( j )} );}}else {for ( j=coordsStart.x ; j>=coordsEnd.x ; j-- ) {cells.push( {node: $('tbody>tr:eq('+i+')>td:eq('+j+')', table)[0],x: coordsStart.x - j,y: coordsStart.y - i,colIdx: columnIndex( j )} );}}}}// An auto-fill requires 2 or more cellsif ( cells.length <= 1 ) {return;}var edited = [];var previous;for ( i=0, iLen=cells.length ; i<iLen ; i++ ) {var cell = cells[i];var column = this.s.columns[ cell.colIdx ];var read = column.read.call( column, cell.node );var stepValue = column.step.call( column, cell.node, read, previous, i, cell.x, cell.y );column.write.call( column, cell.node, stepValue );previous = stepValue;edited.push( {cell: cell,colIdx: cell.colIdx,newValue: stepValue,oldValue: read} );}if ( this.c.complete !== null ) {this.c.complete.call( this, edited );}// In 1.10 we can do a static drawif ( DataTable.Api ) {new DataTable.Api( this.s.dt ).draw( false );}else {this.s.dt.oInstance.fnDraw();}},/*** Display the drag handle on mouse over cell* @method _fnFillerDisplay* @param {Object} e Event object* @returns void*/"_fnFillerDisplay": function (e){var filler = this.dom.filler;/* Don't display automatically when dragging */if ( this.s.drag.dragging){return;}/* Check that we are allowed to AutoFill this column or not */var nTd = (e.target.nodeName.toLowerCase() == 'td') ? e.target : $(e.target).parents('td')[0];var iX = this._fnTargetCoords(nTd).column;if ( !this.s.columns[iX].enable ){filler.style.display = "none";return;}if (e.type == 'mouseover'){this.dom.currentTarget = nTd;this._fnFillerPosition( nTd );filler.style.display = "block";}else if ( !e.relatedTarget || !e.relatedTarget.className.match(/AutoFill/) ){filler.style.display = "none";}},/*** Position the filler icon over a cell* @method _fnFillerPosition* @param {Node} nTd Cell to position filler icon over* @returns void*/"_fnFillerPosition": function ( nTd ){var offset = $(nTd).offset();var filler = this.dom.filler;filler.style.top = (offset.top - (this.s.filler.height / 2)-1 + $(nTd).outerHeight())+"px";filler.style.left = (offset.left - (this.s.filler.width / 2)-1 + $(nTd).outerWidth())+"px";}};// Alias for accessDataTable.AutoFill = AutoFill;DataTable.AutoFill = AutoFill;/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ** Constants* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *//*** AutoFill version* @constant version* @type String* @default See code*/AutoFill.version = "1.2.1";/*** AutoFill defaults* @namespace*/AutoFill.defaults = {/*** Mode for dragging (restrict to y-axis only, x-axis only, either one or none):** * `y` - y-axis only (default)* * `x` - x-axis only* * `either` - either one, but not both axis at the same time* * `both` - multiple cells allowed** @type {string}* @default `y`*/mode: 'y',complete: null,/*** Column definition defaults* @namespace*/column: {/*** If AutoFill should be enabled on this column** @type {boolean}* @default true*/enable: true,/*** Allow automatic increment / decrement on this column if a number* is found.** @type {boolean}* @default true*/increment: true,/*** Cell read function** Default function will simply read the value from the HTML of the* cell.** @type {function}* @param {node} cell `th` / `td` element to read the value from* @return {string} Data that has been read*/read: function ( cell ) {return $(cell).html();},/*** Cell write function** Default function will simply write to the HTML and tell the DataTable* to update.** @type {function}* @param {node} cell `th` / `td` element to write the value to* @return {string} Data two write*/write: function ( cell, val ) {var table = $(cell).parents('table');if ( DataTable.Api ) {// 1.10table.DataTable().cell( cell ).data( val );}else {// 1.9var dt = table.dataTable();var pos = dt.fnGetPosition( cell );dt.fnUpdate( val, pos[0], pos[2], false );}},/*** Step function. This provides the ability to customise how the values* are incremented.** @param {node} cell `th` / `td` element that is being operated upon* @param {string} read Cell value from `read` function* @param {string} last Value of the previous cell* @param {integer} i Loop counter* @param {integer} x Cell x-position in the current auto-fill. The* starting cell is coordinate 0 regardless of its physical position* in the DataTable.* @param {integer} y Cell y-position in the current auto-fill. The* starting cell is coordinate 0 regardless of its physical position* in the DataTable.* @return {string} Value to write*/step: function ( cell, read, last, i, x, y ) {// Increment a number if it is foundvar re = /(\-?\d+)/;var match = this.increment && last ? last.match(re) : null;if ( match ) {return last.replace( re, parseInt(match[1],10) + (x<0 || y<0 ? -1 : 1) );}return last === undefined ?read :last;}}};return AutoFill;};// Define as an AMD module if possibleif ( typeof define === 'function' && define.amd ) {define( ['jquery', 'datatables'], factory );}else if ( typeof exports === 'object' ) {// Node/CommonJSfactory( require('jquery'), require('datatables') );}else if ( jQuery && !jQuery.fn.dataTable.AutoFill ) {// Otherwise simply initialise as normal, stopping multiple evaluationfactory( jQuery, jQuery.fn.dataTable );}}(window, document));