bootstrap-table
Version:
An extended table to integration with some of the most widely used CSS frameworks. (Supports Bootstrap, Semantic UI, Bulma, Material Design, Foundation)
263 lines (230 loc) • 8.92 kB
JavaScript
/**
* @author doug-the-guy
* @update zhixin wen <wenzhixin2010@gmail.com>
*
* Bootstrap Table Pipeline
* -----------------------
*
* This plugin enables client side data caching for server side requests which will
* eliminate the need to issue a new request every page change. This will allow
* for a performance balance for a large data set between returning all data at once
* (client side paging) and a new server side request (server side paging).
*
* There are two new options:
* - usePipeline: enables this feature
* - pipelineSize: the size of each cache window
*
* The size of the pipeline must be evenly divisible by the current page size. This is
* assured by rounding up to the nearest evenly divisible value. For example, if
* the pipeline size is 4990 and the current page size is 25, then pipeline size will
* be dynamically set to 5000.
*
* The cache windows are computed based on the pipeline size and the total number of rows
* returned by the server side query. For example, with pipeline size 500 and total rows
* 1300, the cache windows will be:
*
* [{'lower': 0, 'upper': 499}, {'lower': 500, 'upper': 999}, {'lower': 1000, 'upper': 1499}]
*
* Using the limit (i.e. the pipelineSize) and offset parameters, the server side request
* **MUST** return only the data in the requested cache window **AND** the total number of rows.
* To wit, the server side code must use the offset and limit parameters to prepare the response
* data.
*
* On a page change, the new offset is checked if it is within the current cache window. If so,
* the requested page data is returned from the cached data set. Otherwise, a new server side
* request will be issued for the new cache window.
*
* The current cached data is only invalidated on these events:
* * sorting
* * searching
* * page size change
* * page change moves into a new cache window
*
* There are two new events:
* - cached-data-hit.bs.table: issued when cached data is used on a page change
* - cached-data-reset.bs.table: issued when the cached data is invalidated and a
* new server side request is issued
*
**/
const Utils = $.fn.bootstrapTable.utils
Object.assign($.fn.bootstrapTable.defaults, {
usePipeline: false,
pipelineSize: 1000,
// eslint-disable-next-line no-unused-vars
onCachedDataHit (data) {
return false
},
// eslint-disable-next-line no-unused-vars
onCachedDataReset (data) {
return false
}
})
Object.assign($.fn.bootstrapTable.events, {
'cached-data-hit.bs.table': 'onCachedDataHit',
'cached-data-reset.bs.table': 'onCachedDataReset'
})
$.BootstrapTable = class extends $.BootstrapTable {
// needs to be called before initServer
init (...args) {
if (this.options.usePipeline) {
this.initPipeline()
}
super.init(...args)
}
initPipeline () {
this.cacheRequestJSON = {}
this.cacheWindows = []
this.currWindow = 0
this.resetCache = true
}
// force a cache reset on search
onSearch (...args) {
if (this.options.usePipeline) {
this.resetCache = true
}
super.onSearch(...args)
}
// force a cache reset on sort
onSort (...args) {
if (this.options.usePipeline) {
this.resetCache = true
}
super.onSort(...args)
}
// rebuild cache window on page size change
onPageListChange (event) {
const target = $(event.currentTarget)
const newPageSize = parseInt(target.text(), 10)
this.options.pipelineSize = this.calculatePipelineSize(this.options.pipelineSize, newPageSize)
this.resetCache = true
super.onPageListChange(event)
}
// calculate pipeline size by rounding up to
// the nearest value evenly divisible by the pageSize
calculatePipelineSize (pipelineSize, pageSize) {
if (pageSize === 0) {
return 0
}
return Math.ceil(pipelineSize / pageSize) * pageSize
}
// set cache windows based on the total number of rows returned
// by server side request and the pipelineSize
setCacheWindows () {
this.cacheWindows = []
for (let i = 0; i <= this.options.totalRows / this.options.pipelineSize; i++) {
const lower = i * this.options.pipelineSize
this.cacheWindows[i] = { lower, upper: lower + this.options.pipelineSize - 1 }
}
}
// set the current cache window index, based on where the current offset falls
setCurrWindow (offset) {
this.currWindow = 0
for (let i = 0; i < this.cacheWindows.length; i++) {
if (this.cacheWindows[i].lower <= offset && offset <= this.cacheWindows[i].upper) {
this.currWindow = i
break
}
}
}
// draw rows from the cache using offset and limit
drawFromCache (offset, limit) {
const res = Utils.extend(true, {}, this.cacheRequestJSON)
const drawStart = offset - this.cacheWindows[this.currWindow].lower
const drawEnd = drawStart + limit
res.rows = res.rows.slice(drawStart, drawEnd)
return res
}
/*
* determine if requested data is in cache (on paging) or if
* a new ajax request needs to be issued (sorting, searching, paging
* moving outside of cached data, page size change)
* initial version of this extension will entirely override base initServer
*/
initServer (silent, query) {
if (!this.options.usePipeline) {
return super.initServer(silent, query)
}
let useAjax = true
const params = {}
if (
this.options.queryParamsType === 'limit' &&
this.options.pagination &&
this.options.sidePagination === 'server'
) {
// same as parent initServer: params.offset
params.offset = this.options.pageSize === this.options.formatAllRows() ?
0 : this.options.pageSize * (this.options.pageNumber - 1)
params.limit = this.options.pageSize
// if cacheWindows is empty, this is the initial request
if (!this.cacheWindows.length) {
useAjax = true
params.drawOffset = params.offset
// cache exists: determine if the page request is entirely within the current cached window
} else {
const w = this.cacheWindows[this.currWindow]
// case 1: reset cache but stay within current window (e.g. column sort)
// case 2: move outside of the current window (e.g. search or paging)
// since each cache window is aligned with the current page size
// checking if params.offset is outside the current window is sufficient.
// need to re-query for preceding or succeeding cache window
// also handle case
if (this.resetCache || (params.offset < w.lower || params.offset > w.upper)) {
useAjax = true
this.setCurrWindow(params.offset)
// store the relative offset for drawing the page data afterwards
params.drawOffset = params.offset
// now set params.offset to the lower bound of the new cache window
// the server will return that whole cache window
params.offset = this.cacheWindows[this.currWindow].lower
// within current cache window
} else {
useAjax = false
}
}
}
// force an ajax call - this is on search, sort or page size change
if (this.resetCache) {
useAjax = true
this.resetCache = false
}
if (useAjax) {
// in this scenario limit is used on the server to get the cache window
// and drawLimit is used to get the page data afterwards
params.drawLimit = params.limit
params.limit = this.options.pipelineSize
}
// cached results can be used
if (!useAjax) {
const res = this.drawFromCache(params.offset, params.limit)
this.load(res)
this.trigger('load-success', res)
this.trigger('cached-data-hit', res)
return
}
if (!this.pipelineResponseHandler) {
this.pipelineResponseHandler = this.options.responseHandler
this.options.responseHandler = (_res, jqXHR) => {
let res = Utils.calculateObjectValue(this.options, this.pipelineResponseHandler, [_res, jqXHR], _res)
// store entire request in cache
this.cacheRequestJSON = Utils.extend(true, {}, res)
// this gets set in load() also but needs to be set before
// setting cacheWindows
this.options.totalRows = res[this.options.totalField]
// if this is a search, potentially less results will be returned
// so cache windows need to be rebuilt. Otherwise it
// will come out the same
this.setCacheWindows()
// just load data for the page
res = this.drawFromCache(params.drawOffset, params.drawLimit)
this.trigger('cached-data-reset', res)
return res
}
}
return super.initServer(silent, { ...query, ...params })
}
destroy (...args) {
this.options.responseHandler = this.pipelineResponseHandler
this.pipelineResponseHandler = null
super.destroy(...args)
}
}