468 lines
15 KiB
JavaScript
468 lines
15 KiB
JavaScript
/*===============================================================================
|
|
************ Virtual List ************
|
|
===============================================================================*/
|
|
var VirtualList = function (listBlock, params) {
|
|
var defaults = {
|
|
cols: 1,
|
|
height: app.params.material ? 48 : 44,
|
|
cache: true,
|
|
dynamicHeightBufferSize: 1,
|
|
showFilteredItemsOnly: false
|
|
};
|
|
params = params || {};
|
|
for (var def in defaults) {
|
|
if (typeof params[def] === 'undefined') {
|
|
params[def] = defaults[def];
|
|
}
|
|
}
|
|
|
|
// Preparation
|
|
var vl = this;
|
|
vl.listBlock = $(listBlock);
|
|
vl.params = params;
|
|
vl.items = vl.params.items;
|
|
if (vl.params.showFilteredItemsOnly) {
|
|
vl.filteredItems = [];
|
|
}
|
|
if (vl.params.template) {
|
|
if (typeof vl.params.template === 'string') vl.template = t7.compile(vl.params.template);
|
|
else if (typeof vl.params.template === 'function') vl.template = vl.params.template;
|
|
}
|
|
vl.pageContent = vl.listBlock.parents('.page-content');
|
|
|
|
// Bad scroll
|
|
var updatableScroll;
|
|
if (typeof vl.params.updatableScroll !== 'undefined') {
|
|
updatableScroll = vl.params.updatableScroll;
|
|
}
|
|
else {
|
|
updatableScroll = true;
|
|
if (app.device.ios && app.device.osVersion.split('.')[0] < 8) {
|
|
updatableScroll = false;
|
|
}
|
|
}
|
|
|
|
// Append <ul>
|
|
vl.ul = vl.params.ul ? $(vl.params.ul) : vl.listBlock.children('ul');
|
|
if (vl.ul.length === 0) {
|
|
vl.listBlock.append('<ul></ul>');
|
|
vl.ul = vl.listBlock.children('ul');
|
|
}
|
|
|
|
// DOM cached items
|
|
vl.domCache = {};
|
|
vl.displayDomCache = {};
|
|
|
|
// Temporary DOM Element
|
|
vl.tempDomElement = document.createElement('ul');
|
|
|
|
// Last repain position
|
|
vl.lastRepaintY = null;
|
|
|
|
// Fragment
|
|
vl.fragment = document.createDocumentFragment();
|
|
|
|
// Filter
|
|
vl.filterItems = function (indexes, resetScrollTop) {
|
|
vl.filteredItems = [];
|
|
var firstIndex = indexes[0];
|
|
var lastIndex = indexes[indexes.length - 1];
|
|
for (var i = 0; i < indexes.length; i++) {
|
|
vl.filteredItems.push(vl.items[indexes[i]]);
|
|
}
|
|
if (typeof resetScrollTop === 'undefined') resetScrollTop = true;
|
|
if (resetScrollTop) {
|
|
vl.pageContent[0].scrollTop = 0;
|
|
}
|
|
vl.update();
|
|
};
|
|
vl.resetFilter = function () {
|
|
if (vl.params.showFilteredItemsOnly) {
|
|
vl.filteredItems = [];
|
|
}
|
|
else {
|
|
vl.filteredItems = null;
|
|
delete vl.filteredItems;
|
|
}
|
|
vl.update();
|
|
};
|
|
|
|
var pageHeight, rowsPerScreen, rowsBefore, rowsAfter, rowsToRender, maxBufferHeight = 0, listHeight;
|
|
var dynamicHeight = typeof vl.params.height === 'function';
|
|
|
|
// Set list size
|
|
vl.setListSize = function () {
|
|
var items = vl.filteredItems || vl.items;
|
|
pageHeight = vl.pageContent[0].offsetHeight;
|
|
if (dynamicHeight) {
|
|
listHeight = 0;
|
|
vl.heights = [];
|
|
for (var i = 0; i < items.length; i++) {
|
|
var itemHeight = vl.params.height(items[i]);
|
|
listHeight += itemHeight;
|
|
vl.heights.push(itemHeight);
|
|
}
|
|
}
|
|
else {
|
|
listHeight = Math.ceil(items.length / vl.params.cols) * vl.params.height;
|
|
rowsPerScreen = Math.ceil(pageHeight / vl.params.height);
|
|
rowsBefore = vl.params.rowsBefore || rowsPerScreen * 2;
|
|
rowsAfter = vl.params.rowsAfter || rowsPerScreen;
|
|
rowsToRender = (rowsPerScreen + rowsBefore + rowsAfter);
|
|
maxBufferHeight = rowsBefore / 2 * vl.params.height;
|
|
}
|
|
|
|
if (updatableScroll) {
|
|
vl.ul.css({height: listHeight + 'px'});
|
|
}
|
|
};
|
|
|
|
// Render items
|
|
vl.render = function (force, forceScrollTop) {
|
|
if (force) vl.lastRepaintY = null;
|
|
|
|
var scrollTop = -(vl.listBlock[0].getBoundingClientRect().top - vl.pageContent[0].getBoundingClientRect().top);
|
|
|
|
if (typeof forceScrollTop !== 'undefined') scrollTop = forceScrollTop;
|
|
|
|
if (vl.lastRepaintY === null || Math.abs(scrollTop - vl.lastRepaintY) > maxBufferHeight || (!updatableScroll && (vl.pageContent[0].scrollTop + pageHeight >= vl.pageContent[0].scrollHeight))) {
|
|
vl.lastRepaintY = scrollTop;
|
|
}
|
|
else {
|
|
return;
|
|
}
|
|
|
|
var items = vl.filteredItems || vl.items,
|
|
fromIndex, toIndex, heightBeforeFirstItem = 0, heightBeforeLastItem = 0;
|
|
if (dynamicHeight) {
|
|
var itemTop = 0, j, itemHeight;
|
|
maxBufferHeight = pageHeight;
|
|
|
|
for (j = 0; j < vl.heights.length; j++) {
|
|
itemHeight = vl.heights[j];
|
|
if (typeof fromIndex === 'undefined') {
|
|
if (itemTop + itemHeight >= scrollTop - pageHeight * 2 * vl.params.dynamicHeightBufferSize) fromIndex = j;
|
|
else heightBeforeFirstItem += itemHeight;
|
|
}
|
|
|
|
if (typeof toIndex === 'undefined') {
|
|
if (itemTop + itemHeight >= scrollTop + pageHeight * 2 * vl.params.dynamicHeightBufferSize || j === vl.heights.length - 1) toIndex = j + 1;
|
|
heightBeforeLastItem += itemHeight;
|
|
}
|
|
itemTop += itemHeight;
|
|
}
|
|
toIndex = Math.min(toIndex, items.length);
|
|
}
|
|
else {
|
|
fromIndex = (parseInt(scrollTop / vl.params.height) - rowsBefore) * vl.params.cols;
|
|
if (fromIndex < 0) {
|
|
fromIndex = 0;
|
|
}
|
|
toIndex = Math.min(fromIndex + rowsToRender * vl.params.cols, items.length);
|
|
}
|
|
|
|
var topPosition;
|
|
vl.reachEnd = false;
|
|
for (var i = fromIndex; i < toIndex; i++) {
|
|
var item, index;
|
|
// Define real item index
|
|
index = vl.items.indexOf(items[i]);
|
|
|
|
if (i === fromIndex) vl.currentFromIndex = index;
|
|
if (i === toIndex - 1) vl.currentToIndex = index;
|
|
if (index === vl.items.length - 1) vl.reachEnd = true;
|
|
|
|
// Find items
|
|
if (vl.domCache[index]) {
|
|
item = vl.domCache[index];
|
|
}
|
|
else {
|
|
if (vl.template) {
|
|
vl.tempDomElement.innerHTML = vl.template(items[i], {index: index}).trim();
|
|
}
|
|
else if (vl.params.renderItem) {
|
|
vl.tempDomElement.innerHTML = vl.params.renderItem(index, items[i]).trim();
|
|
}
|
|
else {
|
|
vl.tempDomElement.innerHTML = items[i].trim();
|
|
}
|
|
item = vl.tempDomElement.childNodes[0];
|
|
if (vl.params.cache) vl.domCache[index] = item;
|
|
}
|
|
item.f7VirtualListIndex = index;
|
|
|
|
// Set item top position
|
|
if (i === fromIndex) {
|
|
if (dynamicHeight) {
|
|
topPosition = heightBeforeFirstItem;
|
|
}
|
|
else {
|
|
topPosition = (i * vl.params.height / vl.params.cols);
|
|
}
|
|
}
|
|
item.style.top = topPosition + 'px';
|
|
|
|
// Before item insert
|
|
if (vl.params.onItemBeforeInsert) vl.params.onItemBeforeInsert(vl, item);
|
|
|
|
// Append item to fragment
|
|
vl.fragment.appendChild(item);
|
|
|
|
|
|
}
|
|
|
|
// Update list height with not updatable scroll
|
|
if (!updatableScroll) {
|
|
if (dynamicHeight) {
|
|
vl.ul[0].style.height = heightBeforeLastItem + 'px';
|
|
}
|
|
else {
|
|
vl.ul[0].style.height = i * vl.params.height / vl.params.cols + 'px';
|
|
}
|
|
}
|
|
|
|
|
|
// Update list html
|
|
if (vl.params.onBeforeClear) vl.params.onBeforeClear(vl, vl.fragment);
|
|
vl.ul[0].innerHTML = '';
|
|
|
|
if (vl.params.onItemsBeforeInsert) vl.params.onItemsBeforeInsert(vl, vl.fragment);
|
|
vl.ul[0].appendChild(vl.fragment);
|
|
if (vl.params.onItemsAfterInsert) vl.params.onItemsAfterInsert(vl, vl.fragment);
|
|
|
|
if (typeof forceScrollTop !== 'undefined' && force) {
|
|
vl.pageContent.scrollTop(forceScrollTop, 0);
|
|
}
|
|
};
|
|
|
|
vl.scrollToItem = function (index) {
|
|
if (index > vl.items.length) return false;
|
|
|
|
var itemTop = 0, listTop;
|
|
if (dynamicHeight) {
|
|
for (var i = 0; i < index; i++) {
|
|
itemTop += vl.heights[i];
|
|
}
|
|
}
|
|
else {
|
|
itemTop = index * vl.params.height;
|
|
}
|
|
listTop = vl.listBlock[0].offsetTop;
|
|
vl.render(true, listTop + itemTop - parseInt(vl.pageContent.css('padding-top'), 10));
|
|
return true;
|
|
};
|
|
|
|
// Handle scroll event
|
|
vl.handleScroll = function (e) {
|
|
vl.render();
|
|
};
|
|
// Handle resize event
|
|
vl._isVisible = function (el) {
|
|
return !!( el.offsetWidth || el.offsetHeight || el.getClientRects().length );
|
|
};
|
|
vl.handleResize = function (e) {
|
|
if (vl._isVisible(vl.listBlock[0])) {
|
|
vl.setListSize();
|
|
vl.render(true);
|
|
}
|
|
};
|
|
|
|
vl.attachEvents = function (detach) {
|
|
var action = detach ? 'off' : 'on';
|
|
vl.pageContent[action]('scroll', vl.handleScroll);
|
|
vl.listBlock.parents('.tab').eq(0)[action]('show', vl.handleResize);
|
|
$(window)[action]('resize', vl.handleResize);
|
|
};
|
|
|
|
// Init Virtual List
|
|
vl.init = function () {
|
|
vl.attachEvents();
|
|
vl.setListSize();
|
|
vl.render();
|
|
};
|
|
|
|
// Append
|
|
vl.appendItems = function (items) {
|
|
for (var i = 0; i < items.length; i++) {
|
|
vl.items.push(items[i]);
|
|
}
|
|
vl.update();
|
|
};
|
|
vl.appendItem = function (item) {
|
|
vl.appendItems([item]);
|
|
};
|
|
// Replace
|
|
vl.replaceAllItems = function (items) {
|
|
vl.items = items;
|
|
delete vl.filteredItems;
|
|
vl.domCache = {};
|
|
vl.update();
|
|
};
|
|
vl.replaceItem = function (index, item) {
|
|
vl.items[index] = item;
|
|
if (vl.params.cache) delete vl.domCache[index];
|
|
vl.update();
|
|
};
|
|
// Prepend
|
|
vl.prependItems = function (items) {
|
|
for (var i = items.length - 1; i >= 0; i--) {
|
|
vl.items.unshift(items[i]);
|
|
}
|
|
if (vl.params.cache) {
|
|
var newCache = {};
|
|
for (var cached in vl.domCache) {
|
|
newCache[parseInt(cached, 10) + items.length] = vl.domCache[cached];
|
|
}
|
|
vl.domCache = newCache;
|
|
}
|
|
vl.update();
|
|
};
|
|
vl.prependItem = function (item) {
|
|
vl.prependItems([item]);
|
|
};
|
|
|
|
// Move
|
|
vl.moveItem = function (oldIndex, newIndex) {
|
|
if (oldIndex === newIndex) return;
|
|
// remove item from array
|
|
var item = vl.items.splice(oldIndex, 1)[0];
|
|
if (newIndex >= vl.items.length) {
|
|
// Add item to the end
|
|
vl.items.push(item);
|
|
newIndex = vl.items.length - 1;
|
|
}
|
|
else {
|
|
// Add item to new index
|
|
vl.items.splice(newIndex, 0, item);
|
|
}
|
|
// Update cache
|
|
if (vl.params.cache) {
|
|
var newCache = {};
|
|
for (var cached in vl.domCache) {
|
|
var cachedIndex = parseInt(cached, 10);
|
|
var leftIndex = oldIndex < newIndex ? oldIndex : newIndex;
|
|
var rightIndex = oldIndex < newIndex ? newIndex : oldIndex;
|
|
var indexShift = oldIndex < newIndex ? -1 : 1;
|
|
if (cachedIndex < leftIndex || cachedIndex > rightIndex) newCache[cachedIndex] = vl.domCache[cachedIndex];
|
|
if (cachedIndex === leftIndex) newCache[rightIndex] = vl.domCache[cachedIndex];
|
|
if (cachedIndex > leftIndex && cachedIndex <= rightIndex) newCache[cachedIndex + indexShift] = vl.domCache[cachedIndex];
|
|
}
|
|
vl.domCache = newCache;
|
|
}
|
|
vl.update();
|
|
};
|
|
// Insert before
|
|
vl.insertItemBefore = function (index, item) {
|
|
if (index === 0) {
|
|
vl.prependItem(item);
|
|
return;
|
|
}
|
|
if (index >= vl.items.length) {
|
|
vl.appendItem(item);
|
|
return;
|
|
}
|
|
vl.items.splice(index, 0, item);
|
|
// Update cache
|
|
if (vl.params.cache) {
|
|
var newCache = {};
|
|
for (var cached in vl.domCache) {
|
|
var cachedIndex = parseInt(cached, 10);
|
|
if (cachedIndex >= index) {
|
|
newCache[cachedIndex + 1] = vl.domCache[cachedIndex];
|
|
}
|
|
}
|
|
vl.domCache = newCache;
|
|
}
|
|
vl.update();
|
|
};
|
|
// Delete
|
|
vl.deleteItems = function (indexes) {
|
|
var prevIndex, indexShift = 0;
|
|
for (var i = 0; i < indexes.length; i++) {
|
|
var index = indexes[i];
|
|
if (typeof prevIndex !== 'undefined') {
|
|
if (index > prevIndex) {
|
|
indexShift = -i;
|
|
}
|
|
}
|
|
index = index + indexShift;
|
|
prevIndex = indexes[i];
|
|
// Delete item
|
|
var deletedItem = vl.items.splice(index, 1)[0];
|
|
|
|
// Delete from filtered
|
|
if (vl.filteredItems && vl.filteredItems.indexOf(deletedItem) >= 0) {
|
|
vl.filteredItems.splice(vl.filteredItems.indexOf(deletedItem), 1);
|
|
}
|
|
// Update cache
|
|
if (vl.params.cache) {
|
|
var newCache = {};
|
|
for (var cached in vl.domCache) {
|
|
var cachedIndex = parseInt(cached, 10);
|
|
if (cachedIndex === index) {
|
|
delete vl.domCache[index];
|
|
}
|
|
else if (parseInt(cached, 10) > index) {
|
|
newCache[cachedIndex - 1] = vl.domCache[cached];
|
|
}
|
|
else {
|
|
newCache[cachedIndex] = vl.domCache[cached];
|
|
}
|
|
}
|
|
vl.domCache = newCache;
|
|
}
|
|
}
|
|
vl.update();
|
|
};
|
|
vl.deleteAllItems = function () {
|
|
vl.items = [];
|
|
delete vl.filteredItems;
|
|
if (vl.params.cache) vl.domCache = {};
|
|
vl.update();
|
|
};
|
|
vl.deleteItem = function (index) {
|
|
vl.deleteItems([index]);
|
|
};
|
|
|
|
// Clear cache
|
|
vl.clearCache = function () {
|
|
vl.domCache = {};
|
|
};
|
|
|
|
// Update Virtual List
|
|
vl.update = function () {
|
|
vl.setListSize();
|
|
vl.render(true);
|
|
};
|
|
|
|
// Destroy
|
|
vl.destroy = function () {
|
|
vl.attachEvents(true);
|
|
delete vl.items;
|
|
delete vl.domCache;
|
|
};
|
|
|
|
// Init Virtual List
|
|
vl.init();
|
|
|
|
// Store vl in container
|
|
vl.listBlock[0].f7VirtualList = vl;
|
|
return vl;
|
|
};
|
|
|
|
// App Method
|
|
app.virtualList = function (listBlock, params) {
|
|
return new VirtualList(listBlock, params);
|
|
};
|
|
|
|
app.reinitVirtualList = function (pageContainer) {
|
|
var page = $(pageContainer);
|
|
var vlists = page.find('.virtual-list');
|
|
if (vlists.length === 0) return;
|
|
for (var i = 0; i < vlists.length; i++) {
|
|
var vlistInstance = vlists[i].f7VirtualList;
|
|
if (vlistInstance) {
|
|
vlistInstance.update();
|
|
}
|
|
}
|
|
}; |