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);