');
						this.options.setValue($cell, value, i, j);
						cellValue = $cell.text();
					} else if (_.isObject(value)) {
						cellValue = value.value;
					} else {
						cellValue = value || '';
					}
					row.push(cellValue);
				}
			}
			return JSON.stringify(state);
		},
		/**
   * Update the dataprovider with a new one, and call onDataChange for a rerender
   * @param {object} dataProvider - the new dataProvider used by the grid
   * @param {object} options - an options object which contains fixedRows and fixedColumns
   * @public
   */
		updateDataProvider: function updateDataProvider(dataProvider, options) {
			this.dataProvider = dataProvider;
			this.onDataChange({
				fixedRows: options.fixedRows,
				fixedColumns: options.fixedColumns
			});
		},
		/**
   * Notify the grid whenever the data provider is modified. This will update the number of rows/columns,
   * number of fixed rows/columns. Takes an options object:
   * 		isNewDataAvailable flag (boolean) used to keep track whether or not we're
   * 			appending data to the dataprovider or completely changing the data. If we're merely appending, then we should keep existing cell
   * 			stats and handle the grid scrolling to keep the right positioning for the grid. Else we can create new cell stats.
   * @param {object} options
   * @public
   */
		onDataChange: function onDataChange() {
			var _this = this;
			var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
			var isForceRedraw = this._setProvideDataCount();
			// Reapply the pinned rows/columns
			this._setFixedRowsAndColumns(options);
			// If this parameter is passed in as true, then we assumed that we were appending data to the dataprovider.
			// In this case we'd like to keep our position in the grid so scrolling vertically will appear seamless. For now
			// we have to do this.scrollTopOffset - 1 pixel because if you scroll down fast enough, scrolling will hang at the bottom
			// of the grid when we fetch additional data (async operation) since handleOffset() will set this.scrollTopOffset to be equal to
			// the top offset of the scrollable area. By subtracting a single pixel, this is inconspicuous to the eye and essentially forces
			// a recalculation of the scrolling.
			if (options.isNewDataAvailable) {
				this.scrollTopOffset -= 1;
				this.handleOffset();
				return Promise.resolve(this.isRendered);
			} else {
				//The data has changed. Force a rerender
				return this.resolveDelayedPromises(options).then(function () {
					_this.reRenderAfterDataChange(isForceRedraw, options);
					return _this.isRendered;
				});
			}
		},
		reRenderAfterDataChange: function reRenderAfterDataChange(isForceRedraw, options) {
			var _this2 = this;
			//silent scroll is used to handle the case where a filter was being set
			//and we didnt want it to trigger a scroll event as this hid the the flyout
			//for the filter
			this.silentScroll = true;
			if (isForceRedraw) {
				this.createCellStat(this.verticalScrollConfig);
				this.createCellStat(this.horizontalScrollConfig);
			}
			this._recalculateRenderingIndexes();
			if (isForceRedraw || !this.isRendered) {
				// redraw the table cells
				this.prepareTableForRendering();
			}
			options.forceScrollUpdate = true;
			this.updateTables(options);
			setTimeout(function () {
				_this2.silentScroll = false;
			}, 500);
		},
		updateTables: function updateTables() {
			var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
			this.updateTableValues();
			//Recalculate stat size statistics after all values have been updated in the UI
			this._reCalcTableStats();
			//Now resize the tables with new stats so all tables line up
			this.updateTableSize();
			this.updateScroll(options.forceScrollUpdate);
			this.autoFitContentIfNeeded();
		},
		resolveDelayedPromises: function resolveDelayedPromises() {
			var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
			return options.onDelayedUpdateReady ? options.onDelayedUpdateReady() : Promise.resolve();
		},
		/**
  * A cell width/height can change after all values are rendered in the grid. When this happens it can cause the tables not to line up.
  * This function fixes this by resetting the column header cells to initial minimum size, recalculate all table stats then resize the table headers so that
  * they line up with the virtual table cells
  */
		_reCalcTableStats: function _reCalcTableStats() {
			this._resetColumnHeaderTableMinCellWidth();
			this._createCellStats();
			this.recordTableStats(this.$table);
			this.recordTableStats(this.$columnHeadersTable);
			this.recordTableStats(this.$rowHeadersTable);
			this.recordTableStats(this.$cornerTable);
			this.resizeTableIfNeeded(this.$table);
			this.resizeTableIfNeeded(this.$columnHeadersTable);
			this.resizeTableIfNeeded(this.$rowHeadersTable);
			this.resizeTableIfNeeded(this.$cornerTable);
		},
		/**
  * Reset column header cells to initial minimum sizes
  *
  */
		_resetColumnHeaderTableMinCellWidth: function _resetColumnHeaderTableMinCellWidth() {
			var $columnHeaderRows = this.$columnHeadersTable.find('tr');
			var $tr = void 0,
			    $columns = void 0,
			    $td = void 0;
			for (var index = 0; index < this.fixedRows; ++index) {
				$tr = $columnHeaderRows.eq(index);
				$columns = $tr.find('td');
				for (var i = 0; i < $columns.length; i++) {
					$td = $columns.eq(i);
					this.setCellMinWidth($td);
				}
			}
		},
		/**
   * Make sure that the current padding is not too big.
   *
   * If we are re-rendering after an initial render and we have scrolled and then the grid changes size,
   *  then we need to update the padding in case it is now too big after resizing the table
   * @private
   */
		_recalculateTablePositionForInitialRendering: function _recalculateTablePositionForInitialRendering() {
			var padding = this.$scrollableArea[this.verticalScrollConfig.scrollFunction]();
			var scrollInfo = this.getScrollIncrement(padding, this.verticalScrollConfig);
			this.verticalScrollConfig.setScrolledIdx(scrollInfo.steps);
			this.verticalScrollConfig.translate(scrollInfo.padding);
			padding = this.$scrollableArea[this.horizontalScrollConfig.scrollFunction]();
			scrollInfo = this.getScrollIncrement(padding, this.horizontalScrollConfig);
			this.horizontalScrollConfig.setScrolledIdx(scrollInfo.steps);
			this.horizontalScrollConfig.translate(scrollInfo.padding);
		},
		/**
   * Recalculate the rendering indexes
   * If the rendering index will only render less than what we are currently rendering,
   * then we need to asjust the index to render more
   *
   */
		_recalculateRenderingIndexes: function _recalculateRenderingIndexes() {
			this.verticalScrollConfig.recalculateRenderingIndexes();
			this.horizontalScrollConfig.recalculateRenderingIndexes();
		},
		_setProvideDataCount: function _setProvideDataCount() {
			var isCountChanged = false;
			// Get the new data count
			if (this.verticalScrollConfig.count !== this.dataProvider.getNumRows()) {
				this.verticalScrollConfig.count = this.dataProvider.getNumRows();
				isCountChanged = true;
			}
			if (this.horizontalScrollConfig.count !== this.dataProvider.getNumColumns()) {
				this.horizontalScrollConfig.count = this.dataProvider.getNumColumns();
				isCountChanged = true;
			}
			return isCountChanged;
		},
		_setFixedRowsAndColumns: function _setFixedRowsAndColumns(options) {
			this.verticalScrollConfig.count = this.dataProvider.getNumRows();
			this.horizontalScrollConfig.count = this.dataProvider.getNumColumns();
			this.fixedRows = !_.isUndefined(options.fixedRows) ? options.fixedRows : this.fixedRows;
			this.fixedColumns = !_.isUndefined(options.fixedColumns) ? options.fixedColumns : this.fixedColumns;
		},
		/**
   * Recalculate positioning for the grid table and then rerender
   * @public
   */
		onResize: function onResize() {
			if (this.$scrollableArea.scrollTop() === 0 && this.$scrollableArea.scrollLeft() === 0 && (this.scrollTopOffset !== 0 || this.scrollLeftOffset !== 0)) {
				// We lost the scrolling state. We need to restore it. This might happen if the node was removed from the dom and restored
				this.resetScroll();
			}
			//reset the cell stats if we have maximized the content so that they don't affect the new rendering
			// We will only clear the affected orientation
			if (this.autoResizeVerticalEnabled) {
				this.createCellStat(this.verticalScrollConfig);
			}
			if (this.autoResizeHorizontalEnabled) {
				this.createCellStat(this.horizontalScrollConfig);
			}
			// Adjust the padding in case we are at the end of the scrolling and we are showing less than what we can after the resize.
			this._recalculateTablePositionForInitialRendering();
			// re-render
			return this.render();
		},
		/**
   * Returns an object detailing some information about the cells
   * @private
   */
		createCellStat: function createCellStat(config) {
			config.stats = {
				map: {},
				totalRecordedSize: 0,
				count: 0,
				minSize: config.defaultSize(),
				maxSize: config.defaultSize(),
				averageSize: config.defaultSize(),
				totalSize: config.count * config.defaultSize()
			};
		},
		_createCellStats: function _createCellStats() {
			this.createCellStat(this.verticalScrollConfig);
			this.createCellStat(this.horizontalScrollConfig);
		},
		cacheMostUsedValues: function cacheMostUsedValues() {
			// Cache the most common used elements so that we
			// can easily have access to them
			this.$cells = this.$table.find('td');
			this.$rows = this.$table.find('tr');
			this.$firstRow = function () {
				return this.$table.find('tr:first');
			}.bind(this);
			this.$lastRow = function () {
				return this.$table.find('tr:last');
			}.bind(this);
			this.$firstRowFirstCell = function () {
				return this.$firstRow().find('td:first');
			}.bind(this);
			this.$firstRowLastCell = function () {
				return this.$firstRow().find('td:last');
			}.bind(this);
			this.horizontalScrollConfig.$firstCell = function () {
				return this.$firstRow().find('td:first');
			}.bind(this); //this.$firstRowFirstCell;
			this.horizontalScrollConfig.$lastCell = function () {
				return this.$firstRow().find('td:last');
			}.bind(this); //this.$firstRowLastCell;
			this.verticalScrollConfig.$firstCell = function () {
				return this.$table.find('tr:first');
			}.bind(this); //this.$firstRow;
			this.verticalScrollConfig.$lastCell = function () {
				return this.$table.find('tr:last');
			}.bind(this); // this.$lastRow;
		},
		/**
   * Render the grid table
   * @public
   */
		render: function render(options) {
			var _this3 = this;
			if (!this.$table) {
				this.$table = $('
');
				if (this.options.label) {
					this.$table.attr('aria-label', this.options.label);
				}
				this.$columnHeadersTable = $('
');
				this.$scrollableArea.attr('id', UniqueId.get('scrollableArea_'));
				this.$virtualTable = $('
');
				this.$virtualTable.attr('id', UniqueId.get('virtualTable_'));
				this.$virtualTable.append(this.$table);
				this.$scrollableArea.append(this.$virtualTable);
				this.$headerContainer = $('
');
				this.$headerContainer.attr('id', UniqueId.get('headerContainer_'));
				this.$headerContainer.append(this.$columnHeadersTable);
				if (this.fixedColumns && this.fixedColumns > 0) {
					this.bHasCornerTable = true;
					this.$headerContainer.append(this.$rowHeadersTable);
					this.$headerContainer.append(this.$cornerTable);
				}
				this.$contentWrapper = $('
');
				this.$contentWrapper.attr('id', UniqueId.get('contentWrapper_'));
				this.$contentWrapper.append(this.$scrollableArea);
				this.$contentWrapper.append(this.$headerContainer);
				this.$el.append(this.$contentWrapper);
				this.scrollTopOffset = 0;
				this.scrollLeftOffset = 0;
				this.registerScrollEvents();
			}
			return this.resolveDelayedPromises(options).then(function () {
				_this3.prepareTableForRendering();
				if (_this3.isRendered) {
					_this3.updateTables(options);
				}
				return _this3.isRendered;
			});
		},
		prepareTableForRendering: function prepareTableForRendering() {
			this.$scrollableArea.css('overflow-x', 'auto');
			this.$scrollableArea.css('overflow-y', 'auto');
			this.$headerContainer.height(this.$scrollableArea[0].clientHeight);
			this.$headerContainer.width(this.$scrollableArea[0].clientWidth);
			this._clearTables();
			if (!this.dataProvider.getNumRows()) {
				this.isRendered = false;
				return;
			}
			this.isRendered = this.renderTable();
			if (this.isRendered) {
				this.renderPinnedColumnHeaders();
				if (this.fixedColumns && this.fixedColumns > 0) {
					this.renderPinnedRowHeaders();
					this.renderPinnedCorner();
				}
			}
			this.cacheMostUsedValues();
		},
		_clearTables: function _clearTables() {
			this.$table.empty();
			this.$columnHeadersTable.empty();
			this.$rowHeadersTable.empty();
			this.$cornerTable.empty();
		},
		autoFitContentIfNeeded: function autoFitContentIfNeeded() {
			if (this.autoResizeHorizontal || this.autoResizeVertical) {
				this.autoResizeHorizontalEnabled = false;
				this.autoResizeVerticalEnabled = false;
				var extra, nodes, index, i;
				if (this.autoResizeHorizontal && this.$virtualTable.outerWidth() <= this.$el.innerWidth()) {
					var width = this.$scrollableArea[0].clientWidth / this.horizontalScrollConfig.count - 2;
					extra = 0;
					nodes = [];
					for (index in this.horizontalScrollConfig.stats.map) {
						if (this.horizontalScrollConfig.stats.map.hasOwnProperty(index)) {
							if (this.horizontalScrollConfig.stats.map[index] > width) {
								extra += this.horizontalScrollConfig.stats.map[index] - width;
							} else {
								nodes.push(this.$el.find('td[col="' + index + '"]'));
							}
						}
					}
					width -= extra / nodes.length;
					for (i = 0; i < nodes.length; i++) {
						nodes[i].css('min-width', width + 'px');
					}
					this.$virtualTable.css(this.horizontalScrollConfig.minSizeProp, '0px');
					this.$scrollableArea.css('overflow-x', 'visible');
					this.autoResizeHorizontalEnabled = true;
				}
				if (this.autoResizeVertical && this.$virtualTable.outerHeight() <= this.$el.innerHeight()) {
					var height = this.$scrollableArea[0].clientHeight / this.verticalScrollConfig.count - 2;
					extra = 0;
					nodes = [];
					for (index in this.verticalScrollConfig.stats.map) {
						if (this.verticalScrollConfig.stats.map[index] > height) {
							extra += this.verticalScrollConfig.stats.map[index] - height;
						} else {
							nodes.push(this.$el.find('td[row="' + index + '"]'));
						}
					}
					height -= extra / nodes.length;
					for (i = 0; i < nodes.length; i++) {
						nodes[i].css('height', height + 'px');
					}
					this.$virtualTable.css(this.verticalScrollConfig.minSizeProp, '0px');
					this.$scrollableArea.css('overflow-y', 'visible');
					this.autoResizeVerticalEnabled = true;
				}
				if (this.autoResizeVerticalEnabled || this.autoResizeHorizontalEnabled) {
					this.recordTableStats(this.$table);
					this.resizeTableIfNeeded(this.$columnHeadersTable);
					this.resizeTableIfNeeded(this.$rowHeadersTable);
				}
			}
		},
		registerScrollEvents: function registerScrollEvents() {
			this.$scrollableArea.on('scroll.virtualScroll', this.onScroll.bind(this));
			this.$headerContainer.on('wheel', this.onWheel.bind(this));
		},
		onWheel: function onWheel(event) {
			var scrollTop = this.$scrollableArea.scrollTop();
			if (event.originalEvent.deltaY < 0) {
				this.$scrollableArea.scrollTop(this.$scrollableArea.scrollTop() - 90);
			} else if (event.originalEvent.deltaY > 0) {
				this.$scrollableArea.scrollTop(this.$scrollableArea.scrollTop() + 90);
			}
			if (event.originalEvent.deltaX < 0) {
				this.$scrollableArea.scrollLeft(this.$scrollableArea.scrollLeft() - 90);
			} else if (event.originalEvent.deltaX > 0) {
				this.$scrollableArea.scrollLeft(this.$scrollableArea.scrollLeft() + 90);
			}
			if (scrollTop !== this.$scrollableArea.scrollTop()) {
				event.preventDefault();
			}
		},
		remove: function remove() {
			if (this.scrollTimer) {
				clearTimeout(this.scrollTimer);
			}
			this.$scrollableArea && this.$scrollableArea.remove();
			this.$headerContainer && this.$headerContainer.remove();
			if (this.$contentWrapper) {
				this.$contentWrapper.remove();
				this.$contentWrapper = null;
			}
			this.dataProvider = null;
			View.inherited('remove', this, arguments);
		},
		/**
   * scroll handler. Called when the user scrolls the
   * virtual table
   */
		onScroll: function onScroll() {
			var isScrollStarted = false;
			if (this.scrollTimer) {
				clearTimeout(this.scrollTimer);
				isScrollStarted = true;
			}
			// Notify the start scroll handler
			if (!isScrollStarted && this.options.onStartScroll && !this.silentScroll) {
				this.options.onStartScroll();
			}
			this.scrollTimer = setTimeout(function () {
				this.scrollTimer = null;
				if (this.options.onFinishScroll && !this.silentScroll) {
					this.options.onFinishScroll();
				}
				this._setFocusAfterScroll();
			}.bind(this), 800);
			this.updateScroll();
		},
		/**
   * Update the positions of the pinned headers. Also, if there are 5 or fewer rowers left to render, determine whether or not we
   * need to fetch more data from our server by checking if we passed in a callback function inside our options object in init(). If
   * so, make the call to fetch more data, wait for the response, and then update position and offsets.
   * @private
   */
		updateScroll: function updateScroll(isForceUpdate) {
			var scrollTopOffset = this.$scrollableArea.scrollTop();
			var scrollLeftOffset = this.$scrollableArea.scrollLeft();
			this.updatePinnedHeaderPosition(scrollTopOffset, scrollLeftOffset);
			var scrolledIdx = this.verticalScrollConfig.getScrolledIdx();
			var numberOfDataRowsInGrid = this.options.getMoreData && this.verticalScrollConfig.count - (scrolledIdx + this.verticalScrollConfig.renderedCount);
			if (numberOfDataRowsInGrid !== false && numberOfDataRowsInGrid >= 0 && numberOfDataRowsInGrid < 5 && this.scrollTopOffset <= scrollTopOffset) {
				this.options.getMoreData();
			}
			this.handleOffset(scrollTopOffset, scrollLeftOffset, isForceUpdate);
		},
		resetScroll: function resetScroll() {
			this.$scrollableArea.scrollTop(this.scrollTopOffset);
			this.$scrollableArea.scrollLeft(this.scrollLeftOffset);
		},
		/**
   * Handles the scrolling if we have scrolled horizontally or vertically by changing the vertical/horizontal offsets.
   * @private
   */
		handleOffset: function handleOffset(scrollTop, scrollLeft, isForceUpdate) {
			var scrollTopOffset = scrollTop || this.$scrollableArea.scrollTop();
			var scrollLeftOffset = scrollLeft || this.$scrollableArea.scrollLeft();
			if (this.scrollTopOffset !== scrollTopOffset || isForceUpdate) {
				this.handleScroll(scrollTopOffset - this.scrollTopOffset, this.verticalScrollConfig, isForceUpdate);
			}
			if (this.scrollLeftOffset !== scrollLeftOffset || isForceUpdate) {
				this.handleScroll(scrollLeftOffset - this.scrollLeftOffset, this.horizontalScrollConfig, isForceUpdate);
			}
			// update the current scrolltop and scrollLeft
			// values.
			// Don't use the variables above
			// (scrollTopOffset/scrollLeftOffset), the scroll
			// values might have changed while processing the
			// scroll event
			this.scrollTopOffset = this.$scrollableArea.scrollTop();
			this.scrollLeftOffset = this.$scrollableArea.scrollLeft();
		},
		/**
   * Remove any colspan/rowspan that was set on the cell
   * when the grouping is enabled This method will save
   * the current state of the grouping so it can be
   * restored using the restoreGrouping method
   */
		removeGrouping: function removeGrouping() {
			if (!this.isGroupEnabled) {
				return;
			}
			this.$cells.each(function () {
				// we use font-size set to 0px to hide the text to avoid the height/width impact on the cell
				var $td = $(this);
				var display = $td.css('display');
				var fontSize = $td.css('font-size');
				if (display === 'none') {
					$td.css({
						'display': '',
						'visibility': 'hidden',
						'font-size': '0px'
					});
				}
				if (fontSize) {
					$td.attr({ '_fontSize': fontSize });
				}
				var colspan = $td.attr('colspan');
				$td.attr({ '_colspan': null });
				if (colspan) {
					$td.attr({
						'colspan': null,
						'_colspan': colspan
					});
					$td.css({ 'font-size': '0px' });
				}
				var rowspan = $td.attr('rowspan');
				$td.attr({ '_rowspan': null });
				if (rowspan) {
					$td.attr({
						'rowspan': null,
						'_rowspan': rowspan
					});
					$td.css({ 'font-size': '0px' });
				}
			});
		},
		/**
   * Restore any grouping by adding colspan/rowspan to the
   * cells. This method is supposed to restore the
   * grouping that was removed using the removeGrouping
   * method
   * @private
   */
		restoreGrouping: function restoreGrouping() {
			if (!this.isGroupEnabled) {
				return;
			}
			this.$cells.each(function () {
				var $td = $(this);
				var display = $td.css('visibility');
				var fontSize = $td.attr('_fontSize');
				//when restore happens, we used the saved state of the fontSize to set the font-size value
				if (display === 'hidden') {
					$td.css({
						'display': 'none',
						'visibility': '',
						'font-size': fontSize ? fontSize : ''
					});
					$td.attr({
						'_fontSize': null
					});
				}
				var colspan = $td.attr('_colspan');
				if (colspan) {
					$td.attr({
						'_colspan': null,
						'colspan': colspan,
						'_fontSize': null
					});
					$td.css({
						'font-size': fontSize ? fontSize : ''
					});
				}
				var rowspan = $td.attr('_rowspan');
				if (rowspan) {
					$td.attr({
						'_rowspan': null,
						'rowspan': rowspan,
						'_fontSize': null
					});
					$td.css({
						'font-size': fontSize ? fontSize : ''
					});
				}
			});
		},
		/**
   * Offset is positive when moving right and negative
   * when moving left.
   *
   * @param offset
   */
		handleScroll: function handleScroll(offset, config, isForceUpdate) {
			if (!this.dataProvider.getNumRows()) {
				return;
			}
			// We need to temporarily remove the group to
			// determine the cell width so that we can properly
			// do the proper horizontal scrolling
			this.removeGrouping();
			var shifted = false;
			// Calculate how many columns we are shifting based on the current scroll position
			// if the increment steps is different than the current index we are rendering, then we need to redraw
			var increment = this.getScrollIncrement(this.$scrollableArea[config.scrollFunction](), config);
			if (increment.steps !== config.getScrolledIdx() || isForceUpdate) {
				shifted = this.redrawTable(increment.steps, increment.padding, config);
			}
			if (!shifted) {
				// If nothing has changed, we restore the
				// grouping. Otherwise the grouping would be
				// properly rendered when we shift the values
				this.restoreGrouping();
			} else if (this.options.onAfterGridUpdate) {
				// Cells have been updated as a result of a scroll. Notify the callback if it exists
				this.options.onAfterGridUpdate();
			}
		},
		redrawTable: function redrawTable(newScrolledToRowOrColumnIndex, paddingOffset, config) {
			this.updateTablePadding(paddingOffset, config);
			var redrawn = this.updateIndexAndValues(newScrolledToRowOrColumnIndex, config);
			this.validateTablePadding(paddingOffset, config);
			return redrawn;
		},
		updateIndexAndValues: function updateIndexAndValues(newScrolledToRowOrColumnIndex, config) {
			if (newScrolledToRowOrColumnIndex < 0 || newScrolledToRowOrColumnIndex + config.renderedCount > config.count) {
				return false;
			}
			var previousScrolledIdx = config.getScrolledIdx();
			config.setScrolledIdx(newScrolledToRowOrColumnIndex);
			this.updateTableValues({ previousScrolledIdx: previousScrolledIdx, isVerticalScrolling: config.isVerticalScrolling });
			return true;
		},
		resizeTableIfNeeded: function resizeTableIfNeeded($table) {
			//update the size of the columns in the header here if they differ from stats
			var $rows = $table.find('tr');
			for (var i = 0; i < $rows.length; i++) {
				var $tr = $rows.eq(i);
				var $columns = $tr.find('td');
				for (var j = 0; j < $columns.length; j++) {
					var $td = $columns.eq(j);
					if ($td.hasClass('corner')) {
						if ($td.is(':visible')) {
							this.resizeCorner($td);
						}
					} else {
						this.updateCellSize($td);
					}
				}
			}
			this.resizeCornerTable();
		},
		/** in the column header table, must size the corner width independently when multiple row nesting is involved since it arbitrarily chooses
   * the 2nd cell in the first array to be the visible cell and sizes it according to that cell stat when it should
   * in fact be sized based on the width of the 1st and 2nd(0th and 1st) cell width stat.
   * @param {object} td - the cell
   * @private
   */
		resizeCorner: function resizeCorner($td) {
			var colspan = parseInt($td.attr('colspan'), 10);
			var rowspan = parseInt($td.attr('rowspan'), 10);
			var i;
			var width = 0;
			var height = 0;
			for (i = 0; i < this.fixedColumns; i++) {
				width += this.horizontalScrollConfig.stats.map[i];
			}
			for (i = 0; i < this.fixedRows; i++) {
				height += this.verticalScrollConfig.stats.map[i];
			}
			//prevents the corner from becoming the size of more cells than are visible after scrolling to the right
			if (colspan !== this.fixedColumns) {
				for (i = 0; i < this.fixedColumns - colspan; i++) {
					width -= this.horizontalScrollConfig.stats.map[i];
				}
			}
			if (rowspan !== this.fixedRows) {
				for (i = 0; i < this.fixedRows - rowspan; i++) {
					height -= this.verticalScrollConfig.stats.map[i];
				}
			}
			$td.css('min-width', width + 'px');
			$td.css('height', height + 'px');
		},
		/**
   * Resize the corner table to match the stats set by _resizeCorner
   * @private
   */
		resizeCornerTable: function resizeCornerTable() {
			// we only care about the corner table if we have a pinned header
			if (this.fixedColumns > 0) {
				var width = 0;
				var height = 0;
				for (var i = 0; i < this.fixedColumns; i++) {
					width += this.horizontalScrollConfig.stats.map[i];
				}
				for (var k = 0; k < this.fixedRows; k++) {
					height += this.verticalScrollConfig.stats.map[k];
				}
				this.$corner.css({
					'min-width': width
				});
				this.$corner.css({
					'height': height
				});
			}
		},
		/**
   * Update the positions of the pinned column/row headers tables
   * @param {number} scrollTopOffset - the offset we should pad to the top
   * @param {number} scrollLeftOffset - the offset we should pad to te left
   * @private
   */
		updatePinnedHeaderPosition: function updatePinnedHeaderPosition() /*scrollTopOffset, scrollLeftOffset*/{
			this.$columnHeadersTable.css('margin-left', this.horizontalScrollConfig.getTranslateValue() - this.$scrollableArea.scrollLeft() + 'px');
			this.$rowHeadersTable.css('margin-top', this.verticalScrollConfig.getTranslateValue() - this.$scrollableArea.scrollTop() + 'px');
		},
		/**
   * based on the cells that are currently visible, update the minimum widths and heights for the columns and rows, respectively.
   * @param {object} table - the table
   * @private
   */
		recordTableStats: function recordTableStats($table) {
			var $rows = $table.find('tr');
			for (var index = $rows.length - 1; index >= 0; index--) {
				var $tr = $rows.eq(index);
				var $columns = $tr.find('td:visible');
				var $td;
				// set the values and the formats
				for (var i = 0; i < $columns.length; i++) {
					$td = $columns.eq(i);
					var colspan = parseInt($td.attr('colspan'), 10) || 1;
					var rowspan = parseInt($td.attr('rowspan'), 10) || 1;
					//only record the width of cells that have col span 1 not including corners that span multiple columns
					if (colspan === 1 && (!$td.hasClass('corner') || this.fixedColumns === 1)) {
						var col = $td.attr(ATTRIBUTES.COLUMN_INDEX);
						this.recordCellStats($td, this.horizontalScrollConfig, col);
					}
					if (rowspan === 1 && (!$td.hasClass('corner') || this.fixedRows === 1)) {
						var row = $td.attr(ATTRIBUTES.ROW_INDEX);
						this.recordCellStats($td, this.verticalScrollConfig, row);
					}
				}
			}
		},
		/**
   * adjust the size of the virtual table
   * @private
   */
		updateTableSize: function updateTableSize() {
			this.$virtualTable.css(this.horizontalScrollConfig.minSizeProp, this.horizontalScrollConfig.stats.totalSize + 'px');
			this.$virtualTable.css(this.verticalScrollConfig.minSizeProp, this.verticalScrollConfig.stats.totalSize + 'px');
		},
		/**
   * Updates the table padding in the vertical or horizontal directionshi
   * @param {number} newPadding - the new padding for the table
   * @param {config} config - the vertical/horizontal config
   * @private
   */
		updateTablePadding: function updateTablePadding(newPadding, config) {
			// adjust the padding to simulate the scroll
			var padding = newPadding;
			config.translate(padding);
		},
		validateTablePadding: function validateTablePadding(newPadding, config) {
			this.updateTableSize();
			var previousScrolledIdx = void 0;
			if (newPadding <= 0) {
				newPadding = 0;
				if (config.getScrolledIdx() !== 0) {
					previousScrolledIdx = config.getScrolledIdx();
					config.setScrolledIdx(0);
					this.updateTableValues({ previousScrolledIdx: config.getScrolledIdx(), isVerticalScrolling: config.isVerticalScrolling });
				}
				config.translate(newPadding);
			} else if (newPadding > this.$scrollableArea[config.scrollFunction]()) {
				newPadding = this.$scrollableArea[config.scrollFunction]();
				var scrollInfo = this.getScrollIncrement(newPadding, config);
				if (config.getScrolledIdx() !== scrollInfo.steps) {
					previousScrolledIdx = config.getScrolledIdx();
					config.setScrolledIdx(scrollInfo.steps);
					this.updateTableValues({ previousScrolledIdx: previousScrolledIdx, isVerticalScrolling: config.isVerticalScrolling });
				}
				config.translate(newPadding);
			}
			// The virtual table is too big or too small(some
			// cell sizes have changed).. adjust it.
			var expectedTableSize = config.sizeFunction(this.$table) + newPadding;
			var currentTableSize = config.sizeFunction(this.$virtualTable);
			if (config.getScrolledIdx() + config.renderedCount === config.count && expectedTableSize !== currentTableSize) {
				config.stats.totalSize -= currentTableSize - expectedTableSize;
				this.updateTableSize();
			}
		},
		/**
   * @private
   */
		updateTableValues: function updateTableValues() {
			var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
			var previousScrolledIdx = options.previousScrolledIdx;
			var isVerticalScrolling = options.isVerticalScrolling;
			if (this.dataProvider.getNumRows() && !this.partialValuesUpdate(previousScrolledIdx, isVerticalScrolling)) {
				this.allValuesUpdate();
			}
		},
		/**
   * @private
   */
		updateDataVertically: function updateDataVertically(previousScrolledIdx, delta) {
			this.resetTableGroups(this.$table);
			this._updateTableScrolledVertically({ $table: this.$table, previousScrolledIdx: previousScrolledIdx, delta: delta, handleGrouping: true });
			this.updateTableGroups(this.$table);
			//Remove a row and placing it at top or end of the table at the current scroll position.
			// Also, updating the table groups might change the current scroll position
			// We need to restore the scroll position
			var scrollTop = this.$scrollableArea.scrollTop();
			this.$scrollableArea.scrollTop(scrollTop);
			this.recordTableStats(this.$table);
			this.resizeTableIfNeeded(this.$columnHeadersTable);
			this.resizeTableIfNeeded(this.$rowHeadersTable);
		},
		/**
   * @private
   */
		updateRowHeaderVertically: function updateRowHeaderVertically(previousScrolledIdx, delta) {
			this.resetTableGroups(this.$rowHeadersTable);
			this._updateTableScrolledVertically({ $table: this.$rowHeadersTable, previousScrolledIdx: previousScrolledIdx, delta: delta, handleGrouping: true, isRowHeader: true });
			this.updateTableGroups(this.$rowHeadersTable);
			this.recordTableStats(this.$rowHeadersTable);
		},
		/**
   *
   * Do a partial update of the values in the given $rows array.
   * If we are not changing all values, we simply move the rows/columns and keep the unchanged cells. This is done for better performance.
   *
   * @param previousScrolledIdx  - old scrolled row or columnd index location before the updated
   * @param isVerticalScrolling - true if scrolling vertically else false
   * @param $rows
   * @returns {Boolean}
   */
		partialValuesUpdate: function partialValuesUpdate(previousScrolledIdx, isVerticalScrolling) {
			var _this4 = this;
			var scrolledVerticalIdx = this.verticalScrollConfig.getScrolledIdx();
			var index = isVerticalScrolling ? scrolledVerticalIdx : this.horizontalScrollConfig.getScrolledIdx();
			var diff = Math.abs(previousScrolledIdx - index);
			var $columnHeaderRows = this.$columnHeadersTable.find('tr');
			var $tableRows = this.$table.find('tr');
			var scrollVertical = isVerticalScrolling && diff < this.verticalScrollConfig.renderedCount;
			var scrollHorizontal = !isVerticalScrolling && diff < this.horizontalScrollConfig.renderedCount;
			if (scrollVertical) {
				var delta = this.fixedRows && this.fixedRows > 0 ? this.fixedRows : 0;
				//updateRowHeaders
				this.updateRowHeaderVertically(previousScrolledIdx, delta);
				//update data
				this.updateDataVertically(previousScrolledIdx, delta);
				this.resizeTableIfNeeded(this.$columnHeadersTable);
				this.resizeTableIfNeeded(this.$rowHeadersTable);
			} else if (scrollHorizontal) {
				this.resetTableGroups(this.$columnHeadersTable);
				var _delta = this.fixedColumns && this.fixedColumns > 0 ? this.fixedColumns : 0;
				var getTableRowIndex = function getTableRowIndex(rowIndex) {
					return scrolledVerticalIdx + rowIndex;
				};
				var options = {
					previousScrolledIdx: previousScrolledIdx,
					delta: _delta,
					cbUpdateCell: function cbUpdateCell($td, isRowHeaders, rowIndex, tdIndex) {
						rowIndex = isRowHeaders ? rowIndex : getTableRowIndex(rowIndex);
						if (previousScrolledIdx < _this4.horizontalScrollConfig.getScrolledIdx()) {
							tdIndex = _this4.horizontalScrollConfig.renderedCount - tdIndex - 1;
						} else {
							tdIndex = tdIndex + _delta;
						}
						_this4.updateCell($td, rowIndex, tdIndex);
					},
					isRowHeaders: true
				};
				//update column headers
				this._updateColumnHeadersWhenScrolleFastdHorizontally($columnHeaderRows, options);
				this.updateTableGroups(this.$columnHeadersTable);
				this.recordTableStats(this.$columnHeadersTable);
				//update data
				this.resetTableGroups(this.$table);
				options.isRowHeaders = false;
				this._updateColumnHeadersWhenScrolleFastdHorizontally($tableRows, options);
				this.updateTableGroups(this.$table);
				this.recordTableStats(this.$table);
				this.resizeTableIfNeeded(this.$columnHeadersTable);
			}
			if (scrollVertical || scrollHorizontal) {
				this.cacheMostUsedValues();
			}
			return scrollVertical || scrollHorizontal;
		},
		/**
  * Updates the table cell column headers after scrolled fast horizontally
  *
  * @param {object} $rows - the jQuery rows object
  * @param {object} options - options to update the table cells position and their contents within an table row
  */
		_updateColumnHeadersWhenScrolleFastdHorizontally: function _updateColumnHeadersWhenScrolleFastdHorizontally($rows) {
			var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
			if ($rows instanceof $) {
				var $td = void 0;
				var i = void 0;
				var j = void 0;
				var isRowHeaders = options.isRowHeaders;
				var delta = options.delta;
				var previousScrolledIdx = options.previousScrolledIdx;
				for (i = 0; i < $rows.length; i++) {
					var $row = $rows.eq(i);
					var $tds = $row.children();
					if (previousScrolledIdx < this.horizontalScrollConfig.getScrolledIdx()) {
						for (j = this.horizontalScrollConfig.getScrolledIdx() - previousScrolledIdx - 1; j >= 0; j--) {
							$td = $tds.eq(j + delta);
							$td.appendTo($row);
							options.cbUpdateCell($td, isRowHeaders, i, j);
						}
					} else {
						var $tdAfter = this.fixedColumns && this.fixedColumns > 0 ? $tds.eq(this.fixedColumns - 1) : null;
						for (j = previousScrolledIdx - this.horizontalScrollConfig.getScrolledIdx() - 1; j >= 0; j--) {
							$td = $tds.eq($tds.length - j - 1);
							if ($tdAfter) {
								$td.insertAfter($tdAfter);
							} else {
								$td.prependTo($row);
							}
							options.cbUpdateCell($td, isRowHeaders, i, j);
						}
					}
				}
			}
		},
		/**
   *  Update all the values in the given $rows array.
   *
   * @param $rows
   */
		allValuesUpdate: function allValuesUpdate() {
			var _this5 = this;
			var _cbGetScrolledPinnedRowHeaders = function _cbGetScrolledPinnedRowHeaders(rowIndex) {
				//row headers are pinned, should not be moved when scrolled vertically
				return _this5.fixedRows && rowIndex < _this5.fixedRows ? rowIndex : rowIndex + _this5.verticalScrollConfig.getScrolledIdx();
			};
			//update column headers
			var $columnHeaderRows = this.$columnHeadersTable.find('tr');
			var $tr;
			for (var index = this.fixedRows - 1; index >= 0; index--) {
				$tr = $columnHeaderRows.eq(index);
				this.updateRow({ $tr: $tr, rowIndex: index, handleGrouping: true, isRowHeader: false, cbGetScrolledFastPinnedCornerCellIdx: cbGetScrolledFastPinnedCornerCellIdx });
			}
			this.recordTableStats(this.$columnHeadersTable);
			//update row headers
			var $rowHeaderRows = this.$rowHeadersTable.find('tr');
			for (index = $rowHeaderRows.length - 1; index >= 0; index--) {
				$tr = $rowHeaderRows.eq(index);
				this.updateRow({ $tr: $tr, rowIndex: _cbGetScrolledPinnedRowHeaders(index), handleGrouping: true, isRowHeader: true });
			}
			this.recordTableStats(this.$rowHeadersTable);
			//update corner table
			var $cornerTableRows = this.$cornerTable.find('tr');
			for (index = this.fixedRows - 1; index >= 0; index--) {
				$tr = $cornerTableRows.eq(index);
				this.updateRow({ $tr: $tr, rowIndex: index, handleGrouping: true, isRowHeader: false, cbGetScrolledFastPinnedCornerCellIdx: cbGetScrolledFastPinnedCornerCellIdx });
			}
			this.recordTableStats(this.$cornerTable);
			//update data
			var $tableRows = this.$table.find('tr');
			for (index = $tableRows.length - 1; index >= 0; index--) {
				$tr = $tableRows.eq(index);
				this.updateRow({ $tr: $tr, rowIndex: _cbGetScrolledPinnedRowHeaders(index), handleGrouping: true, isRowHeader: false, cbGetScrolledFastPinnedCornerCellIdx: cbGetScrolledFastPinnedCornerCellIdx });
			}
			this.recordTableStats(this.$table);
			this.resizeTableIfNeeded(this.$columnHeadersTable);
			this.resizeTableIfNeeded(this.$rowHeadersTable);
		},
		/**
   * Updates the passed in row from the table
   * @param {object} tr - the row
   * @param {number} rowIndex - the rowIndex of the cell
   * @param {boolean} handleGrouping
   * @param {boolean} isRowHeader
   * @param {function} cbGetScrolledFastPinnedCornerCellIdx - optional callback to get the correct corner cell colunm index within a row
   * @private
   */
		updateRow: function updateRow() {
			var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
			var $tr = options.$tr;
			var rowIndex = options.rowIndex;
			var handleGrouping = options.handleGrouping;
			var isRowHeader = options.isRowHeader;
			var cbGetScrolledFastPinnedCornerCellIdx = options.cbGetScrolledFastPinnedCornerCellIdx;
			var $columns = $tr.find('td');
			var $td;
			$tr.attr(ATTRIBUTES.ROW_INDEX, rowIndex);
			if (this.options.formatRow) {
				this.options.formatRow($tr, rowIndex);
			}
			// set the values and the formats
			for (var i = 0; i < $columns.length; i++) {
				$td = $columns.eq(i);
				this.updateCell($td, rowIndex, i, handleGrouping, isRowHeader, cbGetScrolledFastPinnedCornerCellIdx);
			}
		},
		/**
   * Updates and, if necessary, formats the passed in cell with new styles and text. Called whenever we need to render the cell,
   * eg. when we scroll/resize, etc
   * @param {object} td - a jQuery object representing the cell
   * @param {number} rowIndex - the rowIndex of the cell
   * @param {number} tdIndex - the column index of the cell. If the grid has been scrolled horizontally, we must manually calculate this value
   * @param {boolean} handleGrouping
   * @param {boolean} isRowHeader
   * @param {function} cbGetScrolledFastPinnedCornerCellIdx - optional callback to get the correct corner cell colunm index within a row
   * @private
   */
		updateCell: function updateCell($td, rowIndex, tdIndex, handleGrouping, isRowHeader, cbGetScrolledFastPinnedCornerCellIdx) {
			var colIndex = cbGetScrolledFastPinnedCornerCellIdx ? cbGetScrolledFastPinnedCornerCellIdx(tdIndex, this.fixedColumns, this.fixedRows) : null;
			if (!_.isNumber(colIndex)) {
				colIndex = isRowHeader ? tdIndex : this.horizontalScrollConfig.getScrolledIdx() + tdIndex;
			}
			var value = this.dataProvider.getValue(rowIndex, colIndex);
			$td.attr(ATTRIBUTES.COLUMN_INDEX, colIndex);
			$td.attr('row', rowIndex);
			this.setCellMinWidth($td);
			$td.attr('data-group', null);
			if (handleGrouping) {
				this.resetGroupSettings($td);
			}
			if (this.options.setValue) {
				this.options.setValue($td, value, rowIndex, colIndex);
			} else {
				var cellValue;
				if (_.isObject(value)) {
					cellValue = value.value;
				} else {
					cellValue = value || '';
				}
				$td.attr('aria-label', cellValue);
				$td.text(cellValue);
			}
			if (this.options.formatCell) {
				this.options.formatCell($td, value, rowIndex, colIndex);
			}
			this.updateCellSize($td);
			if (handleGrouping) {
				this.renderGroup($td, tdIndex);
			}
		},
		/**
   * Updates the cell value with value when GridDataProvider cell value changes
   * @param {number} rowIndex - the rowIndex of the cell
   * @param {number} colIndex - the column index of the cell.
   * @param {number} value - the new value to replace the old one
   * @private
   */
		onCellDataChange: function onCellDataChange(rowIndex, colIndex, value) {
			var $td = this.$table.find('td[' + ATTRIBUTES.ROW_INDEX + '="' + rowIndex + '"][' + ATTRIBUTES.COLUMN_INDEX + '="' + colIndex + '"]');
			if ($td && $td.length && $td.is(':visible') && this.options.setValue) {
				this.options.setValue($td, value, rowIndex, colIndex);
			}
		},
		/**
   * Updates the cell size based on the largest width/height of a cell in the same row/column
   * @param {object} td - a jQuery object represeting the table cell
   * @private
   */
		updateCellSize: function updateCellSize($td) {
			var colIndex = $td.attr(ATTRIBUTES.COLUMN_INDEX);
			var rowIndex = $td.attr(ATTRIBUTES.ROW_INDEX);
			var width = this.horizontalScrollConfig.stats.map[colIndex];
			if (width) {
				//var borderleftSize = parseInt($td.css('border-left-width'), 10);
				$td.css('min-width', width + 'px');
			}
			var height = this.verticalScrollConfig.stats.map[rowIndex];
			if (height) {
				//	var borderTopSize = parseInt($td).css('border-top-width'), 10);
				$td.css('height', height + 'px');
			}
		},
		/**
   * @private
   */
		resetTableGroups: function resetTableGroups($table) {
			var $rows = $table.find('tr');
			for (var index = $rows.length - 1; index >= 0; index--) {
				var $tr = $rows.eq(index);
				var $columns = $tr.find('td');
				var $td;
				// set the values and the formats
				for (var i = 0; i < $columns.length; i++) {
					$td = $columns.eq(i);
					this.resetGroupSettings($td);
				}
			}
		},
		/**
   * @private
   */
		updateTableGroups: function updateTableGroups($table) {
			var $rows = $table.find('tr');
			for (var index = $rows.length - 1; index >= 0; index--) {
				var $tr = $rows.eq(index);
				var $columns = $tr.find('td');
				var $td;
				// set the values and the formats
				for (var i = 0; i < $columns.length; i++) {
					$td = $columns.eq(i);
					this.renderGroup($td, i);
				}
			}
		},
		/**
   * @private
   */
		resetGroupSettings: function resetGroupSettings($td) {
			if (this.isGroupEnabled) {
				var fontSize = $td.attr('_fontSize') ? $td.attr('_fontSize') : $td.css('font-size');
				$td.attr({
					'colspan': null,
					'rowspan': null,
					'_colspan': null,
					'_rowspan': null,
					'_fontSize': null
				});
				$td.css({
					'display': '',
					'visibility': '',
					'font-size': fontSize
				});
			}
		},
		/**
   * @private
   */
		renderGroup: function renderGroup($td, i) {
			if (this.isGroupEnabled) {
				var group = $td.attr('data-group');
				if (group) {
					var $prev = $td.prev();
					var prevGroup = $prev.attr('data-group');
					if (prevGroup === group) {
						var colspan = $prev.attr('colspan');
						$td.attr('colspan', 1 + parseInt(colspan, 10));
						$td.attr('rowspan', $prev.attr('rowspan'));
						$prev.css('display', 'none');
					} else {
						$td.attr('colspan', '1');
					}
					var $next = $td.parent().next().children().eq(i);
					prevGroup = $next.attr('data-group');
					if (prevGroup === group) {
						var rowspan = $next.attr('rowspan');
						$td.attr('rowspan', 1 + parseInt(rowspan, 10));
						$td.attr('colspan', $next.attr('colspan'));
						$next.css('display', 'none');
					} else {
						$td.attr('rowspan', '1');
					}
				}
			}
		},
		/**
   * @private
   */
		recordCellStats: function recordCellStats($td, config, index) {
			var width = this.getCellSize($td, config);
			var minWidth = config.stats.map[index] || 0;
			if (width > minWidth) {
				this.addCellStat(config.stats, width, index, config.count);
				return width;
			}
		},
		/**
   * Returns the size of a cell
   * @param {object} td - the cell
   * @param {object} config - the horizontal/vertical config object for the cell
   * @private
   */
		getCellSize: function getCellSize($td, config) {
			return config.sizeFunction($td);
		},
		/**
   * @private
   */
		getScrollIncrement: function getScrollIncrement(scrollOffset, config) {
			var index = 0;
			var stats = config.stats;
			var stopAt = config.count;
			var currentOffset = scrollOffset;
			var rendered = config.renderedCount;
			var step;
			var steps = 0;
			var padding = 0;
			while (index < stopAt - rendered && currentOffset > 0) {
				step = stats.map[index];
				step = step ? step : stats.maxSize;
				currentOffset -= step;
				index++;
				if (currentOffset >= 0) {
					padding += step;
					steps++;
				}
			}
			return {
				steps: steps,
				padding: padding
			};
		},
		/**
   * @private
   */
		addCellStat: function addCellStat(stat, size, index, cellCount) {
			var current = stat.map[index];
			if (current && size <= current) {
				return;
			}
			stat.map[index] = size;
			stat.totalRecordedSize += size;
			if (current) {
				stat.totalRecordedSize -= current;
			} else {
				stat.count++;
			}
			stat.minSize = stat.minSize ? Math.min(stat.minSize, size) : size;
			stat.maxSize = Math.max(stat.maxSize || 0, size);
			stat.totalSize = stat.totalRecordedSize + (cellCount - stat.count) * stat.maxSize;
			stat.averageSize = stat.totalSize / cellCount;
		},
		/**
   * Renders the table row by row
   * @private
   */
		renderTable: function renderTable() {
			var currentRow = this.verticalScrollConfig.getScrolledIdx();
			var $tr = null;
			var bottomOffset = 0;
			this.horizontalScrollConfig.renderedCount = 0;
			this.verticalScrollConfig.renderedCount = 0;
			if (this.$el.is(':hidden')) {
				return false;
			}
			do {
				$tr = this.appendRow(this.$table, currentRow, false, this.horizontalScrollConfig.renderedCount);
				if ($tr) {
					if ($tr.is(':hidden')) {
						return false;
					}
					this.verticalScrollConfig.renderedCount++;
					if (!this.horizontalScrollConfig.renderedCount) {
						this.horizontalScrollConfig.renderedCount = $tr.children().length;
					}
					currentRow++;
					if (this.isOutOfViewFromBottom($tr)) {
						bottomOffset++;
					}
					if (bottomOffset === this.offsetCount) {
						break;
					}
				}
			} while ($tr);
			return true;
		},
		/**
   * Renders the pinned column headers
   * @private
   */
		renderPinnedColumnHeaders: function renderPinnedColumnHeaders() {
			for (var i = 0; i < this.fixedRows; i++) {
				this.appendRow(this.$columnHeadersTable, i, false, this.horizontalScrollConfig.renderedCount);
			}
		},
		/**
   * Renders the pineed row headers
   * @private
   */
		renderPinnedRowHeaders: function renderPinnedRowHeaders() {
			var $tr = void 0;
			var scrolledVerticalIdx = this.verticalScrollConfig.getScrolledIdx();
			for (var i = scrolledVerticalIdx; i < scrolledVerticalIdx + this.verticalScrollConfig.renderedCount; i++) {
				if (i === this.verticalScrollConfig.count) {
					break;
				}
				$tr = $('
');
				this.$rowHeadersTable.append($tr);
				$tr.attr(ATTRIBUTES.ROW_INDEX, i);
				for (var j = 0; j < this.fixedColumns; j++) {
					this.appendCell($tr, i, j, false);
				}
			}
		},
		/**
   * Renders the pinned corners
   * @private
   */
		renderPinnedCorner: function renderPinnedCorner() {
			this.$corner = $('| ' + this.dataProvider.getValue(0, 0).value + '');
			this.$cornerTable.append($(' | 
|
'));
			this.$cornerTable.find('tr:first').append(this.$corner);
		},
		/**
   * Append/Prepend a row to the table, and then append/create all the cells for that row
   * @param {object} table - the grid table
   * @param {number} rowIndex - a number representing the row index
   * @param {boolean} isPrepend - a boolean indicating whether to append or prepend the row to the table
   * @param {number} renderColumnCount - number indicating how many columns are rendered to the DOM
   * @private
   */
		appendRow: function appendRow($table, rowIndex, isPrepend, renderColumnCount) {
			var $tr = null;
			if (rowIndex < this.verticalScrollConfig.count) {
				$tr = $('
');
				if (isPrepend) {
					$table.prepend($tr);
				} else {
					$table.append($tr);
				}
				$tr.attr(ATTRIBUTES.ROW_INDEX, rowIndex);
				var $td;
				var colIndex = this.horizontalScrollConfig.getScrolledIdx();
				var rightOffset = 0;
				for (var i = colIndex; i < this.horizontalScrollConfig.count; i++) {
					$td = this.appendCell($tr, rowIndex, i);
					if (!renderColumnCount) {
						if (this.isOutOfViewFromRight($td)) {
							rightOffset++;
						}
						if (rightOffset === this.offsetCount) {
							break;
						}
					} else {
						if (renderColumnCount === i - this.horizontalScrollConfig.getScrolledIdx() + 1) {
							break;
						}
					}
				}
			}
			return $tr;
		},
		/**
   * Create the actual cell, and then set the cell's minimum width
   * @private
   */
		createCell: function createCell() {
			var $td = $('| ');
			this.setCellMinWidth($td);
			return $td;
		},
		/**
   * Set's the cell's minimum width in pixels
   * @param {object} td - the cell
   * @private
   */
		setCellMinWidth: function setCellMinWidth($td) {
			$td.css({
				'min-width': this.minCellWidth + 'px',
				'height': this.minCellHeight + 'px'
			});
		},
		/**
   * Create the actual cell, set the cell's minimum width, and then append/prepend it to the table
   * @param {object} tr - the row in the table to append/prepend the cell to
   * @param {number} rowIndex - number representing the rowIndex of the cell
   * @param {number} colIndex - number representing the colIndex of the cell
   * @param {boolean} isPrepend - a boolean representing whether to prepend the cell to the given row, or to append it to the row
   * @private
   */
		appendCell: function appendCell($tr, rowIndex, colIndex, isPrepend) {
			var $td = this.createCell(rowIndex, colIndex);
			if (isPrepend) {
				$tr.prepend($td);
			} else {
				$tr.append($td);
			}
			return $td;
		},
		_getPositionFromTop: function _getPositionFromTop($el) {
			var top = 0;
			$el.prevAll().each(function () {
				top += $(this).outerHeight();
			});
			return top;
		},
		_getPositionFromLeft: function _getPositionFromLeft($el) {
			var left = 0;
			$el.prevAll().each(function () {
				left += $(this).outerWidth();
			});
			return left;
		},
		/**
   * Function that returns whether or not the element is out of view from the top
   * @param {object} el - a jQuery object
   * @private
   */
		isOutOfViewFromTop: function isOutOfViewFromTop($el) {
			return this._getPositionFromTop($el) + $el.outerHeight() < 0;
		},
		/**
   * Function that returns whether or not the element is out of view from the bottom
   * @param {object} el - a jQuery object
   * @private
   */
		isOutOfViewFromBottom: function isOutOfViewFromBottom($el) {
			return this._getPositionFromTop($el) > this.$scrollableArea.height();
		},
		/**
   * Function that returns whether or not the element is out of view from the right
   * @param {object} el - a jQuery object
   * @private
   */
		isOutOfViewFromRight: function isOutOfViewFromRight($el) {
			return this._getPositionFromLeft($el) > this.$scrollableArea.width();
		},
		/**
   * Function that returns whether or not the element is out of view from the left
   * @param {object} el - a jQuery object
   * @private
   */
		isOutOfViewFromLeft: function isOutOfViewFromLeft($el) {
			return this._getPositionFromLeft($el) + $el.outerWidth() < 0;
		},
		getRowValues: function getRowValues(rowIndex) {
			var rowData = [];
			var nodes = this.getVisibleRowNodes(rowIndex);
			for (var i = 0; i < nodes.length; i++) {
				var $cell = $(nodes[i]);
				rowData.push($cell.text());
			}
			return rowData;
		},
		//Returns an array of tds for the given row index.  If there is grouping, it returns
		//the visible td (which may be on a different row)
		getVisibleRowNodes: function getVisibleRowNodes(rowIndex) {
			var nodes = this.$rows.find('[' + ATTRIBUTES.ROW_INDEX + '="' + rowIndex + '"]');
			for (var i = 0; i < nodes.length; i++) {
				var $cell = $(nodes[i]);
				if (!$cell.is(':visible')) {
					var datagroup = $cell.attr('data-group');
					if (!datagroup || !datagroup.length) {
						continue;
					}
					var element = this._getPreviousClosestDatagroupElement($cell, datagroup);
					nodes.splice(i, 1, element[0]);
				}
			}
			return nodes;
		},
		getDisplayedRows: function getDisplayedRows() {
			var a_rowCells = [];
			this.$rows.each(function (idx, row) {
				a_rowCells.push($(row).find('td'));
			}.bind(this));
			return a_rowCells;
		},
		/** return a visible element that matches the given datagroup in the previous rows.
   * @private
   */
		_getPreviousClosestDatagroupElement: function _getPreviousClosestDatagroupElement($srcElement, datagroup) {
			var allPreviousRows = $srcElement.parent().prevAll('tr');
			if (!allPreviousRows.length) {
				return null;
			}
			var prevClosestVisibleDatagroupElement = allPreviousRows.find('td[data-group="' + datagroup + '"]:visible');
			if (!prevClosestVisibleDatagroupElement.length) {
				return null;
			}
			return prevClosestVisibleDatagroupElement.last();
		},
		_targetIsOnTheFirstRowAfterFixedRowHeadersTable: function _targetIsOnTheFirstRowAfterFixedRowHeadersTable($target) {
			return Number($target.attr(ATTRIBUTES.ROW_INDEX)) === this.fixedRows;
		},
		_moveUpToColumnHeadersTableOrCornerTable: function _moveUpToColumnHeadersTableOrCornerTable($target) {
			if ($target.closest('table.gridRowHeaders').length) {
				this.setInitialFocus();
				return;
			}
			var position = this._getCurrentTargetPosition($target);
			var columnPosition = position.column + 1;
			var newRowPosition = position.row;
			var previousRowElement = this.$columnHeadersTable.find('tr:nth-child(' + newRowPosition + ') td:nth-child(' + columnPosition + ')');
			if (previousRowElement.length) {
				this._setFocus(previousRowElement, 'up');
			}
		},
		_moveUp: function _moveUp($target) {
			//:nth-child() is 1-based so need to add 1
			var columnIdx = $target.index() + 1;
			var previousRow = $target.parent().prev('tr');
			if (!previousRow.length) {
				var bTargetIsAtTheTopOfGrid = Number($target.attr(ATTRIBUTES.ROW_INDEX)) === 0;
				if (bTargetIsAtTheTopOfGrid) {
					return;
				}
				var index = this.verticalScrollConfig.getScrolledIdx() - 1;
				this._addColumnOrRowAndSetFocus(this.verticalScrollConfig, index, $target, this._moveUp.bind(this));
				return;
			}
			if (this._targetIsOnTheFirstRowAfterFixedRowHeadersTable($target)) {
				this._moveUpToColumnHeadersTableOrCornerTable($target);
				return;
			}
			var previousRowElement = previousRow.find('td:nth-child(' + columnIdx + ')');
			if (previousRowElement.length > 0 && previousRowElement.is(':visible')) {
				this._setFocus(previousRowElement, 'up');
				return;
			}
			//previous row element that is in the same column is not visible, see if it is in a different column
			var newDatagroup = previousRowElement.attr('data-group');
			//get next visible item in same newDatagroup in same row
			var visibleDatagroupItem = previousRowElement.nextAll('td[data-group="' + newDatagroup + '"]:visible:first');
			if (visibleDatagroupItem && visibleDatagroupItem.length) {
				visibleDatagroupItem.focus();
				return;
			}
			// no visible item found in same row, try previous rows
			previousRowElement = this._getPreviousClosestDatagroupElement(previousRowElement, newDatagroup);
			if (previousRowElement && previousRowElement.length) {
				this._setFocus(previousRowElement, 'up');
			}
		},
		_moveDownToGridFromColumnHeaders: function _moveDownToGridFromColumnHeaders($target, columnIdx) {
			//to get to the next row, add 1 and another 1 as row is 0 based but nth-child is 1-based
			var rowIdx = $target.parent().index() + 2;
			var nextRowElement = this.$table.find('tr:nth-child(' + rowIdx + ') td:nth-child(' + columnIdx + ')');
			if (nextRowElement.length) {
				nextRowElement.focus();
			}
		},
		_moveDown: function _moveDown($target) {
			var columnIdx = $target.index() + 1;
			var colSpan = Number($target.attr('colSpan'));
			if (colSpan > 1) {
				//if colspan is > 1, then on the next row, we want to focus on the first element in the span
				columnIdx = columnIdx - colSpan + 1;
			}
			var oldDatagroup = $target.attr('data-group');
			var nextRows = $target.parent().nextAll('tr');
			if (!nextRows.length) {
				var bTargetIsOnColumnHeaderTable = $target.closest('table.gridColumnHeaders').length > 0;
				if (bTargetIsOnColumnHeaderTable) {
					this._moveDownToGridFromColumnHeaders($target, columnIdx);
					return;
				}
				//the corner table cell's row attribute does not accurately reflect the
				var bTargetIsOnCornerTable = $target.closest('table.cornerTable').length > 0;
				if (bTargetIsOnCornerTable) {
					//setup the nextRows for moving from cornerTable to rowHeadersTable
					nextRows = this.$rowHeadersTable.find('tr');
				}
			}
			//handles moving down on nested row header
			var nextRowElement = nextRows.find('td[data-group!="' + oldDatagroup + '"]:nth-child(' + columnIdx + ')').first();
			var newDatagroup;
			if (nextRowElement.length) {
				if (nextRowElement.is(':visible')) {
					nextRowElement.focus();
					return;
				}
				newDatagroup = nextRowElement.attr('data-group');
			}
			//handles moving down on nested column headers
			//next row element in  the same column idx is not visible, see if it is in a different column
			var sameDatagropuItemInRow = nextRowElement.nextAll('td[data-group="' + newDatagroup + '"]:visible:first');
			if (sameDatagropuItemInRow && sameDatagropuItemInRow.length > 0) {
				sameDatagropuItemInRow.focus();
				return;
			}
			//if there is no more visible item, check if all the rows had been retrieved
			var rowIndex = Number(nextRowElement.attr(ATTRIBUTES.ROW_INDEX));
			var totalNumberOfRows = this.verticalScrollConfig.count;
			if (rowIndex < totalNumberOfRows) {
				return;
			}
			//if we get here, it means that the new data-group is not fully rendered so get more rows
			var index = this.verticalScrollConfig.getScrolledIdx() + 1;
			this._addColumnOrRowAndSetFocus(this.verticalScrollConfig, index, $target, this._moveDown.bind(this));
		},
		_moveRightFromCornerTableToColumnHeader: function _moveRightFromCornerTableToColumnHeader($target, oldDatagroup) {
			var firstCellElement = this.$columnHeadersTable.find('tr:visible:first td:first');
			var nextElement = firstCellElement.nextAll('td[data-group!="' + oldDatagroup + '"]:visible:first');
			if (nextElement.length) {
				nextElement.focus();
			}
		},
		_getCurrentTargetPosition: function _getCurrentTargetPosition($target) {
			var colPosition = $target.index();
			var rowPosition = $target.parent().index();
			return {
				'column': colPosition,
				'row': rowPosition
			};
		},
		_moveRightFromRowHeadersToGrid: function _moveRightFromRowHeadersToGrid($target) {
			var position = this._getCurrentTargetPosition($target);
			var columnIdx = position.column + 2;
			var rowIdx = position.row + 1;
			var nextColumnElement = this.$table.find('tr:nth-child(' + rowIdx + ') td:nth-child(' + columnIdx + ')');
			if (nextColumnElement.length && nextColumnElement.is(':visible')) {
				nextColumnElement.focus();
				return;
			}
			var datagroup = nextColumnElement.attr('data-group');
			nextColumnElement.find('td[data-group="' + datagroup + '"]:visible:first');
			if (nextColumnElement.length) {
				nextColumnElement.focus();
			}
		},
		_targetIsOnLastColumnInRowHeadersTable: function _targetIsOnLastColumnInRowHeadersTable($target) {
			if (!$target.closest('table.gridRowHeaders').length) {
				return false;
			}
			var columnPosition = Number($target.attr(ATTRIBUTES.COLUMN_INDEX)) + 1;
			return columnPosition === this.fixedColumns;
		},
		_moveRight: function _moveRight($target) {
			var bTargetIsOnCornerTable = $target.closest('table.cornerTable').length > 0;
			var oldDatagroup = $target.attr('data-group');
			if (bTargetIsOnCornerTable) {
				this._moveRightFromCornerTableToColumnHeader($target, oldDatagroup);
				return;
			}
			var nextElement;
			if (this._targetIsOnLastColumnInRowHeadersTable($target)) {
				this._moveRightFromRowHeadersToGrid($target);
				return;
			}
			nextElement = $target.nextAll('td:visible:first');
			if (nextElement.length) {
				nextElement.focus();
				return;
			}
			//There are no visible item to focus on, so check if there's a different data-group following $target
			var nextGroupDataElement = $target.nextAll('td[data-group!="' + oldDatagroup + '"]:first');
			if (!nextGroupDataElement.length) {
				var colIndex = Number($target.attr(ATTRIBUTES.COLUMN_INDEX)) + 1;
				var totalNumberOfColumns = this.horizontalScrollConfig.count;
				if (colIndex === totalNumberOfColumns) {
					return;
				}
				//There isn't a different datagroup and no visible element, so the current datagroup must be partially rendered,
				//so get more data
				var index = this.horizontalScrollConfig.getScrolledIdx() + 1;
				this._addColumnOrRowAndSetFocus(this.horizontalScrollConfig, index, $target, this._moveRight.bind(this));
				return;
			}
			var newDatagroup = nextGroupDataElement.attr('data-group');
			nextElement = this._getPreviousClosestDatagroupElement(nextGroupDataElement, newDatagroup);
			if (nextElement && nextElement.length) {
				nextElement.focus();
				return;
			}
			nextElement = this.$rowHeadersTable.nextAll('td[data-group!="' + oldDatagroup + '"]:first');
			if (nextElement.length) {
				nextElement.focus();
			}
		},
		_addColumnOrRowAndSetFocus: function _addColumnOrRowAndSetFocus(config, index, $target, setFocusFunction) {
			if (typeof setFocusFunction !== 'function') {
				return;
			}
			if (!this.updateIndexAndValues(index, config)) {
				return;
			}
			var colIdx = $target.attr(ATTRIBUTES.COLUMN_INDEX);
			var rowIdx = $target.attr(ATTRIBUTES.ROW_INDEX);
			//find the target node in the updated table
			var table = $target.closest('table');
			var currentElement = table.find('td[col="' + colIdx + '"][row="' + rowIdx + '"]');
			if (!currentElement.length) {
				return;
			}
			setFocusFunction(currentElement);
		},
		_moveLeftToRowHeadersTable: function _moveLeftToRowHeadersTable($target) {
			var position = this._getCurrentTargetPosition($target);
			var rowPosition = position.row + 1; //nth-child is 1-based
			var newColumnPosition = position.column; // column preceding
			var previousColumnElement = this.$rowHeadersTable.find('tr:nth-child(' + rowPosition + ') td:nth-child(' + newColumnPosition + ')');
			previousColumnElement.focus();
		},
		_moveLeftToRowHeadersOrCornerTable: function _moveLeftToRowHeadersOrCornerTable($target) {
			var bTargetIsOnColumnHeaderTable = $target.closest('table.gridColumnHeaders').length > 0;
			if (bTargetIsOnColumnHeaderTable) {
				this.setInitialFocus();
				return;
			}
			this._moveLeftToRowHeadersTable($target);
		},
		_targetIsOnColumnOrDataGroupBeforeRowHeadersTable: function _targetIsOnColumnOrDataGroupBeforeRowHeadersTable($target) {
			var colSpan = $target.attr('colspan');
			if (colSpan) {
				colSpan = Number(colSpan);
			}
			var columnIndex = $target.index();
			var columnIdxToUse;
			if (colSpan && colSpan > 1) {
				columnIdxToUse = columnIndex - colSpan + 1;
			} else {
				columnIdxToUse = columnIndex;
			}
			return this.fixedColumns && columnIdxToUse === this.fixedColumns;
		},
		_moveLeft: function _moveLeft($target) {
			var bTargetIsOnFirstColumn = Number($target.attr(ATTRIBUTES.COLUMN_INDEX)) === 0;
			if (bTargetIsOnFirstColumn) {
				return;
			}
			if (this._targetIsOnColumnOrDataGroupBeforeRowHeadersTable($target)) {
				this._moveLeftToRowHeadersOrCornerTable($target);
				return;
			}
			var previousElement = $target.prevAll('td:visible:first');
			if (previousElement.length) {
				this._setFocus(previousElement, 'left');
				return;
			}
			//There are no visible element to focus on, so check if there is a different datagroup preceding $target
			var oldDatagroup = $target.attr('data-group');
			var previousGroupDataElement = $target.prevAll('td[data-group!="' + oldDatagroup + '"]:first');
			if (!previousGroupDataElement.length) {
				var colIndex = Number($target.attr(ATTRIBUTES.COLUMN_INDEX));
				if (colIndex === 0) {
					return;
				}
				// there isn't a different datagroup, therefore, the current datagroup must be only partially rendered
				// so get more data
				this._addColumnOrRowAndSetFocus(this.horizontalScrollConfig, this.horizontalScrollConfig.getScrolledIdx() - 1, $target, this._moveLeft.bind(this));
				return;
			}
			// There is a different datagroup but since there is no visible element on the same row, it means that the visible element must be on one of the
			// previous rows
			var newDatagroup = previousGroupDataElement.attr('data-group');
			previousElement = this._getPreviousClosestDatagroupElement(previousGroupDataElement, newDatagroup);
			if (previousElement && previousElement.length > 0) {
				this._setFocus(previousElement, 'left');
				return;
			}
		},
		_setFocus: function _setFocus($nodeToFocus, moveDirection) {
			$nodeToFocus.focus();
			var nodeToFocusRect = $nodeToFocus[0].getBoundingClientRect();
			switch (moveDirection) {
				case 'left':
					{
						var rowHeadersTableRect = this.$rowHeadersTable[0].getBoundingClientRect();
						if (nodeToFocusRect.left + 1 > rowHeadersTableRect.right) {
							return;
						}
						var leftScrollAmount = rowHeadersTableRect.width - (nodeToFocusRect.left - rowHeadersTableRect.left);
						this.$scrollableArea.scrollLeft(this.$scrollableArea.scrollLeft() - leftScrollAmount);
						break;
					}
				case 'up':
					{
						var columnHeadersTable = this.$columnHeadersTable[0].getBoundingClientRect();
						if (nodeToFocusRect.top + 1 > columnHeadersTable.bottom) {
							return;
						}
						var topScrollAmount = columnHeadersTable.height - (nodeToFocusRect.top - columnHeadersTable.top);
						this.$scrollableArea.scrollTop(this.$scrollableArea.scrollTop() - topScrollAmount);
						break;
					}
			}
		},
		_moveToFirstRow: function _moveToFirstRow() {
			this.setInitialFocus();
		},
		_moveToLastRow: function _moveToLastRow() {
			this.$table.find('tr:visible:last td:visible:last').focus();
		},
		onKeyDown: function onKeyDown(event) {
			var $target = $(event.target);
			var bStopPropagation = false;
			switch (event.keyCode) {
				case KeyCodes.KEY_UP:
					{
						bStopPropagation = true;
						this._moveUp($target);
						break;
					}
				case KeyCodes.KEY_DOWN:
					{
						bStopPropagation = true;
						this._moveDown($target);
						break;
					}
				case KeyCodes.KEY_RIGHT:
					{
						bStopPropagation = true;
						this._moveRight($target);
						break;
					}
				case KeyCodes.KEY_LEFT:
					{
						bStopPropagation = true;
						this._moveLeft($target);
						break;
					}
				case KeyCodes.KEY_h:
				case KeyCodes.KEY_HOME:
					{
						if (!event.ctrlKey) {
							return;
						}
						bStopPropagation = true;
						this._moveToFirstRow();
						break;
					}
				case KeyCodes.KEY_e:
				case KeyCodes.KEY_END:
					{
						if (!event.ctrlKey) {
							return;
						}
						bStopPropagation = true;
						this._moveToLastRow();
						break;
					}
			}
			if (bStopPropagation) {
				this._savedKeyboardNavigationInfo = {
					'keycode': event.keyCode,
					'$target': $target
				};
				event.preventDefault();
				event.stopPropagation();
			}
		},
		setInitialFocus: function setInitialFocus() {
			var startingTable = this.bHasCornerTable ? this.$cornerTable : this.$columnHeadersTable;
			var initialNode = startingTable.find('tr:visible:first td:visible:first');
			if (initialNode.length) {
				initialNode.focus();
			}
		},
		_setFocusAfterScroll: function _setFocusAfterScroll() {
			if (!this._savedKeyboardNavigationInfo) {
				return;
			}
			switch (this._savedKeyboardNavigationInfo.keycode) {
				case KeyCodes.KEY_UP:
					this._moveUp(this._savedKeyboardNavigationInfo.$target);
					break;
				case KeyCodes.KEY_DOWN:
					this._moveDown(this._savedKeyboardNavigationInfo.$target);
					break;
				case KeyCodes.KEY_RIGHT:
					this._moveRight(this._savedKeyboardNavigationInfo.$target);
					break;
				case KeyCodes.KEY_LEFT:
					this._moveLeft(this._savedKeyboardNavigationInfo.$target);
					break;
				case KeyCodes.KEY_e:
				case KeyCodes.KEY_END:
					this._moveToLastRow();
					break;
				case KeyCodes.KEY_h:
				case KeyCodes.KEY_HOME:
					this._moveToFirstRow();
					break;
			}
		},
		/**
  * Update the specified table when scrollled vertically
  *
  * @param {object} options - the options to scroll the table vertically
  *
  */
		_updateTableScrolledVertically: function _updateTableScrolledVertically() {
			var _this6 = this;
			var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
			var $table = options.$table;
			var previousScrolledIdx = options.previousScrolledIdx;
			var delta = options.delta;
			var handleGrouping = options.handleGrouping || false;
			var isRowHeader = options.isRowHeader || false;
			var scrolledVerticalIdx = this.verticalScrollConfig.getScrolledIdx();
			var scrollDown = previousScrolledIdx < scrolledVerticalIdx;
			var $rows = $table.find('tr');
			var $trAfter = delta > 0 ? $rows.eq(delta - 1) : null;
			var count = scrollDown ? scrolledVerticalIdx - previousScrolledIdx - 1 : previousScrolledIdx - scrolledVerticalIdx - 1;
			var $row = void 0;
			var rowContext = void 0;
			var _getRowUpdateContext = function _getRowUpdateContext($row, idx) {
				return {
					$tr: $row,
					rowIndex: scrollDown ? _this6.verticalScrollConfig.renderedCount + scrolledVerticalIdx - 1 - idx : idx + scrolledVerticalIdx + delta,
					handleGrouping: handleGrouping,
					isRowHeader: isRowHeader,
					cbGetScrolledFastPinnedCornerCellIdx: cbGetScrolledFastPinnedCornerCellIdx
				};
			};
			for (var idx = count; idx >= 0; idx--) {
				rowContext = null;
				if (scrollDown) {
					$row = $rows.eq(idx + delta);
					$row.appendTo($rows.parent());
				} else {
					$row = $rows.eq($rows.length - idx - 1);
					if ($trAfter) {
						$row.insertAfter($trAfter);
					} else {
						$row.prependTo($rows.parent());
					}
				}
				rowContext = _getRowUpdateContext($row, idx);
				this.updateRow(rowContext);
			}
		}
	});
	View.getColumnPosition = function ($tdNode) {
		return Number($tdNode.attr(ATTRIBUTES.COLUMN_INDEX));
	};
	View.getRowPosition = function ($tdNode) {
		return Number($tdNode.attr(ATTRIBUTES.ROW_INDEX));
	};
	return View;
});
//# sourceMappingURL=Grid.js.map |