      1 /*
      2  * File:        AutoFill.js
      3  * Version:     1.1.1
      4  * CVS:         $Id$
      5  * Description: AutoFill for DataTables
      6  * Author:      Allan Jardine (www.sprymedia.co.uk)
      7  * Created:     Mon  6 Sep 2010 16:54:41 BST
      8  * Modified:    $Date$ by $Author$
      9  * Language:    Javascript
     10  * License:     GPL v2 or BSD 3 point
     11  * Project:     DataTables
     12  * Contact:     www.sprymedia.co.uk/contact
     13  *
     14  * Copyright 2010 Allan Jardine, all rights reserved.
     15  *
     16  */
     17
     18 /* Global scope for AutoFill */
     19 var AutoFill;
     20
     21 (function($) {
     22
     23 /**
     24  * AutoFill provides Excel like auto fill features for a DataTable
     25  * @class AutoFill
     26  * @constructor
     27  * @param {object} DataTables settings object
     28  * @param {object} Configuration object for AutoFill
     29  */
     30 AutoFill = function( oDT, oConfig )
     31 {
     32 	/* Santiy check that we are a new instance */
     33 	if ( !this.CLASS || this.CLASS != "AutoFill" )
     34 	{
     35 		alert( "Warning: AutoFill must be initialised with the keyword 'new'" );
     36 		return;
     37 	}
     38
     39 	if ( !$.fn.dataTableExt.fnVersionCheck('1.7.0') )
     40 	{
     41 		alert( "Warning: AutoFill requires DataTables 1.7 or greater - www.datatables.net/download");
     42 		return;
     43 	}
     44
     45
     46 	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
     47 	 * Public class variables
     48 	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
     49
     50 	/**
     51 	 * @namespace Settings object which contains customisable information for AutoFill instance
     52 	 */
     53 	this.s = {
     54 		/**
     55 		 * @namespace Cached information about the little dragging icon (the filler)
     56 		 */
     57 		filler: {
     58 			height: 0,
     59 			width: 0
     60 		},
     61
     62 		/**
     63 		 * @namespace Cached information about the border display
     64 		 */
     65 		border: {
     66 			width: 2
     67 		},
     68
     69 		/**
     70 		 * @namespace Store for live information for the current drag
     71 		 */
     72 		drag: {
     73 			startX: -1,
     74 			startY: -1,
     75 			startTd: null,
     76 			endTd: null,
     77 			dragging: false
     78 		},
     79
     80 		/**
     81 		 * @namespace Data cache for information that we need for scrolling the screen when we near
     82 		 *   the edges
     83 		 */
     84 		screen: {
     85 			interval: null,
     86 			y: 0,
     87 			height: 0,
     88 			scrollTop: 0
     89 		},
     90
     91 		/**
     92 		 * @namespace Data cache for the position of the DataTables scrolling element (when scrolling
     93 		 *   is enabled)
     94 		 */
     95 		scroller: {
     96 			top: 0,
     97 			bottom: 0
     98 		},
     99
    100
    101 		/**
    102 		 * @namespace Information stored for each column. An array of objects
    103 		 */
    104 		columns: []
    105 	};
    106
    107
    108 	/**
    109 	 * @namespace Common and useful DOM elements for the class instance
    110 	 */
    111 	this.dom = {
    112 		table: null,
    113 		filler: null,
    114 		borderTop: null,
    115 		borderRight: null,
    116 		borderBottom: null,
    117 		borderLeft: null,
    118 		currentTarget: null
    119 	};
    120
    121
    122
    123 	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    124 	 * Public class methods
    125 	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
    126
    127 	/**
    128 	 * Retreieve the settings object from an instance
    129 	 *  @method fnSettings
    130 	 *  @returns {object} AutoFill settings object
    131 	 */
    132 	this.fnSettings = function () {
    133 		return this.s;
    134 	};
    135
    136
    137 	/* Constructor logic */
    138 	this._fnInit( oDT, oConfig );
    139 	return this;
    140 };
    141
    142
    143
    144 AutoFill.prototype = {
    145 	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    146 	 * Private methods (they are of course public in JS, but recommended as private)
    147 	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
    148
    149 	/**
    150 	 * Initialisation
    151 	 *  @method _fnInit
    152  	 *  @param {object} oDT DataTables settings object
    153  	 *  @param {object} oConfig Configuration object for AutoFill
    154 	 *  @returns void
    155 	 */
    156 	_fnInit: function ( oDT, oConfig )
    157 	{
    158 		var
    159 			that = this,
    160 			i, iLen;
    161
    162 		/*
    163 		 * Settings
    164 		 */
    165 		this.s.dt = oDT.fnSettings();
    166
    167 		this.dom.table = this.s.dt.nTable;
    168
    169 		/* Add and configure the columns */
    170 		for ( i=0, iLen=this.s.dt.aoColumns.length ; i<iLen ; i++ )
    171 		{
    172 			this._fnAddColumn( i );
    173 		}
    174
    175 		if ( typeof oConfig != 'undefined' && typeof oConfig.aoColumnDefs != 'undefined' )
    176 		{
    177 			this._fnColumnDefs( oConfig.aoColumnDefs );
    178 		}
    179
    180 		if ( typeof oConfig != 'undefined' && typeof oConfig.aoColumns != 'undefined' )
    181 		{
    182 			this._fnColumnsAll( oConfig.aoColumns );
    183 		}
    184
    185
    186 		/*
    187 		 * DOM
    188 		 */
    189
    190 		/* Auto Fill click and drag icon */
    191 		var filler = document.createElement('div');
    192 		filler.className = "AutoFill_filler";
    193 		document.body.appendChild( filler );
    194 		this.dom.filler = filler;
    195
    196 		filler.style.display = "block";
    197 		this.s.filler.height = $(filler).height();
    198 		this.s.filler.width = $(filler).width();
    199 		filler.style.display = "none";
    200
    201 		/* Border display - one div for each side. We can't just use a single one with a border, as
    202 		 * we want the events to effectively pass through the transparent bit of the box
    203 		 */
    204 		var border;
    205 		var appender = document.body;
    206 		if ( that.s.dt.oScroll.sY !== "" )
    207 		{
    208 			that.s.dt.nTable.parentNode.style.position = "relative";
    209 			appender = that.s.dt.nTable.parentNode;
    210 		}
    211
    212 		border = document.createElement('div');
    213 		border.className = "AutoFill_border";
    214 		appender.appendChild( border );
    215 		this.dom.borderTop = border;
    216
    217 		border = document.createElement('div');
    218 		border.className = "AutoFill_border";
    219 		appender.appendChild( border );
    220 		this.dom.borderRight = border;
    221
    222 		border = document.createElement('div');
    223 		border.className = "AutoFill_border";
    224 		appender.appendChild( border );
    225 		this.dom.borderBottom = border;
    226
    227 		border = document.createElement('div');
    228 		border.className = "AutoFill_border";
    229 		appender.appendChild( border );
    230 		this.dom.borderLeft = border;
    231
    232 		/*
    233 		 * Events
    234 		 */
    235
    236 		$(filler).mousedown( function (e) {
    237 			this.onselectstart = function() { return false; };
    238 			that._fnFillerDragStart.call( that, e );
    239 			return false;
    240 		} );
    241
    242 		$('tbody>tr>td', this.dom.table).live( 'mouseover mouseout', function (e) {
    243 			that._fnFillerDisplay.call( that, e );
    244 		} );
    245 	},
    246
    247
    248 	_fnColumnDefs: function ( aoColumnDefs )
    249 	{
    250 		var
    251 			i, j, k, iLen, jLen, kLen,
    252 			aTargets;
    253
    254 		/* Loop over the column defs array - loop in reverse so first instace has priority */
    255 		for ( i=aoColumnDefs.length-1 ; i>=0 ; i-- )
    256 		{
    257 			/* Each column def can target multiple columns, as it is an array */
    258 			aTargets = aoColumnDefs[i].aTargets;
    259 			for ( j=0, jLen=aTargets.length ; j<jLen ; j++ )
    260 			{
    261 				if ( typeof aTargets[j] == 'number' && aTargets[j] >= 0 )
    262 				{
    263 					/* 0+ integer, left to right column counting. */
    264 					this._fnColumnOptions( aTargets[j], aoColumnDefs[i] );
    265 				}
    266 				else if ( typeof aTargets[j] == 'number' && aTargets[j] < 0 )
    267 				{
    268 					/* Negative integer, right to left column counting */
    269 					this._fnColumnOptions( this.s.dt.aoColumns.length+aTargets[j], aoColumnDefs[i] );
    270 				}
    271 				else if ( typeof aTargets[j] == 'string' )
    272 				{
    273 					/* Class name matching on TH element */
    274 					for ( k=0, kLen=this.s.dt.aoColumns.length ; k<kLen ; k++ )
    275 					{
    276 						if ( aTargets[j] == "_all" ||
    277 						     this.s.dt.aoColumns[k].nTh.className.indexOf( aTargets[j] ) != -1 )
    278 						{
    279 							this._fnColumnOptions( k, aoColumnDefs[i] );
    280 						}
    281 					}
    282 				}
    283 			}
    284 		}
    285 	},
    286
    287
    288 	_fnColumnsAll: function ( aoColumns )
    289 	{
    290 		for ( var i=0, iLen=this.s.dt.aoColumns.length ; i<iLen ; i++ )
    291 		{
    292 			this._fnColumnOptions( i, aoColumns[i] );
    293 		}
    294 	},
    295
    296
    297 	_fnAddColumn: function ( i )
    298 	{
    299 		this.s.columns[i] = {
    300 			enable: true,
    301 			read: this._fnReadCell,
    302 			write: this._fnWriteCell,
    303 			step: this._fnStep,
    304 			complete: null
    305 		};
    306 	},
    307
    308 	_fnColumnOptions: function ( i, opts )
    309 	{
    310 		if ( typeof opts.bEnable != 'undefined' )
    311 		{
    312 			this.s.columns[i].enable = opts.bEnable;
    313 		}
    314
    315 		if ( typeof opts.fnRead != 'undefined' )
    316 		{
    317 			this.s.columns[i].read = opts.fnRead;
    318 		}
    319
    320 		if ( typeof opts.fnWrite != 'undefined' )
    321 		{
    322 			this.s.columns[i].write = opts.fnWrite;
    323 		}
    324
    325 		if ( typeof opts.fnStep != 'undefined' )
    326 		{
    327 			this.s.columns[i].step = opts.fnStep;
    328 		}
    329
    330 		if ( typeof opts.fnCallback != 'undefined' )
    331 		{
    332 			this.s.columns[i].complete = opts.fnCallback;
    333 		}
    334 	},
    335
    336
    337 	/**
    338 	 * Find out the coordinates of a given TD cell in a table
    339 	 *  @method  _fnTargetCoords
    340 	 *  @param   {Node} nTd
    341 	 *  @returns {Object} x and y properties, for the position of the cell in the tables DOM
    342 	 */
    343 	_fnTargetCoords: function ( nTd )
    344 	{
    345 		var nTr = nTd.parentNode;
    346
    347 		return {
    348 			x: $('td', nTr).index(nTd),
    349 			y: $('tr', nTr.parentNode).index(nTr)
    350 		};
    351 	},
    352
    353
    354 	/**
    355 	 * Display the border around one or more cells (from start to end)
    356 	 *  @method  _fnUpdateBorder
    357 	 *  @param   {Node} nStart Starting cell
    358 	 *  @param   {Node} nEnd Ending cell
    359 	 *  @returns void
    360 	 */
    361 	_fnUpdateBorder: function ( nStart, nEnd )
    362 	{
    363 		var
    364 			border = this.s.border.width,
    365 			offsetStart = $(nStart).offset(),
    366 			offsetEnd = $(nEnd).offset(),
    367 			x1 = offsetStart.left - border,
    368 			x2 = offsetEnd.left + $(nEnd).outerWidth(),
    369 			y1 = offsetStart.top - border,
    370 			y2 = offsetEnd.top + $(nEnd).outerHeight(),
    371 			width = offsetEnd.left + $(nEnd).outerWidth() - offsetStart.left + (2*border),
    372 			height = offsetEnd.top + $(nEnd).outerHeight() - offsetStart.top + (2*border),
    373 			oStyle;
    374
    375 		if ( this.s.dt.oScroll.sY !== "" )
    376 		{
    377 			/* The border elements are inside the DT scroller - so position relative to that */
    378 			var
    379 				offsetScroll = $(this.s.dt.nTable.parentNode).offset(),
    380 				scrollTop = $(this.s.dt.nTable.parentNode).scrollTop(),
    381 				scrollLeft = $(this.s.dt.nTable.parentNode).scrollLeft();
    382
    383 			x1 -= offsetScroll.left - scrollLeft;
    384 			x2 -= offsetScroll.left - scrollLeft;
    385 			y1 -= offsetScroll.top - scrollTop;
    386 			y2 -= offsetScroll.top - scrollTop;
    387 		}
    388
    389 		/* Top */
    390 		oStyle = this.dom.borderTop.style;
    391 		oStyle.top = y1+"px";
    392 		oStyle.left = x1+"px";
    393 		oStyle.height = this.s.border.width+"px";
    394 		oStyle.width = width+"px";
    395
    396 		/* Bottom */
    397 		oStyle = this.dom.borderBottom.style;
    398 		oStyle.top = y2+"px";
    399 		oStyle.left = x1+"px";
    400 		oStyle.height = this.s.border.width+"px";
    401 		oStyle.width = width+"px";
    402
    403 		/* Left */
    404 		oStyle = this.dom.borderLeft.style;
    405 		oStyle.top = y1+"px";
    406 		oStyle.left = x1+"px";
    407 		oStyle.height = height+"px";
    408 		oStyle.width = this.s.border.width+"px";
    409
    410 		/* Right */
    411 		oStyle = this.dom.borderRight.style;
    412 		oStyle.top = y1+"px";
    413 		oStyle.left = x2+"px";
    414 		oStyle.height = height+"px";
    415 		oStyle.width = this.s.border.width+"px";
    416 	},
    417
    418
    419 	/**
    420 	 * Mouse down event handler for starting a drag
    421 	 *  @method  _fnFillerDragStart
    422 	 *  @param   {Object} e Event object
    423 	 *  @returns void
    424 	 */
    425 	_fnFillerDragStart: function (e)
    426 	{
    427 		var that = this;
    428 		var startingTd = this.dom.currentTarget;
    429
    430 		this.s.drag.dragging = true;
    431
    432 		that.dom.borderTop.style.display = "block";
    433 		that.dom.borderRight.style.display = "block";
    434 		that.dom.borderBottom.style.display = "block";
    435 		that.dom.borderLeft.style.display = "block";
    436
    437 		var coords = this._fnTargetCoords( startingTd );
    438 		this.s.drag.startX = coords.x;
    439 		this.s.drag.startY = coords.y;
    440
    441 		this.s.drag.startTd = startingTd;
    442 		this.s.drag.endTd = startingTd;
    443
    444 		this._fnUpdateBorder( startingTd, startingTd );
    445
    446 		$(document).bind('mousemove.AutoFill', function (e) {
    447 			that._fnFillerDragMove.call( that, e );
    448 		} );
    449
    450 		$(document).bind('mouseup.AutoFill', function (e) {
    451 			that._fnFillerFinish.call( that, e );
    452 		} );
    453
    454 		/* Scrolling information cache */
    455 		this.s.screen.y = e.pageY;
    456 		this.s.screen.height = $(window).height();
    457 		this.s.screen.scrollTop = $(document).scrollTop();
    458
    459 		if ( this.s.dt.oScroll.sY !== "" )
    460 		{
    461 			this.s.scroller.top = $(this.s.dt.nTable.parentNode).offset().top;
    462 			this.s.scroller.bottom = this.s.scroller.top + $(this.s.dt.nTable.parentNode).height();
    463 		}
    464
    465 		/* Scrolling handler - we set an interval (which is cancelled on mouse up) which will fire
    466 		 * regularly and see if we need to do any scrolling
    467 		 */
    468 		this.s.screen.interval = setInterval( function () {
    469 			var iScrollTop = $(document).scrollTop();
    470 			var iScrollDelta = iScrollTop - that.s.screen.scrollTop;
    471 			that.s.screen.y += iScrollDelta;
    472
    473 			if ( that.s.screen.height - that.s.screen.y + iScrollTop < 50 )
    474 			{
    475 				$('html, body').animate( {
    476 					scrollTop: iScrollTop + 50
    477 				}, 240, 'linear' );
    478 			}
    479 			else if ( that.s.screen.y - iScrollTop < 50 )
    480 			{
    481 				$('html, body').animate( {
    482 					scrollTop: iScrollTop - 50
    483 				}, 240, 'linear' );
    484 			}
    485
    486 			if ( that.s.dt.oScroll.sY !== "" )
    487 			{
    488 				if ( that.s.screen.y > that.s.scroller.bottom - 50 )
    489 				{
    490 					$(that.s.dt.nTable.parentNode).animate( {
    491 						scrollTop: $(that.s.dt.nTable.parentNode).scrollTop() + 50
    492 					}, 240, 'linear' );
    493 				}
    494 				else if ( that.s.screen.y < that.s.scroller.top + 50 )
    495 				{
    496 					$(that.s.dt.nTable.parentNode).animate( {
    497 						scrollTop: $(that.s.dt.nTable.parentNode).scrollTop() - 50
    498 					}, 240, 'linear' );
    499 				}
    500 			}
    501 		}, 250 );
    502 	},
    503
    504
    505 	/**
    506 	 * Mouse move event handler for during a move. See if we want to update the display based on the
    507 	 * new cursor position
    508 	 *  @method  _fnFillerDragMove
    509 	 *  @param   {Object} e Event object
    510 	 *  @returns void
    511 	 */
    512 	_fnFillerDragMove: function (e)
    513 	{
    514 		if ( e.target && e.target.nodeName.toUpperCase() == "TD" &&
    515 		 	e.target != this.s.drag.endTd )
    516 		{
    517 			var coords = this._fnTargetCoords( e.target );
    518
    519 			if ( coords.x != this.s.drag.startX )
    520 			{
    521 				e.target = $('tbody>tr:eq('+coords.y+')>td:eq('+this.s.drag.startX+')', this.dom.table)[0];
    522 			 	coords = this._fnTargetCoords( e.target );
    523 			}
    524
    525 			if ( coords.x == this.s.drag.startX )
    526 			{
    527 				var drag = this.s.drag;
    528 				drag.endTd = e.target;
    529
    530 				if ( coords.y >= this.s.drag.startY )
    531 				{
    532 					this._fnUpdateBorder( drag.startTd, drag.endTd );
    533 				}
    534 				else
    535 				{
    536 					this._fnUpdateBorder( drag.endTd, drag.startTd );
    537 				}
    538 				this._fnFillerPosition( e.target );
    539 			}
    540 		}
    541
    542 		/* Update the screen information so we can perform scrolling */
    543 		this.s.screen.y = e.pageY;
    544 		this.s.screen.scrollTop = $(document).scrollTop();
    545
    546 		if ( this.s.dt.oScroll.sY !== "" )
    547 		{
    548 			this.s.scroller.scrollTop = $(this.s.dt.nTable.parentNode).scrollTop();
    549 			this.s.scroller.top = $(this.s.dt.nTable.parentNode).offset().top;
    550 			this.s.scroller.bottom = this.s.scroller.top + $(this.s.dt.nTable.parentNode).height();
    551 		}
    552 	},
    553
    554
    555 	/**
    556 	 * Mouse release handler - end the drag and take action to update the cells with the needed values
    557 	 *  @method  _fnFillerFinish
    558 	 *  @param   {Object} e Event object
    559 	 *  @returns void
    560 	 */
    561 	_fnFillerFinish: function (e)
    562 	{
    563 		var that = this;
    564
    565 		$(document).unbind('mousemove.AutoFill');
    566 		$(document).unbind('mouseup.AutoFill');
    567
    568 		this.dom.borderTop.style.display = "none";
    569 		this.dom.borderRight.style.display = "none";
    570 		this.dom.borderBottom.style.display = "none";
    571 		this.dom.borderLeft.style.display = "none";
    572
    573 		this.s.drag.dragging = false;
    574
    575 		clearInterval( this.s.screen.interval );
    576
    577 		var coordsStart = this._fnTargetCoords( this.s.drag.startTd );
    578 		var coordsEnd = this._fnTargetCoords( this.s.drag.endTd );
    579 		var aTds = [];
    580 		var bIncrement;
    581
    582 		if ( coordsStart.y <= coordsEnd.y )
    583 		{
    584 			bIncrement = true;
    585 			for ( i=coordsStart.y ; i<=coordsEnd.y ; i++ )
    586 			{
    587 				aTds.push( $('tbody>tr:eq('+i+')>td:eq('+coordsStart.x+')', this.dom.table)[0] );
    588 			}
    589 		}
    590 		else
    591 		{
    592 			bIncrement = false;
    593 			for ( i=coordsStart.y ; i>=coordsEnd.y ; i-- )
    594 			{
    595 				aTds.push( $('tbody>tr:eq('+i+')>td:eq('+coordsStart.x+')', this.dom.table)[0] );
    596 			}
    597 		}
    598
    599
    600 		var iColumn = coordsStart.x;
    601 		var bLast = false;
    602 		var aoEdited = [];
    603 		var sStart = this.s.columns[iColumn].read.call( this, this.s.drag.startTd );
    604 		var oPrepped = this._fnPrep( sStart );
    605
    606 		for ( i=0, iLen=aTds.length ; i<iLen ; i++ )
    607 		{
    608 			if ( i==iLen-1 )
    609 			{
    610 				bLast = true;
    611 			}
    612
    613 			var original = this.s.columns[iColumn].read.call( this, aTds[i] );
    614 			var step = this.s.columns[iColumn].step.call( this, aTds[i], oPrepped, i, bIncrement,
    615 				'SPRYMEDIA_AUTOFILL_STEPPER' );
    616 			this.s.columns[iColumn].write.call( this, aTds[i], step, bLast );
    617
    618 			aoEdited.push( {
    619 				td: aTds[i],
    620 				newValue: step,
    621 				oldValue: original
    622 			} );
    623 		}
    624
    625 		if ( this.s.columns[iColumn].complete !== null )
    626 		{
    627 			this.s.columns[iColumn].complete.call( this, aoEdited );
    628 		}
    629 	},
    630
    631
    632 	/**
    633 	 * Chunk a string such that it can be filled in by the stepper function
    634 	 *  @method  _fnPrep
    635 	 *  @param   {String} sStr String to prep
    636 	 *  @returns {Object} with parameters, iStart, sStr and sPostFix
    637 	 */
    638 	_fnPrep: function ( sStr )
    639 	{
    640 		var aMatch = sStr.match(/[\d\.]+/g);
    641 		if ( !aMatch || aMatch.length === 0 )
    642 		{
    643 			return {
    644 				iStart: 0,
    645 				sStr: sStr,
    646 				sPostFix: ""
    647 			};
    648 		}
    649
    650 		var sLast = aMatch[ aMatch.length-1 ];
    651 		var num = parseInt(sLast, 10);
    652 		var regex = new RegExp( '^(.*)'+sLast+'(.*?)$' );
    653 		var decimal = sLast.match(/\./) ? "."+sLast.split('.')[1] : "";
    654
    655 		return {
    656 			iStart: num,
    657 			sStr: sStr.replace(regex, "$1SPRYMEDIA_AUTOFILL_STEPPER$2"),
    658 			sPostFix: decimal
    659 		};
    660 	},
    661
    662
    663 	/**
    664 	 * Render a string for it's position in the table after the drag (incrememt numbers)
    665 	 *  @method  _fnStep
    666 	 *  @param   {Node} nTd Cell being written to
    667 	 *  @param   {Object} oPrepped Prepared object for the stepper (from _fnPrep)
    668 	 *  @param   {Int} iDiff Step difference
    669 	 *  @param   {Boolean} bIncrement Increment (true) or decriment (false)
    670 	 *  @param   {String} sToken Token to replace
    671 	 *  @returns {String} Rendered information
    672 	 */
    673 	_fnStep: function ( nTd, oPrepped, iDiff, bIncrement, sToken )
    674 	{
    675 		var iReplace = bIncrement ? (oPrepped.iStart+iDiff) : (oPrepped.iStart-iDiff);
    676 		if ( isNaN(iReplace) )
    677 		{
    678 			iReplace = "";
    679 		}
    680 		return oPrepped.sStr.replace( sToken, iReplace+oPrepped.sPostFix );
    681 	},
    682
    683
    684 	/**
    685 	 * Read informaiton from a cell, possibly using live DOM elements if suitable
    686 	 *  @method  _fnReadCell
    687 	 *  @param   {Node} nTd Cell to read
    688 	 *  @returns {String} Read value
    689 	 */
    690 	_fnReadCell: function ( nTd )
    691 	{
    692 		var jq = $('input', nTd);
    693 		if ( jq.length > 0 )
    694 		{
    695 			return $(jq).val();
    696 		}
    697
    698 		jq = $('select', nTd);
    699 		if ( jq.length > 0 )
    700 		{
    701 			return $(jq).val();
    702 		}
    703
    704 		return nTd.innerHTML;
    705 	},
    706
    707
    708 	/**
    709 	 * Write informaiton to a cell, possibly using live DOM elements if suitable
    710 	 *  @method  _fnWriteCell
    711 	 *  @param   {Node} nTd Cell to write
    712 	 *  @param   {String} sVal Value to write
    713 	 *  @param   {Boolean} bLast Flag to show if this is that last update
    714 	 *  @returns void
    715 	 */
    716 	_fnWriteCell: function ( nTd, sVal, bLast )
    717 	{
    718 		var jq = $('input', nTd);
    719 		if ( jq.length > 0 )
    720 		{
    721 			$(jq).val( sVal );
    722 			return;
    723 		}
    724
    725 		jq = $('select', nTd);
    726 		if ( jq.length > 0 )
    727 		{
    728 			$(jq).val( sVal );
    729 			return;
    730 		}
    731
    732 		var pos = this.s.dt.oInstance.fnGetPosition( nTd );
    733 		this.s.dt.oInstance.fnUpdate( sVal, pos[0], pos[2], bLast );
    734 	},
    735
    736
    737 	/**
    738 	 * Display the drag handle on mouse over cell
    739 	 *  @method  _fnFillerDisplay
    740 	 *  @param   {Object} e Event object
    741 	 *  @returns void
    742 	 */
    743 	_fnFillerDisplay: function (e)
    744 	{
    745 		/* Don't display automatically when dragging */
    746 		if ( this.s.drag.dragging)
    747 		{
    748 			return;
    749 		}
    750
    751 		/* Check that we are allowed to AutoFill this column or not */
    752 		var iX = this._fnTargetCoords(e.target).x;
    753 		if ( !this.s.columns[iX].enable )
    754 		{
    755 			return;
    756 		}
    757
    758 		var filler = this.dom.filler;
    759 		if (e.type == 'mouseover')
    760 		{
    761 			this.dom.currentTarget = e.target;
    762 			this._fnFillerPosition( e.target );
    763
    764 			filler.style.display = "block";
    765 		}
    766 		else if ( !e.relatedTarget || !e.relatedTarget.className.match(/AutoFill/) )
    767 		{
    768 			filler.style.display = "none";
    769 		}
    770 	},
    771
    772
    773 	/**
    774 	 * Position the filler icon over a cell
    775 	 *  @method  _fnFillerPosition
    776 	 *  @param   {Node} nTd Cell to position filler icon over
    777 	 *  @returns void
    778 	 */
    779 	_fnFillerPosition: function ( nTd )
    780 	{
    781 		var offset = $(nTd).offset();
    782 		var filler = this.dom.filler;
    783 		filler.style.top = (offset.top - (this.s.filler.height / 2)-1 + $(nTd).outerHeight())+"px";
    784 		filler.style.left = (offset.left - (this.s.filler.width / 2)-1 + $(nTd).outerWidth())+"px";
    785 	}
    786 };
    787
    788
    789
    790
    791 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    792  * Constants
    793  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
    794
    795 /**
    796  * Name of this class
    797  *  @constant CLASS
    798  *  @type     String
    799  *  @default  AutoFill
    800  */
    801 AutoFill.prototype.CLASS = "AutoFill";
    802
    803
    804 /**
    805  * AutoFill version
    806  *  @constant  VERSION
    807  *  @type      String
    808  *  @default   1.1.1
    809  */
    810 AutoFill.VERSION = "1.1.1";
    811 AutoFill.prototype.VERSION = "1.1.1";
    812
    813
    814 })(jQuery);