UNPKG

framework7-without-localstorage

Version:

Full featured mobile HTML framework for building iOS & Android apps

514 lines (469 loc) 17.4 kB
/*=============================================================================== ************ 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(); } } };