canvas-lms/packages/slickgrid/slick.grid.js

4153 lines
126 KiB
JavaScript

/*
* Copyright (c) 2010 Michael Leibman, http://github.com/mleibman/slickgrid
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import jQuery from 'jquery'
import './slick.core'
import './jquery.event.drag-2.2'
import {isRTL} from '@canvas/i18n/rtlHelper'
import {getNormalizedScrollLeft, setNormalizedScrollLeft} from 'normalize-scroll-left'
import 'jqueryui/sortable'
/*
* These eslint configurations are just becase that's how this file was
* originally written and we want the file to remain as much like the original
* source as possible but still want it to tell us about the important stuff.
*/
/* eslint-disable notice/notice, no-var, vars-on-top, prefer-template, object-shorthand, no-use-before-define, max-len, eqeqeq, no-multi-assign, no-cond-assign, one-var */
/* eslint linebreak-style: ["error", "windows"] */
/**
* @license
* (c) 2009-2013 Michael Leibman
* michael{dot}leibman{at}gmail{dot}com
* http://github.com/mleibman/slickgrid
*
* Distributed under MIT license.
* All rights reserved.
*
* SlickGrid v2.2
*
* NOTES:
* Cell/row DOM manipulations are done directly bypassing jQuery's DOM manipulation methods.
* This increases the speed dramatically, but can only be done safely because there are no event handlers
* or data associated with any cell/row DOM nodes. Cell editors must make sure they implement .destroy()
* and do proper cleanup.
*/
// make sure required JavaScript modules are loaded
if (typeof jQuery === 'undefined') {
throw 'SlickGrid requires jquery module to be loaded'
}
if (!jQuery.fn.drag) {
throw 'SlickGrid requires jquery.event.drag module to be loaded'
}
if (typeof Slick === 'undefined') {
throw 'slick.core.js not loaded'
}
;(function($) {
// Slick.Grid
$.extend(true, window, {
Slick: {
Grid: SlickGrid
}
})
// shared across all grids on the page
var scrollbarDimensions
var maxSupportedCssHeight // browser's breaking point
// ////////////////////////////////////////////////////////////////////////////////////////////
// SlickGrid class implementation (available as Slick.Grid)
/**
* Creates a new instance of the grid.
* @class SlickGrid
* @constructor
* @param {Node} container Container node to create the grid in.
* @param {Array,Object} data An array of objects for databinding.
* @param {Array} columns An array of column definitions.
* @param {Object} options Grid options.
* */
function SlickGrid(container, data, columns, options) {
// settings
var defaults = {
explicitInitialization: false,
rowHeight: 25,
defaultColumnWidth: 80,
enableAddRow: false,
leaveSpaceForNewRows: false,
editable: false,
autoEdit: true,
enableCellNavigation: true,
enableColumnReorder: true,
asyncEditorLoading: false,
asyncEditorLoadDelay: 100,
forceFitColumns: false,
enableAsyncPostRender: false,
asyncPostRenderDelay: 50,
autoHeight: false,
editorLock: Slick.GlobalEditorLock,
showHeaderRow: false,
headerRowHeight: 25,
showTopPanel: false,
topPanelHeight: 25,
formatterFactory: null,
editorFactory: null,
cellFlashingCssClass: 'flashing',
selectedCellCssClass: 'selected',
multiSelect: true,
enableTextSelectionOnCells: false,
dataItemColumnValueExtractor: null,
fullWidthRows: false,
multiColumnSort: false,
defaultFormatter: defaultFormatter,
forceSyncScrolling: false,
numberOfColumnsToFreeze: 0 // Number of left-most columns to freeze from scrolling
}
var columnDefaults = {
name: '',
resizable: true,
sortable: false,
minWidth: 30,
rerenderOnResize: false,
headerCssClass: null,
defaultSortAsc: true,
focusable: true,
selectable: true
}
// scroller
var th // virtual height
var h // real scrollable height
var ph // page height
var n // number of pages
var cj // "jumpiness" coefficient
var page = 0 // current page
var offset = 0 // current page offset
var vScrollDir = 1
// private
var initialized = false
var uid = 'slickgrid_' + Math.round(1000000 * Math.random())
var self = this
var $focusSink, $focusSink2
var $outerContainer
var $container_0
var $container_1
var $headerScroller_0
var $headerScroller_1
var $headers_0
var $headers_1
var $headerRow_0
var $headerRow_1
var $headerRowScroller_0
var $headerRowScroller_1
var $headerRowSpacer_0
var $headerRowSpacer_1
var $topPanelScroller_0
var $topPanelScroller_1
var $topPanel_0
var $topPanel_1
var $viewport_0
var $viewport_1
var $canvas_0
var $canvas_1
var canvasWidth_0
var canvasWidth_1
var $style
var $boundAncestors
var stylesheet, columnCssRulesB, columnCssRulesF
var viewportH_1
var viewportW_1
var viewportHasHScroll_1
var viewportHasVScroll_1
// viewport_0 will never have scroll bars so the above two vars are only necessary for viewport_1.
var headerColumnWidthDiff = 0,
headerColumnHeightDiff = 0, // border+padding
cellWidthDiff = 0,
cellHeightDiff = 0
var absoluteColumnMinWidth
var numberOfRows = 0
var tabbingDirection = 1
var activePosX
var activeRow, activeCell
var activeCellNode = null
var currentEditor = null
var serializedEditorValue
var editController
var rowsCache = {}
var renderedRows = 0
var numVisibleRows
var prevScrollTop = 0
var scrollTop = 0
var lastRenderedScrollTop = 0
var lastRenderedScrollLeft = 0
var prevScrollLeft = 0
var scrollLeft = 0
var selectionModel
var selectedRows = []
var plugins = []
var cellCssClasses = {}
var columnsById = {}
var sortColumns = []
var columnPosRear = []
var columnPosFront = []
// async call handles
var h_editorLoader = null
var h_render = null
var h_postrender = null
var postProcessedRows = {}
var postProcessToRow = null
var postProcessFromRow = null
// perf counters
var counter_rows_rendered = 0
var counter_rows_removed = 0
var rtl = false
var rear = 'left'
var front = 'right'
var gotoRight
var gotoLeft
var getOffsetRear
// ////////////////////////////////////////////////////////////////////////////////////////////
// Initialization
function init() {
$outerContainer = $(container)
$container_1 = $outerContainer
if ($outerContainer.length < 1) {
throw new Error(
'SlickGrid requires a valid container, ' + container + ' does not exist in the DOM.'
)
}
// calculate these only once and share between grid instances
maxSupportedCssHeight = maxSupportedCssHeight || getMaxSupportedCssHeight()
scrollbarDimensions = scrollbarDimensions || measureScrollbar()
options = $.extend({}, defaults, options)
validateAndEnforceOptions()
columnDefaults.width = options.defaultColumnWidth
columnsById = {}
for (var i = 0; i < columns.length; i++) {
var m = (columns[i] = $.extend({}, columnDefaults, columns[i]))
columnsById[m.id] = i
if (m.minWidth && m.width < m.minWidth) {
m.width = m.minWidth
}
if (m.maxWidth && m.width > m.maxWidth) {
m.width = m.maxWidth
}
}
// validate loaded JavaScript modules against requested options
if (options.enableColumnReorder && !$.fn.sortable) {
throw new Error(
"SlickGrid's 'enableColumnReorder = true' option requires jquery-ui.sortable module to be loaded"
)
}
editController = {
commitCurrentEdit: commitCurrentEdit,
cancelCurrentEdit: cancelCurrentEdit
}
$outerContainer
.empty()
.css('overflow', 'hidden')
.css('outline', 0)
.addClass(uid)
.addClass('ui-widget')
$container_1
.empty()
.css('overflow', 'hidden')
.css('outline', 0)
.addClass(uid)
.addClass('ui-widget')
if (isRTL($outerContainer[0])) {
rtl = true
rear = 'right'
front = 'left'
gotoRight = gotoRear
gotoLeft = gotoFront
getOffsetRear = getOffsetRight
getScrollLeft = el => getNormalizedScrollLeft(el, 'rtl')
setScrollLeft = (el, val) => setNormalizedScrollLeft(el, val, 'rtl')
}
// set up a positioning container if needed
if (!/relative|absolute|fixed/.test($outerContainer.css('position'))) {
$outerContainer.css('position', 'relative')
}
// set up a positioning container if needed
if (!/relative|absolute|fixed/.test($container_1.css('position'))) {
$container_1.css('position', 'relative')
}
$focusSink = $(
"<div tabIndex='0' hideFocus style='position:fixed;width:0;height:0;top:0;left:0;outline:0;'></div>"
).appendTo($outerContainer)
// FreezeColumns - Add outerContainer and frozen column structure - Begin
if (options.numberOfColumnsToFreeze > 0) {
var totalWidthOfFrozenColumns = 0
var container_1Width = 0
var containerCSS = {
overflow: 'hidden',
position: 'absolute',
[rear]: 0,
top: 0,
bottom: 0,
outline: 0
}
var containerClass = uid + ' ui-widget'
// Calculate frozen widths
for (var i = 0, len = columns.length; i < len; i++) {
if (i < options.numberOfColumnsToFreeze) {
totalWidthOfFrozenColumns += columns[i].width
} else {
break
}
}
$container_0 = $("<div class='container_0'></div>")
.css($.extend({}, containerCSS, {width: totalWidthOfFrozenColumns}))
.addClass(containerClass)
.appendTo($outerContainer)
$container_1 = $("<div class='container_1'></div>")
.css($.extend({}, containerCSS, {[rear]: totalWidthOfFrozenColumns, [front]: 0}))
.addClass(containerClass)
.appendTo($outerContainer)
$headerScroller_0 = $(
"<div class='headerScroller_0 slick-header ui-state-default' style='overflow:hidden;position:relative;' />"
).appendTo($container_0)
$headers_0 = $(
"<div class='headers_0 slick-header-columns' style='" + rear + ":-1000px' />"
).appendTo($headerScroller_0)
$headerRowScroller_0 = $(
"<div class='headerRowScroller_0 slick-headerrow ui-state-default' style='overflow:hidden;position:relative;' />"
).appendTo($container_0)
$headerRow_0 = $("<div class='headerRow_0 slick-headerrow-columns' />").appendTo(
$headerRowScroller_0
)
$headerRowSpacer_0 = $(
"<div class='headerRowSpacer_0' style='display:block;height:1px;position:absolute;top:0;left:0;'></div>"
).appendTo($headerRowScroller_0)
$topPanelScroller_0 = $(
"<div class='topPanelScroller_0 slick-top-panel-scroller ui-state-default' style='overflow:hidden;position:relative;' />"
).appendTo($container_0)
$topPanel_0 = $(
"<div class='topPanel_0 slick-top-panel' style='width:10000px' />"
).appendTo($topPanelScroller_0)
if (!options.showTopPanel) {
$topPanelScroller_0.hide()
}
if (!options.showHeaderRow) {
$headerRowScroller_0.hide()
}
$viewport_0 = $(
"<div class='viewport_0 slick-viewport' style='width:100%;overflow:hidden;outline:0;position:relative;'>"
).appendTo($container_0)
$canvas_0 = $("<div class='canvas_0 grid-canvas' />").appendTo($viewport_0)
}
// FreezeColumns - Add outerContainer and frozen column structure - End
$headerScroller_1 = $(
"<div class='headerScroller_1 slick-header ui-state-default' style='overflow:hidden;position:relative;' />"
).appendTo($container_1)
$headers_1 = $(
"<div class='headers_1 slick-header-columns' style='" + rear + ":-1000px' />"
).appendTo($headerScroller_1)
// FreezeColumns - Set width of headers - Begin
var headersWidthObj = getHeadersWidth()
if (options.numberOfColumnsToFreeze > 0) {
$headers_0.width(headersWidthObj.frozen + 1000)
$headers_1.width(headersWidthObj.nonFrozen)
} else {
$headers_1.width(headersWidthObj.nonFrozen)
}
// FreezeColumns - Set width of headers - End
$headerRowScroller_1 = $(
"<div class='headerRowScroller_1 slick-headerrow ui-state-default' style='overflow:hidden;position:relative;' />"
).appendTo($container_1)
$headerRow_1 = $("<div class='headerRow_1 slick-headerrow-columns' />").appendTo(
$headerRowScroller_1
)
$headerRowSpacer_1 = $(
"<div class='headerRowSpacer_1' style='display:block;height:1px;position:absolute;top:0;left:0;'></div>"
).appendTo($headerRowScroller_1)
// FreezeColumns - Set width of header row spacer - Begin
var canvasWidthObj = getCanvasWidth()
$headerRowSpacer_1.css('width', canvasWidthObj.nonFrozen + scrollbarDimensions.width + 'px')
if (options.numberOfColumnsToFreeze > 0) {
$headerRowSpacer_0.css('width', canvasWidthObj.frozen + 'px')
}
// FreezeColumns - Set width of header row spacer - End
$topPanelScroller_1 = $(
"<div class='topPanelScroller_1 slick-top-panel-scroller ui-state-default' style='overflow:hidden;position:relative;' />"
).appendTo($container_1)
$topPanel_1 = $("<div class='topPanel_1 slick-top-panel' style='width:10000px' />").appendTo(
$topPanelScroller_1
)
if (!options.showTopPanel) {
$topPanelScroller_1.hide()
}
if (!options.showHeaderRow) {
$headerRowScroller_1.hide()
}
$viewport_1 = $(
"<div class='viewport_1 slick-viewport' style='width:100%;overflow:auto;outline:0;position:relative;'>"
).appendTo($container_1)
$viewport_1.css('overflow-y', options.autoHeight ? 'hidden' : 'auto')
$canvas_1 = $("<div class='canvas_1 grid-canvas' />").appendTo($viewport_1)
$focusSink2 = $focusSink.clone().appendTo($outerContainer)
if (!options.explicitInitialization) {
finishInitialization()
}
}
function finishInitialization() {
if (!initialized) {
initialized = true
viewportW_1 = parseFloat($.css($outerContainer[0], 'width', true))
// header columns and cells may have different padding/border skewing width calculations (box-sizing, hello?)
// calculate the diff so we can set consistent sizes
measureCellPaddingAndBorder()
// for usability reasons, all text selection in SlickGrid is disabled
// with the exception of input and textarea elements (selection must
// be enabled there so that editors work as expected); note that
// selection in grid cells (grid body) is already unavailable in
// all browsers except IE
disableSelection($headers_1) // disable all text selection in header (including input and textarea)
if (!options.enableTextSelectionOnCells) {
// disable text selection in grid cells except in input and textarea elements
// (this is IE-specific, because selectstart event will only fire in IE)
$viewport_1.bind('selectstart.ui', event => $(event.target).is('input,textarea'))
}
updateColumnCaches()
createColumnHeaders()
setupColumnSort()
createCssRules()
resizeCanvas()
bindAncestorScrollEvents()
$outerContainer.bind('resize.slickgrid', resizeCanvas)
$container_1.bind('resize.slickgrid', resizeCanvas)
$viewport_1.bind('scroll', handleScroll).bind('click', handleClick)
$headerScroller_1
.bind('contextmenu', handleHeaderContextMenu)
.bind('click', handleHeaderClick)
.delegate('.slick-header-column', 'mouseenter', handleHeaderMouseEnter)
.delegate('.slick-header-column', 'mouseleave', handleHeaderMouseLeave)
$headerRowScroller_1.bind('scroll', handleHeaderRowScroll)
$focusSink.add($focusSink2).bind('keydown', handleKeyDown)
$canvas_1
.bind('keydown', handleKeyDown)
.bind('click', handleClick)
.bind('dblclick', handleDblClick)
.bind('contextmenu', handleContextMenu)
.bind('draginit', handleDragInit)
.bind('dragstart', {distance: 3}, handleDragStart)
.bind('drag', handleDrag)
.bind('dragend', handleDragEnd)
.delegate('.slick-cell', 'mouseenter', handleMouseEnter)
.delegate('.slick-cell', 'mouseleave', handleMouseLeave)
if (options.numberOfColumnsToFreeze > 0) {
$container_0.bind('resize.slickgrid', resizeCanvas)
$viewport_0.bind('mousewheel', e => {
var wheelDelta = e.originalEvent.wheelDelta
var newScrollTop = scrollTop - wheelDelta
if (newScrollTop < 0) {
newScrollTop = 0
}
handleScroll({wheelDelta: wheelDelta, scrollTop: newScrollTop})
})
$headerScroller_0
.bind('contextmenu', handleHeaderContextMenu)
.bind('click', handleHeaderClick)
.delegate('.slick-header-column', 'mouseenter', handleHeaderMouseEnter)
.delegate('.slick-header-column', 'mouseleave', handleHeaderMouseLeave)
$headerRowScroller_0.bind('scroll', handleHeaderRowScroll)
$canvas_0
.bind('keydown', handleKeyDown)
.bind('click', handleClick)
.bind('dblclick', handleDblClick)
.bind('contextmenu', handleContextMenu)
.bind('draginit', handleDragInit)
.bind('dragstart', {distance: 3}, handleDragStart)
.bind('drag', handleDrag)
.bind('dragend', handleDragEnd)
.delegate('.slick-cell', 'mouseenter', handleMouseEnter)
.delegate('.slick-cell', 'mouseleave', handleMouseLeave)
}
}
}
function registerPlugin(plugin) {
plugins.unshift(plugin)
plugin.init(self)
}
function unregisterPlugin(plugin) {
for (var i = plugins.length; i >= 0; i--) {
if (plugins[i] === plugin) {
if (plugins[i].destroy) {
plugins[i].destroy()
}
plugins.splice(i, 1)
break
}
}
}
function setSelectionModel(model) {
if (selectionModel) {
selectionModel.onSelectedRangesChanged.unsubscribe(handleSelectedRangesChanged)
if (selectionModel.destroy) {
selectionModel.destroy()
}
}
selectionModel = model
if (selectionModel) {
selectionModel.init(self)
selectionModel.onSelectedRangesChanged.subscribe(handleSelectedRangesChanged)
}
}
function getSelectionModel() {
return selectionModel
}
function getCanvasNode(nodeClassName) {
// TODO: SFA: This is the entry point into fixing the row reordering style issue
var canvasNode = $canvas_1[0] // Original code
// New code not yet complete
// if(nodeClassName != undefined){
// canvasNode = {"frozen": $canvas_0[0],
// "nonFrozen": $canvas_1[0]
// };
// }
return canvasNode
}
function measureScrollbar() {
var $c = $(
"<div style='position:absolute; top:-10000px; left:-10000px; width:100px; height:100px; overflow:scroll;'></div>"
).appendTo('body')
var dim = {
width: $c.width() - $c[0].clientWidth,
height: $c.height() - $c[0].clientHeight
}
$c.remove()
return dim
}
function getHeadersWidth() {
var headersWidth_0 = 0
var headersWidth_1 = 0
var numberOfColumnsToFreeze = options.numberOfColumnsToFreeze
for (var i = 0, ii = columns.length; i < ii; i++) {
var width = columns[i].width
if (i < numberOfColumnsToFreeze) {
headersWidth_0 += width
} else {
headersWidth_1 += width
}
}
headersWidth_0 += 1000
headersWidth_1 += scrollbarDimensions.width
headersWidth_1 = Math.max(headersWidth_1, viewportW_1) + 1000
return {frozen: headersWidth_0, nonFrozen: headersWidth_1}
}
function getCanvasWidth() {
var availableWidth = viewportHasVScroll_1
? viewportW_1 - scrollbarDimensions.width
: viewportW_1
var rowWidth_0 = 0
var rowWidth_1 = 0
var numberOfColumnsToFreeze = options.numberOfColumnsToFreeze
var i = columns.length
while (i--) {
var colWidth = columns[i].width
if (i < numberOfColumnsToFreeze) {
rowWidth_0 += colWidth
} else {
rowWidth_1 += colWidth
}
}
rowWidth_1 = options.fullWidthRows ? Math.max(rowWidth_1, availableWidth) : rowWidth_1
return {frozen: rowWidth_0, nonFrozen: rowWidth_1}
}
function updateCanvasWidth(forceColumnWidthsUpdate) {
var oldCanvasWidth_0 = canvasWidth_0
var oldCanvasWidth_1 = canvasWidth_1
var newCanvasWidth = getCanvasWidth()
canvasWidth_0 = newCanvasWidth.frozen
canvasWidth_1 = newCanvasWidth.nonFrozen
// FreezeColumns - Handle left and main canvas widths
var headersWidthObj = getHeadersWidth()
var canvasWidthDelta_0 = 0
if (options.numberOfColumnsToFreeze > 0 && canvasWidth_0 != oldCanvasWidth_0) {
$canvas_0.width(canvasWidth_0)
$headerRow_0.width(canvasWidth_0)
$headers_0.width(headersWidthObj.frozen)
$headerRowSpacer_0.width(canvasWidth_0)
if (oldCanvasWidth_0 != undefined) {
canvasWidthDelta_0 = canvasWidth_0 - oldCanvasWidth_0
canvasWidth_1 -= canvasWidthDelta_0
viewportW_1 -= canvasWidthDelta_0
$container_0[0].style.width =
parseInt($container_0[0].style.width) + canvasWidthDelta_0 + 'px'
$container_1[0].style[rear] = canvasWidth_0 + 'px'
$container_1[0].style.width =
parseInt($container_1[0].style.width) - canvasWidthDelta_0 + 'px'
}
}
if (canvasWidth_1 != oldCanvasWidth_1 || canvasWidthDelta_0 != 0) {
$canvas_1.width(canvasWidth_1)
$headerRow_1.width(canvasWidth_1)
$headers_1.width(headersWidthObj.nonFrozen)
viewportHasHScroll_1 = canvasWidth_1 > viewportW_1 - scrollbarDimensions.width
}
$headerRowSpacer_1.width(
canvasWidth_1 + (viewportHasVScroll_1 ? scrollbarDimensions.width : 0)
)
if (canvasWidth_1 != oldCanvasWidth_1 || forceColumnWidthsUpdate || canvasWidthDelta_0 != 0) {
applyColumnWidths()
}
}
function disableSelection($target) {
if ($target && $target.jquery) {
$target
.attr('unselectable', 'on')
.css('MozUserSelect', 'none')
.bind('selectstart.ui', () => false) // from jquery:ui.core.js 1.7.2
}
}
function getMaxSupportedCssHeight() {
var supportedHeight = 1000000
// FF reports the height back but still renders blank after ~6M px
var testUpTo = navigator.userAgent.toLowerCase().match(/firefox/) ? 6000000 : 1000000000
var div = $("<div style='display:none' />").appendTo(document.body)
while (true) {
var test = supportedHeight * 2
div.css('height', test)
if (test > testUpTo || div.height() !== test) {
break
} else {
supportedHeight = test
}
}
div.remove()
return supportedHeight
}
// TODO: this is static. need to handle page mutation.
function bindAncestorScrollEvents() {
var elem = $canvas_1[0]
while ((elem = elem.parentNode) != document.body && elem != null) {
// bind to scroll containers only
if (
elem == $viewport_1[0] ||
elem.scrollWidth != elem.clientWidth ||
elem.scrollHeight != elem.clientHeight
) {
var $elem = $(elem)
if (!$boundAncestors) {
$boundAncestors = $elem
} else {
$boundAncestors = $boundAncestors.add($elem)
}
$elem.bind('scroll.' + uid, handleActiveCellPositionChange)
}
}
}
function unbindAncestorScrollEvents() {
if (!$boundAncestors) {
return
}
$boundAncestors.unbind('scroll.' + uid)
$boundAncestors = null
}
function updateColumnHeader(columnId, title, toolTip) {
if (!initialized) {
return
}
var idx = getColumnIndex(columnId)
if (idx == null) {
return
}
var columnDef = columns[idx]
let $header
if (options.numberOfColumnsToFreeze > 0) {
if (options.numberOfColumnsToFreeze > idx) {
$header = $headers_0.children().eq(idx)
} else {
$header = $headers_1.children().eq(idx - options.numberOfColumnsToFreeze)
}
}
if ($header) {
if (title !== undefined) {
columns[idx].name = title
}
if (toolTip !== undefined) {
columns[idx].toolTip = toolTip
}
trigger(self.onBeforeHeaderCellDestroy, {
node: $header[0],
column: columnDef
})
$header
.attr('title', toolTip || '')
.children()
.eq(0)
.html(title)
trigger(self.onHeaderCellRendered, {
node: $header[0],
column: columnDef
})
}
}
function getHeaderRow() {
return $headerRow_1[0]
}
function getColumnHeaderNode(columnId) {
var idx = getColumnIndex(columnId)
// FreezeColumn - Combine frozen and nonFrozen side header row objects
var $headersObject
if (!options.numberOfColumnsToFreeze) {
$headersObject = $headers_1
} else {
// Combine frozen and nonFrozen
$headersObject = $($.merge($.merge([], $headers_0), $headers_1))
}
var $header = $headersObject.children().eq(idx)
return $header && $header[0]
}
function getHeaderRowColumn(columnId) {
var idx = getColumnIndex(columnId)
// FreezeColumn - Combine frozen and nonFrozen side header row objects
var $headerRowObject
if (!options.numberOfColumnsToFreeze) {
$headerRowObject = $headerRow_1
} else {
// Combine frozen and nonFrozen
$headerRowObject = $($.merge($.merge([], $headerRow_0), $headerRow_1))
}
var $header = $headerRowObject.children().eq(idx)
return $header && $header[0]
}
function createColumnHeaders() {
function onMouseEnter() {
$(this).addClass('ui-state-hover')
}
function onMouseLeave() {
$(this).removeClass('ui-state-hover')
}
$headers_1.find('.slick-header-column').each(function() {
var columnDef = $(this).data('column')
if (columnDef) {
trigger(self.onBeforeHeaderCellDestroy, {
node: this,
column: columnDef
})
}
})
$headers_1.empty()
var headersWidthObj = getHeadersWidth()
$headers_1.width(headersWidthObj.nonFrozen)
// FreezeColumns - handle header empty
var numberOfColumnsToFreeze = options.numberOfColumnsToFreeze
if (numberOfColumnsToFreeze > 0) {
$headers_0.empty()
$headers_0.width(headersWidthObj.frozen)
}
// FreezeColumns - handle header row empty
var $headerRowObject
$headerRowObject = $headerRow_1
if (numberOfColumnsToFreeze > 0) {
// Combine frozen and nonFrozen
$headerRowObject = $($.merge($.merge([], $headerRow_0), $headerRow_1))
}
$headerRowObject.find('.slick-headerrow-column').each(function() {
var columnDef = $(this).data('column')
if (columnDef) {
trigger(self.onBeforeHeaderRowCellDestroy, {
node: this,
column: columnDef
})
}
})
$headerRowObject.empty()
for (var i = 0; i < columns.length; i++) {
var m = columns[i]
var header = $("<div class='ui-state-default slick-header-column' />")
.html("<span class='slick-column-name'>" + m.name + '</span>')
.width(m.width - headerColumnWidthDiff)
.attr('id', '' + uid + m.id)
.attr('title', m.toolTip || '')
.data('column', m)
.addClass(m.headerCssClass || '')
.appendTo(i < numberOfColumnsToFreeze ? $headers_0 : $headers_1)
if (options.enableColumnReorder || m.sortable) {
header.on('mouseenter', onMouseEnter).on('mouseleave', onMouseLeave)
}
if (m.sortable) {
header.addClass('slick-header-sortable')
header.append("<span class='slick-sort-indicator' />")
}
trigger(self.onHeaderCellRendered, {
node: header[0],
column: m
})
if (options.showHeaderRow) {
var headerRowCell = $(
"<div class='ui-state-default slick-headerrow-column b" + i + ' f' + i + "'></div>"
)
.data('column', m)
.appendTo(i < numberOfColumnsToFreeze ? $headerRow_0 : $headerRow_1)
trigger(self.onHeaderRowCellRendered, {
node: headerRowCell[0],
column: m
})
}
}
setSortColumns(sortColumns)
setupColumnResize()
if (options.enableColumnReorder) {
setupColumnReorder()
}
}
function setupColumnSort() {
$headers_1.add(options.numberOfColumnsToFreeze ? $headers_0 : null).click(e => {
// temporary workaround for a bug in jQuery 1.7.1 (http://bugs.jquery.com/ticket/11328)
e.metaKey = e.metaKey || e.ctrlKey
if ($(e.target).hasClass('slick-resizable-handle')) {
return
}
var $col = $(e.target).closest('.slick-header-column')
if (!$col.length) {
return
}
var column = $col.data('column')
if (column.sortable) {
if (!getEditorLock().commitCurrentEdit()) {
return
}
var sortOpts = null
var i = 0
for (; i < sortColumns.length; i++) {
if (sortColumns[i].columnId == column.id) {
sortOpts = sortColumns[i]
sortOpts.sortAsc = !sortOpts.sortAsc
break
}
}
if (e.metaKey && options.multiColumnSort) {
if (sortOpts) {
sortColumns.splice(i, 1)
}
} else {
if ((!e.shiftKey && !e.metaKey) || !options.multiColumnSort) {
sortColumns = []
}
if (!sortOpts) {
sortOpts = {columnId: column.id, sortAsc: column.defaultSortAsc}
sortColumns.push(sortOpts)
} else if (sortColumns.length == 0) {
sortColumns.push(sortOpts)
}
}
setSortColumns(sortColumns)
if (!options.multiColumnSort) {
trigger(
self.onSort,
{
multiColumnSort: false,
sortCol: column,
sortAsc: sortOpts.sortAsc
},
e
)
} else {
trigger(
self.onSort,
{
multiColumnSort: true,
sortCols: $.map(sortColumns, col => ({
sortCol: columns[getColumnIndex(col.columnId)],
sortAsc: col.sortAsc
}))
},
e
)
}
}
})
}
function setupColumnReorder() {
const sortableHeaderGroups = [$headers_1]
if (options.numberOfColumnsToFreeze > 0) sortableHeaderGroups.push($headers_0)
sortableHeaderGroups.forEach($headers => {
$headers.filter(':ui-sortable').sortable('destroy')
$headers.sortable({
containment: 'parent',
distance: 3,
axis: 'x',
cursor: 'default',
tolerance: 'intersection',
helper: 'clone',
placeholder: 'slick-sortable-placeholder ui-state-default slick-header-column',
forcePlaceholderSize: true,
start: function(e, ui) {
ui.placeholder.width(ui.helper.outerWidth() - headerColumnWidthDiff)
$(ui.helper).addClass('slick-header-column-active')
},
beforeStop: function(e, ui) {
$(ui.helper).removeClass('slick-header-column-active')
},
stop: function(e) {
if (!getEditorLock().commitCurrentEdit()) {
$(this).sortable('cancel')
return
}
var reorderedIds = $headers.sortable('toArray')
var reorderedColumns = []
// handle the reordering
for (var i = 0; i < reorderedIds.length; i++) {
var reorderedIndex = reorderedIds[i].replace(uid, '')
var columnIndex = getColumnIndex(reorderedIndex)
var thingToPush = columns[columnIndex]
reorderedColumns.push(thingToPush)
}
// Preserve the other (frozen/nonfrozen) set of columns
reorderedColumns =
$headers === $headers_1
? [...columns.slice(0, options.numberOfColumnsToFreeze), ...reorderedColumns]
: [...reorderedColumns, ...columns.slice(options.numberOfColumnsToFreeze)]
setColumns(reorderedColumns)
trigger(self.onColumnsReordered, {})
e.stopPropagation()
setupColumnResize()
}
})
})
}
function setupColumnResize() {
var $col, j, c, pageX, columnElements, minPageX, maxPageX, firstResizable, lastResizable
var isFrozenColumn
var headerElements = []
if (options.numberOfColumnsToFreeze > 0) {
headerElements.push($headers_0.children())
}
headerElements.push($headers_1.children())
for (var h = 0; h < headerElements.length; h++) {
columnElements = headerElements[h]
isFrozenColumn = isPartOfAFrozenColumn(columnElements)
columnElements.find('.slick-resizable-handle').remove()
columnElements.each((i, e) => {
var columnIndex = getIndexOffset(isFrozenColumn, i)
if (columns[columnIndex].resizable) {
if (firstResizable === undefined) {
firstResizable = i
}
lastResizable = i
}
})
if (firstResizable === undefined) {
return
}
columnElements.each((i, e) => {
if (i < firstResizable || (options.forceFitColumns && i >= lastResizable)) {
return
}
$col = $(e)
$("<div class='slick-resizable-handle' />")
.appendTo(e)
.bind('dragstart', function(e, dd) {
isFrozenColumn = isPartOfAFrozenColumn(this)
columnElements = getColumnElements(this)
if (!getEditorLock().commitCurrentEdit()) {
return false
}
pageX = e.pageX
$(this)
.parent()
.addClass('slick-header-column-active')
var shrinkLeewayOnFront = null,
stretchLeewayOnFront = null
// lock each column's width option to current width
columnElements.each((i, e) => {
var columnIndex = getIndexOffset(isFrozenColumn, i)
columns[columnIndex].previousWidth = $(e).outerWidth()
})
if (options.forceFitColumns) {
shrinkLeewayOnFront = 0
stretchLeewayOnFront = 0
// colums on front affect maxPageX/minPageX
var nextColumnIndex
for (j = i + 1; j < columnElements.length; j++) {
nextColumnIndex = getIndexOffset(isFrozenColumn, j)
c = columns[nextColumnIndex]
if (c.resizable) {
if (stretchLeewayOnFront !== null) {
if (c.maxWidth) {
stretchLeewayOnFront += c.maxWidth - c.previousWidth
} else {
stretchLeewayOnFront = null
}
}
shrinkLeewayOnFront +=
c.previousWidth - Math.max(c.minWidth || 0, absoluteColumnMinWidth)
}
}
}
var shrinkLeewayOnRear = 0,
stretchLeewayOnRear = 0
for (j = 0; j <= i; j++) {
// columns on rear only affect minPageX
var columnIndex = getIndexOffset(isFrozenColumn, j)
c = columns[columnIndex]
if (c.resizable) {
if (stretchLeewayOnRear !== null) {
if (c.maxWidth) {
stretchLeewayOnRear += c.maxWidth - c.previousWidth
} else {
stretchLeewayOnRear = null
}
}
shrinkLeewayOnRear +=
c.previousWidth - Math.max(c.minWidth || 0, absoluteColumnMinWidth)
}
}
if (shrinkLeewayOnFront === null) {
shrinkLeewayOnFront = 100000
}
if (shrinkLeewayOnRear === null) {
shrinkLeewayOnRear = 100000
}
if (stretchLeewayOnFront === null) {
stretchLeewayOnFront = 100000
}
if (stretchLeewayOnRear === null) {
stretchLeewayOnRear = 100000
}
if (rtl) {
maxPageX = pageX - Math.min(shrinkLeewayOnFront, stretchLeewayOnRear)
minPageX = pageX + Math.min(shrinkLeewayOnRear, stretchLeewayOnFront)
} else {
maxPageX = pageX + Math.min(shrinkLeewayOnFront, stretchLeewayOnRear)
minPageX = pageX - Math.min(shrinkLeewayOnRear, stretchLeewayOnFront)
}
})
.bind('drag', function(e, dd) {
isFrozenColumn = isPartOfAFrozenColumn(this)
columnElements = getColumnElements(this)
var actualMinWidth, d, x
d = rtl
? Math.max(maxPageX, Math.min(minPageX, e.pageX)) - pageX
: Math.min(maxPageX, Math.max(minPageX, e.pageX)) - pageX
var isShrink = (d < 0 && !rtl) || (d > 0 && rtl)
if (isShrink) {
// shrink column
x = d * (rtl ? -1 : 1)
for (j = i; j >= 0; j--) {
c = columns[getIndexOffset(isFrozenColumn, j)]
if (c.resizable) {
actualMinWidth = Math.max(c.minWidth || 0, absoluteColumnMinWidth)
if (x && c.previousWidth + x < actualMinWidth) {
x += c.previousWidth - actualMinWidth
c.width = actualMinWidth
} else {
c.width = c.previousWidth + x
x = 0
}
}
}
if (options.forceFitColumns) {
x = -d * (rtl ? -1 : 1)
for (j = i + 1; j < columnElements.length; j++) {
c = columns[getIndexOffset(isFrozenColumn, j)]
if (c.resizable) {
if (x && c.maxWidth && c.maxWidth - c.previousWidth < x) {
x -= c.maxWidth - c.previousWidth
c.width = c.maxWidth
} else {
c.width = c.previousWidth + x
x = 0
}
}
}
}
} else {
// stretch column
x = d * (rtl ? -1 : 1)
for (j = i; j >= 0; j--) {
c = columns[getIndexOffset(isFrozenColumn, j)]
if (c.resizable) {
if (x && c.maxWidth && c.maxWidth - c.previousWidth < x) {
x -= c.maxWidth - c.previousWidth
c.width = c.maxWidth
} else {
c.width = c.previousWidth + x
x = 0
}
}
}
if (options.forceFitColumns) {
x = -d * (rtl ? -1 : 1)
for (j = i + 1; j < columnElements.length; j++) {
c = columns[getIndexOffset(isFrozenColumn, j)]
if (c.resizable) {
actualMinWidth = Math.max(c.minWidth || 0, absoluteColumnMinWidth)
if (x && c.previousWidth + x < actualMinWidth) {
x += c.previousWidth - actualMinWidth
c.width = actualMinWidth
} else {
c.width = c.previousWidth + x
x = 0
}
}
}
}
}
applyColumnHeaderWidths()
if (options.numberOfColumnsToFreeze > 0) {
updateCanvasWidth(true)
}
if (options.syncColumnCellResize) {
applyColumnWidths()
}
})
.bind('dragend', function(e, dd) {
isFrozenColumn = isPartOfAFrozenColumn(this)
columnElements = getColumnElements(this)
var newWidth
$(this)
.parent()
.removeClass('slick-header-column-active')
for (var j = 0; j < columnElements.length; j++) {
c = columns[getIndexOffset(isFrozenColumn, j)]
newWidth = $(columnElements[j]).outerWidth()
if (c.previousWidth !== newWidth) {
if (c.rerenderOnResize) {
invalidateAllRows()
}
}
}
updateCanvasWidth(true)
render()
trigger(self.onColumnsResized, {})
})
})
}
}
function getIndexOffset(isFrozen, index) {
var newIndex = isFrozen ? index : index + options.numberOfColumnsToFreeze
return newIndex
}
function isPartOfAFrozenColumn(target) {
var results = false
var targetIsFrozen = $(target).hasClass('headers_0')
if (targetIsFrozen) {
results = true
} else {
results = $(target).parents('.headers_0').length > 0
}
return results
}
function getColumnElements(target) {
var results = $(target)
.parents('.slick-header-columns')
.children()
return results
}
function getVBoxDelta($el) {
var p = ['borderTopWidth', 'borderBottomWidth', 'paddingTop', 'paddingBottom']
var delta = 0
$.each(p, (n, val) => {
delta += parseFloat($el.css(val)) || 0
})
return delta
}
function measureCellPaddingAndBorder() {
var el
var h = ['borderLeftWidth', 'borderRightWidth', 'paddingLeft', 'paddingRight']
var v = ['borderTopWidth', 'borderBottomWidth', 'paddingTop', 'paddingBottom']
el = $(
"<div class='ui-state-default slick-header-column' style='visibility:hidden'>-</div>"
).appendTo($headers_1)
headerColumnWidthDiff = headerColumnHeightDiff = 0
if (
el.css('box-sizing') != 'border-box' &&
el.css('-moz-box-sizing') != 'border-box' &&
el.css('-webkit-box-sizing') != 'border-box'
) {
$.each(h, (n, val) => {
headerColumnWidthDiff += parseFloat(el.css(val)) || 0
})
$.each(v, (n, val) => {
headerColumnHeightDiff += parseFloat(el.css(val)) || 0
})
}
el.remove()
var r = $("<div class='slick-row' />").appendTo($canvas_1)
el = $("<div class='slick-cell' id='' style='visibility:hidden'>-</div>").appendTo(r)
cellWidthDiff = cellHeightDiff = 0
if (
el.css('box-sizing') != 'border-box' &&
el.css('-moz-box-sizing') != 'border-box' &&
el.css('-webkit-box-sizing') != 'border-box'
) {
$.each(h, (n, val) => {
cellWidthDiff += parseFloat(el.css(val)) || 0
})
$.each(v, (n, val) => {
cellHeightDiff += parseFloat(el.css(val)) || 0
})
}
r.remove()
absoluteColumnMinWidth = Math.max(headerColumnWidthDiff, cellWidthDiff)
}
function createCssRules() {
$style = $("<style type='text/css' rel='stylesheet' />").appendTo($('head'))
var rowHeight = options.rowHeight - cellHeightDiff
var rules = [
'.' + uid + ' .slick-header-column { ' + rear + ': 1000px; }',
'.' + uid + ' .slick-top-panel { height:' + options.topPanelHeight + 'px; }',
'.' + uid + ' .slick-headerrow-columns { height:' + options.headerRowHeight + 'px; }',
'.' + uid + ' .slick-cell { height:' + rowHeight + 'px; }',
'.' + uid + ' .slick-row { height:' + options.rowHeight + 'px; }'
]
for (var i = 0; i < columns.length; i++) {
rules.push('.' + uid + ' .b' + i + ' { }')
rules.push('.' + uid + ' .f' + i + ' { }')
}
if ($style[0].styleSheet) {
// IE
$style[0].styleSheet.cssText = rules.join(' ')
} else {
$style[0].appendChild(document.createTextNode(rules.join(' ')))
}
}
function getColumnCssRules(idx) {
if (!stylesheet) {
var sheets = document.styleSheets
for (var i = 0; i < sheets.length; i++) {
if ((sheets[i].ownerNode || sheets[i].owningElement) == $style[0]) {
stylesheet = sheets[i]
break
}
}
if (!stylesheet) {
throw new Error('Cannot find stylesheet.')
}
// find and cache column CSS rules
columnCssRulesB = []
columnCssRulesF = []
var cssRules = stylesheet.cssRules || stylesheet.rules
var matches, columnIdx
for (var i = 0; i < cssRules.length; i++) {
var selector = cssRules[i].selectorText
if ((matches = /\.b(\d+)/.exec(selector))) {
columnIdx = parseInt(matches[1], 10)
columnCssRulesB[columnIdx] = cssRules[i]
} else if ((matches = /\.f(\d+)/.exec(selector))) {
columnIdx = parseInt(matches[1], 10)
columnCssRulesF[columnIdx] = cssRules[i]
}
}
}
return {
[rear]: columnCssRulesB[idx],
[front]: columnCssRulesF[idx]
}
}
function removeCssRules() {
$style.remove()
stylesheet = null
}
function destroy() {
getEditorLock().cancelCurrentEdit()
trigger(self.onBeforeDestroy, {})
var i = plugins.length
while (i--) {
unregisterPlugin(plugins[i])
}
if (options.enableColumnReorder) {
if (options.numberOfColumnsToFreeze > 0) {
$headers_0.filter(':ui-sortable').sortable('destroy')
}
$headers_1.filter(':ui-sortable').sortable('destroy')
}
unbindAncestorScrollEvents()
$outerContainer.unbind('.slickgrid')
removeCssRules()
$canvas_1.unbind('draginit dragstart dragend drag')
$outerContainer.empty().removeClass(uid)
}
// ////////////////////////////////////////////////////////////////////////////////////////////
// General
function trigger(evt, args, e) {
e = e || new Slick.EventData()
args = args || {}
args.grid = self
return evt.notify(args, e, self)
}
function getUID() {
return uid
}
function getEditorLock() {
return options.editorLock
}
function getEditController() {
return editController
}
function getColumnIndex(id) {
return columnsById[id]
}
function autosizeColumns() {
var i,
c,
widths = [],
shrinkLeeway = 0,
total = 0,
prevTotal,
availWidth = viewportHasVScroll_1 ? viewportW_1 - scrollbarDimensions.width : viewportW_1
for (i = 0; i < columns.length; i++) {
c = columns[i]
widths.push(c.width)
total += c.width
if (c.resizable) {
shrinkLeeway += c.width - Math.max(c.minWidth, absoluteColumnMinWidth)
}
}
// shrink
prevTotal = total
while (total > availWidth && shrinkLeeway) {
var shrinkProportion = (total - availWidth) / shrinkLeeway
for (i = 0; i < columns.length && total > availWidth; i++) {
c = columns[i]
var width = widths[i]
if (!c.resizable || width <= c.minWidth || width <= absoluteColumnMinWidth) {
continue
}
var absMinWidth = Math.max(c.minWidth, absoluteColumnMinWidth)
var shrinkSize = Math.floor(shrinkProportion * (width - absMinWidth)) || 1
shrinkSize = Math.min(shrinkSize, width - absMinWidth)
total -= shrinkSize
shrinkLeeway -= shrinkSize
widths[i] -= shrinkSize
}
if (prevTotal == total) {
// avoid infinite loop
break
}
prevTotal = total
}
// grow
prevTotal = total
while (total < availWidth) {
var growProportion = availWidth / total
for (i = 0; i < columns.length && total < availWidth; i++) {
c = columns[i]
if (!c.resizable || c.maxWidth <= c.width) {
continue
}
var growSize =
Math.min(
Math.floor(growProportion * c.width) - c.width,
c.maxWidth - c.width || 1000000
) || 1
total += growSize
widths[i] += growSize
}
if (prevTotal == total) {
// avoid infinite loop
break
}
prevTotal = total
}
var reRender = false
for (i = 0; i < columns.length; i++) {
if (columns[i].rerenderOnResize && columns[i].width != widths[i]) {
reRender = true
}
columns[i].width = widths[i]
}
applyColumnHeaderWidths()
updateCanvasWidth(true)
if (reRender) {
invalidateAllRows()
render()
}
}
function applyWidthToHeaders(header) {
var isFrozenColumn = isPartOfAFrozenColumn(header)
var headers = header.children()
for (var i = 0, h, ii = headers.length; i < ii; i++) {
h = $(headers[i])
var columnIndex = getIndexOffset(isFrozenColumn, i)
if (h.width() !== columns[columnIndex].width - headerColumnWidthDiff) {
h.width(columns[columnIndex].width - headerColumnWidthDiff)
}
}
}
function applyColumnHeaderWidths() {
if (!initialized) {
return
}
if (options.numberOfColumnsToFreeze > 0) {
applyWidthToHeaders($headers_0) // Frozen Columns
}
applyWidthToHeaders($headers_1) // NonFrozen Columns
updateColumnCaches()
}
function applyColumnWidths() {
var nonFrozenWidth = 0,
w,
rule
var frozenWidth = 0
var numberOfColumnsToFreeze = options.numberOfColumnsToFreeze
var ruleIndex = 0
var columnCount = columns.length
for (var i = 0; i < columnCount; i++) {
w = columns[i].width
rule = getColumnCssRules(i)
if (i < numberOfColumnsToFreeze) {
// FrozenColumns
rule[rear].style[rear] = frozenWidth + 'px'
rule[front].style[front] = canvasWidth_0 - frozenWidth - w + 'px'
frozenWidth += columns[i].width
} else {
// NonFrozenColumns
rule[rear].style[rear] = nonFrozenWidth + 'px'
rule[front].style[front] = canvasWidth_1 - nonFrozenWidth - w + 'px'
nonFrozenWidth += columns[i].width
}
}
}
/*
* updates the numberOfColumnsToFreeze.
*
* doesn't change the number of frozen columns until you do something to
* re-build the grid (like setColumns)
*/
function setNumberOfColumnsToFreeze(n) {
options.numberOfColumnsToFreeze = n
}
function setSortColumn(columnId, ascending) {
setSortColumns([{columnId: columnId, sortAsc: ascending}])
}
function setSortColumns(cols) {
sortColumns = cols
// Combine frozen and nonFrozen
var headerColumnEls = $headers_1
.children()
.add(options.numberOfColumnsToFreeze ? $headers_0.children() : null)
headerColumnEls
.removeClass('slick-header-column-sorted')
.find('.slick-sort-indicator')
.removeClass('slick-sort-indicator-asc slick-sort-indicator-desc')
$.each(sortColumns, (i, col) => {
if (col.sortAsc == null) {
col.sortAsc = true
}
var columnIndex = getColumnIndex(col.columnId)
if (columnIndex != null) {
headerColumnEls
.eq(columnIndex)
.addClass('slick-header-column-sorted')
.find('.slick-sort-indicator')
.addClass(col.sortAsc ? 'slick-sort-indicator-asc' : 'slick-sort-indicator-desc')
}
})
}
function getSortColumns() {
return sortColumns
}
function handleSelectedRangesChanged(e, ranges) {
selectedRows = []
var hash = {}
for (var i = 0; i < ranges.length; i++) {
for (var j = ranges[i].fromRow; j <= ranges[i].toRow; j++) {
if (!hash[j]) {
// prevent duplicates
selectedRows.push(j)
hash[j] = {}
}
for (var k = ranges[i].fromCell; k <= ranges[i].toCell; k++) {
if (canCellBeSelected(j, k)) {
hash[j][columns[k].id] = options.selectedCellCssClass
}
}
}
}
setCellCssStyles(options.selectedCellCssClass, hash)
trigger(self.onSelectedRowsChanged, {rows: getSelectedRows()}, e)
}
function getColumns() {
return columns
}
function updateColumnCaches() {
// Pre-calculate cell boundaries.
columnPosRear = []
columnPosFront = []
var frozenWidth = 0
var nonFrozenWidth = 0
var numberOfColumnsToFreeze = options.numberOfColumnsToFreeze
for (var i = 0, ii = columns.length; i < ii; i++) {
if (i < numberOfColumnsToFreeze) {
// Frozen Columns
columnPosRear[i] = frozenWidth
columnPosFront[i] = frozenWidth + columns[i].width
frozenWidth += columns[i].width
} else {
// NonFrozen Columns
columnPosRear[i] = nonFrozenWidth
columnPosFront[i] = nonFrozenWidth + columns[i].width
nonFrozenWidth += columns[i].width
}
}
}
function setColumns(columnDefinitions) {
columns = columnDefinitions
columnsById = {}
for (var i = 0; i < columns.length; i++) {
var m = (columns[i] = $.extend({}, columnDefaults, columns[i]))
columnsById[m.id] = i
if (m.minWidth && m.width < m.minWidth) {
m.width = m.minWidth
}
if (m.maxWidth && m.width > m.maxWidth) {
m.width = m.maxWidth
}
}
updateColumnCaches()
if (initialized) {
invalidateAllRows()
createColumnHeaders()
removeCssRules()
createCssRules()
resizeCanvas()
applyColumnWidths()
handleScroll()
}
}
function getOptions() {
return options
}
function setOptions(args) {
if (!getEditorLock().commitCurrentEdit()) {
return
}
makeActiveCellNormal()
if (options.enableAddRow !== args.enableAddRow) {
invalidateRow(getDataLength())
}
options = $.extend(options, args)
validateAndEnforceOptions()
$viewport_1.css('overflow-y', options.autoHeight ? 'hidden' : 'auto')
render()
}
function validateAndEnforceOptions() {
if (options.autoHeight) {
options.leaveSpaceForNewRows = false
}
}
function setData(newData, scrollToTop) {
data = newData
invalidateAllRows()
updateRowCount()
if (scrollToTop) {
scrollTo(0)
}
}
function getData() {
return data
}
function getDataLength() {
if (data.getLength) {
return data.getLength()
} else {
return data.length
}
}
function getDataLengthIncludingAddNew() {
return getDataLength() + (options.enableAddRow ? 1 : 0)
}
function getDataItem(i) {
if (data.getItem) {
return data.getItem(i)
} else {
return data[i]
}
}
function getTopPanel() {
return $topPanel_1[0]
}
function setTopPanelVisibility(visible) {
if (options.showTopPanel != visible) {
options.showTopPanel = visible
if (visible) {
$topPanelScroller_1.slideDown('fast', resizeCanvas)
} else {
$topPanelScroller_1.slideUp('fast', resizeCanvas)
}
}
}
function setHeaderRowVisibility(visible) {
if (options.showHeaderRow != visible) {
options.showHeaderRow = visible
if (visible) {
$headerRowScroller_1.slideDown('fast', resizeCanvas)
} else {
$headerRowScroller_1.slideUp('fast', resizeCanvas)
}
}
}
function getContainerNode() {
return $outerContainer.get(0)
}
// ////////////////////////////////////////////////////////////////////////////////////////////
// Rendering / Scrolling
function getRowTop(row) {
return options.rowHeight * row - offset
}
function getRowFromPosition(y) {
return Math.floor((y + offset) / options.rowHeight)
}
function scrollTo(y) {
y = Math.max(y, 0)
y = Math.min(y, th - viewportH_1 + (viewportHasHScroll_1 ? scrollbarDimensions.height : 0))
var oldOffset = offset
page = Math.min(n - 1, Math.floor(y / ph))
offset = Math.round(page * cj)
var newScrollTop = y - offset
if (offset != oldOffset) {
var range = getVisibleRange(newScrollTop)
cleanupRows(range)
updateRowPositions()
}
if (prevScrollTop != newScrollTop) {
vScrollDir = prevScrollTop + oldOffset < newScrollTop + offset ? 1 : -1
$viewport_1[0].scrollTop = lastRenderedScrollTop = scrollTop = prevScrollTop = newScrollTop
if (options.numberOfColumnsToFreeze > 0) {
$viewport_0[0].scrollTop = scrollTop
}
trigger(self.onViewportChanged, {})
}
}
function defaultFormatter(row, cell, value, columnDef, dataContext) {
if (value == null) {
return ''
} else {
return (value + '')
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
}
}
function getFormatter(row, column) {
var rowMetadata = data.getItemMetadata && data.getItemMetadata(row)
// look up by id, then index
var columnOverrides =
rowMetadata &&
rowMetadata.columns &&
(rowMetadata.columns[column.id] || rowMetadata.columns[getColumnIndex(column.id)])
return (
(columnOverrides && columnOverrides.formatter) ||
(rowMetadata && rowMetadata.formatter) ||
column.formatter ||
(options.formatterFactory && options.formatterFactory.getFormatter(column)) ||
options.defaultFormatter
)
}
function getEditor(row, cell) {
var column = columns[cell]
var rowMetadata = data.getItemMetadata && data.getItemMetadata(row)
var columnMetadata = rowMetadata && rowMetadata.columns
if (
columnMetadata &&
columnMetadata[column.id] &&
columnMetadata[column.id].editor !== undefined
) {
return columnMetadata[column.id].editor
}
if (columnMetadata && columnMetadata[cell] && columnMetadata[cell].editor !== undefined) {
return columnMetadata[cell].editor
}
return column.editor || (options.editorFactory && options.editorFactory.getEditor(column))
}
function getDataItemValueForColumn(item, columnDef) {
if (options.dataItemColumnValueExtractor) {
return options.dataItemColumnValueExtractor(item, columnDef)
}
return item[columnDef.field]
}
function appendRowHtml(stringArray, row, range, dataLength) {
var numberOfColumnsToFreeze = options.numberOfColumnsToFreeze // CLICK CUSTOM CODE
var d = getDataItem(row)
var dataLoading = row < dataLength && !d
var rowCss =
'slick-row' +
(dataLoading ? ' loading' : '') +
(row === activeRow ? ' active' : '') +
(row % 2 == 1 ? ' odd' : ' even')
var metadata = data.getItemMetadata && data.getItemMetadata(row)
if (metadata && metadata.cssClasses) {
rowCss += ' ' + metadata.cssClasses
}
var rowString =
"<div class='ui-widget-content " + rowCss + "' style='top:" + getRowTop(row) + "px;' >"
// only use role in normal rows
stringArray.nonFrozen.push(rowString)
if (numberOfColumnsToFreeze > 0) {
stringArray.frozen.push(rowString)
}
var colspan, m
for (var i = 0, ii = columns.length; i < ii; i++) {
m = columns[i]
colspan = 1
if (metadata && metadata.columns) {
var columnData = metadata.columns[m.id] || metadata.columns[i]
colspan = (columnData && columnData.colspan) || 1
if (colspan === '*') {
colspan = ii - i
}
}
// Always render frozen columns
if (numberOfColumnsToFreeze && i < numberOfColumnsToFreeze) {
appendCellHtml(stringArray, row, i, colspan, d)
} else if (
columnPosFront[Math.min(ii - 1, i + colspan - 1)] >
(rtl ? canvasWidth_1 - range.rightPx : range.leftPx)
) {
// Do not render cells before those in range.
if (columnPosRear[i] > (rtl ? canvasWidth_1 - range.leftPx : range.rightPx)) {
// All columns after are outside the range.
break
}
appendCellHtml(stringArray, row, i, colspan, d)
}
if (colspan > 1) {
i += colspan - 1
}
}
stringArray.nonFrozen.push('</div>')
if (numberOfColumnsToFreeze > 0) {
stringArray.frozen.push('</div>')
}
}
function appendCellHtml(stringArray, row, cell, colspan, item) {
var m = columns[cell]
// var d = getDataItem(row);
var cellCss =
'slick-cell b' +
cell +
' f' +
Math.min(columns.length - 1, cell + colspan - 1) +
(m.cssClass ? ' ' + m.cssClass : '')
if (row === activeRow && cell === activeCell) {
cellCss += ' active'
}
// TODO: merge them together in the setter
for (var key in cellCssClasses) {
if (cellCssClasses[key][row] && cellCssClasses[key][row][m.id]) {
cellCss += ' ' + cellCssClasses[key][row][m.id]
}
}
var cellString = "<div class='" + cellCss + "'>"
var numberOfColumnsToFreeze = options.numberOfColumnsToFreeze
// FrozenColumns - Add the cells html depending on the cell index - frozen or not
if (cell < numberOfColumnsToFreeze) {
stringArray.frozen.push(cellString)
} else {
stringArray.nonFrozen.push(cellString)
}
// if there is a corresponding row (if not, this is the Add New row or this data hasn't been loaded yet)
if (item) {
var value = getDataItemValueForColumn(item, m)
var contents = getFormatter(row, m)(row, cell, value, m, item)
if (cell < numberOfColumnsToFreeze) {
stringArray.frozen.push(contents)
} else {
stringArray.nonFrozen.push(getFormatter(row, m)(row, cell, value, m, item))
}
}
if (cell < numberOfColumnsToFreeze) {
stringArray.frozen.push('</div>')
} else {
stringArray.nonFrozen.push('</div>')
}
rowsCache[row].cellRenderQueue.push(cell)
rowsCache[row].cellColSpans[cell] = colspan
}
function cleanupRows(rangeToKeep) {
for (var i in rowsCache) {
if (
(i = parseInt(i, 10)) !== activeRow &&
(i < rangeToKeep.top || i > rangeToKeep.bottom)
) {
removeRowFromCache(i)
}
}
}
function invalidate() {
updateRowCount()
invalidateAllRows()
render()
}
function invalidateAllRows() {
if (currentEditor) {
makeActiveCellNormal()
}
for (var row in rowsCache) {
removeRowFromCache(row)
}
}
function removeRowFromCache(row) {
var cacheEntry = rowsCache[row]
if (!cacheEntry) {
return
}
var childToRemove = cacheEntry.rowNode
// Frozen Columns - remove row from frozen and nonFrozen canvas_x
if (options.numberOfColumnsToFreeze > 0) {
if (childToRemove.nonFrozen) {
$canvas_1[0].removeChild(childToRemove.nonFrozen)
}
if (childToRemove.frozen) {
$canvas_0[0].removeChild(childToRemove.frozen)
}
} else {
$canvas_1[0].removeChild(childToRemove.nonFrozen)
}
delete rowsCache[row]
delete postProcessedRows[row]
renderedRows--
counter_rows_removed++
}
function invalidateRows(rows) {
var i, rl
if (!rows || !rows.length) {
return
}
vScrollDir = 0
for (i = 0, rl = rows.length; i < rl; i++) {
if (currentEditor && activeRow === rows[i]) {
makeActiveCellNormal()
}
if (rowsCache[rows[i]]) {
removeRowFromCache(rows[i])
}
}
}
function invalidateRow(row) {
invalidateRows([row])
}
function updateCell(row, cell) {
var cellNode = getCellNode(row, cell)
if (!cellNode) {
return
}
var m = columns[cell],
d = getDataItem(row)
if (currentEditor && activeRow === row && activeCell === cell) {
currentEditor.loadValue(d)
} else {
cellNode.innerHTML = d
? getFormatter(row, m)(row, cell, getDataItemValueForColumn(d, m), m, d)
: ''
invalidatePostProcessingResults(row)
}
}
function updateRow(row) {
var cacheEntry = rowsCache[row]
if (!cacheEntry) {
return
}
ensureCellNodesInRowsCache(row)
var d = getDataItem(row)
for (var columnIdx in cacheEntry.cellNodesByColumnIdx) {
if (!cacheEntry.cellNodesByColumnIdx.hasOwnProperty(columnIdx)) {
continue
}
columnIdx |= 0
var m = columns[columnIdx],
node = cacheEntry.cellNodesByColumnIdx[columnIdx]
if (row === activeRow && columnIdx === activeCell && currentEditor) {
currentEditor.loadValue(d)
} else if (d) {
node.innerHTML = getFormatter(row, m)(
row,
columnIdx,
getDataItemValueForColumn(d, m),
m,
d
)
} else {
node.innerHTML = ''
}
}
invalidatePostProcessingResults(row)
}
function getViewportHeight() {
return (
parseFloat($.css($outerContainer[0], 'height', true)) -
parseFloat($.css($outerContainer[0], 'paddingTop', true)) -
parseFloat($.css($outerContainer[0], 'paddingBottom', true)) -
parseFloat($.css($headerScroller_1[0], 'height')) -
getVBoxDelta($headerScroller_1) -
(options.showTopPanel ? options.topPanelHeight + getVBoxDelta($topPanelScroller_1) : 0) -
(options.showHeaderRow ? options.headerRowHeight + getVBoxDelta($headerRowScroller_1) : 0)
)
}
function resizeCanvas() {
if (!initialized) {
return
}
if (options.autoHeight) {
viewportH_1 = options.rowHeight * getDataLengthIncludingAddNew()
} else {
viewportH_1 = getViewportHeight()
}
numVisibleRows = Math.ceil(viewportH_1 / options.rowHeight)
viewportW_1 = parseFloat($.css($container_1[0], 'width', true))
if (!options.autoHeight) {
$viewport_1.height(viewportH_1)
// Frozen Columns - also set left viewport height
if (options.numberOfColumnsToFreeze) {
$viewport_0.height(viewportH_1)
}
}
if (options.forceFitColumns) {
autosizeColumns()
}
updateRowCount()
handleScroll()
// Since the width has changed, force the render() to reevaluate virtually rendered cells.
lastRenderedScrollLeft = -1
render()
}
function updateRowCount() {
var dataLength = getDataLength()
if (!initialized) {
return
}
numberOfRows =
getDataLengthIncludingAddNew() + (options.leaveSpaceForNewRows ? numVisibleRows - 1 : 0)
var oldViewportHasVScroll = viewportHasVScroll_1
// with autoHeight, we do not need to accommodate the vertical scroll bar
viewportHasVScroll_1 = !options.autoHeight && numberOfRows * options.rowHeight > viewportH_1
// remove the rows that are now outside of the data range
// this helps avoid redundant calls to .removeRow() when the size of the data decreased by thousands of rows
var l = getDataLengthIncludingAddNew() - 1
for (var i in rowsCache) {
if (i >= l) {
removeRowFromCache(i)
}
}
if (activeCellNode && activeRow > l) {
resetActiveCell()
}
var oldH = h
th = Math.max(options.rowHeight * numberOfRows, viewportH_1 - scrollbarDimensions.height)
if (th < maxSupportedCssHeight) {
// just one page
h = ph = th
n = 1
cj = 0
} else {
// break into pages
h = maxSupportedCssHeight
ph = h / 100
n = Math.floor(th / ph)
cj = (th - h) / (n - 1)
}
if (h !== oldH) {
$canvas_1.css('height', h)
scrollTop = $viewport_1[0].scrollTop
// Frozen Columns - Set left viewport height to h + scrollbar height
if (options.numberOfColumnsToFreeze > 0) {
$canvas_0.css('height', h + scrollbarDimensions.height)
}
}
var oldScrollTopInRange = scrollTop + offset <= th - viewportH_1
if (th == 0 || scrollTop == 0) {
page = offset = 0
} else if (oldScrollTopInRange) {
// maintain virtual position
scrollTo(scrollTop + offset)
} else {
// scroll to bottom
scrollTo(th - viewportH_1)
}
if (h != oldH && options.autoHeight) {
resizeCanvas()
}
if (options.forceFitColumns && oldViewportHasVScroll != viewportHasVScroll_1) {
autosizeColumns()
}
updateCanvasWidth(false)
}
// ///////////////////////////////////////////////////////////////////////////////////////////////////////
// Returns an object with the following values.
// top - row number of the top visible row. applicable to viewport_0 and viewport_1.
// bottom - row number of the bottom visible row. applicable to viewport_0 and viewport_1.
// leftPx - pixel number of the leftmost visible pixel. applicable to viewPort_1 only.
// As viewport_0 is frozen it is assumed to always be visible
// rightPx - pixel number. applicable to viewPort_1 only.
// As viewport_0 is frozen it is assumed to always be visible
// ///////////////////////////////////////////////////////////////////////////////////////////////////////
function getVisibleRange(viewportTop, viewportLeft) {
if (viewportTop == null) {
viewportTop = scrollTop
}
if (viewportLeft == null) {
viewportLeft = scrollLeft
}
return {
top: getRowFromPosition(viewportTop),
bottom: getRowFromPosition(viewportTop + viewportH_1),
leftPx: viewportLeft,
rightPx: viewportLeft + viewportW_1
}
}
// ///////////////////////////////////////////////////////////////////////////////////////////////////////
// Returns an object with the following values.
// top - row number. applicable to viewport_0 and viewport_1.
// bottom - row number. applicable to viewport_0 and viewport_1.
// leftPx - pixel number. applicable to viewPort_1 only. As viewport_0 is frozen it
// is assumed to always be rendered
// rightPx - pixel number. applicable to viewPort_1 only. As viewport_0 is frozen it
// is assumed to always be rendered
// ///////////////////////////////////////////////////////////////////////////////////////////////////////
function getRenderedRange(viewportTop, viewportLeft) {
var range = getVisibleRange(viewportTop, viewportLeft)
var buffer = Math.round(viewportH_1 / options.rowHeight)
var minBuffer = 3
if (vScrollDir == -1) {
range.top -= buffer
range.bottom += minBuffer
} else if (vScrollDir == 1) {
range.top -= minBuffer
range.bottom += buffer
} else {
range.top -= minBuffer
range.bottom += minBuffer
}
range.top = Math.max(0, range.top)
range.bottom = Math.min(getDataLengthIncludingAddNew() - 1, range.bottom)
range.leftPx -= viewportW_1
range.rightPx += viewportW_1
range.leftPx = Math.max(0, range.leftPx)
range.rightPx = Math.min(canvasWidth_1, range.rightPx)
return range
}
function ensureCellNodesInRowsCache(row) {
var cacheEntry = rowsCache[row]
if (cacheEntry) {
if (cacheEntry.cellRenderQueue.length) {
var lastChild_1 = cacheEntry.rowNode.nonFrozen.lastChild
if (options.numberOfColumnsToFreeze > 0) {
var lastChild_0 = cacheEntry.rowNode.frozen.lastChild
}
var numberOfColumnsToFreeze = options.numberOfColumnsToFreeze
while (cacheEntry.cellRenderQueue.length) {
var columnIdx = cacheEntry.cellRenderQueue.pop()
if (numberOfColumnsToFreeze > columnIdx) {
cacheEntry.cellNodesByColumnIdx[columnIdx] = lastChild_0
if (lastChild_0.previousSibling) {
lastChild_0 = lastChild_0.previousSibling
}
} else {
cacheEntry.cellNodesByColumnIdx[columnIdx] = lastChild_1
if (lastChild_1.previousSibling) {
lastChild_1 = lastChild_1.previousSibling
}
}
}
}
}
}
function cleanUpCells(range, row) {
var totalCellsRemoved = 0
var cacheEntry = rowsCache[row]
// Remove cells outside the range.
var cellsToRemove = []
for (var i in cacheEntry.cellNodesByColumnIdx) {
// I really hate it when people mess with Array.prototype.
if (!cacheEntry.cellNodesByColumnIdx.hasOwnProperty(i)) {
continue
}
// This is a string, so it needs to be cast back to a number.
i |= 0
var colspan = cacheEntry.cellColSpans[i]
const outOfRange = rtl
? columnPosRear[i] > canvasWidth_1 - range.leftPx ||
columnPosFront[Math.min(columns.length - 1, i + colspan - 1)] <
canvasWidth_1 - range.rightPx
: columnPosRear[i] > range.rightPx ||
columnPosFront[Math.min(columns.length - 1, i + colspan - 1)] < range.leftPx
if (outOfRange) {
if (!(row == activeRow && i == activeCell)) {
cellsToRemove.push(i)
}
}
}
var cellToRemove = cellsToRemove.pop()
var childToRemove, children
while (cellToRemove != null && cellToRemove != 0) {
childToRemove = cacheEntry.cellNodesByColumnIdx[cellToRemove]
// cacheEntry.rowNode.frozen.removeChild(childToRemove);
children = cacheEntry.rowNode.nonFrozen.childNodes
for (var i = 0; i < children.length; i++) {
if (children[i] == childToRemove) {
cacheEntry.rowNode.nonFrozen.removeChild(childToRemove)
delete cacheEntry.cellColSpans[cellToRemove]
delete cacheEntry.cellNodesByColumnIdx[cellToRemove]
if (postProcessedRows[row]) {
delete postProcessedRows[row][cellToRemove]
}
totalCellsRemoved++
break
}
}
if (cellsToRemove.length > 0) {
cellToRemove = cellsToRemove.pop()
} else {
cellToRemove = null
}
}
}
function cleanUpAndRenderCells(range) {
var cacheEntry
var stringArray = {frozen: [], nonFrozen: []}
var processedRows = []
var cellsAdded
var totalCellsAdded = 0
var colspan
for (var row = range.top, btm = range.bottom; row <= btm; row++) {
cacheEntry = rowsCache[row]
if (!cacheEntry) {
continue
}
// cellRenderQueue populated in renderRows() needs to be cleared first
ensureCellNodesInRowsCache(row)
cleanUpCells(range, row)
// Render missing cells.
cellsAdded = 0
var metadata = data.getItemMetadata && data.getItemMetadata(row)
metadata = metadata && metadata.columns
var d = getDataItem(row)
// TODO: shorten this loop (index? heuristics? binary search?)
for (var i = 0, ii = columns.length; i < ii; i++) {
// Cells to the right are outside the range.
if (columnPosRear[i] > (rtl ? range.rightPx + canvasWidth_1 : range.rightPx)) {
break
}
// Already rendered.
if ((colspan = cacheEntry.cellColSpans[i]) != null) {
i += colspan > 1 ? colspan - 1 : 0
continue
}
colspan = 1
if (metadata) {
var columnData = metadata[columns[i].id] || metadata[i]
colspan = (columnData && columnData.colspan) || 1
if (colspan === '*') {
colspan = ii - i
}
}
if (
columnPosFront[Math.min(ii - 1, i + colspan - 1)] >
(rtl ? canvasWidth_1 - range.rightPx : range.leftPx)
) {
appendCellHtml(stringArray, row, i, colspan, d)
cellsAdded++
}
i += colspan > 1 ? colspan - 1 : 0
}
if (cellsAdded) {
totalCellsAdded += cellsAdded
processedRows.push(row)
}
}
if (!stringArray.nonFrozen.length) {
return
}
var nonFrozenDiv = document.createElement('div')
nonFrozenDiv.innerHTML = stringArray.nonFrozen.join('')
var frozenDiv = document.createElement('div')
frozenDiv.innerHTML = stringArray.frozen.join('')
var processedRow
var nonFrozenNode
var frozenNode
var numberOfColumnsToFreeze = options.numberOfColumnsToFreeze
while ((processedRow = processedRows.pop()) != null) {
cacheEntry = rowsCache[processedRow]
var columnIdx
while ((columnIdx = cacheEntry.cellRenderQueue.pop()) != null) {
frozenNode = frozenDiv.lastChild
nonFrozenNode = nonFrozenDiv.lastChild
if (numberOfColumnsToFreeze > columnIdx) {
cacheEntry.rowNode.frozen.appendChild(frozenNode)
cacheEntry.cellNodesByColumnIdx[columnIdx] = frozenNode
} else {
cacheEntry.rowNode.nonFrozen.appendChild(nonFrozenNode)
cacheEntry.cellNodesByColumnIdx[columnIdx] = nonFrozenNode
}
}
}
}
function renderRows(range) {
var parentNode_1 = $canvas_1[0],
stringArray = {frozen: [], nonFrozen: []},
rows = [],
needToReselectCell = false,
dataLength = getDataLength()
var numberOfColumnsToFreeze = options.numberOfColumnsToFreeze
if (numberOfColumnsToFreeze) {
var parentNode_0 = $canvas_0[0]
}
for (var i = range.top, ii = range.bottom; i <= ii; i++) {
if (rowsCache[i]) {
continue
}
renderedRows++
rows.push(i)
// Create an entry right away so that appendRowHtml() can
// start populatating it.
rowsCache[i] = {
rowNode: null,
// ColSpans of rendered cells (by column idx).
// Can also be used for checking whether a cell has been rendered.
cellColSpans: [],
// Cell nodes (by column idx). Lazy-populated by ensureCellNodesInRowsCache().
cellNodesByColumnIdx: [],
// Column indices of cell nodes that have been rendered, but not yet indexed in
// cellNodesByColumnIdx. These are in the same order as cell nodes added at the
// end of the row.
cellRenderQueue: []
}
appendRowHtml(stringArray, i, range, dataLength)
if (activeCellNode && activeRow === i) {
needToReselectCell = true
}
counter_rows_rendered++
}
if (!rows.length) {
return
}
var nonFrozenDiv = document.createElement('div')
nonFrozenDiv.innerHTML = stringArray.nonFrozen.join('')
if (numberOfColumnsToFreeze > 0) {
// FreezeColumns - Add divs for both frozen columns and update contents
var frozenDiv = document.createElement('div')
frozenDiv.innerHTML = stringArray.frozen.join('')
}
var currentRowCache
for (var i = 0, ii = rows.length; i < ii; i++) {
currentRowCache = rowsCache[rows[i]]
currentRowCache.rowNode = {
frozen: numberOfColumnsToFreeze > 0 ? parentNode_0.appendChild(frozenDiv.firstChild) : '',
nonFrozen: parentNode_1.appendChild(nonFrozenDiv.firstChild)
}
}
if (needToReselectCell) {
activeCellNode = getCellNode(activeRow, activeCell)
}
}
function startPostProcessing() {
if (!options.enableAsyncPostRender) {
return
}
clearTimeout(h_postrender)
h_postrender = setTimeout(asyncPostProcessRows, options.asyncPostRenderDelay)
}
function invalidatePostProcessingResults(row) {
delete postProcessedRows[row]
postProcessFromRow = Math.min(postProcessFromRow, row)
postProcessToRow = Math.max(postProcessToRow, row)
startPostProcessing()
}
function updateRowPositions() {
for (var row in rowsCache) {
var rowTop = getRowTop(row) + 'px'
rowsCache[row].rowNode.frozen.style.top = rowTop
rowsCache[row].rowNode.nonFrozen.style.top = rowTop
}
}
function render() {
if (!initialized) {
return
}
var visible = getVisibleRange()
var rendered = getRenderedRange()
// remove rows no longer in the viewport
cleanupRows(rendered)
// add new rows & missing cells in existing rows
if (lastRenderedScrollLeft != scrollLeft) {
cleanUpAndRenderCells(rendered)
}
// render missing rows
renderRows(rendered)
postProcessFromRow = visible.top
postProcessToRow = Math.min(getDataLengthIncludingAddNew() - 1, visible.bottom)
startPostProcessing()
lastRenderedScrollTop = scrollTop
lastRenderedScrollLeft = scrollLeft
h_render = null
}
function handleHeaderRowScroll() {
var scrollLeft = getScrollLeft($headerRowScroller_1[0])
if (scrollLeft != getScrollLeft($viewport_1[0])) {
$viewport_1[0].scrollLeft = scrollLeft
}
}
function handleScroll(scrollInfo) {
if (scrollInfo != undefined && scrollInfo.scrollTop != undefined) {
$viewport_1[0].scrollTop = scrollInfo.scrollTop
}
scrollTop = $viewport_1[0].scrollTop
scrollLeft = getScrollLeft($viewport_1[0])
var vScrollDist = Math.abs(scrollTop - prevScrollTop)
var hScrollDist = Math.abs(scrollLeft - prevScrollLeft)
if (hScrollDist) {
prevScrollLeft = scrollLeft
setScrollLeft($headerScroller_1[0], scrollLeft)
setScrollLeft($topPanelScroller_1[0], scrollLeft)
setScrollLeft($headerRowScroller_1[0], scrollLeft)
}
if (vScrollDist) {
vScrollDir = prevScrollTop < scrollTop ? 1 : -1
prevScrollTop = scrollTop
if (options.numberOfColumnsToFreeze > 0) {
$viewport_0[0].scrollTop = scrollTop
}
// switch virtual pages if needed
if (vScrollDist < viewportH_1) {
scrollTo(scrollTop + offset)
} else {
var oldOffset = offset
if (h == viewportH_1) {
page = 0
} else {
page = Math.min(
n - 1,
Math.floor(scrollTop * ((th - viewportH_1) / (h - viewportH_1)) * (1 / ph))
)
}
offset = Math.round(page * cj)
if (oldOffset != offset) {
invalidateAllRows()
}
}
}
if (hScrollDist || vScrollDist) {
if (h_render) {
clearTimeout(h_render)
}
if (
Math.abs(lastRenderedScrollTop - scrollTop) > 20 ||
Math.abs(lastRenderedScrollLeft - scrollLeft) > 20
) {
if (
options.forceSyncScrolling ||
(Math.abs(lastRenderedScrollTop - scrollTop) < viewportH_1 &&
Math.abs(lastRenderedScrollLeft - scrollLeft) < viewportW_1)
) {
render()
} else {
h_render = setTimeout(render, 50)
}
trigger(self.onViewportChanged, {})
}
}
trigger(self.onScroll, {scrollLeft: scrollLeft, scrollTop: scrollTop})
}
function asyncPostProcessRows() {
while (postProcessFromRow <= postProcessToRow) {
var row = vScrollDir >= 0 ? postProcessFromRow++ : postProcessToRow--
var cacheEntry = rowsCache[row]
if (!cacheEntry || row >= getDataLength()) {
continue
}
if (!postProcessedRows[row]) {
postProcessedRows[row] = {}
}
ensureCellNodesInRowsCache(row)
for (var columnIdx in cacheEntry.cellNodesByColumnIdx) {
if (!cacheEntry.cellNodesByColumnIdx.hasOwnProperty(columnIdx)) {
continue
}
columnIdx |= 0
var m = columns[columnIdx]
if (m.asyncPostRender && !postProcessedRows[row][columnIdx]) {
var node = cacheEntry.cellNodesByColumnIdx[columnIdx]
if (node) {
m.asyncPostRender(node, row, getDataItem(row), m)
}
postProcessedRows[row][columnIdx] = true
}
}
h_postrender = setTimeout(asyncPostProcessRows, options.asyncPostRenderDelay)
return
}
}
function updateCellCssStylesOnRenderedRows(addedHash, removedHash) {
var node, columnId, addedRowHash, removedRowHash
for (var row in rowsCache) {
removedRowHash = removedHash && removedHash[row]
addedRowHash = addedHash && addedHash[row]
if (removedRowHash) {
for (columnId in removedRowHash) {
if (!addedRowHash || removedRowHash[columnId] != addedRowHash[columnId]) {
node = getCellNode(row, getColumnIndex(columnId))
if (node) {
$(node).removeClass(removedRowHash[columnId])
}
}
}
}
if (addedRowHash) {
for (columnId in addedRowHash) {
if (!removedRowHash || removedRowHash[columnId] != addedRowHash[columnId]) {
node = getCellNode(row, getColumnIndex(columnId))
if (node) {
$(node).addClass(addedRowHash[columnId])
}
}
}
}
}
}
function addCellCssStyles(key, hash) {
if (cellCssClasses[key]) {
throw "addCellCssStyles: cell CSS hash with key '" + key + "' already exists."
}
cellCssClasses[key] = hash
updateCellCssStylesOnRenderedRows(hash, null)
trigger(self.onCellCssStylesChanged, {key: key, hash: hash})
}
function removeCellCssStyles(key) {
if (!cellCssClasses[key]) {
return
}
updateCellCssStylesOnRenderedRows(null, cellCssClasses[key])
delete cellCssClasses[key]
trigger(self.onCellCssStylesChanged, {key: key, hash: null})
}
function setCellCssStyles(key, hash) {
var prevHash = cellCssClasses[key]
cellCssClasses[key] = hash
updateCellCssStylesOnRenderedRows(hash, prevHash)
trigger(self.onCellCssStylesChanged, {key: key, hash: hash})
}
function getCellCssStyles(key) {
return cellCssClasses[key]
}
function flashCell(row, cell, speed) {
speed = speed || 100
if (rowsCache[row]) {
var $cell = $(getCellNode(row, cell))
var toggleCellClass = function(times) {
if (!times) {
return
}
setTimeout(() => {
$cell.queue(() => {
$cell.toggleClass(options.cellFlashingCssClass).dequeue()
toggleCellClass(times - 1)
})
}, speed)
}
toggleCellClass(4)
}
}
// ////////////////////////////////////////////////////////////////////////////////////////////
// Interactivity
function handleDragInit(e, dd) {
var cell = getCellFromEvent(e)
if (!cell || !cellExists(cell.row, cell.cell)) {
return false
}
var retval = trigger(self.onDragInit, dd, e)
if (e.isImmediatePropagationStopped()) {
return retval
}
// if nobody claims to be handling drag'n'drop by stopping immediate propagation,
// cancel out of it
return false
}
function handleDragStart(e, dd) {
var cell = getCellFromEvent(e)
if (!cell || !cellExists(cell.row, cell.cell)) {
return false
}
var retval = trigger(self.onDragStart, dd, e)
if (e.isImmediatePropagationStopped()) {
return retval
}
return false
}
function handleDrag(e, dd) {
return trigger(self.onDrag, dd, e)
}
function handleDragEnd(e, dd) {
trigger(self.onDragEnd, dd, e)
}
function handleKeyDown(e) {
trigger(self.onKeyDown, {row: activeRow, cell: activeCell}, e)
// Canvas Hack: SlickGrid has unreasonable default behavior that is unavoidable without an early return here.
if (e.originalEvent.skipSlickGridDefaults) {
return
}
var handled = e.isImmediatePropagationStopped()
if (!handled) {
if (!e.shiftKey && !e.altKey && !e.ctrlKey) {
if (e.which == 27) {
if (!getEditorLock().isActive()) {
return // no editing mode to cancel, allow bubbling and default processing (exit without cancelling the event)
}
cancelEditAndSetFocus()
} else if (e.which == 34) {
navigatePageDown()
handled = true
} else if (e.which == 33) {
navigatePageUp()
handled = true
} else if (e.which == 37) {
handled = navigateLeft()
} else if (e.which == 39) {
handled = navigateRight()
} else if (e.which == 38) {
handled = navigateUp()
} else if (e.which == 40) {
handled = navigateDown()
} else if (e.which == 9) {
handled = navigateNext()
} else if (e.which == 13) {
if (options.editable) {
if (currentEditor) {
// adding new row
if (activeRow === getDataLength()) {
navigateDown()
} else {
commitEditAndSetFocus()
}
} else if (getEditorLock().commitCurrentEdit()) {
makeActiveCellEditable()
}
}
handled = true
}
} else if (e.which == 9 && e.shiftKey && !e.ctrlKey && !e.altKey) {
handled = navigatePrev()
}
}
if (handled) {
// the event has been handled so don't let parent element (bubbling/propagation) or browser (default) handle it
e.stopPropagation()
e.preventDefault()
try {
e.originalEvent.keyCode = 0 // prevent default behaviour for special keys in IE browsers (F3, F5, etc.)
} catch (error) {
// ignore exceptions - setting the original event's keycode throws access denied exception for "Ctrl"
// (hitting control key only, nothing else), "Shift" (maybe others)
}
}
}
function handleClick(e) {
if (!currentEditor) {
// if this click resulted in some cell child node getting focus,
// don't steal it back - keyboard events will still bubble up
// IE9+ seems to default DIVs to tabIndex=0 instead of -1, so check for cell clicks directly.
if (e.target != document.activeElement || $(e.target).hasClass('slick-cell')) {
setFocus()
}
}
var cell = getCellFromEvent(e)
if (!cell || (currentEditor !== null && activeRow == cell.row && activeCell == cell.cell)) {
return
}
trigger(self.onClick, {row: cell.row, cell: cell.cell}, e)
if (e.isImmediatePropagationStopped()) {
return
}
if (
(activeCell != cell.cell || activeRow != cell.row) &&
canCellBeActive(cell.row, cell.cell)
) {
if (!getEditorLock().isActive() || getEditorLock().commitCurrentEdit()) {
scrollRowIntoView(cell.row, false)
// Always switch to edit mode if possible in response to a click
setActiveCellInternal(getCellNode(cell.row, cell.cell), true)
}
}
}
function handleContextMenu(e) {
var $cell = $(e.target).closest('.slick-cell', $canvas_1)
if ($cell.length === 0) {
return
}
// are we editing this cell?
if (activeCellNode === $cell[0] && currentEditor !== null) {
return
}
trigger(self.onContextMenu, {}, e)
}
function handleDblClick(e) {
var cell = getCellFromEvent(e)
if (!cell || (currentEditor !== null && activeRow == cell.row && activeCell == cell.cell)) {
return
}
trigger(self.onDblClick, {row: cell.row, cell: cell.cell}, e)
if (e.isImmediatePropagationStopped()) {
return
}
if (options.editable) {
gotoCell(cell.row, cell.cell, true)
}
}
function handleHeaderMouseEnter(e) {
trigger(
self.onHeaderMouseEnter,
{
column: $(this).data('column')
},
e
)
}
function handleHeaderMouseLeave(e) {
trigger(
self.onHeaderMouseLeave,
{
column: $(this).data('column')
},
e
)
}
function handleHeaderContextMenu(e) {
var $header = $(e.target).closest('.slick-header-column', '.slick-header-columns')
var column = $header && $header.data('column')
trigger(self.onHeaderContextMenu, {column: column}, e)
}
function handleHeaderClick(e) {
var $header = $(e.target).closest('.slick-header-column', '.slick-header-columns')
var column = $header && $header.data('column')
if (column) {
trigger(self.onHeaderClick, {column: column}, e)
}
}
function handleMouseEnter(e) {
trigger(self.onMouseEnter, {}, e)
}
function handleMouseLeave(e) {
trigger(self.onMouseLeave, {}, e)
}
function cellExists(row, cell) {
return !(row < 0 || row >= getDataLength() || cell < 0 || cell >= columns.length)
}
function getCellFromPoint(x, y) {
var row = getRowFromPosition(y)
var cell = 0
var w = 0
for (var i = 0; i < columns.length && w < x; i++) {
w += columns[i].width
cell++
}
if (cell < 0) {
cell = 0
}
return {row: row, cell: cell - 1}
}
function getCellFromNode(cellNode) {
// read column number from .b<columnNumber> CSS class
var cls = /b\d+/.exec(cellNode.className)
if (!cls) {
throw 'getCellFromNode: cannot get cell - ' + cellNode.className
}
return parseInt(cls[0].substr(1, cls[0].length - 1), 10)
}
function getRowFromNode(rowNode) {
for (var row in rowsCache) {
var rowItem = rowsCache[row].rowNode
if (rowItem.frozen === rowNode || rowItem.nonFrozen === rowNode) {
return row | 0
}
}
return null
}
function getCanvasFromEvent(e) {
return jQuery(e.target).closest('.grid-canvas')
}
function getCellFromEvent(e) {
var closestCanvas = getCanvasFromEvent(e)
var $cell = $(e.target).closest('.slick-cell', closestCanvas)
if (!$cell.length) {
return null
}
var row = getRowFromNode($cell[0].parentNode)
var cell = getCellFromNode($cell[0])
if (row == null || cell == null) {
return null
} else {
return {
row: row,
cell: cell
}
}
}
function getCellNodeBox(row, cell) {
if (!cellExists(row, cell)) {
return null
}
var y1 = getRowTop(row)
var y2 = y1 + options.rowHeight - 1
var x1 = 0
for (var i = 0; i < cell; i++) {
x1 += columns[i].width
}
var x2 = x1 + columns[cell].width
return {
top: y1,
[rear]: x1,
bottom: y2,
[front]: x2
}
}
// ////////////////////////////////////////////////////////////////////////////////////////////
// Cell switching
function resetActiveCell() {
setActiveCellInternal(null, false)
}
function setFocus() {
if (tabbingDirection == -1) {
$focusSink[0].focus()
} else {
$focusSink2[0].focus()
}
}
function scrollCellIntoView(row, cell, doPaging) {
scrollRowIntoView(row, doPaging)
var colspan = getColspan(row, cell)
var rearVal = columnPosRear[cell],
frontVal = columnPosFront[cell + (colspan > 1 ? colspan - 1 : 0)],
scrollRight = scrollLeft + viewportW_1
if ((rtl ? adjustXToRight(frontVal) : rearVal) < scrollLeft) {
// cell is to left of displayed cells
setScrollLeft($viewport_1[0], rtl ? Math.max(0, adjustXToRight(frontVal)) : rearVal)
handleScroll()
render()
} else if ((rtl ? adjustXToRight(rearVal) : frontVal) > scrollRight) {
// cell is to right of displayed cells
setScrollLeft(
$viewport_1[0],
rtl ? adjustXToRight(frontVal) : Math.min(rearVal, frontVal - $viewport_1[0].clientWidth)
)
handleScroll()
render()
}
}
function setActiveCellInternal(newCell, opt_editMode) {
if (activeCellNode !== null) {
makeActiveCellNormal()
$(activeCellNode).removeClass('active')
if (rowsCache[activeRow]) {
$(rowsCache[activeRow].rowNode.frozen).removeClass('active')
$(rowsCache[activeRow].rowNode.nonFrozen).removeClass('active')
}
}
var activeCellChanged = activeCellNode !== newCell
activeCellNode = newCell
if (activeCellNode != null) {
activeRow = getRowFromNode(activeCellNode.parentNode)
activeCell = activePosX = getCellFromNode(activeCellNode)
if (opt_editMode == null) {
opt_editMode = (activeRow == getDataLength() || options.autoEdit) && !isCustomColumn(activeCell)
}
$(activeCellNode).addClass('active')
$(rowsCache[activeRow].rowNode.frozen).addClass('active')
$(rowsCache[activeRow].rowNode.nonFrozen).addClass('active')
if (options.editable && opt_editMode && isCellPotentiallyEditable(activeRow, activeCell)) {
clearTimeout(h_editorLoader)
if (options.asyncEditorLoading) {
h_editorLoader = setTimeout(() => {
makeActiveCellEditable()
}, options.asyncEditorLoadDelay)
} else {
makeActiveCellEditable()
}
}
} else {
activeRow = activeCell = null
}
if (activeCellChanged) {
trigger(self.onActiveCellChanged, getActiveCell())
}
}
function clearTextSelection() {
if (document.selection && document.selection.empty) {
try {
// IE fails here if selected element is not in dom
document.selection.empty()
} catch (e) {}
} else if (window.getSelection) {
var sel = window.getSelection()
if (sel && sel.removeAllRanges) {
sel.removeAllRanges()
}
}
}
function isCellPotentiallyEditable(row, cell) {
// is the data for this row loaded?
if (row < getDataLength() && !getDataItem(row)) {
return false
}
// are we in the Add New row? can we create new from this cell?
if (columns[cell].cannotTriggerInsert && row >= getDataLength()) {
return false
}
// does this cell have an editor?
if (!getEditor(row, cell)) {
return false
}
return true
}
function makeActiveCellNormal() {
if (!currentEditor) {
return
}
trigger(self.onBeforeCellEditorDestroy, {editor: currentEditor})
currentEditor.destroy()
currentEditor = null
if (activeCellNode) {
var d = getDataItem(activeRow)
$(activeCellNode).removeClass('editable invalid')
if (d) {
var column = columns[activeCell]
var formatter = getFormatter(activeRow, column)
activeCellNode.innerHTML = formatter(
activeRow,
activeCell,
getDataItemValueForColumn(d, column),
column,
d
)
invalidatePostProcessingResults(activeRow)
}
}
// if there previously was text selected on a page (such as selected text in the edit cell just removed),
// IE can't set focus to anything else correctly
if (navigator.userAgent.toLowerCase().match(/msie/)) {
clearTextSelection()
}
getEditorLock().deactivate(editController)
}
function makeActiveCellEditable(editor) {
if (!activeCellNode) {
return
}
if (!options.editable) {
throw 'Grid : makeActiveCellEditable : should never get called when options.editable is false'
}
// cancel pending async call if there is one
clearTimeout(h_editorLoader)
if (!isCellPotentiallyEditable(activeRow, activeCell)) {
return
}
var columnDef = columns[activeCell]
var item = getDataItem(activeRow)
if (
trigger(self.onBeforeEditCell, {
row: activeRow,
cell: activeCell,
item: item,
column: columnDef
}) === false
) {
setFocus()
return
}
getEditorLock().activate(editController)
$(activeCellNode).addClass('editable')
// don't clear the cell if a custom editor is passed through
if (!editor) {
activeCellNode.innerHTML = ''
}
currentEditor = new (editor || getEditor(activeRow, activeCell))({
grid: self,
gridPosition: absBox($outerContainer[0]),
position: absBox(activeCellNode),
container: activeCellNode,
column: columnDef,
item: item || {},
commitChanges: commitEditAndSetFocus,
cancelChanges: cancelEditAndSetFocus,
maxLength: columns[activeCell].maxLength
})
if (item) {
currentEditor.loadValue(item)
}
serializedEditorValue = currentEditor.serializeValue()
if (currentEditor.position) {
handleActiveCellPositionChange()
}
}
function commitEditAndSetFocus() {
// if the commit fails, it would do so due to a validation error
// if so, do not steal the focus from the editor
if (getEditorLock().commitCurrentEdit()) {
setFocus()
if (options.autoEdit && !isCustomColumn(activeCell)) {
navigateDown()
}
}
}
function cancelEditAndSetFocus() {
if (getEditorLock().cancelCurrentEdit()) {
setFocus()
}
}
function adjustXToRight(x) {
return canvasWidth_1 - x
}
function getScrollLeft(elm) {
return elm.scrollLeft
}
function setScrollLeft(elm, val) {
elm.scrollLeft = val
}
function getOffsetLeft(elm) {
return elm.offsetLeft
}
function getOffsetRight(elm) {
return document.body.offsetWidth - (elm.offsetLeft + elm.offsetWidth)
}
getOffsetRear = getOffsetLeft
function absBox(elem) {
var box = {
top: elem.offsetTop,
bottom: 0,
width: $(elem).outerWidth(),
height: $(elem).outerHeight(),
visible: true
}
box.bottom = box.top + box.height
box[rear] = getOffsetRear(elem)
box[front] = box[rear] + box.width
// walk up the tree
var offsetParent = elem.offsetParent
while ((elem = elem.parentNode) != document.body) {
if (
box.visible &&
elem.scrollHeight != elem.offsetHeight &&
$(elem).css('overflowY') != 'visible'
) {
box.visible = box.bottom > elem.scrollTop && box.top < elem.scrollTop + elem.clientHeight
}
if (
box.visible &&
elem.scrollWidth != elem.offsetWidth &&
$(elem).css('overflowX') != 'visible'
) {
box.visible =
box[front] > elem.scrollLeft && box[rear] < elem.scrollLeft + elem.clientWidth
}
box[rear] -= elem.scrollLeft
box.top -= elem.scrollTop
if (elem === offsetParent) {
box[rear] += elem.scrollLeft
box.top += elem.offsetTop
offsetParent = elem.offsetParent
}
box.bottom = box.top + box.height
box[front] = box[rear] + box.width
}
return box
}
function getActiveCellPosition() {
return absBox(activeCellNode)
}
function getGridPosition() {
return absBox($outerContainer[0])
}
function handleActiveCellPositionChange() {
if (!activeCellNode) {
return
}
trigger(self.onActiveCellPositionChanged, {})
if (currentEditor) {
var cellBox = getActiveCellPosition()
if (currentEditor.show && currentEditor.hide) {
if (!cellBox.visible) {
currentEditor.hide()
} else {
currentEditor.show()
}
}
if (currentEditor.position) {
currentEditor.position(cellBox)
}
}
}
function getCellEditor() {
return currentEditor
}
function getActiveCell() {
if (!activeCellNode) {
return null
} else {
return {row: activeRow, cell: activeCell}
}
}
function getActiveCellNode() {
return activeCellNode
}
function scrollRowIntoView(row, doPaging) {
var rowAtTop = row * options.rowHeight
var rowAtBottom =
(row + 1) * options.rowHeight -
viewportH_1 +
(viewportHasHScroll_1 ? scrollbarDimensions.height : 0)
// need to page down?
if ((row + 1) * options.rowHeight > scrollTop + viewportH_1 + offset) {
scrollTo(doPaging ? rowAtTop : rowAtBottom)
render()
}
// or page up?
else if (row * options.rowHeight < scrollTop + offset) {
scrollTo(doPaging ? rowAtBottom : rowAtTop)
render()
}
}
function scrollRowToTop(row) {
scrollTo(row * options.rowHeight)
render()
}
function scrollPage(dir) {
var deltaRows = dir * numVisibleRows
scrollTo((getRowFromPosition(scrollTop) + deltaRows) * options.rowHeight)
render()
if (options.enableCellNavigation && activeRow != null) {
var row = activeRow + deltaRows
if (row >= getDataLengthIncludingAddNew()) {
row = getDataLengthIncludingAddNew() - 1
}
if (row < 0) {
row = 0
}
var cell = 0,
prevCell = null
var prevActivePosX = activePosX
while (cell <= activePosX) {
if (canCellBeActive(row, cell)) {
prevCell = cell
}
cell += getColspan(row, cell)
}
if (prevCell !== null) {
setActiveCellInternal(getCellNode(row, prevCell))
activePosX = prevActivePosX
} else {
resetActiveCell()
}
}
}
function navigatePageDown() {
scrollPage(1)
}
function navigatePageUp() {
scrollPage(-1)
}
function getColspan(row, cell) {
var metadata = data.getItemMetadata && data.getItemMetadata(row)
if (!metadata || !metadata.columns) {
return 1
}
var columnData = metadata.columns[columns[cell].id] || metadata.columns[cell]
var colspan = columnData && columnData.colspan
if (colspan === '*') {
colspan = columns.length - cell
} else {
colspan = colspan || 1
}
return colspan
}
function findFirstFocusableCell(row) {
var cell = 0
while (cell < columns.length) {
if (canCellBeActive(row, cell)) {
return cell
}
cell += getColspan(row, cell)
}
return null
}
function findLastFocusableCell(row) {
var cell = 0
var lastFocusableCell = null
while (cell < columns.length) {
if (canCellBeActive(row, cell)) {
lastFocusableCell = cell
}
cell += getColspan(row, cell)
}
return lastFocusableCell
}
function gotoFront(row, cell, _posX) {
if (cell >= columns.length) {
return null
}
do {
cell += getColspan(row, cell)
} while (cell < columns.length && !canCellBeActive(row, cell))
if (cell < columns.length) {
return {
row: row,
cell: cell,
posX: cell
}
}
return null
}
function gotoRear(row, cell, _posX) {
if (cell <= 0) {
return null
}
var firstFocusableCell = findFirstFocusableCell(row)
if (firstFocusableCell === null || firstFocusableCell >= cell) {
return null
}
var prev = {
row: row,
cell: firstFocusableCell,
posX: firstFocusableCell
}
var pos
while (true) {
pos = gotoFront(prev.row, prev.cell, prev.posX)
if (!pos) {
return null
}
if (pos.cell >= cell) {
return prev
}
prev = pos
}
}
gotoRight = gotoFront
gotoLeft = gotoRear
function gotoDown(row, cell, posX) {
var prevCell
while (true) {
if (++row >= getDataLengthIncludingAddNew()) {
return null
}
prevCell = cell = 0
while (cell <= posX) {
prevCell = cell
cell += getColspan(row, cell)
}
if (canCellBeActive(row, prevCell)) {
return {
row: row,
cell: prevCell,
posX: posX
}
}
}
}
function gotoUp(row, cell, posX) {
var prevCell
while (true) {
if (--row < 0) {
return null
}
prevCell = cell = 0
while (cell <= posX) {
prevCell = cell
cell += getColspan(row, cell)
}
if (canCellBeActive(row, prevCell)) {
return {
row: row,
cell: prevCell,
posX: posX
}
}
}
}
function gotoNext(row, cell, posX) {
if (row == null && cell == null) {
row = cell = posX = 0
if (canCellBeActive(row, cell)) {
return {
row: row,
cell: cell,
posX: cell
}
}
}
var pos = gotoFront(row, cell, posX)
if (pos) {
return pos
}
var firstFocusableCell = null
while (++row < getDataLengthIncludingAddNew()) {
firstFocusableCell = findFirstFocusableCell(row)
if (firstFocusableCell !== null) {
return {
row: row,
cell: firstFocusableCell,
posX: firstFocusableCell
}
}
}
return null
}
function gotoPrev(row, cell, posX) {
if (row == null && cell == null) {
row = getDataLengthIncludingAddNew() - 1
cell = posX = columns.length - 1
if (canCellBeActive(row, cell)) {
return {
row: row,
cell: cell,
posX: cell
}
}
}
var pos
var lastSelectableCell
while (!pos) {
pos = gotoRear(row, cell, posX)
if (pos) {
break
}
if (--row < 0) {
return null
}
cell = 0
lastSelectableCell = findLastFocusableCell(row)
if (lastSelectableCell !== null) {
pos = {
row: row,
cell: lastSelectableCell,
posX: lastSelectableCell
}
}
}
return pos
}
function navigateRight() {
return navigate('right')
}
function navigateLeft() {
return navigate('left')
}
function navigateDown() {
return navigate('down')
}
function navigateUp() {
return navigate('up')
}
function navigateNext() {
return navigate('next')
}
function navigatePrev() {
return navigate('prev')
}
/**
* @param {string} dir Navigation direction.
* @return {boolean} Whether navigation resulted in a change of active cell.
*/
function navigate(dir) {
if (!options.enableCellNavigation) {
return false
}
if (!activeCellNode && dir != 'prev' && dir != 'next') {
return false
}
if (!getEditorLock().commitCurrentEdit()) {
return true
}
setFocus()
var tabbingDirections = {
up: -1,
down: 1,
left: -1,
right: 1,
prev: -1,
next: 1
}
tabbingDirection = tabbingDirections[dir]
var stepFunctions = {
up: gotoUp,
down: gotoDown,
left: gotoLeft,
right: gotoRight,
prev: gotoPrev,
next: gotoNext
}
var stepFn = stepFunctions[dir]
var pos = stepFn(activeRow, activeCell, activePosX)
if (pos) {
var isAddNewRow = pos.row == getDataLength()
scrollCellIntoView(pos.row, pos.cell, !isAddNewRow)
setActiveCellInternal(getCellNode(pos.row, pos.cell))
activePosX = pos.posX
return true
} else if (activeRow === getDataLength() - 1) {
if (activeCell === columns.length - 1) {
// When focus is on the last cell in a row
// and the last cell in a column.
// Move focus outside of SlickGrid.
return false
}
// Otherwise, when focus is on the last cell in a column.
// Move focus to the first cell of the next column.
setActiveCell(0, activeCell + 1)
return true
}
}
function getCellNode(row, cell) {
if (rowsCache[row]) {
ensureCellNodesInRowsCache(row)
return rowsCache[row].cellNodesByColumnIdx[cell]
}
return null
}
function setActiveCell(row, cell) {
if (!initialized) {
return
}
if (row > getDataLength() || row < 0 || cell >= columns.length || cell < 0) {
return
}
if (!options.enableCellNavigation) {
return
}
scrollCellIntoView(row, cell, false)
setActiveCellInternal(getCellNode(row, cell), false)
}
function canCellBeActive(row, cell) {
if (
!options.enableCellNavigation ||
row >= getDataLengthIncludingAddNew() ||
row < 0 ||
cell >= columns.length ||
cell < 0
) {
return false
}
var rowMetadata = data.getItemMetadata && data.getItemMetadata(row)
if (rowMetadata && typeof rowMetadata.focusable === 'boolean') {
return rowMetadata.focusable
}
var columnMetadata = rowMetadata && rowMetadata.columns
if (
columnMetadata &&
columnMetadata[columns[cell].id] &&
typeof columnMetadata[columns[cell].id].focusable === 'boolean'
) {
return columnMetadata[columns[cell].id].focusable
}
if (
columnMetadata &&
columnMetadata[cell] &&
typeof columnMetadata[cell].focusable === 'boolean'
) {
return columnMetadata[cell].focusable
}
return columns[cell].focusable
}
function canCellBeSelected(row, cell) {
if (row >= getDataLength() || row < 0 || cell >= columns.length || cell < 0) {
return false
}
var rowMetadata = data.getItemMetadata && data.getItemMetadata(row)
if (rowMetadata && typeof rowMetadata.selectable === 'boolean') {
return rowMetadata.selectable
}
var columnMetadata =
rowMetadata &&
rowMetadata.columns &&
(rowMetadata.columns[columns[cell].id] || rowMetadata.columns[cell])
if (columnMetadata && typeof columnMetadata.selectable === 'boolean') {
return columnMetadata.selectable
}
return columns[cell].selectable
}
function gotoCell(row, cell, forceEdit) {
if (!initialized) {
return
}
if (!canCellBeActive(row, cell)) {
return
}
if (!getEditorLock().commitCurrentEdit()) {
return
}
scrollCellIntoView(row, cell, false)
var newCell = getCellNode(row, cell)
// Custom columns should not auto-edit when accessed via keyboard navigation
const autoEditDestinationCell = options.autoEdit && !isCustomColumn(cell)
// if selecting the 'add new' row, start editing right away
setActiveCellInternal(newCell, forceEdit || row === getDataLength() || autoEditDestinationCell)
// if no editor was created, set the focus back on the grid
if (!currentEditor) {
setFocus()
}
}
// ////////////////////////////////////////////////////////////////////////////////////////////
// IEditor implementation for the editor lock
function commitCurrentEdit() {
var item = getDataItem(activeRow)
var column = columns[activeCell]
if (currentEditor) {
if (currentEditor.isValueChanged()) {
var validationResults = currentEditor.validate()
if (validationResults.valid) {
if (activeRow < getDataLength()) {
var editCommand = {
row: activeRow,
cell: activeCell,
editor: currentEditor,
serializedValue: currentEditor.serializeValue(),
prevSerializedValue: serializedEditorValue,
execute: function() {
this.editor.applyValue(item, this.serializedValue)
updateRow(this.row)
},
undo: function() {
this.editor.applyValue(item, this.prevSerializedValue)
updateRow(this.row)
}
}
if (options.editCommandHandler) {
makeActiveCellNormal()
options.editCommandHandler(item, column, editCommand)
} else {
editCommand.execute()
makeActiveCellNormal()
}
trigger(self.onCellChange, {
row: activeRow,
cell: activeCell,
column: column,
item: item
})
} else {
var newItem = {}
currentEditor.applyValue(newItem, currentEditor.serializeValue())
makeActiveCellNormal()
trigger(self.onAddNewRow, {item: newItem, column: column})
}
// check whether the lock has been re-acquired by event handlers
return !getEditorLock().isActive()
} else {
// Re-add the CSS class to trigger transitions, if any.
$(activeCellNode).removeClass('invalid')
$(activeCellNode).width() // force layout
$(activeCellNode).addClass('invalid')
trigger(self.onValidationError, {
editor: currentEditor,
cellNode: activeCellNode,
validationResults: validationResults,
row: activeRow,
cell: activeCell,
column: column
})
currentEditor.focus()
return false
}
}
makeActiveCellNormal()
}
return true
}
function cancelCurrentEdit() {
makeActiveCellNormal()
return true
}
function rowsToRanges(rows) {
var ranges = []
var lastCell = columns.length - 1
for (var i = 0; i < rows.length; i++) {
ranges.push(new Slick.Range(rows[i], 0, rows[i], lastCell))
}
return ranges
}
function getSelectedRows() {
if (!selectionModel) {
throw 'Selection model is not set'
}
return selectedRows
}
function setSelectedRows(rows) {
if (!selectionModel) {
throw 'Selection model is not set'
}
selectionModel.setSelectedRanges(rowsToRanges(rows))
}
function isCustomColumn(cell) {
return columns[cell]?.type === 'custom_column'
}
// ////////////////////////////////////////////////////////////////////////////////////////////
// Debug
this.debug = function() {
var s = ''
s += '\n' + 'counter_rows_rendered: ' + counter_rows_rendered
s += '\n' + 'counter_rows_removed: ' + counter_rows_removed
s += '\n' + 'renderedRows: ' + renderedRows
s += '\n' + 'numVisibleRows: ' + numVisibleRows
s += '\n' + 'maxSupportedCssHeight: ' + maxSupportedCssHeight
s += '\n' + 'n(umber of pages): ' + n
s += '\n' + '(current) page: ' + page
s += '\n' + 'page height (ph): ' + ph
s += '\n' + 'vScrollDir: ' + vScrollDir
alert(s)
}
// a debug helper to be able to access private members
this.eval = function(expr) {
return eval(expr)
}
// ////////////////////////////////////////////////////////////////////////////////////////////
// Public API
$.extend(this, {
slickGridVersion: '2.1',
// Events
onScroll: new Slick.Event(),
onSort: new Slick.Event(),
onHeaderMouseEnter: new Slick.Event(),
onHeaderMouseLeave: new Slick.Event(),
onHeaderContextMenu: new Slick.Event(),
onHeaderClick: new Slick.Event(),
onHeaderCellRendered: new Slick.Event(),
onBeforeHeaderCellDestroy: new Slick.Event(),
onHeaderRowCellRendered: new Slick.Event(),
onBeforeHeaderRowCellDestroy: new Slick.Event(),
onMouseEnter: new Slick.Event(),
onMouseLeave: new Slick.Event(),
onClick: new Slick.Event(),
onDblClick: new Slick.Event(),
onContextMenu: new Slick.Event(),
onKeyDown: new Slick.Event(),
onAddNewRow: new Slick.Event(),
onValidationError: new Slick.Event(),
onViewportChanged: new Slick.Event(),
onColumnsReordered: new Slick.Event(),
onColumnsResized: new Slick.Event(),
onCellChange: new Slick.Event(),
onBeforeEditCell: new Slick.Event(),
onBeforeCellEditorDestroy: new Slick.Event(),
onBeforeDestroy: new Slick.Event(),
onActiveCellChanged: new Slick.Event(),
onActiveCellPositionChanged: new Slick.Event(),
onDragInit: new Slick.Event(),
onDragStart: new Slick.Event(),
onDrag: new Slick.Event(),
onDragEnd: new Slick.Event(),
onSelectedRowsChanged: new Slick.Event(),
onCellCssStylesChanged: new Slick.Event(),
// Methods
registerPlugin: registerPlugin,
unregisterPlugin: unregisterPlugin,
getColumns: getColumns,
setColumns: setColumns,
getColumnIndex: getColumnIndex,
updateColumnHeader: updateColumnHeader,
setNumberOfColumnsToFreeze: setNumberOfColumnsToFreeze,
setSortColumn: setSortColumn,
setSortColumns: setSortColumns,
getSortColumns: getSortColumns,
autosizeColumns: autosizeColumns,
getOptions: getOptions,
setOptions: setOptions,
getData: getData,
getDataLength: getDataLength,
getDataItem: getDataItem,
setData: setData,
getSelectionModel: getSelectionModel,
setSelectionModel: setSelectionModel,
getSelectedRows: getSelectedRows,
setSelectedRows: setSelectedRows,
getContainerNode: getContainerNode,
render: render,
invalidate: invalidate,
invalidateRow: invalidateRow,
invalidateRows: invalidateRows,
invalidateAllRows: invalidateAllRows,
updateCell: updateCell,
updateRow: updateRow,
getViewport: getVisibleRange,
getRenderedRange: getRenderedRange,
resizeCanvas: resizeCanvas,
updateRowCount: updateRowCount,
scrollRowIntoView: scrollRowIntoView,
scrollRowToTop: scrollRowToTop,
scrollCellIntoView: scrollCellIntoView,
getCanvasNode: getCanvasNode,
focus: setFocus,
getCellFromPoint: getCellFromPoint,
getCellFromEvent: getCellFromEvent,
getActiveCell: getActiveCell,
setActiveCell: setActiveCell,
getActiveCellNode: getActiveCellNode,
getActiveCellPosition: getActiveCellPosition,
resetActiveCell: resetActiveCell,
editActiveCell: makeActiveCellEditable,
getCellEditor: getCellEditor,
getCellNode: getCellNode,
getCellNodeBox: getCellNodeBox,
canCellBeSelected: canCellBeSelected,
canCellBeActive: canCellBeActive,
navigatePrev: navigatePrev,
navigateNext: navigateNext,
navigateUp: navigateUp,
navigateDown: navigateDown,
navigateLeft: navigateLeft,
navigateRight: navigateRight,
navigatePageUp: navigatePageUp,
navigatePageDown: navigatePageDown,
gotoCell: gotoCell,
getTopPanel: getTopPanel,
setTopPanelVisibility: setTopPanelVisibility,
setHeaderRowVisibility: setHeaderRowVisibility,
getHeaderRow: getHeaderRow,
getHeaderRowColumn: getHeaderRowColumn,
getColumnHeaderNode: getColumnHeaderNode,
getGridPosition: getGridPosition,
flashCell: flashCell,
addCellCssStyles: addCellCssStyles,
setCellCssStyles: setCellCssStyles,
removeCellCssStyles: removeCellCssStyles,
getCellCssStyles: getCellCssStyles,
getUID: getUID,
init: finishInitialization,
destroy: destroy,
// IEditor implementation
getEditorLock: getEditorLock,
getEditController: getEditController
})
init()
}
})(jQuery)