You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
DS7/DispatchWeb/Content/hplus/js/plugins/bootstrap-table/extensions/pipeline/bootstrap-table-pipeline.js

341 lines
15 KiB
JavaScript

(function (global, factory) {
if (typeof define === "function" && define.amd) {
define([], factory);
} else if (typeof exports !== "undefined") {
factory();
} else {
var mod = {
exports: {}
};
factory();
global.bootstrapTablePipeline = mod.exports;
}
})(this, function () {
'use strict';
/**
* @author doug-the-guy
* @version v1.0.0
*
* Boostrap 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
*
**/
(function ($) {
'use strict';
var Utils = $.fn.bootstrapTable.utils;
$.extend($.fn.bootstrapTable.defaults, {
usePipeline: false,
pipelineSize: 1000,
onCachedDataHit: function onCachedDataHit(data) {
return false;
},
onCachedDataReset: function onCachedDataReset(data) {
return false;
}
});
$.extend($.fn.bootstrapTable.Constructor.EVENTS, {
'cached-data-hit.bs.table': 'onCachedDataHit',
'cached-data-reset.bs.table': 'onCachedDataReset'
});
var BootstrapTable = $.fn.bootstrapTable.Constructor,
_init = BootstrapTable.prototype.init,
_initServer = BootstrapTable.prototype.initServer,
_onSearch = BootstrapTable.prototype.onSearch,
_onSort = BootstrapTable.prototype.onSort,
_onPageListChange = BootstrapTable.prototype.onPageListChange;
BootstrapTable.prototype.init = function () {
// needs to be called before initServer()
this.initPipeline();
_init.apply(this, Array.prototype.slice.apply(arguments));
};
BootstrapTable.prototype.initPipeline = function () {
this.cacheRequestJSON = {};
this.cacheWindows = [];
this.currWindow = 0;
this.resetCache = true;
};
BootstrapTable.prototype.onSearch = function (event) {
/* force a cache reset on search */
if (this.options.usePipeline) {
this.resetCache = true;
}
_onSearch.apply(this, Array.prototype.slice.apply(arguments));
};
BootstrapTable.prototype.onSort = function (event) {
/* force a cache reset on sort */
if (this.options.usePipeline) {
this.resetCache = true;
}
_onSort.apply(this, Array.prototype.slice.apply(arguments));
};
BootstrapTable.prototype.onPageListChange = function (event) {
/* rebuild cache window on page size change */
var target = $(event.currentTarget);
var newPageSize = parseInt(target.text());
this.options.pipelineSize = this.calculatePipelineSize(this.options.pipelineSize, newPageSize);
this.resetCache = true;
_onPageListChange.apply(this, Array.prototype.slice.apply(arguments));
};
BootstrapTable.prototype.calculatePipelineSize = function (pipelineSize, pageSize) {
/* calculate pipeline size by rounding up to the nearest value evenly divisible
* by the pageSize */
if (pageSize == 0) return 0;
return Math.ceil(pipelineSize / pageSize) * pageSize;
};
BootstrapTable.prototype.setCacheWindows = function () {
/* set cache windows based on the total number of rows returned by server side
* request and the pipelineSize */
this.cacheWindows = [];
var numWindows = this.options.totalRows / this.options.pipelineSize;
for (var i = 0; i <= numWindows; i++) {
var b = i * this.options.pipelineSize;
this.cacheWindows[i] = { 'lower': b, 'upper': b + this.options.pipelineSize - 1 };
}
};
BootstrapTable.prototype.setCurrWindow = function (offset) {
/* set the current cache window index, based on where the current offset falls */
this.currWindow = 0;
for (var i = 0; i < this.cacheWindows.length; i++) {
if (this.cacheWindows[i].lower <= offset && offset <= this.cacheWindows[i].upper) {
this.currWindow = i;
break;
}
}
};
BootstrapTable.prototype.drawFromCache = function (offset, limit) {
/* draw rows from the cache using offset and limit */
var res = $.extend(true, {}, this.cacheRequestJSON);
var drawStart = offset - this.cacheWindows[this.currWindow].lower;
var drawEnd = drawStart + limit;
res.rows = res.rows.slice(drawStart, drawEnd);
return res;
};
BootstrapTable.prototype.initServer = function (silent, query, url) {
/* 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
**/
var data = {};
var index = this.header.fields.indexOf(this.options.sortName);
var params = {
searchText: this.searchText,
sortName: this.options.sortName,
sortOrder: this.options.sortOrder
};
var request = null;
if (this.header.sortNames[index]) {
params.sortName = this.header.sortNames[index];
}
if (this.options.pagination && this.options.sidePagination === 'server') {
params.pageSize = this.options.pageSize === this.options.formatAllRows() ? this.options.totalRows : this.options.pageSize;
params.pageNumber = this.options.pageNumber;
}
if (!(url || this.options.url) && !this.options.ajax) {
return;
}
var useAjax = true;
if (this.options.queryParamsType === 'limit') {
params = {
searchText: params.searchText,
sortName: params.sortName,
sortOrder: params.sortOrder
};
if (this.options.pagination && this.options.sidePagination === 'server') {
params.limit = this.options.pageSize === this.options.formatAllRows() ? this.options.totalRows : this.options.pageSize;
params.offset = (this.options.pageSize === this.options.formatAllRows() ? this.options.totalRows : this.options.pageSize) * (this.options.pageNumber - 1);
if (this.options.usePipeline) {
// 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 {
var 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 requery 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;
}
}
} else {
if (params.limit === 0) {
delete params.limit;
}
}
}
}
// force an ajax call - this is on search, sort or page size change
if (this.resetCache) {
useAjax = true;
this.resetCache = false;
}
if (this.options.usePipeline && 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) {
var res = this.drawFromCache(params.offset, params.limit);
this.load(res);
this.trigger('load-success', res);
this.trigger('cached-data-hit', res);
return;
}
// cached results can't be used
// continue base initServer code
if (!$.isEmptyObject(this.filterColumnsPartial)) {
params.filter = JSON.stringify(this.filterColumnsPartial, null);
}
data = Utils.calculateObjectValue(this.options, this.options.queryParams, [params], data);
$.extend(data, query || {});
// false to stop request
if (data === false) {
return;
}
if (!silent) {
this.$tableLoading.show();
}
var self = this;
request = $.extend({}, Utils.calculateObjectValue(null, this.options.ajaxOptions), {
type: this.options.method,
url: url || this.options.url,
data: this.options.contentType === 'application/json' && this.options.method === 'post' ? JSON.stringify(data) : data,
cache: this.options.cache,
contentType: this.options.contentType,
dataType: this.options.dataType,
success: function success(res) {
res = Utils.calculateObjectValue(self.options, self.options.responseHandler, [res], res);
// cache results if using pipelining
if (self.options.usePipeline) {
// store entire request in cache
self.cacheRequestJSON = $.extend(true, {}, res);
// this gets set in load() also but needs to be set before
// setting cacheWindows
self.options.totalRows = res[self.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
self.setCacheWindows();
self.setCurrWindow(params.drawOffset);
// just load data for the page
res = self.drawFromCache(params.drawOffset, params.drawLimit);
self.trigger('cached-data-reset', res);
}
self.load(res);
self.trigger('load-success', res);
if (!silent) self.$tableLoading.hide();
},
error: function error(res) {
var data = [];
if (self.options.sidePagination === 'server') {
data = {};
data[self.options.totalField] = 0;
data[self.options.dataField] = [];
}
self.load(data);
self.trigger('load-error', res.status, res);
if (!silent) self.$tableLoading.hide();
}
});
if (this.options.ajax) {
Utils.calculateObjectValue(this, this.options.ajax, [request], null);
} else {
if (this._xhr && this._xhr.readyState !== 4) {
this._xhr.abort();
}
this._xhr = $.ajax(request);
}
};
$.fn.bootstrapTable.methods.push();
})(jQuery);
});