@progress/kendo-ui
Version:
This package is part of the [Kendo UI for jQuery](http://www.telerik.com/kendo-ui) suite.
3,008 lines • 106 kB
JavaScript
import { t as valueMapperOptions } from "./valueMapper-D6U5gUYU.js";
//#region ../src/kendo.virtuallist.js
const __meta__ = {
id: "virtuallist",
name: "VirtualList",
category: "framework",
depends: ["data"],
hidden: true
};
(function($, undefined) {
const kendo = window.kendo, ui = kendo.ui, encode = kendo.htmlEncode, Widget = ui.Widget, DataBoundWidget = ui.DataBoundWidget, percentageUnitsRegex = /^\d+(\.\d+)?%$/i, LIST_CONTENT = "k-list-content", TABLE_CONTENT = "k-table-body k-table-scroller", HEADER = "k-list-group-sticky-header", TABLE_HEADER = "k-table-group-sticky-header", LIST_ITEM = "k-list-item", TABLE_ITEM = "k-table-row", HEIGHTCONTAINER = "k-height-container", GROUPITEM = "k-list-item-group-label", GROUP_HEADER_ITEM = "k-list-group-item", LIST_UL = "k-list-ul", TABLE = "k-table", SELECTED = "k-selected", FOCUSED = "k-focus", HOVER = "k-hover", CHANGE = "change", CLICK = "click", LISTBOUND = "listBound", ITEMCHANGE = "itemChange", ACTION = "action", ACTIVATE = "activate", DEACTIVATE = "deactivate", GROUP_ROW_SEL = ".k-table-group-row", VIRTUAL_LIST_NS = ".VirtualList";
function lastFrom(array) {
return array[array.length - 1];
}
function toArray(value) {
return value instanceof Array ? value : [value];
}
function isPrimitive(dataItem) {
return typeof dataItem === "string" || typeof dataItem === "number" || typeof dataItem === "boolean";
}
function getItemCount(screenHeight, listScreens, itemHeight) {
return Math.ceil(screenHeight * listScreens / itemHeight);
}
function appendChild(parent, className, tagName) {
const element = document.createElement(tagName || "div");
if (className) {
element.className = className;
}
parent.appendChild(element);
return element;
}
function getDefaultItemHeight(listSize) {
const mockList = $("<div class=\"k-list " + listSize + " k-virtual-list\">" + "<div class=\"k-list-content\">" + "<ul class=\"k-list-ul\">" + "<li class=\"k-list-item\">" + "<span class=\"k-list-item-text\">test</span>" + "</li>" + "</ul>" + "</div>" + "</div>");
mockList.css({
position: "absolute",
left: "-200000px",
visibility: "hidden"
});
mockList.appendTo(document.body);
const itemHeight = parseFloat(kendo.getComputedStyles(mockList.find(".k-list-item")[0], ["height"]).height);
mockList.remove();
return itemHeight;
}
/**
* Gets the CSS gap value from an existing UL element in the DOM,
* or creates a temporary one to measure if no UL exists.
* @param {jQuery|HTMLElement} containerElement - The container element in the DOM (for creating temp UL)
* @param {jQuery} existingUl - An existing UL element in the DOM (optional)
* @returns {number} - The gap value in pixels (0 if not found)
*/
function getCssGap(containerElement, existingUl) {
if (existingUl && existingUl.length) {
let computedStyle = window.getComputedStyle(existingUl[0]);
let gap = computedStyle.gap || computedStyle.rowGap || "0";
return parseFloat(gap) || 0;
}
const container = containerElement instanceof $ ? containerElement[0] : containerElement;
if (!container || !container.parentNode) {
return 0;
}
const tempUl = document.createElement("ul");
tempUl.className = LIST_UL;
tempUl.style.cssText = "position:absolute;visibility:hidden;left:-9999px;";
container.appendChild(tempUl);
const computedStyle = window.getComputedStyle(tempUl);
const gap = computedStyle.gap || computedStyle.rowGap || "0";
const gapValue = parseFloat(gap) || 0;
container.removeChild(tempUl);
return gapValue;
}
function bufferSizes(screenHeight, listScreens, opposite) {
return {
down: screenHeight * opposite,
up: screenHeight * (listScreens - 1 - opposite)
};
}
function listValidator(options, screenHeight) {
const downThreshold = (options.listScreens - 1 - options.threshold) * screenHeight;
const upThreshold = options.threshold * screenHeight;
return function(list, scrollTop, lastScrollTop) {
if (scrollTop > lastScrollTop) {
return scrollTop - list.top < downThreshold;
} else {
return list.top === 0 || scrollTop - list.top > upThreshold;
}
};
}
function scrollCallback(element, callback) {
return function(force) {
return callback(element.scrollTop, force);
};
}
function syncList(reorder) {
return function(list, force) {
reorder(list.items, list.index, force);
return list;
};
}
function position(element, y) {
element.style.webkitTransform = "translateY(" + y + "px)";
element.style.transform = "translateY(" + y + "px)";
}
/**
* Calculates the absolute position (in pixels) of a flat index in a grouped list.
* Takes into account group headers that appear before each group and CSS gap.
* @param {number} flatIndex - The flat index of an item (0-based, counting only data items)
* @param {number} itemHeight - Height of each item in pixels
* @param {Array} groupRanges - Array of group range objects with startIndex and itemCount
* @param {number} cssGap - CSS gap between items in pixels (default 0)
* @returns {number} - The absolute pixel position
*/
function getGroupedItemPosition(flatIndex, itemHeight, groupRanges, cssGap) {
let position = 0;
cssGap = cssGap || 0;
const effectiveItemHeight = itemHeight + cssGap;
for (let i = 0; i < groupRanges.length; i++) {
const group = groupRanges[i];
const { startIndex, itemCount } = group;
if (flatIndex < startIndex + itemCount) {
if (i > 0) {
position += effectiveItemHeight;
}
position += (flatIndex - startIndex) * effectiveItemHeight;
break;
} else {
if (i > 0) {
position += effectiveItemHeight;
}
position += itemCount * effectiveItemHeight;
}
}
return position;
}
/**
* Calculates the total height of the grouped list including all group headers and CSS gaps.
* First group uses the sticky header (not inline), so we subtract one header height.
* @param {number} totalItems - Total number of data items
* @param {number} itemHeight - Height of each item
* @param {number} groupCount - Number of groups
* @param {number} cssGap - CSS gap between items in pixels (default 0)
* @returns {number} - Total height in pixels
*/
function getGroupedTotalHeight(totalItems, itemHeight, groupCount, cssGap) {
cssGap = cssGap || 0;
const inlineHeaders = Math.max(0, groupCount - 1);
const totalElements = totalItems + inlineHeaders;
const totalGaps = Math.max(0, totalElements - groupCount);
return totalElements * itemHeight + totalGaps * cssGap;
}
/**
* Builds the group ranges array from the dataSource's group info.
* For paged/server-side grouping, this builds ranges relative to the current range.
* @param {Object} dataSource - The Kendo DataSource
* @param {number} rangeStart - The start index of the current data range (for offset calculation)
* @param {number} firstGroupIndex - The global index of the first group in this view (0-based, used as fallback)
* @param {Map} groupValueMap - Optional map of groupValue -> globalGroupIndex for consistent indexing
* @returns {Array} - Array of { value, startIndex, itemCount, globalGroupIndex }
*/
function buildGroupRanges(dataSource, rangeStart, firstGroupIndex, groupValueMap) {
const groups = dataSource.group() || [];
if (!groups.length) {
return [];
}
const view = dataSource.view() || [];
if (!view.length) {
return [];
}
const ranges = [];
let currentIndex = rangeStart || 0;
let nextNewGroupIndex = 0;
if (groupValueMap && groupValueMap.size > 0) {
for (const idx of groupValueMap.values()) {
if (idx >= nextNewGroupIndex) {
nextNewGroupIndex = idx + 1;
}
}
}
for (const group of view) {
const groupItems = group.items || [];
if (!groupItems.length) {
continue;
}
let groupIndex;
if (groupValueMap) {
if (groupValueMap.has(group.value)) {
groupIndex = groupValueMap.get(group.value);
} else {
groupIndex = nextNewGroupIndex++;
groupValueMap.set(group.value, groupIndex);
}
} else {
groupIndex = (firstGroupIndex || 0) + ranges.length;
}
ranges.push({
value: group.value,
startIndex: currentIndex,
itemCount: groupItems.length,
globalGroupIndex: groupIndex
});
currentIndex += groupItems.length;
}
return ranges;
}
function map2(callback, templates) {
return function(arr1, arr2) {
for (let i = 0, len = arr1.length; i < len; i++) {
callback(arr1[i], arr2[i], templates);
if (arr2[i].item) {
this.trigger(ITEMCHANGE, {
item: $(arr1[i]),
data: arr2[i].item,
ns: kendo.ui
});
}
}
};
}
function reshift(items, diff) {
let range;
if (diff > 0) {
range = items.splice(0, diff);
items.push(...range);
} else {
range = items.splice(diff, -diff);
items.unshift(...range);
}
return range;
}
function render(element, data, templates) {
const that = this;
const options = that.options;
const itemTemplate = data.item ? templates.template : templates.placeholderTemplate;
const hasColumns = options.columns?.length;
const altRow = data.index % 2 === 1 ? "k-table-alt-row" : "";
const iconField = options.iconField;
const descriptionField = options.descriptionField;
const actionField = options.actionField;
const dataItem = data.item;
const hasIcon = iconField && dataItem && dataItem[iconField];
const hasDescription = descriptionField && dataItem && dataItem[descriptionField];
const hasAction = actionField && dataItem && dataItem[actionField];
element = $(element);
if (data.index === 0 && this.header && data.group) {
this.header.html(templates.fixedGroupTemplate(data.group));
}
element.attr("data-uid", dataItem ? dataItem.uid : "").attr("data-offset-index", data.index);
if (hasAction) {
element.attr("data-action", dataItem[actionField]);
element.addClass("k-list-item-action");
} else {
element.removeAttr("data-action");
element.removeClass("k-list-item-action");
}
if (hasColumns && dataItem) {
if (altRow.length > 0) {
element.addClass(altRow);
} else {
element.removeClass("k-table-alt-row");
}
const renderedColumns = $(renderColumns(options, dataItem, templates));
kendo.applyStylesFromKendoAttributes(renderedColumns, ["width", "max-width"]);
element.empty().append(renderedColumns);
} else {
element.find("." + GROUPITEM).remove();
const textContainer = element.find(".k-list-item-text");
textContainer.html(itemTemplate(dataItem || {}));
let iconWrapper = element.find(".k-list-item-icon-wrapper");
if (hasIcon) {
const iconHtml = kendo.ui.icon({
icon: dataItem[iconField],
iconClass: "k-list-item-icon",
attr: { "aria-hidden": "true" }
});
if (iconWrapper.length === 0) {
textContainer.before(`<span class="k-list-item-icon-wrapper" role="presentation">${iconHtml}</span>`);
} else {
iconWrapper.attr("role", "presentation").html(iconHtml);
}
} else {
iconWrapper.remove();
}
let descElement = element.find(".k-list-item-description");
if (hasDescription) {
if (descElement.length === 0) {
textContainer.after(`<span class="k-list-item-description">${encode(dataItem[descriptionField])}</span>`);
} else {
descElement.html(encode(dataItem[descriptionField]));
}
} else {
descElement.remove();
}
}
element.toggleClass(FOCUSED, data.current);
element.toggleClass(SELECTED, data.selected);
element.toggleClass("k-first", data.newGroup);
element.toggleClass("k-last", data.isLastGroupedItem);
element.toggleClass("k-loading-item", !dataItem);
if (data.index !== 0 && data.newGroup) {
if (hasColumns) {
$(`<span class="k-table-td k-table-group-td"><span>${templates.groupTemplate(data.group)}</span></span>`).appendTo(element);
} else {
$(`<div class="${GROUPITEM}"></div>`).appendTo(element).html(templates.groupTemplate(data.group));
}
} else if (data.group && hasColumns) {
element.append($("<span class=\"k-table-td k-table-spacer-td\"></span>"));
}
if (data.top !== undefined) {
element.css("top", data.top + "px");
}
}
function renderColumns(options, dataItem, templates) {
let item = "";
for (let i = 0; i < options.columns.length; i++) {
const currentWidth = options.columns[i].width;
const currentWidthInt = parseInt(currentWidth, 10);
let widthStyle = "";
if (currentWidth) {
const widthValue = `${currentWidthInt}${percentageUnitsRegex.test(currentWidth) ? "%" : "px"}`;
widthStyle = `${kendo.attr("style-width")}="${widthValue}" ${kendo.attr("style-max-width")}="${widthValue}"`;
}
item += `<span class='k-table-td' ${widthStyle}>`;
item += templates["column" + i](dataItem);
item += "</span>";
}
return item;
}
function mapChangedItems(selected, itemsToMatch) {
const itemsLength = itemsToMatch.length;
const selectedLength = selected.length;
const changed = [];
const unchanged = [];
if (selectedLength) {
for (let i = 0; i < selectedLength; i++) {
const dataItem = selected[i];
let found = false;
for (let j = 0; j < itemsLength; j++) {
if (dataItem === itemsToMatch[j]) {
found = true;
changed.push({
index: i,
item: dataItem
});
break;
}
}
if (!found) {
unchanged.push(dataItem);
}
}
}
return {
changed,
unchanged
};
}
function isActivePromise(promise) {
return promise && promise.state() !== "resolved";
}
const VirtualList = DataBoundWidget.extend({
init: function(element, options) {
const that = this;
const contentClasses = options.columns?.length ? TABLE_CONTENT : LIST_CONTENT;
that.bound(false);
that._fetching = false;
Widget.fn.init.call(that, element, options);
if (!that.options.itemHeight) {
that.options.itemHeight = getDefaultItemHeight(options.listSize);
}
that._cssGap = 0;
options = that.options;
that.element.attr("role", "listbox");
if (that.options.columns?.length) {
const contentSelector = "." + contentClasses.split(" ").join(".");
that.content = that.wrapper = that.element.find(contentSelector);
if (!that.content.length) {
that.content = that.wrapper = $(`<div unselectable='on' class='${contentClasses}'></div>`).appendTo(that.element);
}
const stickyHeader = $(`<div class="${that._getFixedGroupHeaderClass()}"><span class="k-table-th"></span></div>`);
that.content.before(stickyHeader);
that.header = stickyHeader.find(".k-table-th");
} else {
that.header = $(`<div class='${that._getFixedGroupHeaderClass()}'></div>`).appendTo(that.element);
that.content = that.wrapper = $(`<div unselectable='on' class='${contentClasses}'></div>`).appendTo(that.element);
}
that.element.children(".k-list-footer, .k-table-footer").appendTo(that.element);
if (options.ariaLabel) {
this.element.attr("aria-label", options.ariaLabel);
} else if (options.ariaLabelledBy) {
this.element.attr("aria-labelledby", options.ariaLabelledBy);
}
that.element.on("mouseenter" + VIRTUAL_LIST_NS, "li:not(.k-loading-item)", function() {
$(this).addClass(HOVER);
}).on("mouseleave" + VIRTUAL_LIST_NS, "li", function() {
$(this).removeClass(HOVER);
});
that._values = toArray(that.options.value);
that._selectedDataItems = [];
that._selectedIndexes = [];
that._rangesList = {};
that._promisesList = [];
that._optionID = kendo.guid();
that._templates();
that.setDataSource(options.dataSource);
that.content.on("scroll" + VIRTUAL_LIST_NS, kendo.throttle(function() {
that._renderItems();
that._triggerListBound();
}, options.delay));
that._selectable();
},
options: {
name: "VirtualList",
autoBind: true,
delay: 100,
height: null,
listScreens: 4,
threshold: .5,
itemHeight: null,
oppositeBuffer: 1,
type: "flat",
selectable: false,
value: [],
dataValueField: null,
template: (data) => encode(data),
placeholderTemplate: () => "loading...",
groupTemplate: (data) => encode(data),
fixedGroupTemplate: (data) => encode(data),
fixedGroupHeader: true,
mapValueTo: "index",
valueMapper: null,
ariaLabel: null,
ariaLabelledBy: null,
id: null,
iconField: null,
descriptionField: null,
groupIconField: null,
actionField: null
},
events: [
CHANGE,
CLICK,
LISTBOUND,
ITEMCHANGE,
ACTIVATE,
DEACTIVATE,
ACTION
],
_isTableVariant: function() {
return !!this.options.columns?.length;
},
_getItemClass: function() {
return this._isTableVariant() ? TABLE_ITEM : LIST_ITEM;
},
_getItemHeightStyle: function() {
return this.options.itemHeight + "px";
},
_getHeaderHeight: function() {
return this.header?.is(":visible") ? this.header.outerHeight() : 0;
},
_toggleHeaderVisibility: function(show) {
const target = this.header.closest(GROUP_ROW_SEL).length ? this.header.closest(GROUP_ROW_SEL) : this.header;
target[show ? "show" : "hide"]();
},
_getHeaderTextSelector: function() {
return this._isTableVariant() ? ".k-table-th" : ".k-list-item-text";
},
_getGroupHeaderClass: function() {
return this._isTableVariant() ? "k-table-group-row" : GROUP_HEADER_ITEM;
},
_getFixedGroupHeaderClass: function() {
return this._isTableVariant() ? TABLE_HEADER : HEADER;
},
setOptions: function(options) {
Widget.fn.setOptions.call(this, options);
if (this._selectProxy && this.options.selectable === false) {
this.element.off(CLICK, "." + this._getItemClass(), this._selectProxy);
} else if (!this._selectProxy && this.options.selectable) {
this._selectable();
}
this._templates();
this.refresh();
},
items: function() {
return $(this._items);
},
ulElements: function() {
const that = this;
if (that.options.type === "flat") {
return that.ul || $();
} else {
return that.content.find("." + LIST_UL);
}
},
destroy: function() {
this.wrapper.off(VIRTUAL_LIST_NS);
this.dataSource.unbind(CHANGE, this._refreshHandler);
Widget.fn.destroy.call(this);
},
setDataSource: function(source) {
const that = this;
let dataSource = source || {};
dataSource = Array.isArray(dataSource) ? { data: dataSource } : dataSource;
dataSource = kendo.data.DataSource.create(dataSource);
if (that.dataSource) {
that.dataSource.unbind(CHANGE, that._refreshHandler);
that._clean();
that.bound(false);
that._deferValueSet = true;
const value = that.value();
that.value([]);
that.mute(function() {
that.value(value);
});
} else {
that._refreshHandler = that.refresh.bind(that);
}
that.dataSource = dataSource.bind(CHANGE, that._refreshHandler);
that.setDSFilter(dataSource.filter());
if (dataSource.view().length !== 0) {
that.refresh();
} else if (that.options.autoBind) {
dataSource.fetch();
}
},
skip: function() {
return this.dataSource.currentRangeStart();
},
_triggerListBound: function() {
const that = this;
const skip = that.skip();
if (that.bound() && !that._selectingValue && that._skip !== skip) {
that._skip = skip;
that.trigger(LISTBOUND);
}
},
_getValues: function(dataItems) {
const getter = this._valueGetter;
return $.map(dataItems, (dataItem) => getter(dataItem));
},
_highlightSelectedItems: function() {
for (let i = 0; i < this._selectedDataItems.length; i++) {
const item = this._getElementByDataItem(this._selectedDataItems[i]);
if (item.length) {
item.addClass(SELECTED);
}
}
},
_focusSelectedInFilteredData: function() {
const that = this;
const selectedDataItems = that._selectedDataItems;
const valueGetter = that._valueGetter;
let focusIndex = 0;
if (selectedDataItems.length) {
const data = that.dataSource.flatView();
const selectedValue = valueGetter(selectedDataItems[0]);
for (let i = 0; i < data.length; i++) {
const item = that.options.type === "group" ? data[i].item : data[i];
if (item && valueGetter(item) === selectedValue) {
focusIndex = i;
break;
}
}
}
that.focus(focusIndex);
},
refresh: function(e) {
const that = this;
const action = e?.action;
const isItemChange = action === "itemchange";
const filtered = this.isFiltered();
if (that._mute) {
return;
}
that._deferValueSet = false;
if (!that._fetching) {
if (filtered) {
that._focusSelectedInFilteredData();
}
that._createList();
if (!action && that._values.length && !filtered && !that.options.skipUpdateOnBind && !that._emptySearch) {
that._selectingValue = true;
that.bound(true);
that.value(that._values, true).done(function() {
that._selectingValue = false;
that._triggerListBound();
});
} else {
that.bound(true);
that._highlightSelectedItems();
that._triggerListBound();
}
} else {
if (that._renderItems) {
that._renderItems(true);
}
that._triggerListBound();
}
if (isItemChange || action === "remove") {
const result = mapChangedItems(that._selectedDataItems, e.items);
if (result.changed.length) {
if (isItemChange) {
that.trigger("selectedItemChange", { items: result.changed });
} else {
that.value(that._getValues(result.unchanged));
}
}
}
that._fetching = false;
},
removeAt: function(position) {
const value = this._values.splice(position, 1)[0];
return {
position,
dataItem: this._removeSelectedDataItem(value)
};
},
_removeSelectedDataItem: function(value) {
const that = this;
const valueGetter = that._valueGetter;
for (const idx in that._selectedDataItems) {
if (valueGetter(that._selectedDataItems[idx]) === value) {
that._selectedIndexes.splice(idx, 1);
return that._selectedDataItems.splice(idx, 1)[0];
}
}
},
setValue: function(value) {
this._values = toArray(value);
},
value: function(value, _forcePrefetch) {
const that = this;
if (value === undefined) {
return that._values.slice();
}
if (value === null) {
value = [];
}
value = toArray(value);
if (!that._valueDeferred || that._valueDeferred.state() === "resolved") {
that._valueDeferred = $.Deferred();
}
const shouldClear = that.options.selectable === "multiple" && that.select().length && value.length;
if (shouldClear || !value.length) {
that.select(-1);
}
that._values = value;
if (that.bound() && !that._mute && !that._deferValueSet || _forcePrefetch) {
that._prefetchByValue(value);
}
return that._valueDeferred;
},
_checkValuesOrder: function(value) {
if (this._removedAddedIndexes?.length === value.length) {
const newValue = this._removedAddedIndexes.slice();
this._removedAddedIndexes = null;
return newValue;
}
return value;
},
_prefetchByValue: function(value) {
const that = this;
const dataView = that._dataView;
const valueGetter = that._valueGetter;
const mapValueTo = that.options.mapValueTo;
const forSelection = [];
for (let i = 0; i < value.length; i++) {
for (let idx = 0; idx < dataView.length; idx++) {
const item = dataView[idx].item;
if (item) {
const match = isPrimitive(item) ? value[i] === item : value[i] === valueGetter(item);
if (match) {
forSelection.push(dataView[idx].index);
}
}
}
}
if (forSelection.length === value.length) {
that._values = [];
that.select(forSelection);
return;
}
if (typeof that.options.valueMapper === "function") {
const callback = mapValueTo === "index" ? that.mapValueToIndex : that.mapValueToDataItem;
that.options.valueMapper(valueMapperOptions(this.options, value, callback.bind(that)));
} else {
if (!that.value()[0]) {
that.select([-1]);
} else {
that._selectingValue = false;
that._triggerListBound();
}
}
},
mapValueToIndex: function(indexes) {
if (indexes === undefined || indexes === -1 || indexes === null) {
indexes = [];
} else {
indexes = toArray(indexes);
}
if (!indexes.length) {
indexes = [-1];
} else {
const removed = this._deselect([]).removed;
if (removed.length) {
this._triggerChange(removed, []);
}
}
this.select(indexes);
},
mapValueToDataItem: function(dataItems) {
if (dataItems === undefined || dataItems === null) {
dataItems = [];
} else {
dataItems = toArray(dataItems);
}
if (!dataItems.length) {
this.select([-1]);
} else {
const removed = $.map(this._selectedDataItems, (item, index) => ({
index,
dataItem: item
}));
const added = $.map(dataItems, (item, index) => ({
index,
dataItem: item
}));
this._selectedDataItems = dataItems;
this._selectedIndexes = [];
for (let i = 0; i < this._selectedDataItems.length; i++) {
const item = this._getElementByDataItem(this._selectedDataItems[i]);
this._selectedIndexes.push(this._getIndecies(item)[0]);
item.addClass(SELECTED);
}
this._triggerChange(removed, added);
if (this._valueDeferred) {
this._valueDeferred.resolve();
}
}
},
deferredRange: function(index) {
const dataSource = this.dataSource;
const take = this.itemCount;
const ranges = this._rangesList;
const result = $.Deferred();
const defs = [];
const low = Math.floor(index / take) * take;
const high = Math.ceil(index / take) * take;
const pages = high === low ? [high] : [low, high];
$.each(pages, (_, skip) => {
const end = skip + take;
const existingRange = ranges[skip];
let deferred;
if (!existingRange || existingRange.end !== end) {
deferred = $.Deferred();
ranges[skip] = {
end,
deferred
};
dataSource._multiplePrefetch(skip, take, () => {
deferred.resolve();
});
} else {
deferred = existingRange.deferred;
}
defs.push(deferred);
});
$.when(...defs).done(() => {
result.resolve();
});
return result;
},
prefetch: function(indexes) {
const that = this;
const take = this.itemCount;
const isEmptyList = !that._promisesList.length;
if (!isActivePromise(that._activeDeferred)) {
that._activeDeferred = $.Deferred();
that._promisesList = [];
}
$.each(indexes, (_, index) => {
that._promisesList.push(that.deferredRange(that._getSkip(index, take)));
});
if (isEmptyList) {
$.when(...that._promisesList).done(() => {
that._promisesList = [];
that._activeDeferred.resolve();
});
}
return that._activeDeferred;
},
_findDataItem: function(view, index) {
if (this.options.type === "group") {
for (let i = 0; i < view.length; i++) {
const group = view[i].items;
if (group.length <= index) {
index = index - group.length;
} else {
return group[index];
}
}
}
return view[index];
},
_getRange: function(skip, take) {
return this.dataSource._findRange(skip, Math.min(skip + take, this.dataSource.total()));
},
dataItemByIndex: function(index) {
const that = this;
const take = that.itemCount;
const skip = that._getSkip(index, take);
let view = this._getRange(skip, take);
if (!that._getRange(skip, take).length) {
return null;
}
if (that.options.type === "group") {
kendo.ui.progress($(that.wrapper), true);
that.mute(function() {
that.dataSource.range(skip, take, () => {
kendo.ui.progress($(that.wrapper), false);
});
view = that.dataSource.view();
});
}
return that._findDataItem(view, [index - skip]);
},
selectedDataItems: function() {
return this._selectedDataItems.slice();
},
scrollWith: function(value) {
this.content.scrollTop(this.content.scrollTop() + value);
},
scrollTo: function(y) {
this.content.scrollTop(y);
},
scrollToIndex: function(index) {
const position = this._getElementPosition ? this._getElementPosition(index) : index * (this.options.itemHeight + this._cssGap);
this.scrollTo(position);
},
focus: function(candidate) {
const that = this;
const itemHeight = that.options.itemHeight;
const id = that._optionID;
let element;
let index;
let current;
let triggerEvent = true;
if (candidate === undefined) {
current = that.content.find("." + FOCUSED);
return current.length ? current : null;
}
if (typeof candidate === "function") {
const data = that.dataSource.flatView();
for (let idx = 0; idx < data.length; idx++) {
if (candidate(data[idx])) {
candidate = idx;
break;
}
}
}
if (candidate instanceof Array) {
candidate = lastFrom(candidate);
}
if (isNaN(candidate)) {
element = $(candidate);
index = parseInt($(element).attr("data-offset-index"), 10);
} else {
index = candidate;
element = that._getElementByIndex(index);
}
if (index === -1) {
that.content.find("." + FOCUSED).removeClass(FOCUSED);
that._focusedIndex = undefined;
return;
}
if (element.length) {
if (element.hasClass(FOCUSED)) {
triggerEvent = false;
}
const previousIndex = that._focusedIndex;
if (previousIndex !== undefined) {
current = that._getElementByIndex(previousIndex);
current.removeClass(FOCUSED).removeAttr("id");
if (triggerEvent) {
that.trigger(DEACTIVATE);
}
}
that._focusedIndex = index;
element.addClass(FOCUSED).attr("id", id);
let elementPosition = that._getRenderedElementPosition(element);
if (elementPosition === 0 && index > 0) {
elementPosition = that._getElementPosition(index);
}
const headerHeight = that._getHeaderHeight();
const goingUp = previousIndex !== undefined && index < previousIndex;
const goingDown = previousIndex !== undefined && index > previousIndex;
const scrollTop = this.content.scrollTop();
const visibleTop = scrollTop + headerHeight;
const screenEnd = scrollTop + this._screenHeight;
const elementBottom = elementPosition + itemHeight;
let position;
if (elementPosition < visibleTop && elementBottom > scrollTop) {
position = "top";
} else if (elementPosition === screenEnd || elementPosition < screenEnd && screenEnd < elementBottom) {
position = "bottom";
} else if (elementPosition >= visibleTop && elementBottom <= screenEnd) {
position = "inScreen";
} else {
position = "outScreen";
}
if (position === "top") {
that.scrollTo(Math.max(0, elementPosition - headerHeight));
} else if (position === "bottom") {
that.scrollTo(elementPosition + itemHeight - that._screenHeight);
} else if (position === "outScreen") {
if (goingUp) {
that.scrollTo(Math.max(0, elementPosition - headerHeight));
} else if (goingDown) {
that.scrollTo(elementPosition + itemHeight - that._screenHeight);
} else {
that.scrollTo(Math.max(0, elementPosition - headerHeight));
}
}
if (triggerEvent) {
that.trigger(ACTIVATE);
}
} else {
const previousIndex = that._focusedIndex;
if (previousIndex !== undefined) {
current = that._getElementByIndex(previousIndex);
current.removeClass(FOCUSED).removeAttr("id");
}
that._focusedIndex = index;
that.items().removeClass(FOCUSED);
const elementPosition = that._getElementPosition(index);
const headerHeight = that._getHeaderHeight();
const goingUp = previousIndex !== undefined && index < previousIndex;
const goingDown = previousIndex !== undefined && index > previousIndex;
if (goingUp) {
that.scrollTo(Math.max(0, elementPosition - headerHeight));
} else if (goingDown) {
that.scrollTo(elementPosition + itemHeight - that._screenHeight);
} else {
that.scrollTo(Math.max(0, elementPosition - headerHeight));
}
}
},
focusIndex: function() {
return this._focusedIndex;
},
focusFirst: function() {
this.scrollTo(0);
this.focus(0);
},
focusLast: function() {
const lastIndex = this.dataSource.total();
this.scrollTo(this.heightContainer.offsetHeight);
this.focus(lastIndex - 1);
},
focusPrev: function() {
let index = this._focusedIndex;
if (!isNaN(index) && index > 0) {
index -= 1;
this.focus(index);
const current = this.focus();
if (current?.hasClass("k-loading-item")) {
index += 1;
this.focus(index);
}
return index;
} else {
index = this.dataSource.total() - 1;
this.focus(index);
return index;
}
},
focusNext: function() {
let index = this._focusedIndex;
const lastIndex = this.dataSource.total() - 1;
if (!isNaN(index) && index < lastIndex) {
index += 1;
this.focus(index);
const current = this.focus();
if (current?.hasClass("k-loading-item")) {
index -= 1;
this.focus(index);
}
return index;
} else {
index = 0;
this.focus(index);
return index;
}
},
_triggerChange: function(removed = [], added = []) {
if (removed.length || added.length) {
this.trigger(CHANGE, {
removed,
added
});
}
},
select: function(candidate) {
const that = this;
const singleSelection = that.options.selectable !== "multiple";
let prefetchStarted = isActivePromise(that._activeDeferred);
const filtered = this.isFiltered();
if (candidate === undefined) {
return that._selectedIndexes.slice();
}
if (!that._selectDeferred || that._selectDeferred.state() === "resolved") {
that._selectDeferred = $.Deferred();
}
let indices = that._getIndecies(candidate);
const isAlreadySelected = singleSelection && !filtered && lastFrom(indices) === lastFrom(this._selectedIndexes);
let removed = that._deselectCurrentValues(indices);
if (removed.length || !indices.length || isAlreadySelected) {
that._triggerChange(removed);
if (that._valueDeferred) {
that._valueDeferred.resolve().promise();
}
return that._selectDeferred.resolve().promise();
}
if (indices.length === 1 && indices[0] === -1) {
indices = [];
}
const initialIndices = indices;
const result = that._deselect(indices);
removed = result.removed;
indices = result.indices;
if (singleSelection) {
prefetchStarted = false;
if (indices.length) {
indices = [lastFrom(indices)];
}
}
const done = function() {
const added = that._select(indices);
if (initialIndices.length === indices.length || singleSelection) {
that.focus(indices);
}
that._triggerChange(removed, added);
if (that._valueDeferred) {
that._valueDeferred.resolve();
}
that._selectDeferred.resolve();
};
const deferred = that.prefetch(indices);
if (!prefetchStarted) {
if (deferred) {
deferred.done(done);
} else {
done();
}
}
return that._selectDeferred.promise();
},
bound: function(bound) {
if (bound === undefined) {
return this._listCreated;
}
this._listCreated = bound;
},
mute: function(callback) {
this._mute = true;
callback();
this._mute = false;
},
setDSFilter: function(filter) {
this._lastDSFilter = $.extend({}, filter);
},
isFiltered: function() {
if (!this._lastDSFilter) {
this.setDSFilter(this.dataSource.filter());
}
return !kendo.data.Query.compareFilters(this.dataSource.filter(), this._lastDSFilter);
},
skipUpdate: $.noop,
_getElementByIndex: function(index) {
return this.items().filter((idx, element) => index === parseInt($(element).attr("data-offset-index"), 10));
},
_getElementByDataItem: function(dataItem) {
const dataView = this._dataView;
const valueGetter = this._valueGetter;
let element;
for (let i = 0; i < dataView.length; i++) {
const match = dataView[i].item && isPrimitive(dataView[i].item) ? dataView[i].item === dataItem : dataView[i].item && dataItem && valueGetter(dataView[i].item) == valueGetter(dataItem);
if (match) {
element = dataView[i];
break;
}
}
return element ? this._getElementByIndex(element.index) : $();
},
_clean: function() {
this.result = undefined;
this._lastScrollTop = undefined;
this._skip = undefined;
$(this.heightContainer).remove();
this.heightContainer = undefined;
if (this.content) {
this.content.empty();
}
this._flatUl = null;
this.ul = null;
if (this._groupUlCache) {
for (const key in this._groupUlCache) {
const cached = this._groupUlCache[key];
if (cached?.ul?.parentNode) {
cached.ul.parentNode.removeChild(cached.ul);
}
}
}
this._groupContainers = [];
this._groupUlCache = {};
this._groupRanges = null;
this._items = [];
this._groupValueMap = null;
},
_height: function() {
const hasData = !!this.dataSource.view().length;
let height = this.options.height;
const itemHeight = this.options.itemHeight;
const cssGap = this._cssGap || 0;
const effectiveItemHeight = itemHeight + cssGap;
const total = this.dataSource.total();
if (!hasData) {
height = 0;
} else if (height / effectiveItemHeight > total) {
height = total * effectiveItemHeight;
}
return height;
},
setScreenHeight: function() {
const height = this._height();
this.content.height(height);
this._screenHeight = height;
},
screenHeight: function() {
return this._screenHeight;
},
_getRenderedElementPosition: function(element) {
if (!element || !element.length) {
return 0;
}
const el = element[0];
const parentUl = element.closest("ul")[0];
if (!parentUl) {
return el.offsetTop;
}
const transform = parentUl.style.transform || parentUl.style.webkitTransform || "";
const match = transform.match(/translateY\((-?\d+(?:\.\d+)?)px\)/);
const ulTranslateY = match ? parseFloat(match[1]) : 0;
return ulTranslateY + el.offsetTop;
},
_getElementLocation: function(index) {
const scrollTop = this.content.scrollTop();
const screenHeight = this._screenHeight;
const itemHeight = this.options.itemHeight;
const yPosition = this._getElementPosition(index);
const yDownPostion = yPosition + itemHeight;
const screenEnd = scrollTop + screenHeight;
const headerHeight = this._getHeaderHeight();
const visibleTop = scrollTop + headerHeight;
if (yPosition < visibleTop && yDownPostion > scrollTop) {
return "top";
} else if (yPosition === screenEnd || yPosition < screenEnd && screenEnd < yDownPostion) {
return "bottom";
} else if (yPosition >= visibleTop && yDownPostion <= screenEnd) {
return "inScreen";
} else {
return "outScreen";
}
},
_getElementPosition: function(index) {
const itemHeight = this.options.itemHeight;
const groupRanges = this._groupRanges;
const cssGap = this._cssGap || 0;
if (groupRanges && groupRanges.length) {
const lastRange = groupRanges[groupRanges.length - 1];
const lastKnownIndex = lastRange.startIndex + lastRange.itemCount - 1;
if (index <= lastKnownIndex) {
return getGroupedItemPosition(index, itemHeight, groupRanges, cssGap);
}
const knownPosition = getGroupedItemPosition(lastKnownIndex, itemHeight, groupRanges, cssGap);
const itemsBeyond = index - lastKnownIndex;
const avgItemsPerGroup = lastKnownIndex / groupRanges.length;
const estimatedNewHeaders = Math.floor(itemsBeyond / avgItemsPerGroup);
return knownPosition + itemsBeyond * (itemHeight + cssGap) + estimatedNewHeaders * itemHeight;
}
return index * (itemHeight + cssGap);
},
_templates: function() {
const options = this.options;
const templates = {
template: options.template,
placeholderTemplate: options.placeholderTemplate,
groupTemplate: options.groupTemplate,
fixedGroupTemplate: options.fixedGroupTemplate
};
if (options.columns) {
options.columns.forEach((column, i) => {
const templateText = column.field ? column.field.toString() : "text";
const templateFunc = (data) => encode(kendo.getter(templateText)(data));
templates["column" + i] = column.template || templateFunc;
});
}
for (const key in templates) {
if (typeof templates[key] !== "function") {
templates[key] = kendo.template(templates[key] || "");
}
}
this.templates = templates;
},
_generateItems: function(element, count) {
const items = [];
const itemHeight = this._getItemHeightStyle();
const itemClass = this._getItemClass();
while (count-- > 0) {
const text = document.createElement("span");
text.className = "k-list-item-text";
const item = document.createElement("li");
item.tabIndex = -1;
item.className = itemClass;
item.setAttribute("role", "option");
item.style.height = itemHeight;
item.style.minHeight = itemHeight;
item.style.position = "absolute";
item.style.width = "100%";
item.appendChild(text);
element.appendChild(item);
items.push(item);
}
return items;
},
_generateGroupId: function(groupIndex, groupValue) {
const stringValue = groupValue === null || groupValue === undefined ? "" : String(groupValue);
const safeValue = stringValue.replace(/\s+/g, "-").replace(/[^a-zA-Z0-9-_]/g, "");
return this._optionID + "-group-" + groupIndex + "-" + safeValue;
},
_createGroupUl: function(hasHeader, groupValue, groupIndex, firstDataItem) {
const that = this;
const options = that.options;
const itemHeight = that._getItemHeightStyle();
const ul = document.createElement("ul");
const groupId = that._generateGroupId(groupIndex, groupValue);
const groupIconField = options.groupIconField;
ul.className = that._isTableVariant() ? `${LIST_UL} ${TABLE}` : LIST_UL;
ul.style.position = "absolute";
ul.style.width = "100%";
ul.style.top = "0";
ul.style.left = "0";
if (options.id) {
ul.id = `${options.id}-group-${groupIndex}`;
}
ul.setAttribute("role", "group");
ul.setAttribute("aria-labelledby", groupId);
if (options.ariaLive) {
ul.setAttribute("aria-live", options.ariaLive);
}
const groupContainer = {
ul,
header: null,
items: [],
groupValue,
hasHeader,
groupId
};
if (hasHeader) {
const header = document.createElement("li");
header.className = that._getGroupHeaderClass();
header.setAttribute("role", "presentation");
header.setAttribute("id", groupId);
header.style.height = itemHeight;
header.style.minHeight = itemHeight;
header.style.position = "relative";
header.style.transform = "none";
header.style.overflow = "hidden";
if (groupIconField && firstDataItem && firstDataItem[groupIconField]) {
const iconWrapper = document.createElement("span");
iconWrapper.className = "k-list-item-icon-wrapper";
iconWrapper.setAttribute("role", "presentation");
const iconHtml = kendo.ui.icon({
icon: firstDataItem[groupIconField],
iconClass: "k-list-group-icon",
attr: { "aria-hidden": "true" }
});
$(iconWrapper).append(iconHtml);
$(header).append(iconWrapper);
}
const headerText = document.createElement("span");
headerText.className = that._isTableVariant() ? "k-table-th" : "k-list-item-text";
header.appendChild(headerText);
ul.appendChild(header);
groupContainer.header = header;
}
return groupContainer;
},
_addGroupItems: function(groupContainer, count) {
const that = this;
const itemHeight = that._getItemHeightStyle();
const hasColumns = that._isTableVariant();
const itemClass = that._getItemClass();
for (let i = 0; i < count; i++) {
const item = document.createElement("li");
item.tabIndex = -1;
item.className = itemClass;
item.setAttribute("role", "option");
item.style.height = itemHeight;
item.style.minHeight = itemHeight;
item.style.position = "relative";
item.style.transform = "none";
if (!hasColumns) {
const text = document.createElement("span");
text.className = "k-list-item-text";
item.appendChild(text);
}
groupContainer.ul.appendChild(item);
groupContainer.items.push(item);
}
},
_generateGroupedItems: function(container, visibleGroups, itemsPerGroup) {
const that = this;
const groupContainers = [];
const allItems = [];
container.innerHTML = "";
for (const groupInfo of visibleGroups) {
const groupContainer = that._createGroupUl(groupInfo.hasHeader, groupInfo.groupValue, groupInfo.groupIndex);
const itemCount = Math.min(groupInfo.itemCount, itemsPerGroup);
that._addGroupItems(groupContainer, itemCount);
position(groupContainer.ul, groupInfo.top);
groupContainer.ul.setAttribute("data-group-index", groupInfo.groupIndex);
groupContainer.ul.setAttribute("data-start-index", groupInfo.startIndex);
groupContainer.startIndex = groupInfo.startIndex;
groupContainer.itemCount = itemCount;
groupContainer.top = groupInfo.top;
groupContainer.groupIndex = groupInfo.groupIndex;
container.appendChild(groupContainer.ul);
groupContainers.push(groupContainer);
allItems.push(...groupContainer.items);
}
return {
groupContainers,
allItems
};
},
_saveInitialRanges: function() {
const ranges = this.dataSource._ranges;
const deferred = $.Deferred();
deferred.resolve();
this._rangesList = {};
for (let i = 0; i < ranges.length; i++) {
this._rangesList[ranges[i].start] = {
end: ranges[i].end,
deferred
};
}
},
_createList: function() {
const that = this;
const content = that.content.get(0);
const options = that.options;
const dataSource = that.dataSource;
if (that.bound()) {
that._clean();
}
that._saveInitialRanges();
that._buildValueGetter();
that.setScreenHeight();
that.itemCount = getItemCount(that._screenHeight, options.listScreens, options.itemHeight);
if (that.itemCount > dataSource.total()) {
that.itemCount = dataSource.total();
}
that.options.type = (dataSource.group() || []).length ? "group" : "flat";
if (that.options.type === "flat") {
const ul = document.createElement("ul");
ul.className = that._isTableVariant() ? `${LIST_UL} ${TABLE}` : LIST_UL;
ul.style.position = "relative";
that.content.get(0).appendChild(ul);
that._flatUl = ul;
if (options.id) {
ul.id = options.id;
}
if (options.ariaLive) {
ul.setAttribute("aria-live", options.ariaLive);
}
that.ul = $(ul);
that._cssGap = that._isTableVariant() ? 0 : getCssGap(that.content, that.ul);
that._items = that._generateItems(ul, that.itemCount);
const totalItems = dataSource.total();
const effectiveItemHeight = options.itemHeight + that._cssGap;
const totalHeight = totalItems * effectiveItemHeight;
that._setHeight(totalHeight);
that._toggleHeaderVisibility(false);
that.getter = that._getter(() => {
that._renderItems(true);
});
that._onScroll = (scrollTop, force) => {
const getList = that._listItems(that.getter);
return that._fixedHeader(scrollTop, getList(scrollTop, force));
};
that._renderItems = that._whenChanged(scrollCallback(content, that._onScroll), syncList(that._reorderList(that._items, render.bind(that))));
} else {
that._toggleHeaderVisibility(options.fixedGroupHeader !== false);
that._groupValueMap = new Map();
that._cssGap = getCssGap(that.content, null);
that._groupRanges = buildGroupRanges(dataSource, 0, 0, that._groupValueMap);
let estimatedTotalGroups = options.groupCount;
const isServerOperations = dataSource.options.serverGrouping === true;
if (!estimatedTotalGroups && that._groupRanges.length > 0) {
if (isServerOperations) {
estimatedTotalGroups = that._groupRanges.length;
} else {
const groupDescriptor = dataSource.group();
const allData = dataSource.data();
if (allData?.length && groupDescriptor?.length) {
const fullGrouped = new kendo.data.Query(allData).group(groupDescriptor).data;
estimatedTotalGroups = fullGrouped.length;
} else {
estimatedTotalGroups = that._groupRanges.length;
}
}
}
that._totalGroupCount = estimatedTotalGroups || that._groupRanges.length;
that._initialGroupRanges = that._groupRanges.slice();
const groupCount = that._totalGroupCount;
const isServerGrouping = dataSource.options.serverGrouping === true;
const itemCount = isServerGrouping ? dataSource.total() : dataSource.data().length || dataSource.total();
const totalHeight = getGroupedTotalHeight(itemCount, options.itemHeight, groupCount, that._cssGap);
that._setHeight(totalHeight);
that._initGroupedRendering();
}
that._renderItems();
that._calculateGroupPadding(that._screenHeight);
that._calculateColumnsHeaderPadding();
},
_initGroupedRendering: function() {
const that = this;
const content = that.content.get(0);
that._currentPageStart = 0;
that._renderedPageStart = null;
that._groupUls = [];
that._items = [];
that.getter = that._groupedGetter(() => {
that._renderItems(true);
});
that._onScroll = (scrollTop, force) => that._handleGroupedScroll(scrollTop, force);
that._renderItems = that._whenChangedGrouped(scrollCallback(content, that._onScroll), (list, force) => {
that._renderGroupedPage(list, force);
return list;
});
},
_handleGroupedScroll: function(scrollTop, force) {
const that = this;
const itemHeight = that.options.itemHeight;
const dataSource = that.dataSource;
const pageSize = that.itemCount;
const total = dataSource.total();
const screenHeight = that._screenHeight || 300;
const totalGroupCount = that._totalGroupCount || 1;
const isServerOperations = dataSource.options.serverGrouping === true;
if (!isServerOperations) {
return that._buildGroupedPageData(0, scrollTop);
}
const totalHeight = total * itemHeight + Math.max(0, totalGroupCount - 1) * itemHeight;
const pageInfo = that._scrollPositionToPageInfo(scrollTop);
let pageStart = pageInfo.pageStart;
const maxPageStart = Math.max(0, total - pageSize);
const scrollBottom = scrollTop + screenHeight;
const nearEnd = scrollBottom >= totalHeight - screenHeight * 2;
if (nearEnd && pageStart < maxPageStart) {
pageStart = maxPageStart;
}
let constrainedPageStart = Math.min(Math.max(0, pageStart), maxPageStart);
const pageChanged = that._currentPageStart !== constrainedPageStart;
if (pageChanged || force) {
if (!dataSource.inRange(constrainedPageStart, pageSize)) {
if (that._pageDeferred) {
that._pageDeferred.reject();
}
that._pageDeferred = that.deferredRange(constrainedPageStart);
that._pageDeferred.then(() => {
that._pageDeferred = null;
that.mute(() => {
dataSource.range(constrainedPageStart, pageSize);
});
const firstGroupIdx = that._estimateFirstGroupIndex(constrainedPageStart);
that._groupRanges = buildGroupRanges(dataSource, constrainedPageStart, firstGroupIdx, that._groupValueMap);
that._updateGroupCountAndHeight();
that._currentPageStart = constrainedPageStart;
that._renderItems(true);
});
return that._buildPlaceholderPageData(constrainedPageStart, scrollTop);
}
if (dataSource.currentRangeStart() !== constrainedPageStart) {
that.mute(() => {
dataSource.range(constrainedPageStart, pageSize);
});
const firstGroupIdx = that._estimateFirstGroupIndex(constrainedPageStart);
that._groupRanges = buildGroupRanges(dataSource, constrainedPageStart, firstGroupIdx, that._groupValueMap);
that._updateGroupCountAndHeight();
that._currentPageStart = constrainedPageStart;
const list = that._buildGroupedPageData(constrainedPageStart, scrollTop);
that._renderGroupedPage(list, true, scrollTop);
return list;
}
that._currentPageStart = constrainedPageStart;
}
return that._buildGroupedPageData(constrainedPageStart, scrollTop);
},
_getKnownRangeInfo: function(initialRanges, itemHeight) {
let knownPosition = 0;
for (let i = 0; i < initialRanges.length; i++) {
const range = initialRanges[i];
const headerSlots = i > 0 ? 1 : 0;
knownPosition += (headerSlots + range.itemCount) * itemHeight;
}
const lastRange = initialRanges[initialRanges.length - 1];
const lastKnownIndex = lastRange.startIndex + lastRange.itemCount - 1;
const lastKnownGroupIndex = initialRanges.length - 1;
const avgGroupHeight = knownPosition / initialRanges.length;
const avgItemsPerGroup = (lastKnownIndex + 1) / initialRanges.length;
return {
knownPosition,
lastKnownIndex,
lastKnownGroupIndex,
avgGroupHeight,
avgItemsPerGroup
};
},
_estimateViewportIndicesBeyondKnown: function(scrollTop, viewportEnd, rangeInfo, total) {
const { knownPosition, lastKnownGroupIndex, avgGroupHeight, avgItemsPerGroup } = rangeInfo;
const positionBeyondKnown = scrollTop - knownPosition;
const groupsBeyondKnown = Math.floor(positionBeyondKnown / avgGroupHeight);
const targetGroupIndex = lastKnownGroupIndex + 1 + groupsBeyondKnown;
let viewportStartIndex = Math.floor(targetGroupIndex * avgItemsPerGroup);
viewportStartIndex = Math.min(viewportStartIndex, total - 1);
const endPositionBeyondKnown = viewportEnd - knownPosition;
const endGroupsBeyondKnown = Math.ceil(endPositionBeyondKnown / avgGroupHeight);
const endGroupIndex = lastKnownGroupIndex + 1 + endGroupsBeyondKnown;
let viewportEndIndex = Math.floor(endGroupIndex * avgItemsPerGroup);
viewportEndIndex = Math.min(viewportEndIndex, total - 1);
return {
viewportStartIndex,
viewportEndIndex,
targetGroupIndex
};
},
_findViewportIndicesInKnownRanges: function(scrollTop, viewportEnd, initialRanges, itemHeight, rangeInfo, total) {
const { knownPosition, lastKnownGroupIndex, avgGroupHeight, avgItemsPerGroup } = rangeInfo;
let viewportStartIndex = 0;
let viewportEndIndex = 0;
let targetGroupIndex = 0;
let position = 0;
for (let i = 0; i < initialRanges.length; i++) {
const range = initialRanges[i];
const headerSlots = i > 0 ? 1 : 0;
const groupHeight = (headerSlots + range.itemCount) * itemHeight;
const groupEnd = position + groupHeight;
if (scrollTop >= position && scrollTop < groupEnd) {
const posInGroup = scrollTop - position;
const slotsIntoGroup = Math.floor(posInGroup / itemHeight);
const itemsIntoGroup = Math.max(0, slotsIntoGroup - headerSlots);
viewportStartIndex = range.startIndex + Math.min(itemsIntoGroup, range.itemCount - 1);
targetGroupIndex = i;
}
if (viewportEnd >= position && viewportEnd < groupEnd) {
const posInGroup = viewportEnd - position;
const slotsIntoGroup = Math.ceil(posInGroup / itemHeight);
const itemsIntoGroup = Math.max(0, slotsIntoGroup - headerSlots);
viewportEndIndex = range.startIndex + Math.min(itemsIntoGroup, range.itemCount - 1);
}
position += groupHeight;
}
if (viewportEnd >= knownPosition && viewportEndIndex === 0) {
const endPositionBeyondKnown = viewportEnd - knownPosition;
const endGroupsBeyondKnown = Math.ceil(endPositionBeyondKnown / avgGroupHeight);
const endGroupIndex = lastKnownGroupIndex + 1 + endGroupsBeyondKnown;
viewportEndIndex = Math.floor(endGroupIndex * avgItemsPerGroup);
viewportEndIndex = Math.min(viewportEndIndex, total - 1);
}
return {
viewportStartIndex,
viewportEndIndex,
targetGroupIndex
};
},
_calculateCenteredPageStart: function(viewportStartIndex, viewportEndIndex, pageSize, total) {
const viewportItemCount = viewportEndIndex - viewportStartIndex + 1;
const bufferItems = Math.floor((pageSize - viewportItemCount) / 2);
const maxPageStart = Math.max(0, total - pageSize);
let pageStart = Math.max(0, viewportStartIndex - bufferItems);
pageStart = Math.min(pageStart, maxPageStart);
const pageEnd = pageStart + pageSize - 1;
if (viewportEndIndex > pageEnd) {
pageStart = Math.min(maxPageStart, viewportEndIndex - pageSize + 1);
pageStart = Math.max(0, pageStart);
}
return pageStart;
},
_scrollPositionToPageInfo: function(scrollTop) {
const that = this;
const itemHeight = that.options.itemHeight;
const pageSize = that.itemCount;
const initialRanges = that._initialGroupRanges;
const screenHeight = that._screenHeight || 300;
const total = that.dataSource.total();
if (!initialRanges || !initialRanges.length) {
const itemIndex = Math.floor(scrollTop / itemHeight);
const maxPageStart = Math.max(0, total - pageSize);
let pageStart = Math.max(0, itemIndex - Math.floor(pageSize / 2));
pageStart = Math.min(pageStart, maxPageStart);
return {
pageStart,
groupIndex: 0
};
}
const rangeInfo = that._getKnownRangeInfo(initialRanges, itemHeight);
const viewportEnd = scrollTop + screenHeight;
let result;
if (scrollTop >= rangeInfo.knownPosition) {
result = that._estimateViewportIndicesBeyondKnown(scrollTop, viewportEnd, rangeInfo, total);
} else {
result = that._findViewportIndicesInKnownRanges(scrollTop, viewportEnd, initialRanges, itemHeight, rangeInfo, total);
}
let { viewportStartIndex, viewportEndIndex, targetGroupIndex } = result;
if (viewportEndIndex === 0 || viewportEndIndex < viewportStartIndex) {
viewportEndIndex = Math.min(viewportStartIndex + Math.ceil(screenHeight / itemHeight), total - 1);
}
const pageStart = that._calculateCenteredPageStart(viewportStartIndex, viewportEndIndex, pageSize, total);
return {
pageStart,
groupIndex: targetGroupIndex
};
},
_updateGroupCountAndHeight: function() {
const that = this;
const options = that.options;
const dataSource = that.dataSource;
if (!that._groupValueMap || options.type !== "group") {
return;
}
const discoveredGroupCount = that._groupValueMap.size;
const previousGroupCount = that._totalGroupCount || 0;
const total = dataSource.total();
const pageSize = that.itemCount;
const currentRangeStart = dataSource.currentRangeStart() || 0;
const isAtEnd = currentRangeStart + pageSize >= total;
const itemHeight = that.options.itemHeight;
if (discoveredGroupCount > previousGroupCount || isAtEnd && discoveredGroupCount !== previousGroupCount) {
that._totalGroupCount = discoveredGroupCount;
const totalHeight = getGroupedTotalHeight(total, itemHeight, discoveredGroupCount, that._cssGap);
that._setHeight(totalHeight);
}
},
_estimateFirstGroupIndex: function(startIndex) {
const initialRanges = this._initialGroupRanges;
const totalGroups = this._totalGroupCount || 1;
const total = this.dataSource.total();
const avgGroupSize = total / totalGroups;
if (initialRanges && initialRanges.length) {
const lastRange = initialRanges[initialRanges.length - 1];
const lastRangeEnd = lastRange.startIndex + lastRange.itemCount;
if (startIndex < lastRangeEnd) {
for (let i = 0; i < initialRanges.length; i++) {
const range = initialRanges[i];
const rangeEnd = range.startIndex + range.itemCount;
if (startIndex < rangeEnd) {
return i;
}
}
}
}
return Math.floor(startIndex / avgGroupSize);
},
_buildPlaceholderPageData: function(pageStart, scrollTop) {
const that = this;
const itemHeight = that.options.itemHeight;
const screenHeight = that._screenHeight || 400;
const visibleItemCount = Math.ceil(screenHeight / itemHeight) + 2;
const startIndex = Math.floor(scrollTop / itemHeight);
const placeholderTop = startIndex * itemHeight;
const items = [];
for (let i = 0; i < visibleItemCount; i++) {
const itemIndex = startIndex + i;
items.push({
item: null,
group: "Loading...",
newGroup: i === 0,
selected: false,
current: false,
index: itemIndex,
top: itemIndex * itemHeight,
trueGlobalIndex: itemIndex,
globalGroupIndex: -1,
localIndex: i,
viewIndex: 0,
isPlaceholder: true
});
}
const pageGroups = [{
viewIndex: 0,
groupValue: "Loading...",
startIndex: 0,
actualGlobalStartIndex: startIndex,
globalGroupIndex: -1,
itemCount: visibleItemCount,
fullGroupItemCount: visibleItemCount,
hasHeader: false,
isPartialGroup: false,
top: placeholderTop,
height: visibleItemCount * itemHeight,
itemsBeforeLoad: 0,
isPlaceholder: true
}];
return {
pageStart,
pageGroups,
items,
isPlaceholder: true
};
},
_buildAllClientGroupInfos: function(view, itemHeight, cssGap) {
const that = this;
const allGroupInfos = [];
let globalItemIndex = 0;
let cumulativeHeight = 0;
for (let groupIdx = 0; groupIdx < view.length; groupIdx++) {
const viewGroup = view[groupIdx];
const groupValue = viewGroup.value;
const groupItems = viewGroup.items || [];
const groupItemCount = groupItems.length;
if (groupItemCount === 0) {
continue;
}
const hasHeader = groupIdx > 0 || that.options.fixedGroupHeader === false;
const groupTop = cumulativeHeight;
const elementCount = groupItemCount + (hasHeader ? 1 : 0);
const gapsInGroup = Math.max(0, elementCount - 1) * cssGap;
const visualHeight = elementCount * itemHeight + gapsInGroup;
const groupBottom = groupTop + visualHeight;
allGroupInfos.push({
viewIndex: groupIdx,
groupValue,
startIndex: globalItemIndex,
actualGlobalStartIndex: globalItemIndex,
globalGroupIndex: groupIdx,
itemCount: groupItemCount,
fullGroupItemCount: groupItemCount,
hasHeader,
isPartialGroup: false,
top: groupTop,
bottom: groupBottom,
height: visualHeight,
itemsBeforeLoad: 0,
groupItems
});
cumulativeHeight += visualHeight;
globalItemIndex += groupItemCount;
}
return allGroupInfos;
},
_filterVisibleGroupsAndBuildItems: function(allGroupInfos, scrollTop, screenHeight, bufferSize) {
const that = this;
const visibleTop = Math.max(0, scrollTop - bufferSize);
const visibleBottom = scrollTop + screenHeight + bufferSize;
const pageGroups = [];
const items = [];
for (const groupInfo of allGroupInfos) {
if (groupInfo.bottom >= visibleTop && groupInfo.top <= visibleBottom) {
pageGroups.push(groupInfo);
const groupItems = groupInfo.groupItems || [];
for (let j = 0; j < groupInfo.itemCount; j++) {
const dataItem = groupItems[j];
if (!dataItem) {
continue;
}
const itemIndex = groupInfo.startIndex + j;
const mappedItem = that._itemMapper({
item: dataItem,
group: groupInfo.groupValue
}, itemIndex, that._values.slice());
mappedItem.trueGlobalIndex = itemIndex;
mappedItem.globalGroupIndex = groupInfo.globalGroupIndex;
mappedItem.localIndex = j;
mappedItem.viewIndex = groupInfo.viewIndex;
items.push(mappedItem);
}
}
delete groupInfo.groupItems;
}
return {
pageGroups,
items
};
},
_buildClientGroupedPageData: function(pageStart, scrollTop) {
const that = this;
const dataSource = that.dataSource;
const itemHeight = that.options.itemHeight;
const cssGap = that._cssGap || 0;
const groupDescriptor = dataSource.group();
const allData = dataSource.data();
if (!allData || !allData.length || !groupDescriptor || !groupDescriptor.length) {
return {
pageStart,
pageGroups: [],
items: []
};
}
const view = new kendo.data.Query(allData).group(groupDescriptor).data;
if (!view || !view.length) {
return {
pageStart,
pageGroups: [],
items: []
};
}
const screenHeight = that._screenHeight || 400;
const bufferSize = screenHeight;
const allGroupInfos = that._buildAllClientGroupInfos(view, itemHeight, cssGap);
const { pageGroups, items } = that._filterVisibleGroupsAndBuildItems(allGroupInfos, scrollTop, screenHeight, bufferSize);
that._updateFixedHeaderClientGrouped(scrollTop, pageGroups, itemHeight);
return {
pageStart,
pageGroups,
items
};
},
_calculateSectionHeaderOffset: function(viewIdx, groupRanges, view, pageStart, itemHeight) {
const that = this;
let headerOffset = 0;
for (let i = 0; i < viewIdx; i++) {
const prevRange = groupRanges[i];
const prevViewGroup = view[i];
if (prevRange && prevViewGroup) {
const prevFirstItemInPage = Math.max(prevRange.startIndex, pageStart);
const prevItemsBeforeLoad = prevFirstItemInPage - prevRange.startIndex;
const prevHasHeader = (i > 0 || that.options.fixedGroupHeader === false) && prevItemsBeforeLoad === 0;
if (prevHasHeader) {
headerOffset += itemHeight;
}
}
}
return headerOffset;
},
_buildServerGroupSection: function(viewIdx, viewGroup, groupRange, pageStart, pageEnd, itemHeight, headerOffset) {
const that = this;
const groupStartIndex = groupRange.startIndex;
const groupEndIndex = groupRange.startIndex + groupRange.itemCount;
if (groupEndIndex <= pageStart || groupStartIndex >= pageEnd) {
return null;
}
const firstItemInPage = Math.max(groupStartIndex, pageStart);
const lastItemInPage = Math.min(groupEndIndex, pageEnd);
const itemsInPage = lastItemInPage - firstItemInPage;
const itemOffset = firstItemInPage - groupStartIndex;
const viewGroupItems = viewGroup.items || [];
const groupItems = viewGroupItems.slice(itemOffset, itemOffset + itemsInPage);
const groupItemCount = groupItems.length;
if (groupItemCount === 0) {
return null;
}
const globalStartIndex = firstItemInPage;
const globalGroupIndex = groupRange.globalGroupIndex;
const sectionTop = globalStartIndex * itemHeight;
const itemsBeforeLoad = globalStartIndex - groupRange.startIndex;
const isPartialGroup = itemsBeforeLoad > 0;
const hasHeader = (viewIdx > 0 || that.options.fixedGroupHeader === false) && !isPartialGroup;
const groupTop = sectionTop + headerOffset;
const groupHeight = (hasHeader ? itemHeight : 0) + groupItemCount * itemHeight;
const sectionItems = [];
for (let j = 0; j < groupItemCount; j++) {
const dataItem = groupItems[j];
if (!dataItem) {
continue;
}
const globalIndex = globalStartIndex + j;
const mappedItem = that._itemMapper({
item: dataItem,
group: viewGroup.value
}, globalIndex, that._values.slice());
mappedItem.viewIndex = viewIdx;
mappedItem.globalGroupIndex = globalGroupIndex;
mappedItem.localIndex = j;
sectionItems.push(mappedItem);
}
const groupInfo = {
viewIndex: viewIdx,
groupValue: viewGroup.value,
startIndex: globalStartIndex,
actualGlobalStartIndex: groupRange.startIndex,
globalGroupIndex,
sectionIndex: viewIdx,
itemCount: groupItemCount,
fullGroupItemCount: groupRange.itemCount,
hasHeader,
isPartialGroup,
top: groupTop,
height: groupHeight,
itemsBeforeLoad
};
return {
groupInfo,
sectionItems
};
},
_buildServerGroupedPageData: function(pageStart, scrollTop) {
const that = this;
const dataSource = that.dataSource;
const view = dataSource.view();
const groupRanges = that._groupRanges || [];
const itemHeight = that.options.itemHeight;
const pageSize = that.itemCount;
const pageEnd = pageStart + pageSize;
const pageGroups = [];
const items = [];
for (let viewIdx = 0; viewIdx < view.length; viewIdx++) {
const viewGroup = view[viewIdx];
const groupRange = groupRanges[viewIdx];
if (!groupRange) {
continue;
}
const headerOffset = that._calculateSectionHeaderOffset(viewIdx, groupRanges, view, pageStart, itemHeight);
const result = that._buildServerGroupSection(viewIdx, viewGroup, groupRange, pageStart, pageEnd, itemHeight, headerOffset);
if (result) {
pageGroups.push(result.groupInfo);
items.push(...result.sectionItems);
}
}
that._updateFixedHeader(scrollTop, pageGroups);
return {
pageStart,
pageGroups,
items
};
},
_buildGroupedPageData: function(pageStart, scrollTop) {
const that = this;
const isServerGrouping = that.dataSource.options.serverGrouping;
if (!isServerGrouping) {
return that._buildClientGroupedPageData(pageStart, scrollTop);
}
return that._buildServerGroupedPageData(pageStart, scrollTop);
},
_calculateGroupTopPosition: function(startIndex, globalGroupIndex, itemHeight) {
const headersBefore = Math.max(0, globalGroupIndex - 1);
return startIndex * itemHeight + headersBefore * itemHeight;
},
_updateFixedHeader: function(scrollTop, pageGroups) {
const that = this;
if (!pageGroups.length) {
return;
}
let currentGroup = null;
for (let i = pageGroups.length - 1; i >= 0; i--) {
const group = pageGroups[i];
if (scrollTop >= group.top) {
currentGroup = group;
break;
}
}
if (!currentGroup) {
currentGroup = pageGroups[0];
}
if (that.currentVisibleGroup !== currentGroup.groupValue) {
that.header.html(that.templates.fixedGroupTemplate(currentGroup.groupValue));
that.currentVisibleGroup = currentGroup.groupValue;
that._currentVisibleGroupIndex = currentGroup.globalGroupIndex;
}
},
_updateFixedHeaderClientGrouped: function(scrollTop, pageGroups, itemHeight) {
const that = this;
if (!pageGroups.length) {
return;
}
let currentGroup = null;
for (let i = pageGroups.length - 1; i >= 0; i--) {
const group = pageGroups[i];
if (scrollTop >= group.top) {
currentGroup = group;
break;
}
}
if (!currentGroup) {
currentGroup = pageGroups[0];
}
if (that.currentVisibleGroup !== currentGroup.groupValue) {
that.header.html(that.templates.fixedGroupTemplate(currentGroup.groupValue));
that.currentVisibleGroup = currentGroup.groupValue;
that._currentVisibleGroupIndex = currentGroup.globalGroupIndex;
}
},
_removeOrphanUls: function(content) {
const that = this;
const trackedUlSet = new Set(that._groupUls);
const domUls = content.querySelectorAll("ul[data-group-index]");
for (const domUl of domUls) {
const groupIndex = parseInt(domUl.getAttribute("data-group-index"), 10);
if (groupIndex === -1 || trackedUlSet.has(domUl)) {
continue;
}
if (domUl.parentNode) {
domUl.parentNode.removeChild(domUl);
}
}
},
_buildExistingUlMap: function() {
const that = this;
const existingMap = new Map();
const cleanedGroupUls = [];
for (const ul of that._groupUls) {
const groupIndex = parseInt(ul.getAttribute("data-group-index"), 10);
const sectionIndex = parseInt(ul.getAttribute("data-section-index") || "0", 10);
const groupKey = groupIndex + "_" + sectionIndex;
if (!isNaN(groupIndex)) {
if (existingMap.has(groupKey)) {
if (ul.parentNode) {
ul.parentNode.removeChild(ul);
}
} else {
existingMap.set(groupKey, ul);
cleanedGroupUls.push(ul);
}
}
}
that._groupUls = cleanedGroupUls;
return existingMap;
},
_processExistingUl: function(existingUl, groupInfo, listItems, force, keptUls, newUls, existingMap) {
const that = this;
const groupIndex = groupInfo.globalGroupIndex;
const sectionIndex = groupInfo.sectionIndex !== undefined ? groupInfo.sectionIndex : 0;
const groupKey = groupIndex + "_" + sectionIndex;
const headerSelector = that._isTableVariant() ? ".k-table-group-row" : ".k-list-group-item";
const existingHasHeader = !!existingUl.querySelector(headerSelector);
const headerStatusChanged = existingHasHeader !== groupInfo.hasHeader;
if (headerStatusChanged) {
if (existingUl.parentNode) {
existingUl.parentNode.removeChild(existingUl);
}
existingMap.delete(groupKey);
const newUl = that._createGroupUlElement(groupInfo, listItems);
if (sectionIndex > 0) {
newUl.setAttribute("data-section-index", sectionIndex);
}
newUls.push({
ul: newUl,
groupIndex,
sectionIndex,
groupKey
});
return;
}
const cachedStartIndex = parseInt(existingUl.getAttribute("data-start-index"), 10);
const cachedItemCount = existingUl.querySelectorAll("li[data-offset-index]").length;
const startIndexChanged = cachedStartIndex !== groupInfo.startIndex;
const itemCountChanged = cachedItemCount !== groupInfo.itemCount;
if (startIndexChanged || itemCountChanged || force) {
that._updateGroupUl(existingUl, groupInfo, listItems);
if (startIndexChanged) {
existingUl.setAttribute("data-start-index", groupInfo.startIndex);
}
}
const currentTop = existingUl._cachedTop;
if (currentTop !== groupInfo.top) {
position(existingUl, groupInfo.top);
existingUl._cachedTop = groupInfo.top;
}
keptUls.push(existingUl);
existingMap.delete(groupKey);
const items = existingUl.querySelectorAll("li[data-offset-index]");
that._items.push(...Array.from(items));
},
_insertNewUlsInOrder: function(content, newUls) {
for (const { ul: newUl, sectionIndex: newSectionIndex } of newUls) {
let insertBefore = null;
const existingUls = content.querySelectorAll("ul[data-group-index]");
for (const existingUl of existingUls) {
const existingSectionIndex = parseInt(existingUl.getAttribute("data-section-index") || "0", 10);
if (existingSectionIndex > newSectionIndex) {
insertBefore = existingUl;
break;
}
}
if (insertBefore) {
content.insertBefore(newUl, insertBefore);
} else {
content.appendChild(newUl);
}
}
},
_renderGroupedPage: function(list, force, targetScrollTop) {
const that = this;
const content = that.content.get(0);
const pageGroups = list.pageGroups || [];
const pageStart = list.pageStart;
const scrollTop = typeof targetScrollTop === "number" ? targetScrollTop : content.scrollTop;
if (list.isPlaceholder) {
that._renderPlaceholderPage(list, scrollTop);
return;
}
if (that._placeholderUl && that._placeholderUl.parentNode) {
that._placeholderUl.parentNode.removeChild(that._placeholderUl);
that._placeholderUl = null;
}
that._lastRenderScrollTop = scrollTop;
that._removeOrphanUls(content);
const existingMap = that._buildExistingUlMap();
const keptUls = [];
const newUls = [];
that._items = [];
for (const groupInfo of pageGroups) {
const groupIndex = groupInfo.globalGroupIndex;
const sectionIndex = groupInfo.sectionIndex !== undefined ? groupInfo.sectionIndex : 0;
const groupKey = groupIndex + "_" + sectionIndex;
const existingUl = existingMap.get(groupKey);
if (existingUl) {
that._processExistingUl(existingUl, groupInfo, list.items, force, keptUls, newUls, existingMap);
} else {
const newUl = that._createGroupUlElement(groupInfo, list.items);
if (sectionIndex > 0) {
newUl.setAttribute("data-section-index", sectionIndex);
}
newUls.push({
ul: newUl,
groupIndex,
sectionIndex,
groupKey
});
}
}
for (const ul of existingMap.values()) {
if (ul.parentNode) {
ul.parentNode.removeChild(ul);
}
}
that._insertNewUlsInOrder(content, newUls);
that._groupUls = keptUls.concat(newUls.map((n) => n.ul));
that._renderedPageStart = pageStart;
content.scrollTop = scrollTop;
that._dataView = list.items;
},
_calculateCoveredRange: function(visibleTop, visibleBottom, screenHeight) {
const that = this;
let coveredTop = Infinity;
let coveredBottom = 0;
for (const ul of that._groupUls) {
if (!ul || !ul.parentNode) {
continue;
}
const transform = ul.style.transform;
const yMatch = transform.match(/translateY\((\d+)px\)/);
const ulTop = yMatch ? parseFloat(yMatch[1]) : 0;
const ulBottom = ulTop + ul.offsetHeight;
if (ulBottom >= visibleTop - screenHeight && ulTop <= visibleBottom + screenHeight) {
coveredTop = Math.min(coveredTop, ulTop);
coveredBottom = Math.max(coveredBottom, ulBottom);
}
}
return {
coveredTop,
coveredBottom
};
},
_calculatePlaceholderPosition: function(visibleTop, visibleBottom, coveredTop, coveredBottom, screenHeight, totalContentHeight) {
let placeholderTop = 0;
let placeholderHeight = 0;
if (coveredTop === Infinity) {
placeholderTop = Math.max(0, visibleTop);
placeholderHeight = screenHeight;
} else if (visibleTop < coveredTop) {
placeholderTop = Math.max(0, visibleTop);
placeholderHeight = coveredTop - placeholderTop;
} else if (visibleBottom > coveredBottom && coveredBottom < totalContentHeight) {
placeholderTop = coveredBottom;
placeholderHeight = Math.min(visibleBottom, totalContentHeight) - coveredBottom;
}
return {
placeholderTop,
placeholderHeight
};
},
_ensurePlaceholderUl: function(content) {
const that = this;
if (!that._placeholderUl) {
const ul = document.createElement("ul");
const ulClass = that._isTableVariant() ? `${LIST_UL} ${TABLE}` : LIST_UL;
ul.className = ulClass + " k-loading-placeholder";
ul.style.position = "absolute";
ul.style.width = "100%";
ul.style.top = "0";
ul.style.left = "0";
ul.setAttribute("role", "group");
ul.setAttribute("data-group-index", "-1");
if (that.options.ariaLive) {
ul.setAttribute("aria-live", that.options.ariaLive);
}
that._placeholderUl = ul;
content.appendChild(ul);
}
return that._placeholderUl;
},
_updatePlaceholderItems: function(ul, placeholderItemCount) {
const that = this;
const templates = that.templates;
let currentCount = ul.querySelectorAll("li.k-list-item").length;
while (currentCount < placeholderItemCount) {
const li = that._createItemLi();
li.classList.add("k-loading-item");
const textSpan = li.querySelector(".k-list-item-text");
if (textSpan) {
textSpan.innerHTML = templates.placeholderTemplate({});
}
ul.appendChild(li);
currentCount++;
}
while (ul.children.length > placeholderItemCount) {
ul.removeChild(ul.lastChild);
}
},
_renderPlaceholderPage: function(list, scrollTop) {
const that = this;
const content = that.content.get(0);
const itemHeight = that.options.itemHeight;
const screenHeight = that._screenHeight || 400;
const total = that.dataSource.total();
const groupCount = that._totalGroupCount || 1;
const totalContentHeight = getGroupedTotalHeight(total, itemHeight, groupCount, that._cssGap);
const visibleTop = scrollTop;
const visibleBottom = Math.min(scrollTop + screenHeight, totalContentHeight);
const { coveredTop, coveredBottom } = that._calculateCoveredRange(visibleTop, visibleBottom, screenHeight);
const { placeholderTop, placeholderHeight } = that._calculatePlaceholderPosition(visibleTop, visibleBottom, coveredTop, coveredBottom, screenHeight, totalContentHeight);
if (placeholderHeight <= 0) {
if (that._placeholderUl && that._placeholderUl.parentNode) {
that._placeholderUl.style.display = "none";
}
return;
}
const placeholderItemCount = Math.ceil(placeholderHeight / itemHeight) + 1;
const ul = that._ensurePlaceholderUl(content);
ul.style.display = "";
position(ul, placeholderTop);
that._updatePlaceholderItems(ul, placeholderItemCount);
that.header.html(that.templates.fixedGroupTemplate("Loading..."));
},
_createGroupUlElement: function(groupInfo, allItems) {
const that = this;
const templates = that.templates;
let groupItems;
if (groupInfo.sectionIndex !== undefined) {
groupItems = allItems.filter((item) => item.viewIndex === groupInfo.sectionIndex);
} else {
groupItems = allItems.filter((item) => item.globalGroupIndex === groupInfo.globalGroupIndex);
}
const firstDataItem = groupItems.length > 0 ? groupItems[0].item : null;
const groupContainer = that._createGroupUl(groupInfo.hasHeader, groupInfo.groupValue, groupInfo.globalGroupIndex, firstDataItem);
if (groupContainer.header) {
const headerText = groupContainer.header.querySelector(that._getHeaderTextSelector());
if (headerText) {
headerText.innerHTML = templates.groupTemplate(groupInfo.groupValue);
}
}
that._addGroupItems(groupContainer, groupItems.length);
for (let i = 0; i < groupItems.length; i++) {
that._renderGroupItem(groupContainer.items[i], groupItems[i], templates);
}
position(groupContainer.ul, groupInfo.top);
groupContainer.ul.setAttribute("data-group-index", groupInfo.globalGroupIndex);
groupContainer.ul.setAttribute("data-start-index", groupInfo.startIndex);
groupContainer.ul._cachedTop = groupInfo.top;
that._items.push(...groupContainer.items);
return groupContainer.ul;
},
_updateGroupUl: function(ul, groupInfo, allItems) {
const that = this;
const templates = that.templates;
const globalGroupIndex = groupInfo.globalGroupIndex;
let groupItems;
if (groupInfo.sectionIndex !== undefined) {
groupItems = allItems.filter((item) => item.viewIndex === groupInfo.sectionIndex);
} else {
groupItems = allItems.filter((item) => item.globalGroupIndex === globalGroupIndex);
}
const existingItems = Array.from(ul.querySelectorAll("li[data-offset-index]"));
while (existingItems.length > groupItems.length) {
const li = existingItems.pop();
if (li && li.parentNode) {
li.parentNode.removeChild(li);
}
}
for (let i = 0; i < groupItems.length; i++) {
const itemData = groupItems[i];
let li = existingItems[i];
if (!li) {
li = that._createItemLi();
ul.appendChild(li);
}
that._renderGroupItem(li, itemData, templates);
}
},
_createItemLi: function() {
const that = this;
const itemHeight = that._getItemHeightStyle();
const hasColumns = that._isTableVariant();
const itemClass = that._getItemClass();
const li = document.createElement("li");
li.setAttribute("tabindex", "-1");
li.setAttribute("role", "option");
li.className = itemClass;
li.style.height = itemHeight;
li.style.minHeight = itemHeight;
li.style.position = "relative";
li.style.transform = "none";
if (!hasColumns) {
const textSpan = document.createElement("span");
textSpan.className = "k-list-item-text";
li.appendChild(textSpan);
}
return li;
},
_clearAllGroupUls: function() {
const that = this;
for (const ul of that._groupUls) {
if (ul && ul.parentNode) {
ul.parentNode.removeChild(ul);
}
}
that._groupUls = [];
that._items = [];
},
_createAndRenderGroupUl: function(groupInfo, allItems) {
const that = this;
const content = that.content.get(0);
const templates = that.templates;
const groupItems = allItems.filter((item) => item.globalGroupIndex === groupInfo.globalGroupIndex);
const groupContainer = that._createGroupUl(groupInfo.hasHeader, groupInfo.groupValue, groupInfo.globalGroupIndex);
if (groupContainer.header) {
const headerText = groupContainer.header.querySelector(that._getHeaderTextSelector());
if (headerText) {
headerText.innerHTML = templates.groupTemplate(groupInfo.groupValue);
}
}
that._addGroupItems(groupContainer, groupItems.length);
for (let i = 0; i < groupItems.length; i++) {
that._renderGroupItem(groupContainer.items[i], groupItems[i], templates);
}
position(groupContainer.ul, groupInfo.top);
groupContainer.ul.setAttribute("data-group-index", groupInfo.globalGroupIndex);
groupContainer.ul.setAttribute("data-start-index", groupInfo.startIndex);
groupContainer.ul._cachedTop = groupInfo.top;
content.appendChild(groupContainer.ul);
that._groupUls.push(groupContainer.ul);
that._items.push(...groupContainer.items);
},
_renderGroupUlPaged: function(groupInfo, allItems) {
const that = this;
const content = that.content.get(0);
const templates = that.templates;
const viewIndex = groupInfo.viewIndex;
const groupItems = allItems.filter((item) => item.viewIndex === viewIndex);
const cached = that._groupUlCache[viewIndex];
if (!cached) {
const groupContainer = that._createGroupUl(groupInfo.hasHeader, groupInfo.groupValue, groupInfo.viewIndex);
if (groupContainer.header) {
const headerText = groupContainer.header.querySelector(that._getHeaderTextSelector());
if (headerText) {
headerText.innerHTML = templates.groupTemplate(groupInfo.groupValue);
}
}
that._addGroupItems(groupContainer, groupItems.length);
for (let i = 0; i < groupItems.length; i++) {
that._renderGroupItem(groupContainer.items[i], groupItems[i], templates);
}
position(groupContainer.ul, groupInfo.top);
groupContainer.ul.setAttribute("data-view-index", viewIndex);
groupContainer.ul.setAttribute("data-start-index", groupInfo.startIndex);
groupContainer.startIndex = groupInfo.startIndex;
groupContainer.itemCount = groupItems.length;
groupContainer.top = groupInfo.top;
groupContainer.viewIndex = viewIndex;
content.appendChild(groupContainer.ul);
that._groupUlCache[viewIndex] = groupContainer;
that._items.push(...groupContainer.items);
} else {
const newTop = groupInfo.top;
position(cached.ul, newTop);
cached.top = newTop;
for (let k = 0; k < groupItems.length; k++) {
const li = cached.items[k];
if (li) {
const itemData = groupItems[k];
that._renderGroupItem(li, itemData, templates);
li.style.display = "";
}
}
}
},
_reorderGroupUls: function(content, pageGroups) {
const currentUls = Array.from(content.querySelectorAll("." + LIST_UL));
let needsReorder = false;
let lastIndex = -1;
for (const ul of currentUls) {
const idx = parseInt(ul.getAttribute("data-view-index"), 10);
if (idx < lastIndex) {
needsReorder = true;
break;
}
lastIndex = idx;
}
if (needsReorder) {
const sortedUls = currentUls.sort((a, b) => parseInt(a.getAttribute("data-view-index"), 10) - parseInt(b.getAttribute("data-view-index"), 10));
for (const ul of sortedUls) {
content.appendChild(ul);
}
}
},
_findFirstVisibleItem: function(items, scrollTop, pageGroups) {
const itemHeight = this.options.itemHeight;
if (pageGroups) {
for (let i = 0; i < pageGroups.length; i++) {
const group = pageGroups[i];
const groupBottom = group.top + (group.hasHeader ? itemHeight : 0) + group.itemCount * itemHeight;
if (scrollTop < groupBottom) {
const groupItems = items.filter((item) => item.viewIndex === group.viewIndex);
return groupItems[0] || items[0];
}
}
}
return items[0];
},
_renderGroupItem: function(element, data, templates) {
const that = this;
const options = that.options;
const $element = $(element);
const itemTemplate = data.item ? templates.template : templates.placeholderTemplate;
const hasColumns = options.columns?.length;
const dataItem = data.item;
$element.attr("data-uid", dataItem ? dataItem.uid : "").attr("data-offset-index", data.index);
if (hasColumns && dataItem) {
const renderedColumns = $(renderColumns(options, dataItem, templates));
kendo.applyStylesFromKendoAttributes(renderedColumns, ["width", "max-width"]);
$element.empty().append(renderedColumns);
const altRow = data.index % 2 === 1;
$element.toggleClass("k-table-alt-row", altRow);
} else if (hasColumns && !dataItem) {
let placeholderHtml = "";
for (let i = 0; i < options.columns.length; i++) {
const currentWidth = options.columns[i].width;
const currentWidthInt = parseInt(currentWidth, 10);
let widthStyle = "";
if (currentWidth) {
const widthValue = currentWidthInt + "px";
widthStyle = `${kendo.attr("style-width")}="${widthValue}" ${kendo.attr("style-max-width")}="${widthValue}"`;
}
placeholderHtml += `<span class='k-table-td' ${widthStyle}></span>`;
}
const renderedPlaceholder = $(placeholderHtml);
kendo.applyStylesFromKendoAttributes(renderedPlaceholder, ["width", "max-width"]);
$element.empty().append(renderedPlaceholder);
} else {
const textSpan = $element.find(".k-list-item-text");
textSpan.html(itemTemplate(dataItem || {}));
}
$element.toggleClass(FOCUSED, data.current);
$element.toggleClass(SELECTED, data.selected);
$element.toggleClass("k-loading-item", !dataItem);
element.style.display = "";
},
_groupedGetter: function(callback) {
const that = this;
const dataSource = that.dataSource;
const pageSize = that.itemCount;
let lastRequestedRange = null;
let lastRangeStart = dataSource.skip();
if (dataSource.pageSize() < pageSize) {
that.mute(() => {
dataSource.pageSize(pageSize);
});
}
return function(index, rangeStart) {
if (!dataSource.inRange(rangeStart, pageSize)) {
if (lastRequestedRange !== rangeStart) {
lastRequestedRange = rangeStart;
lastRangeStart = rangeStart;
if (that._getterDeferred) {
that._getterDeferred.reject();
}
that._getterDeferred = that.deferredRange(rangeStart);
that._getterDeferred.then(() => {
that._getterDeferred = null;
const firstGroupIdx = that._estimateFirstGroupIndex(rangeStart);
that._groupRanges = buildGroupRanges(dataSource, rangeStart, firstGroupIdx, that._groupValueMap);
that._updateGroupCountAndHeight();
callback();
});
}
return null;
} else {
if (lastRangeStart !== rangeStart) {
that.mute(() => {
dataSource.range(rangeStart, pageSize);
lastRangeStart = rangeStart;
});
const firstGroupIdx = that._estimateFirstGroupIndex(rangeStart);
that._groupRanges = buildGroupRanges(dataSource, rangeStart, firstGroupIdx, that._groupValueMap);
that._updateGroupCountAndHeight();
}
const view = dataSource.view();
const flatIndex = index - rangeStart;
let currentIndex = 0;
for (const group of view) {
const groupItems = group.items || [];
if (flatIndex < currentIndex + groupItems.length) {
return {
item: groupItems[flatIndex - currentIndex],
group: group.value
};
}
currentIndex += groupItems.length;
}
return null;
}
};
},
_setHeight: function(height) {
let heightContainer = this.heightContainer;
let currentHeight;
if (!heightContainer) {
heightContainer = this.heightContainer = appendChild(this.content[0], HEIGHTCONTAINER);
} else {
currentHeight = heightContainer.offsetHeight;
}
if (height !== currentHeight) {
heightContainer.innerHTML = "";
while (height > 0) {
const padHeight = Math.min(height, 25e4);
appendChild(heightContainer).style.height = padHeight + "px";
height -= padHeight;
}
}
},
_getter: function() {
const dataSource = this.dataSource;
const type = this.options.type;
const pageSize = this.itemCount;
const flatGroups = {};
let lastRequestedRange = null;
let lastRangeStart = dataSource.skip();
if (dataSource.pageSize() < pageSize) {
this.mute(function() {
dataSource.pageSize(pageSize);
});
}
return function(index, rangeStart) {
const that = this;
if (!dataSource.inRange(rangeStart, pageSize)) {
if (lastRequestedRange !== rangeStart) {
lastRequestedRange = rangeStart;
lastRangeStart = rangeStart;
if (that._getterDeferred) {
that._getterDeferred.reject();
}
that._getterDeferred = that.deferredRange(rangeStart);
that._getterDeferred.then(() => {
const firstItemIndex = that._indexConstraint(that.content[0].scrollTop);
that._getterDeferred = null;
if (rangeStart <= firstItemIndex && firstItemIndex <= rangeStart + pageSize) {
that._fetching = true;
dataSource.range(rangeStart, pageSize);
}
});
}
return null;
} else {
if (lastRangeStart !== rangeStart) {
this.mute(() => {
dataSource.range(rangeStart, pageSize);
lastRangeStart = rangeStart;
});
}
let result;
if (type === "group") {
if (!flatGroups[rangeStart]) {
const flatGroup = flatGroups[rangeStart] = [];
const groups = dataSource.view();
for (const group of groups) {
for (const item of group.items) {
flatGroup.push({
item,
group: group.value
});
}
}
}
result = flatGroups[rangeStart][index - rangeStart];
} else {
result = dataSource.view()[index - rangeStart];
}
return result;
}
};
},
_fixedHeader: function(scrollTop, list) {
const group = this.currentVisibleGroup;
const itemHeight = this.options.itemHeight;
const listItems = list.items || [];
const firstVisibleDataItemIndex = Math.floor((scrollTop - list.top) / itemHeight);
const firstVisibleDataItem = listItems[firstVisibleDataItemIndex];
if (firstVisibleDataItem?.item) {
const firstVisibleGroup = firstVisibleDataItem.group;
if (firstVisibleGroup !== group) {
const fixedGroupText = firstVisibleGroup || "";
this.header.html(this.templates.fixedGroupTemplate(fixedGroupText));
this.currentVisibleGroup = firstVisibleGroup;
}
}
return list;
},
_itemMapper: function(item, index, value) {
const listType = this.options.type;
const itemHeight = this.options.itemHeight;
const cssGap = this._cssGap || 0;
const currentIndex = this._focusedIndex;
const valueGetter = this._valueGetter;
let selected = false;
let current = false;
let newGroup = false;
let group = null;
if (listType === "group") {
if (item) {
newGroup = index === 0 || this._currentGroup !== false && this._currentGroup !== item.group;
this._currentGroup = item.group;
}
group = item ? item.group : null;
item = item ? item.item : null;
}
if (this.options.mapValueTo === "dataItem" && this._selectedDataItems.length && item) {
for (let i = 0; i < this._selectedDataItems.length; i++) {
if (valueGetter(this._selectedDataItems[i]) === valueGetter(item)) {
selected = true;
break;
}
}
} else if (!this.isFiltered() && value.length && item) {
for (let j = 0; j < value.length; j++) {
const match = isPrimitive(item) ? value[j] === item : value[j] === valueGetter(item);
if (match) {
value.splice(j, 1);
selected = true;
break;
}
}
}
if (currentIndex === index) {
current = true;
}
return {
item: item ? item : null,
group,
newGroup,
selected,
current,
index,
top: index * (itemHeight + cssGap)
};
},
_range: function(index) {
const itemCount = this.itemCount;
const value = this._values.slice();
const items = [];
this._view = {};
this._currentGroup = false;
for (let i = index, length = index + itemCount; i < length; i++) {
const item = this._itemMapper(this.getter(i, index), i, value);
if (items[items.length - 1]) {
items[items.length - 1].isLastGroupedItem = item.newGroup;
}
items.push(item);
this._view[item.index] = item;
}
this._dataView = items;
return items;
},
_getDataItemsCollection: function(scrollTop, lastScrollTop) {
const items = this._range(this._listIndex(scrollTop, lastScrollTop));
return {
index: items.length ? items[0].index : 0,
top: items.length ? items[0].top : 0,
items
};
},
_listItems: function() {
const screenHeight = this._screenHeight;
const options = this.options;
const theValidator = listValidator(options, screenHeight);
return function(value, force) {
let result = this.result;
const lastScrollTop = this._lastScrollTop;
if (force || !result || !theValidator(result, value, lastScrollTop)) {
result = this._getDataItemsCollection(value, lastScrollTop);
}
this._lastScrollTop = value;
this.result = result;
return result;
}.bind(this);
},
_whenChanged: function(getter, callback) {
let current;
return (force) => {
const theNew = getter(force);
if (theNew !== current) {
current = theNew;
callback(theNew, force);
}
};
},
_whenChangedGrouped: function(getter, callback) {
let currentPageStart = null;
let currentGroupKeys = null;
return (force) => {
const theNew = getter(force);
if (!theNew) {
return;
}
const newPageStart = theNew.pageStart;
const newGroups = theNew.pageGroups || [];
const newGroupKeys = newGroups.map((g) => `${g.globalGroupIndex}:${g.startIndex}:${g.itemCount}`).join(",");
const pageChanged = currentPageStart !== newPageStart;
const groupsChanged = currentGroupKeys !== newGroupKeys;
if (force || pageChanged || groupsChanged) {
currentPageStart = newPageStart;
currentGroupKeys = newGroupKeys;
callback(theNew, force);
}
};
},
_reorderList: function(list, reorder) {
const that = this;
const length = list.length;
let currentOffset = -Infinity;
reorder = map2(reorder, this.templates).bind(this);
return function(list2, offset, force) {
const diff = offset - currentOffset;
let range, range2;
if (force || Math.abs(diff) >= length) {
range = list;
range2 = list2;
} else {
range = reshift(list, diff);
range2 = diff > 0 ? list2.slice(-diff) : list2.slice(0, -diff);
}
reorder(range, range2, that.bound());
currentOffset = offset;
};
},
_bufferSizes: function() {
const options = this.options;
return bufferSizes(this._screenHeight, options.listScreens, options.oppositeBuffer);
},
_indexConstraint: function(position) {
const itemCount = this.itemCount;
const itemHeight = this.options.itemHeight;
const cssGap = this._cssGap || 0;
const effectiveItemHeight = itemHeight + cssGap;
const total = this.dataSource.total();
return Math.min(Math.max(total - itemCount, 0), Math.max(0, Math.floor(position / effectiveItemHeight)));
},
_listIndex: function(scrollTop, lastScrollTop) {
const buffers = this._bufferSizes();
const position = scrollTop - (scrollTop > lastScrollTop ? buffers.down : buffers.up);
return this._indexConstraint(position);
},
_selectable: function() {
if (this.options.selectable) {
this._selectProxy = this._clickHandler.bind(this);
this.element.on(CLICK + VIRTUAL_LIST_NS, "." + this._getItemClass(), this._selectProxy);
}
},
getElementIndex: function(element) {
if (!(element instanceof jQuery)) {
return undefined;
}
return parseInt(element.attr("data-offset-index"), 10);
},
_getIndecies: function(candidate) {
let result = [];
if (typeof candidate === "function") {
const data = this.dataSource.flatView();
for (let idx = 0; idx < data.length; idx++) {
if (candidate(data[idx])) {
result.push(idx);
break;
}
}
}
if (typeof candidate === "number") {
result.push(candidate);
}
const elementIndex = this.getElementIndex(candidate);
if (!isNaN(elementIndex)) {
result.push(elementIndex);
}
if (candidate instanceof Array) {
result = candidate;
}
return result;
},
_deselect: function(indices) {
const selectedIndexes = this._selectedIndexes;
const selectedDataItems = this._selectedDataItems;
const selectable = this.options.selectable;
const valueGetter = this._valueGetter;
const removed = [];
let position = 0;
let removedindexesCounter = 0;
indices = indices.slice();
if (selectable === true || !indices.length) {
for (let idx = 0; idx < selectedIndexes.length; idx++) {
if (selectedIndexes[idx] !== undefined) {
this._getElementByIndex(selectedIndexes[idx]).removeClass(SELECTED);
} else if (selectedDataItems[idx]) {
this._getElementByDataItem(selectedDataItems[idx]).removeClass(SELECTED);
}
removed.push({
index: selectedIndexes[idx],
position: idx,
dataItem: selectedDataItems[idx]
});
}
this._values = [];
this._selectedDataItems = [];
this._selectedIndexes = [];
} else if (selectable === "multiple") {
let i = 0;
while (i < indices.length) {
let result = null;
position = $.inArray(indices[i], selectedIndexes);
const dataItem = this.dataItemByIndex(indices[i]);
if (position === -1 && dataItem) {
for (let j = 0; j < selectedDataItems.length; j++) {
const match = isPrimitive(dataItem) ? selectedDataItems[j] === dataItem : valueGetter(selectedDataItems[j]) === valueGetter(dataItem);
if (match) {
const item = this._getElementByIndex(indices[i]);
result = this._deselectSingleItem(item, j, indices[i], removedindexesCounter);
}
}
} else {
const selectedIndex = selectedIndexes[position];
if (selectedIndex !== undefined) {
const item = this._getElementByIndex(selectedIndex);
result = this._deselectSingleItem(item, position, selectedIndex, removedindexesCounter);
}
}
if (result) {
indices.splice(i, 1);
removed.push(result);
removedindexesCounter++;
i--;
}
i++;
}
}
return {
indices,
removed
};
},
_deselectSingleItem: function(item, position, selectedIndex, removedindexesCounter) {
if (!item.hasClass(SELECTED)) {
return;
}
item.removeClass(SELECTED);
this._values.splice(position, 1);
this._selectedIndexes.splice(position, 1);
const dataItem = this._selectedDataItems.splice(position, 1)[0];
return {
index: selectedIndex,
position: position + removedindexesCounter,
dataItem
};
},
_deselectCurrentValues: function(indices) {
const items = this._items || [];
const values = this._values;
const removed = [];
if (this.options.selectable !== "multiple" || !this.isFiltered()) {
return [];
}
if (indices[0] === -1) {
$(items).removeClass(SELECTED);
const removedItems = $.map(this._selectedDataItems.slice(0), (dataItem, idx) => ({
dataItem,
position: idx
}));
this._selectedIndexes = [];
this._selectedDataItems = [];
this._values = [];
return removedItems;
}
for (let idx = 0; idx < indices.length; idx++) {
let position = -1;
const index = indices[idx];
let value;
if (this.dataItemByIndex(index)) {
value = this._valueGetter(this.dataItemByIndex(index));
}
for (let j = 0; j < values.length; j++) {
if (value == values[j]) {
position = j;
break;
}
}
if (position > -1) {
removed.push(this.removeAt(position));
$(items[index]).removeClass(SELECTED);
}
}
return removed;
},
_getSkip: function(index, take) {
const page = index < take ? 1 : Math.floor(index / take) + 1;
return (page - 1) * take;
},
_select: function(indexes) {
const that = this;
const singleSelection = this.options.selectable !== "multiple";
const dataSource = this.dataSource;
const take = this.itemCount;
const valueGetter = this._valueGetter;
const added = [];
if (singleSelection) {
that._selectedIndexes = [];
that._selectedDataItems = [];
that._values = [];
}
const oldSkip = dataSource.skip();
$.each(indexes, (_, index) => {
const skip = that._getSkip(index, take);
that.mute(() => {
dataSource.range(skip, take);
const dataItem = that._findDataItem(dataSource.view(), [index - skip]);
that._selectedIndexes.push(index);
that._selectedDataItems.push(dataItem);
that._values.push(isPrimitive(dataItem) ? dataItem : valueGetter(dataItem));
added.push({
index,
dataItem
});
that._getElementByIndex(index).addClass(SELECTED);
dataSource.range(oldSkip, take);
});
});
that._values = that._checkValuesOrder(that._values);
return added;
},
_clickHandler: function(e) {
const that = this;
const item = $(e.currentTarget);
const options = that.options;
const actionField = options.actionField;
if (!e.isDefaultPrevented() && item.attr("data-uid")) {
if (actionField) {
const dataItem = that.dataItemByIndex(that.getElementIndex(item));
if (dataItem && dataItem[actionField]) {
if (!that.trigger("action", {
item,
dataItem,
action: dataItem[actionField]
})) {
return;
}
}
}
this.trigger(CLICK, { item });
}
},
_buildValueGetter: function() {
this._valueGetter = kendo.getter(this.options.dataValueField);
},
_calculateGroupPadding: function(height) {
const firstItem = this.items().first();
const groupHeader = this.header;
let padding = 0;
if (groupHeader[0] && groupHeader[0].style.display !== "none") {
if (height !== "auto") {
padding = kendo.support.scrollbar();
}
padding += parseFloat(firstItem.css("border-right-width"), 10) + parseFloat(firstItem.children(".k-group").css("right"), 10);
groupHeader.css("padding-right", padding);
}
},
_calculateColumnsHeaderPadding: function() {
if (this._isTableVariant()) {
const isRtl = kendo.support.isRtl(this.wrapper);
const scrollbar = kendo.support.scrollbar();
const columnsHeader = this.content.parent().parent().find(".k-table-header");
const total = this.dataSource.total();
columnsHeader.css(isRtl ? "padding-left" : "padding-right", total ? scrollbar : 0);
}
}
});
kendo.ui.VirtualList = VirtualList;
kendo.ui.plugin(VirtualList);
})(window.kendo.jQuery);
var kendo_virtuallist_default = kendo;
//#endregion
export { kendo_virtuallist_default as n, __meta__ as t };