framework7-without-localstorage
Version:
Full featured mobile HTML framework for building iOS & Android apps
514 lines (469 loc) • 17.4 kB
JavaScript
/*===============================================================================
************ Virtual List ************
===============================================================================*/
var VirtualList = function (listBlock, params) {
var defaults = {
cols: 1,
height: app.params.material ? 48 : 44,
cache: true,
dynamicHeightBufferSize: 1,
showFilteredItemsOnly: false,
renderExternal: undefined,
template:
'<li>' +
'<div class="item-content">' +
'<div class="item-inner">' +
'<div class="item-title">{{this}}</div>' +
'</div>' +
'</div>' +
'</li>'
};
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 && !vl.params.renderItem) {
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;
}
vl.params.updatableScroll = updatableScroll;
}
// 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, renderExternalItems = [];
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 (vl.filteredItems) {
if (vl.items[index] === vl.filteredItems[vl.filteredItems.length - 1]) vl.reachEnd = true;
}
else {
if (index === vl.items.length - 1) vl.reachEnd = true;
}
// Find items
if (vl.params.renderExternal) {
renderExternalItems.push(items[i])
}
else {
if (vl.domCache[index]) {
item = vl.domCache[index];
item.f7VirtualListIndex = index;
}
else {
if (vl.template && !vl.params.renderItem) {
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].toString().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);
}
}
if (!vl.params.renderExternal) {
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.renderExternal) {
if (items && items.length === 0) {
vl.reachEnd = true;
}
}
else {
if (vl.params.onBeforeClear) vl.params.onBeforeClear(vl, vl.fragment);
vl.ul[0].innerHTML = '';
if (vl.params.onItemsBeforeInsert) vl.params.onItemsBeforeInsert(vl, vl.fragment);
if (items && items.length === 0) {
vl.reachEnd = true;
if (vl.params.emptyTemplate) vl.ul[0].innerHTML = vl.params.emptyTemplate;
}
else {
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);
}
if (vl.params.renderExternal) {
vl.params.renderExternal(vl, {
fromIndex: fromIndex,
toIndex: toIndex,
listHeight: listHeight,
topPosition: topPosition,
items: renderExternalItems
});
}
};
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]('tab:show', vl.handleResize);
vl.listBlock.parents('.panel').eq(0)[action]('panel:open', vl.handleResize);
vl.listBlock.parents('.popup').eq(0)[action]('popup:open', vl.handleResize);
app[action === 'on' ? 'onResize' : 'offResize'](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();
}
}
};