Browse Source

Add virtual-scroll to support large data

zhixin 6 years ago
parent
commit
6f5aafd5c3
2 changed files with 137 additions and 6 deletions
  1. 17 6
      src/bootstrap-table.js
  2. 120 0
      src/virtual-scroll/index.js

+ 17 - 6
src/bootstrap-table.js

@@ -6,6 +6,7 @@
 
 import Constants from './constants/index.js'
 import Utils from './utils/index.js'
+import VirtualScroll from './virtual-scroll/index.js'
 
 class BootstrapTable {
   constructor (el, options) {
@@ -1161,7 +1162,7 @@ class BootstrapTable {
     return false
   }
 
-  initRow (item, i, data, parentDom) {
+  initRow (item, i) {
     const html = []
     let style = {}
     const csses = []
@@ -1379,15 +1380,15 @@ class BootstrapTable {
       this.pageTo = data.length
     }
 
-    const trFragments = $(document.createDocumentFragment())
+    const rows = []
     let hasTr = false
 
     for (let i = this.pageFrom - 1; i < this.pageTo; i++) {
       const item = data[i]
-      const tr = this.initRow(item, i, data, trFragments)
+      const tr = this.initRow(item, i)
       hasTr = hasTr || !!tr
       if (tr && typeof tr === 'string') {
-        trFragments.append(tr)
+        rows.push(tr)
       }
     }
 
@@ -1397,7 +1398,17 @@ class BootstrapTable {
         this.$header.find('th').length,
         this.options.formatNoMatches())}</tr>`)
     } else {
-      this.$body.html(trFragments)
+      if (this.virtualScroll) {
+        this.virtualScroll.destroy()
+      }
+      this.virtualScroll = new VirtualScroll({
+        rows,
+        scrollEl: this.$tableBody[0],
+        contentEl: this.$body[0],
+        callback: () => {
+          this.fitHeader()
+        }
+      })
     }
 
     if (!fixedScroll) {
@@ -1739,7 +1750,7 @@ class BootstrapTable {
 
     const visibleFields = this.getVisibleFields()
     const $ths = this.$header_.find('th')
-    let $tr = this.$body.find('>tr:first-child:not(.no-records-found)')
+    let $tr = this.$body.find('>tr:not(.no-records-found,.virtual-scroll-top)').eq(0)
 
     while ($tr.length && $tr.find('>td[colspan]:not([colspan="1"])').length) {
       $tr = $tr.next()

+ 120 - 0
src/virtual-scroll/index.js

@@ -0,0 +1,120 @@
+const BLOCK_ROWS = 50
+const CLUSTER_BLOCKS = 4
+
+class VirtualScroll {
+
+  constructor (options) {
+    this.rows = options.rows
+    this.scrollEl = options.scrollEl
+    this.contentEl = options.contentEl
+    this.callback = options.callback
+
+    this.cache = {}
+    this.scrollTop = this.scrollEl.scrollTop
+
+    this.initDOM(this.rows)
+
+    this.scrollEl.scrollTop = this.scrollTop
+    this.lastCluster = 0
+
+    const onScroll = () => {
+      if (this.lastCluster !== (this.lastCluster = this.getNum())) {
+        this.initDOM(this.rows)
+        this.callback()
+      }
+    }
+
+    this.scrollEl.addEventListener('scroll', onScroll, false)
+    this.destroy = () => {
+      this.contentEl.innerHtml = ''
+      this.scrollEl.removeEventListener('scroll', onScroll, false)
+    }
+  }
+
+  initDOM (rows) {
+    if (!this.clusterHeight) {
+      this.cache.data = this.contentEl.innerHTML = rows[0] + rows[0] + rows[0]
+      this.getRowsHeight(rows)
+    }
+
+    const data = this.initData(rows, this.getNum())
+    const thisRows = data.rows.join('')
+    const dataChanged = this.checkChanges('data', thisRows)
+    const topOffsetChanged = this.checkChanges('top', data.topOffset)
+    const bottomOffsetChanged = this.checkChanges('bottom', data.bottomOffset)
+    const html = []
+
+    if (dataChanged && topOffsetChanged) {
+      if (data.topOffset) {
+        html.push(this.getExtra('top', data.topOffset))
+      }
+      html.push(thisRows)
+      if (data.bottomOffset) {
+        html.push(this.getExtra('bottom', data.bottomOffset))
+      }
+      this.contentEl.innerHTML = html.join('')
+    } else if (bottomOffsetChanged) {
+      this.contentEl.lastChild.style.height = `${data.bottomOffset}px`
+    }
+  }
+
+  getRowsHeight () {
+    const nodes = this.contentEl.children
+    const node = nodes[Math.floor(nodes.length / 2)]
+    this.itemHeight = node.offsetHeight
+    this.blockHeight = this.itemHeight * BLOCK_ROWS
+    this.clusterRows = BLOCK_ROWS * CLUSTER_BLOCKS
+    this.clusterHeight = this.blockHeight * CLUSTER_BLOCKS
+  }
+
+  getNum () {
+    this.scrollTop = this.scrollEl.scrollTop
+    return Math.floor(this.scrollTop / (this.clusterHeight - this.blockHeight)) || 0
+  }
+
+  initData (rows, num) {
+    if (rows.length < BLOCK_ROWS) {
+      return {
+        topOffset: 0,
+        bottomOffset: 0,
+        rowsAbove: 0,
+        rows
+      }
+    }
+    const start = Math.max((this.clusterRows - BLOCK_ROWS) * num, 0)
+    const end = start + this.clusterRows
+    const topOffset = Math.max(start * this.itemHeight, 0)
+    const bottomOffset = Math.max((rows.length - end) * this.itemHeight, 0)
+    const thisRows = []
+    let rowsAbove = start
+    if (topOffset < 1) {
+      rowsAbove++
+    }
+    for (let i = start; i < end; i++) {
+      rows[i] && thisRows.push(rows[i])
+    }
+    return {
+      topOffset,
+      bottomOffset,
+      rowsAbove,
+      rows: thisRows
+    }
+  }
+
+  checkChanges (type, value) {
+    const changed = value !== this.cache[type]
+    this.cache[type] = value
+    return changed
+  }
+
+  getExtra (className, height) {
+    const tag = document.createElement('tr')
+    tag.className = `virtual-scroll-${className}`
+    if (height) {
+      tag.style.height = `${height}px`
+    }
+    return tag.outerHTML
+  }
+}
+
+export default VirtualScroll