1152 lines
54 KiB
JavaScript
1152 lines
54 KiB
JavaScript
/**
|
|
* Kendo UI v2016.1.226 (http://www.telerik.com/kendo-ui)
|
|
* Copyright 2016 Telerik AD. All rights reserved.
|
|
*
|
|
* Kendo UI commercial licenses may be obtained at
|
|
* http://www.telerik.com/purchase/license-agreement/kendo-ui-complete
|
|
* If you do not own a commercial license, this file shall be governed by the trial license terms.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/
|
|
(function (f, define) {
|
|
define('kendo.virtuallist', ['kendo.data'], f);
|
|
}(function () {
|
|
var __meta__ = {
|
|
id: 'virtuallist',
|
|
name: 'VirtualList',
|
|
category: 'framework',
|
|
depends: ['data'],
|
|
hidden: true
|
|
};
|
|
(function ($, undefined) {
|
|
var kendo = window.kendo, ui = kendo.ui, Widget = ui.Widget, DataBoundWidget = ui.DataBoundWidget, proxy = $.proxy, WRAPPER = 'k-virtual-wrap', VIRTUALLIST = 'k-virtual-list', CONTENT = 'k-virtual-content', LIST = 'k-list', HEADER = 'k-group-header', VIRTUALITEM = 'k-virtual-item', ITEM = 'k-item', HEIGHTCONTAINER = 'k-height-container', GROUPITEM = 'k-group', SELECTED = 'k-state-selected', FOCUSED = 'k-state-focused', HOVER = 'k-state-hover', CHANGE = 'change', CLICK = 'click', LISTBOUND = 'listBound', ITEMCHANGE = 'itemChange', ACTIVATE = 'activate', DEACTIVATE = 'deactivate', 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) {
|
|
var element = document.createElement(tagName || 'div');
|
|
if (className) {
|
|
element.className = className;
|
|
}
|
|
parent.appendChild(element);
|
|
return element;
|
|
}
|
|
function getDefaultItemHeight() {
|
|
var mockList = $('<div class="k-popup"><ul class="k-list"><li class="k-item"><li></ul></div>'), lineHeight;
|
|
mockList.css({
|
|
position: 'absolute',
|
|
left: '-200000px',
|
|
visibility: 'hidden'
|
|
});
|
|
mockList.appendTo(document.body);
|
|
lineHeight = parseFloat(kendo.getComputedStyles(mockList.find('.k-item')[0], ['line-height'])['line-height']);
|
|
mockList.remove();
|
|
return lineHeight;
|
|
}
|
|
function bufferSizes(screenHeight, listScreens, opposite) {
|
|
return {
|
|
down: screenHeight * opposite,
|
|
up: screenHeight * (listScreens - 1 - opposite)
|
|
};
|
|
}
|
|
function listValidator(options, screenHeight) {
|
|
var downThreshold = (options.listScreens - 1 - options.threshold) * screenHeight;
|
|
var 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) {
|
|
if (kendo.support.browser.msie && kendo.support.browser.version < 10) {
|
|
element.style.top = y + 'px';
|
|
} else {
|
|
element.style.webkitTransform = 'translateY(' + y + 'px)';
|
|
element.style.transform = 'translateY(' + y + 'px)';
|
|
}
|
|
}
|
|
function map2(callback, templates) {
|
|
return function (arr1, arr2) {
|
|
for (var 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) {
|
|
var range;
|
|
if (diff > 0) {
|
|
range = items.splice(0, diff);
|
|
items.push.apply(items, range);
|
|
} else {
|
|
range = items.splice(diff, -diff);
|
|
items.unshift.apply(items, range);
|
|
}
|
|
return range;
|
|
}
|
|
function render(element, data, templates) {
|
|
var itemTemplate = templates.template;
|
|
element = $(element);
|
|
if (!data.item) {
|
|
itemTemplate = templates.placeholderTemplate;
|
|
}
|
|
this.angular('cleanup', function () {
|
|
return { elements: [element] };
|
|
});
|
|
element.attr('data-uid', data.item ? data.item.uid : '').attr('data-offset-index', data.index).html(itemTemplate(data.item || {}));
|
|
element.toggleClass(FOCUSED, data.current);
|
|
element.toggleClass(SELECTED, data.selected);
|
|
element.toggleClass('k-first', data.newGroup);
|
|
element.toggleClass('k-loading-item', !data.item);
|
|
if (data.index !== 0 && data.newGroup) {
|
|
$('<div class=' + GROUPITEM + '></div>').appendTo(element).html(templates.groupTemplate(data.group));
|
|
}
|
|
if (data.top !== undefined) {
|
|
position(element[0], data.top);
|
|
}
|
|
this.angular('compile', function () {
|
|
return {
|
|
elements: [element],
|
|
data: [{
|
|
dataItem: data.item,
|
|
group: data.group,
|
|
newGroup: data.newGroup
|
|
}]
|
|
};
|
|
});
|
|
}
|
|
function mapChangedItems(selected, itemsToMatch) {
|
|
var itemsLength = itemsToMatch.length;
|
|
var selectedLength = selected.length;
|
|
var dataItem;
|
|
var found;
|
|
var i, j;
|
|
var changed = [];
|
|
var unchanged = [];
|
|
if (selectedLength) {
|
|
for (i = 0; i < selectedLength; i++) {
|
|
dataItem = selected[i];
|
|
found = false;
|
|
for (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: changed,
|
|
unchanged: unchanged
|
|
};
|
|
}
|
|
var VirtualList = DataBoundWidget.extend({
|
|
init: function (element, options) {
|
|
var that = this;
|
|
that.bound(false);
|
|
that._fetching = false;
|
|
Widget.fn.init.call(that, element, options);
|
|
if (!that.options.itemHeight) {
|
|
that.options.itemHeight = getDefaultItemHeight();
|
|
}
|
|
options = that.options;
|
|
that.element.addClass(LIST + ' ' + VIRTUALLIST).attr('role', 'listbox');
|
|
that.content = that.element.wrap('<div unselectable=\'on\' class=\'' + CONTENT + '\'></div>').parent();
|
|
that.wrapper = that.content.wrap('<div class=\'' + WRAPPER + '\'></div>').parent();
|
|
that.header = that.content.before('<div class=\'' + HEADER + '\'></div>').prev();
|
|
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._activeDeferred = null;
|
|
that._promisesList = [];
|
|
that._optionID = kendo.guid();
|
|
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: 0.5,
|
|
itemHeight: null,
|
|
oppositeBuffer: 1,
|
|
type: 'flat',
|
|
selectable: false,
|
|
value: [],
|
|
dataValueField: null,
|
|
template: '#:data#',
|
|
placeholderTemplate: 'loading...',
|
|
groupTemplate: '#:data#',
|
|
fixedGroupTemplate: 'fixed header template',
|
|
valueMapper: null
|
|
},
|
|
events: [
|
|
CHANGE,
|
|
CLICK,
|
|
LISTBOUND,
|
|
ITEMCHANGE,
|
|
ACTIVATE,
|
|
DEACTIVATE
|
|
],
|
|
setOptions: function (options) {
|
|
Widget.fn.setOptions.call(this, options);
|
|
if (this._selectProxy && this.options.selectable === false) {
|
|
this.element.off(CLICK, '.' + VIRTUALITEM, this._selectProxy);
|
|
} else if (!this._selectProxy && this.options.selectable) {
|
|
this._selectable();
|
|
}
|
|
this.refresh();
|
|
},
|
|
items: function () {
|
|
return $(this._items);
|
|
},
|
|
destroy: function () {
|
|
this.wrapper.off(VIRTUAL_LIST_NS);
|
|
this.dataSource.unbind(CHANGE, this._refreshHandler);
|
|
Widget.fn.destroy.call(this);
|
|
},
|
|
setDataSource: function (source) {
|
|
var that = this;
|
|
var dataSource = source || {};
|
|
var value;
|
|
dataSource = $.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;
|
|
value = that.value();
|
|
that.value([]);
|
|
that.mute(function () {
|
|
that.value(value);
|
|
});
|
|
} else {
|
|
that._refreshHandler = $.proxy(that.refresh, 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 () {
|
|
var that = this;
|
|
var skip = that.skip();
|
|
if (that.bound() && !that._selectingValue && that._skip !== skip) {
|
|
that._skip = skip;
|
|
that.trigger(LISTBOUND);
|
|
}
|
|
},
|
|
_getValues: function (dataItems) {
|
|
var getter = this._valueGetter;
|
|
return $.map(dataItems, function (dataItem) {
|
|
return getter(dataItem);
|
|
});
|
|
},
|
|
refresh: function (e) {
|
|
var that = this;
|
|
var action = e && e.action;
|
|
var isItemChange = action === 'itemchange';
|
|
var filtered = this.isFiltered();
|
|
var result;
|
|
if (that._mute) {
|
|
return;
|
|
}
|
|
that._deferValueSet = false;
|
|
if (!that._fetching) {
|
|
if (filtered) {
|
|
that.focus(0);
|
|
}
|
|
that._createList();
|
|
if (!action && that._values.length && !filtered && !that.options.skipUpdateOnBind) {
|
|
that._selectingValue = true;
|
|
that.value(that._values, true).done(function () {
|
|
that.bound(true);
|
|
that._selectingValue = false;
|
|
that._triggerListBound();
|
|
});
|
|
} else {
|
|
that.bound(true);
|
|
that._triggerListBound();
|
|
}
|
|
} else {
|
|
if (that._renderItems) {
|
|
that._renderItems(true);
|
|
}
|
|
that._triggerListBound();
|
|
}
|
|
if (isItemChange || action === 'remove') {
|
|
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) {
|
|
this._selectedIndexes.splice(position, 1);
|
|
this._values.splice(position, 1);
|
|
return {
|
|
position: position,
|
|
dataItem: this._selectedDataItems.splice(position, 1)[0]
|
|
};
|
|
},
|
|
setValue: function (value) {
|
|
this._values = toArray(value);
|
|
},
|
|
value: function (value, _forcePrefetch) {
|
|
var that = this;
|
|
if (value === undefined) {
|
|
return that._values.slice();
|
|
}
|
|
if (value === null) {
|
|
value = [];
|
|
}
|
|
value = toArray(value);
|
|
if (that.options.selectable === 'multiple' && that.select().length && value.length) {
|
|
that.select(-1);
|
|
}
|
|
if (!that._valueDeferred || that._valueDeferred.state() === 'resolved') {
|
|
that._valueDeferred = $.Deferred();
|
|
}
|
|
if (!value.length) {
|
|
that.select(-1);
|
|
}
|
|
that._values = value;
|
|
if (that.bound() && !that._mute && !that._deferValueSet || _forcePrefetch) {
|
|
that._prefetchByValue(value);
|
|
}
|
|
return that._valueDeferred;
|
|
},
|
|
_prefetchByValue: function (value) {
|
|
var that = this, dataView = that._dataView, valueGetter = that._valueGetter, item, match = false, forSelection = [];
|
|
for (var i = 0; i < value.length; i++) {
|
|
for (var idx = 0; idx < dataView.length; idx++) {
|
|
item = dataView[idx].item;
|
|
if (item) {
|
|
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') {
|
|
that.options.valueMapper({
|
|
value: this.options.selectable === 'multiple' ? value : value[0],
|
|
success: function (indexes) {
|
|
that._values = [];
|
|
that._selectedIndexes = [];
|
|
that._selectedDataItems = [];
|
|
indexes = toArray(indexes);
|
|
if (!indexes.length) {
|
|
indexes = [-1];
|
|
}
|
|
that.select(indexes);
|
|
}
|
|
});
|
|
} else {
|
|
throw new Error('valueMapper is not provided');
|
|
}
|
|
},
|
|
deferredRange: function (index) {
|
|
var dataSource = this.dataSource;
|
|
var take = this.itemCount;
|
|
var ranges = this._rangesList;
|
|
var result = $.Deferred();
|
|
var defs = [];
|
|
var low = Math.floor(index / take) * take;
|
|
var high = Math.ceil(index / take) * take;
|
|
var pages = high === low ? [high] : [
|
|
low,
|
|
high
|
|
];
|
|
$.each(pages, function (_, skip) {
|
|
var end = skip + take;
|
|
var existingRange = ranges[skip];
|
|
var deferred;
|
|
if (!existingRange || existingRange.end !== end) {
|
|
deferred = $.Deferred();
|
|
ranges[skip] = {
|
|
end: end,
|
|
deferred: deferred
|
|
};
|
|
dataSource._multiplePrefetch(skip, take, function () {
|
|
deferred.resolve();
|
|
});
|
|
} else {
|
|
deferred = existingRange.deferred;
|
|
}
|
|
defs.push(deferred);
|
|
});
|
|
$.when.apply($, defs).then(function () {
|
|
result.resolve();
|
|
});
|
|
return result;
|
|
},
|
|
prefetch: function (indexes) {
|
|
var that = this, take = this.itemCount, isEmptyList = !that._promisesList.length;
|
|
if (!that._activeDeferred) {
|
|
that._activeDeferred = $.Deferred();
|
|
that._promisesList = [];
|
|
}
|
|
$.each(indexes, function (_, index) {
|
|
var rangeStart = Math.floor(index / take) * take;
|
|
that._promisesList.push(that.deferredRange(rangeStart));
|
|
});
|
|
if (isEmptyList) {
|
|
$.when.apply($, that._promisesList).done(function () {
|
|
that._activeDeferred.resolve();
|
|
that._activeDeferred = null;
|
|
that._promisesList = [];
|
|
});
|
|
}
|
|
return that._activeDeferred;
|
|
},
|
|
_findDataItem: function (index) {
|
|
var view = this.dataSource.view(), group;
|
|
if (this.options.type === 'group') {
|
|
for (var i = 0; i < view.length; i++) {
|
|
group = view[i].items;
|
|
if (group.length <= index) {
|
|
index = index - group.length;
|
|
} else {
|
|
return group[index];
|
|
}
|
|
}
|
|
}
|
|
return view[index];
|
|
},
|
|
selectedDataItems: function () {
|
|
return this._selectedDataItems.slice();
|
|
},
|
|
scrollTo: function (y) {
|
|
this.content.scrollTop(y);
|
|
},
|
|
scrollToIndex: function (index) {
|
|
this.scrollTo(index * this.options.itemHeight);
|
|
},
|
|
focus: function (candidate) {
|
|
var element, index, data, current, itemHeight = this.options.itemHeight, id = this._optionID, triggerEvent = true;
|
|
if (candidate === undefined) {
|
|
current = this.element.find('.' + FOCUSED);
|
|
return current.length ? current : null;
|
|
}
|
|
if (typeof candidate === 'function') {
|
|
data = this.dataSource.flatView();
|
|
for (var 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 = this._getElementByIndex(index);
|
|
}
|
|
if (index === -1) {
|
|
this.element.find('.' + FOCUSED).removeClass(FOCUSED);
|
|
this._focusedIndex = undefined;
|
|
return;
|
|
}
|
|
if (element.length) {
|
|
if (element.hasClass(FOCUSED)) {
|
|
triggerEvent = false;
|
|
}
|
|
if (this._focusedIndex !== undefined) {
|
|
current = this._getElementByIndex(this._focusedIndex);
|
|
current.removeClass(FOCUSED).removeAttr('id');
|
|
if (triggerEvent) {
|
|
this.trigger(DEACTIVATE);
|
|
}
|
|
}
|
|
this._focusedIndex = index;
|
|
element.addClass(FOCUSED).attr('id', id);
|
|
var position = this._getElementLocation(index);
|
|
if (position === 'top') {
|
|
this.scrollTo(index * itemHeight);
|
|
} else if (position === 'bottom') {
|
|
this.scrollTo(index * itemHeight + itemHeight - this.screenHeight);
|
|
} else if (position === 'outScreen') {
|
|
this.scrollTo(index * itemHeight);
|
|
}
|
|
if (triggerEvent) {
|
|
this.trigger(ACTIVATE);
|
|
}
|
|
} else {
|
|
this._focusedIndex = index;
|
|
this.items().removeClass(FOCUSED);
|
|
this.scrollToIndex(index);
|
|
}
|
|
},
|
|
focusIndex: function () {
|
|
return this._focusedIndex;
|
|
},
|
|
focusFirst: function () {
|
|
this.scrollTo(0);
|
|
this.focus(0);
|
|
},
|
|
focusLast: function () {
|
|
var lastIndex = this.dataSource.total();
|
|
this.scrollTo(this.heightContainer.offsetHeight);
|
|
this.focus(lastIndex);
|
|
},
|
|
focusPrev: function () {
|
|
var index = this._focusedIndex;
|
|
var current;
|
|
if (!isNaN(index) && index > 0) {
|
|
index -= 1;
|
|
this.focus(index);
|
|
current = this.focus();
|
|
if (current && 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 () {
|
|
var index = this._focusedIndex;
|
|
var lastIndex = this.dataSource.total() - 1;
|
|
var current;
|
|
if (!isNaN(index) && index < lastIndex) {
|
|
index += 1;
|
|
this.focus(index);
|
|
current = this.focus();
|
|
if (current && current.hasClass('k-loading-item')) {
|
|
index -= 1;
|
|
this.focus(index);
|
|
}
|
|
return index;
|
|
} else {
|
|
index = 0;
|
|
this.focus(index);
|
|
return index;
|
|
}
|
|
},
|
|
_triggerChange: function (removed, added) {
|
|
removed = removed || [];
|
|
added = added || [];
|
|
if (removed.length || added.length) {
|
|
this.trigger(CHANGE, {
|
|
removed: removed,
|
|
added: added
|
|
});
|
|
}
|
|
},
|
|
select: function (candidate) {
|
|
var that = this, indices, singleSelection = that.options.selectable !== 'multiple', prefetchStarted = !!that._activeDeferred, filtered = this.isFiltered(), isAlreadySelected, deferred, result, removed = [];
|
|
if (candidate === undefined) {
|
|
return that._selectedIndexes.slice();
|
|
}
|
|
indices = that._getIndecies(candidate);
|
|
isAlreadySelected = singleSelection && !filtered && lastFrom(indices) === lastFrom(this._selectedIndexes);
|
|
removed = that._deselectCurrentValues(indices);
|
|
if (removed.length || !indices.length || isAlreadySelected) {
|
|
that._triggerChange(removed);
|
|
if (that._valueDeferred) {
|
|
that._valueDeferred.resolve();
|
|
}
|
|
return;
|
|
}
|
|
if (indices.length === 1 && indices[0] === -1) {
|
|
indices = [];
|
|
}
|
|
result = that._deselect(indices);
|
|
removed = result.removed;
|
|
indices = result.indices;
|
|
if (singleSelection) {
|
|
that._activeDeferred = null;
|
|
prefetchStarted = false;
|
|
if (indices.length) {
|
|
indices = [lastFrom(indices)];
|
|
}
|
|
}
|
|
var done = function () {
|
|
var added = that._select(indices);
|
|
that.focus(indices);
|
|
that._triggerChange(removed, added);
|
|
if (that._valueDeferred) {
|
|
that._valueDeferred.resolve();
|
|
}
|
|
};
|
|
deferred = that.prefetch(indices);
|
|
if (!prefetchStarted) {
|
|
if (deferred) {
|
|
deferred.done(done);
|
|
} else {
|
|
done();
|
|
}
|
|
}
|
|
},
|
|
bound: function (bound) {
|
|
if (bound === undefined) {
|
|
return this._listCreated;
|
|
}
|
|
this._listCreated = bound;
|
|
},
|
|
mute: function (callback) {
|
|
this._mute = true;
|
|
proxy(callback(), this);
|
|
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(function (idx, element) {
|
|
return index === parseInt($(element).attr('data-offset-index'), 10);
|
|
});
|
|
},
|
|
_clean: function () {
|
|
this.result = undefined;
|
|
this._lastScrollTop = undefined;
|
|
this._skip = undefined;
|
|
$(this.heightContainer).remove();
|
|
this.heightContainer = undefined;
|
|
this.element.empty();
|
|
},
|
|
_height: function () {
|
|
var hasData = !!this.dataSource.view().length, height = this.options.height, itemHeight = this.options.itemHeight, total = this.dataSource.total();
|
|
if (!hasData) {
|
|
height = 0;
|
|
} else if (height / itemHeight > total) {
|
|
height = total * itemHeight;
|
|
}
|
|
return height;
|
|
},
|
|
_screenHeight: function () {
|
|
var height = this._height(), content = this.content;
|
|
content.height(height);
|
|
this.screenHeight = height;
|
|
},
|
|
_getElementLocation: function (index) {
|
|
var scrollTop = this.content.scrollTop(), screenHeight = this.screenHeight, itemHeight = this.options.itemHeight, yPosition = index * itemHeight, yDownPostion = yPosition + itemHeight, screenEnd = scrollTop + screenHeight, position;
|
|
if (yPosition === scrollTop - itemHeight || yDownPostion > scrollTop && yPosition < scrollTop) {
|
|
position = 'top';
|
|
} else if (yPosition === screenEnd || yPosition < screenEnd && screenEnd < yDownPostion) {
|
|
position = 'bottom';
|
|
} else if (yPosition >= scrollTop && yPosition <= scrollTop + (screenHeight - itemHeight)) {
|
|
position = 'inScreen';
|
|
} else {
|
|
position = 'outScreen';
|
|
}
|
|
return position;
|
|
},
|
|
_templates: function () {
|
|
var templates = {
|
|
template: this.options.template,
|
|
placeholderTemplate: this.options.placeholderTemplate,
|
|
groupTemplate: this.options.groupTemplate,
|
|
fixedGroupTemplate: this.options.fixedGroupTemplate
|
|
};
|
|
for (var key in templates) {
|
|
if (typeof templates[key] !== 'function') {
|
|
templates[key] = kendo.template(templates[key]);
|
|
}
|
|
}
|
|
this.templates = templates;
|
|
},
|
|
_generateItems: function (element, count) {
|
|
var items = [], item, itemHeight = this.options.itemHeight + 'px';
|
|
while (count-- > 0) {
|
|
item = document.createElement('li');
|
|
item.tabIndex = -1;
|
|
item.className = VIRTUALITEM + ' ' + ITEM;
|
|
item.setAttribute('role', 'option');
|
|
item.style.height = itemHeight;
|
|
item.style.minHeight = itemHeight;
|
|
element.appendChild(item);
|
|
items.push(item);
|
|
}
|
|
return items;
|
|
},
|
|
_saveInitialRanges: function () {
|
|
var ranges = this.dataSource._ranges;
|
|
var deferred = $.Deferred();
|
|
deferred.resolve();
|
|
this._rangesList = {};
|
|
for (var i = 0; i < ranges.length; i++) {
|
|
this._rangesList[ranges[i].start] = {
|
|
end: ranges[i].end,
|
|
deferred: deferred
|
|
};
|
|
}
|
|
},
|
|
_createList: function () {
|
|
var that = this, content = that.content.get(0), options = that.options, dataSource = that.dataSource;
|
|
if (that.bound()) {
|
|
that._clean();
|
|
}
|
|
that._saveInitialRanges();
|
|
that._screenHeight();
|
|
that._buildValueGetter();
|
|
that.itemCount = getItemCount(that.screenHeight, options.listScreens, options.itemHeight);
|
|
if (that.itemCount > dataSource.total()) {
|
|
that.itemCount = dataSource.total();
|
|
}
|
|
that._templates();
|
|
that._items = that._generateItems(that.element[0], that.itemCount);
|
|
that._setHeight(options.itemHeight * dataSource.total());
|
|
that.options.type = (dataSource.group() || []).length ? 'group' : 'flat';
|
|
if (that.options.type === 'flat') {
|
|
that.header.hide();
|
|
} else {
|
|
that.header.show();
|
|
}
|
|
that.getter = that._getter(function () {
|
|
that._renderItems(true);
|
|
});
|
|
that._onScroll = function (scrollTop, force) {
|
|
var 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, $.proxy(render, that))));
|
|
that._renderItems();
|
|
that._calculateGroupPadding(that.screenHeight);
|
|
},
|
|
_setHeight: function (height) {
|
|
var currentHeight, heightContainer = this.heightContainer;
|
|
if (!heightContainer) {
|
|
heightContainer = this.heightContainer = appendChild(this.content[0], HEIGHTCONTAINER);
|
|
} else {
|
|
currentHeight = heightContainer.offsetHeight;
|
|
}
|
|
if (height !== currentHeight) {
|
|
heightContainer.innerHTML = '';
|
|
while (height > 0) {
|
|
var padHeight = Math.min(height, 250000);
|
|
appendChild(heightContainer).style.height = padHeight + 'px';
|
|
height -= padHeight;
|
|
}
|
|
}
|
|
},
|
|
_getter: function () {
|
|
var lastRequestedRange = null, dataSource = this.dataSource, lastRangeStart = dataSource.skip(), type = this.options.type, pageSize = this.itemCount, flatGroups = {};
|
|
if (dataSource.pageSize() < pageSize) {
|
|
this.mute(function () {
|
|
dataSource.pageSize(pageSize);
|
|
});
|
|
}
|
|
return function (index, rangeStart) {
|
|
var 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(function () {
|
|
var 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(function () {
|
|
dataSource.range(rangeStart, pageSize);
|
|
lastRangeStart = rangeStart;
|
|
});
|
|
}
|
|
var result;
|
|
if (type === 'group') {
|
|
if (!flatGroups[rangeStart]) {
|
|
var flatGroup = flatGroups[rangeStart] = [];
|
|
var groups = dataSource.view();
|
|
for (var i = 0, len = groups.length; i < len; i++) {
|
|
var group = groups[i];
|
|
for (var j = 0, groupLength = group.items.length; j < groupLength; j++) {
|
|
flatGroup.push({
|
|
item: group.items[j],
|
|
group: group.value
|
|
});
|
|
}
|
|
}
|
|
}
|
|
result = flatGroups[rangeStart][index - rangeStart];
|
|
} else {
|
|
result = dataSource.view()[index - rangeStart];
|
|
}
|
|
return result;
|
|
}
|
|
};
|
|
},
|
|
_fixedHeader: function (scrollTop, list) {
|
|
var group = this.currentVisibleGroup, itemHeight = this.options.itemHeight, firstVisibleDataItemIndex = Math.floor((scrollTop - list.top) / itemHeight), firstVisibleDataItem = list.items[firstVisibleDataItemIndex];
|
|
if (firstVisibleDataItem && firstVisibleDataItem.item) {
|
|
var firstVisibleGroup = firstVisibleDataItem.group;
|
|
if (firstVisibleGroup !== group) {
|
|
this.header[0].innerHTML = firstVisibleGroup || '';
|
|
this.currentVisibleGroup = firstVisibleGroup;
|
|
}
|
|
}
|
|
return list;
|
|
},
|
|
_itemMapper: function (item, index, value) {
|
|
var listType = this.options.type, itemHeight = this.options.itemHeight, currentIndex = this._focusedIndex, selected = false, current = false, newGroup = false, group = null, match = false, valueGetter = this._valueGetter;
|
|
if (listType === 'group') {
|
|
if (item) {
|
|
newGroup = index === 0 || this._currentGroup && this._currentGroup !== item.group;
|
|
this._currentGroup = item.group;
|
|
}
|
|
group = item ? item.group : null;
|
|
item = item ? item.item : null;
|
|
}
|
|
if (!this.isFiltered() && value.length && item) {
|
|
for (var i = 0; i < value.length; i++) {
|
|
match = isPrimitive(item) ? value[i] === item : value[i] === valueGetter(item);
|
|
if (match) {
|
|
value.splice(i, 1);
|
|
selected = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (currentIndex === index) {
|
|
current = true;
|
|
}
|
|
return {
|
|
item: item ? item : null,
|
|
group: group,
|
|
newGroup: newGroup,
|
|
selected: selected,
|
|
current: current,
|
|
index: index,
|
|
top: index * itemHeight
|
|
};
|
|
},
|
|
_range: function (index) {
|
|
var itemCount = this.itemCount, value = this._values.slice(), items = [], item;
|
|
this._view = {};
|
|
this._currentGroup = null;
|
|
for (var i = index, length = index + itemCount; i < length; i++) {
|
|
item = this._itemMapper(this.getter(i, index), i, value);
|
|
items.push(item);
|
|
this._view[item.index] = item;
|
|
}
|
|
this._dataView = items;
|
|
return items;
|
|
},
|
|
_getDataItemsCollection: function (scrollTop, lastScrollTop) {
|
|
var items = this._range(this._listIndex(scrollTop, lastScrollTop));
|
|
return {
|
|
index: items.length ? items[0].index : 0,
|
|
top: items.length ? items[0].top : 0,
|
|
items: items
|
|
};
|
|
},
|
|
_listItems: function () {
|
|
var screenHeight = this.screenHeight, options = this.options;
|
|
var theValidator = listValidator(options, screenHeight);
|
|
return $.proxy(function (value, force) {
|
|
var result = this.result, lastScrollTop = this._lastScrollTop;
|
|
if (force || !result || !theValidator(result, value, lastScrollTop)) {
|
|
result = this._getDataItemsCollection(value, lastScrollTop);
|
|
}
|
|
this._lastScrollTop = value;
|
|
this.result = result;
|
|
return result;
|
|
}, this);
|
|
},
|
|
_whenChanged: function (getter, callback) {
|
|
var current;
|
|
return function (force) {
|
|
var theNew = getter(force);
|
|
if (theNew !== current) {
|
|
current = theNew;
|
|
callback(theNew, force);
|
|
}
|
|
};
|
|
},
|
|
_reorderList: function (list, reorder) {
|
|
var that = this;
|
|
var length = list.length;
|
|
var currentOffset = -Infinity;
|
|
reorder = $.proxy(map2(reorder, this.templates), this);
|
|
return function (list2, offset, force) {
|
|
var diff = offset - currentOffset;
|
|
var 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 () {
|
|
var options = this.options;
|
|
return bufferSizes(this.screenHeight, options.listScreens, options.oppositeBuffer);
|
|
},
|
|
_indexConstraint: function (position) {
|
|
var itemCount = this.itemCount, itemHeight = this.options.itemHeight, total = this.dataSource.total();
|
|
return Math.min(Math.max(total - itemCount, 0), Math.max(0, Math.floor(position / itemHeight)));
|
|
},
|
|
_listIndex: function (scrollTop, lastScrollTop) {
|
|
var buffers = this._bufferSizes(), position;
|
|
position = scrollTop - (scrollTop > lastScrollTop ? buffers.down : buffers.up);
|
|
return this._indexConstraint(position);
|
|
},
|
|
_selectable: function () {
|
|
if (this.options.selectable) {
|
|
this._selectProxy = $.proxy(this, '_clickHandler');
|
|
this.element.on(CLICK + VIRTUAL_LIST_NS, '.' + VIRTUALITEM, this._selectProxy);
|
|
}
|
|
},
|
|
_getIndecies: function (candidate) {
|
|
var result = [], data;
|
|
if (typeof candidate === 'function') {
|
|
data = this.dataSource.flatView();
|
|
for (var idx = 0; idx < data.length; idx++) {
|
|
if (candidate(data[idx])) {
|
|
result.push(idx);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (typeof candidate === 'number') {
|
|
result.push(candidate);
|
|
}
|
|
if (candidate instanceof jQuery) {
|
|
candidate = parseInt(candidate.attr('data-offset-index'), 10);
|
|
if (!isNaN(candidate)) {
|
|
result.push(candidate);
|
|
}
|
|
}
|
|
if (candidate instanceof Array) {
|
|
result = candidate;
|
|
}
|
|
return result;
|
|
},
|
|
_deselect: function (indices) {
|
|
var removed = [], selectedIndex, dataItem, selectedIndexes = this._selectedIndexes, position = 0, selectable = this.options.selectable, removedindexesCounter = 0, item;
|
|
indices = indices.slice();
|
|
if (selectable === true || !indices.length) {
|
|
for (var idx = 0; idx < selectedIndexes.length; idx++) {
|
|
if (selectedIndexes[idx] !== undefined) {
|
|
this._getElementByIndex(selectedIndexes[idx]).removeClass(SELECTED);
|
|
removed.push({
|
|
index: selectedIndexes[idx],
|
|
position: idx,
|
|
dataItem: this._selectedDataItems[idx]
|
|
});
|
|
}
|
|
}
|
|
this._values = [];
|
|
this._selectedDataItems = [];
|
|
this._selectedIndexes = [];
|
|
} else if (selectable === 'multiple') {
|
|
for (var i = 0; i < indices.length; i++) {
|
|
position = $.inArray(indices[i], selectedIndexes);
|
|
selectedIndex = selectedIndexes[position];
|
|
if (selectedIndex !== undefined) {
|
|
item = this._getElementByIndex(selectedIndex);
|
|
if (!item.hasClass('k-state-selected')) {
|
|
continue;
|
|
}
|
|
item.removeClass(SELECTED);
|
|
this._values.splice(position, 1);
|
|
this._selectedIndexes.splice(position, 1);
|
|
dataItem = this._selectedDataItems.splice(position, 1)[0];
|
|
indices.splice(i, 1);
|
|
removed.push({
|
|
index: selectedIndex,
|
|
position: position + removedindexesCounter,
|
|
dataItem: dataItem
|
|
});
|
|
removedindexesCounter++;
|
|
i--;
|
|
}
|
|
}
|
|
}
|
|
return {
|
|
indices: indices,
|
|
removed: removed
|
|
};
|
|
},
|
|
_deselectCurrentValues: function (indices) {
|
|
var children = this.element[0].children;
|
|
var value, index, position;
|
|
var values = this._values;
|
|
var removed = [];
|
|
var idx = 0;
|
|
var j;
|
|
if (this.options.selectable !== 'multiple' || !this.isFiltered()) {
|
|
return [];
|
|
}
|
|
for (; idx < indices.length; idx++) {
|
|
position = -1;
|
|
index = indices[idx];
|
|
value = this._valueGetter(this._view[index].item);
|
|
for (j = 0; j < values.length; j++) {
|
|
if (value == values[j]) {
|
|
position = j;
|
|
break;
|
|
}
|
|
}
|
|
if (position > -1) {
|
|
removed.push(this.removeAt(position));
|
|
$(children[index]).removeClass('k-state-selected');
|
|
}
|
|
}
|
|
return removed;
|
|
},
|
|
_select: function (indexes) {
|
|
var that = this, singleSelection = this.options.selectable !== 'multiple', dataSource = this.dataSource, dataItem, oldSkip, take = this.itemCount, valueGetter = this._valueGetter, added = [];
|
|
if (singleSelection) {
|
|
that._selectedIndexes = [];
|
|
that._selectedDataItems = [];
|
|
that._values = [];
|
|
}
|
|
oldSkip = dataSource.skip();
|
|
$.each(indexes, function (_, index) {
|
|
var page = index < take ? 1 : Math.floor(index / take) + 1;
|
|
var skip = (page - 1) * take;
|
|
that.mute(function () {
|
|
dataSource.range(skip, take);
|
|
dataItem = that._findDataItem([index - skip]);
|
|
that._selectedIndexes.push(index);
|
|
that._selectedDataItems.push(dataItem);
|
|
that._values.push(isPrimitive(dataItem) ? dataItem : valueGetter(dataItem));
|
|
added.push({
|
|
index: index,
|
|
dataItem: dataItem
|
|
});
|
|
that._getElementByIndex(index).addClass(SELECTED);
|
|
dataSource.range(oldSkip, take);
|
|
});
|
|
});
|
|
return added;
|
|
},
|
|
_clickHandler: function (e) {
|
|
var item = $(e.currentTarget);
|
|
if (!e.isDefaultPrevented() && item.attr('data-uid')) {
|
|
this.trigger(CLICK, { item: item });
|
|
}
|
|
},
|
|
_buildValueGetter: function () {
|
|
this._valueGetter = kendo.getter(this.options.dataValueField);
|
|
},
|
|
_calculateGroupPadding: function (height) {
|
|
var firstItem = this.items().first(), groupHeader = this.header, 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);
|
|
}
|
|
}
|
|
});
|
|
kendo.ui.VirtualList = VirtualList;
|
|
kendo.ui.plugin(VirtualList);
|
|
}(window.kendo.jQuery));
|
|
return window.kendo;
|
|
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
|
|
(a3 || a2)();
|
|
})); |