/* * File: AutoFill.js * Version: 1.1.2 * CVS: $Id$ * Description: AutoFill for DataTables * Author: Allan Jardine (www.sprymedia.co.uk) * Created: Mon 6 Sep 2010 16:54:41 BST * Modified: $Date$ by $Author$ * Language: Javascript * License: GPL v2 or BSD 3 point * Project: DataTables * Contact: www.sprymedia.co.uk/contact * * Copyright 2010-2011 Allan Jardine, all rights reserved. * * This source file is free software, under either the GPL v2 license or a * BSD style license, available at: * http://datatables.net/license_gpl2 * http://datatables.net/license_bsd * */ /* Global scope for AutoFill */ var AutoFill; (function($) { /** * AutoFill provides Excel like auto fill features for a DataTable * @class AutoFill * @constructor * @param {object} DataTables settings object * @param {object} Configuration object for AutoFill */ AutoFill = function( oDT, oConfig ) { /* Santiy check that we are a new instance */ if ( !this.CLASS || this.CLASS != "AutoFill" ) { alert( "Warning: AutoFill must be initialised with the keyword 'new'" ); return; } if ( !$.fn.dataTableExt.fnVersionCheck('1.7.0') ) { alert( "Warning: AutoFill requires DataTables 1.7 or greater - www.datatables.net/download"); return; } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Public class variables * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /** * @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} oDT DataTables settings object * @param {object} oConfig Configuration object for AutoFill * @returns void */ "_fnInit": function ( oDT, oConfig ) { var that = this, i, iLen; /* * Settings */ this.s.dt = oDT.fnSettings(); this.dom.table = this.s.dt.nTable; /* Add and configure the columns */ for ( i=0, iLen=this.s.dt.aoColumns.length ; itr>td', this.dom.table).live( 'mouseover mouseout', function (e) { that._fnFillerDisplay.call( that, e ); } ); }, "_fnColumnDefs": function ( aoColumnDefs ) { var i, j, k, iLen, jLen, kLen, aTargets; /* Loop over the column defs array - loop in reverse so first instace has priority */ for ( i=aoColumnDefs.length-1 ; i>=0 ; i-- ) { /* Each column def can target multiple columns, as it is an array */ aTargets = aoColumnDefs[i].aTargets; for ( j=0, jLen=aTargets.length ; j= 0 ) { /* 0+ integer, left to right column counting. */ this._fnColumnOptions( aTargets[j], aoColumnDefs[i] ); } else if ( typeof aTargets[j] == 'number' && aTargets[j] < 0 ) { /* Negative integer, right to left column counting */ this._fnColumnOptions( this.s.dt.aoColumns.length+aTargets[j], aoColumnDefs[i] ); } else if ( typeof aTargets[j] == 'string' ) { /* Class name matching on TH element */ for ( k=0, kLen=this.s.dt.aoColumns.length ; k 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 ( coords.x != this.s.drag.startX ) { e.target = $('tbody>tr:eq('+coords.y+')>td:eq('+this.s.drag.startX+')', this.dom.table)[0]; coords = this._fnTargetCoords( e.target ); } if ( coords.x == this.s.drag.startX ) { 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; $(document).unbind('mousemove.AutoFill'); $(document).unbind('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 coordsStart = this._fnTargetCoords( this.s.drag.startTd ); var coordsEnd = this._fnTargetCoords( this.s.drag.endTd ); var aTds = []; var bIncrement; if ( coordsStart.y <= coordsEnd.y ) { bIncrement = true; for ( i=coordsStart.y ; i<=coordsEnd.y ; i++ ) { aTds.push( $('tbody>tr:eq('+i+')>td:eq('+coordsStart.x+')', this.dom.table)[0] ); } } else { bIncrement = false; for ( i=coordsStart.y ; i>=coordsEnd.y ; i-- ) { aTds.push( $('tbody>tr:eq('+i+')>td:eq('+coordsStart.x+')', this.dom.table)[0] ); } } var iColumn = coordsStart.x; var bLast = false; var aoEdited = []; var sStart = this.s.columns[iColumn].read.call( this, this.s.drag.startTd ); var oPrepped = this._fnPrep( sStart ); for ( i=0, iLen=aTds.length ; i 0 ) { return $(jq).val(); } jq = $('select', nTd); if ( jq.length > 0 ) { return $(jq).val(); } return nTd.innerHTML; }, /** * Write informaiton to a cell, possibly using live DOM elements if suitable * @method _fnWriteCell * @param {Node} nTd Cell to write * @param {String} sVal Value to write * @param {Boolean} bLast Flag to show if this is that last update * @returns void */ "_fnWriteCell": function ( nTd, sVal, bLast ) { var jq = $('input', nTd); if ( jq.length > 0 ) { $(jq).val( sVal ); return; } jq = $('select', nTd); if ( jq.length > 0 ) { $(jq).val( sVal ); return; } var pos = this.s.dt.oInstance.fnGetPosition( nTd ); this.s.dt.oInstance.fnUpdate( sVal, pos[0], pos[2], bLast ); }, /** * Display the drag handle on mouse over cell * @method _fnFillerDisplay * @param {Object} e Event object * @returns void */ "_fnFillerDisplay": function (e) { /* 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).x; if ( !this.s.columns[iX].enable ) { return; } var filler = this.dom.filler; 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"; } }; /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Constants * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /** * Name of this class * @constant CLASS * @type String * @default AutoFill */ AutoFill.prototype.CLASS = "AutoFill"; /** * AutoFill version * @constant VERSION * @type String * @default 1.1.2 */ AutoFill.VERSION = "1.1.2"; AutoFill.prototype.VERSION = AutoFill.VERSION; })(jQuery);