UNPKG

15.3 kBJavaScriptView Raw
1(function (global, factory) {
2 if (typeof define === "function" && define.amd) {
3 define([], factory);
4 } else if (typeof exports !== "undefined") {
5 factory();
6 } else {
7 var mod = {
8 exports: {}
9 };
10 factory();
11 global.bootstrapTablePipeline = mod.exports;
12 }
13})(this, function () {
14 'use strict';
15
16 /**
17 * @author doug-the-guy
18 * @version v1.0.0
19 *
20 * Boostrap Table Pipeline
21 * -----------------------
22 *
23 * This plugin enables client side data caching for server side requests which will
24 * eliminate the need to issue a new request every page change. This will allow
25 * for a performance balance for a large data set between returning all data at once
26 * (client side paging) and a new server side request (server side paging).
27 *
28 * There are two new options:
29 * - usePipeline: enables this feature
30 * - pipelineSize: the size of each cache window
31 *
32 * The size of the pipeline must be evenly divisible by the current page size. This is
33 * assured by rounding up to the nearest evenly divisible value. For example, if
34 * the pipeline size is 4990 and the current page size is 25, then pipeline size will
35 * be dynamically set to 5000.
36 *
37 * The cache windows are computed based on the pipeline size and the total number of rows
38 * returned by the server side query. For example, with pipeline size 500 and total rows
39 * 1300, the cache windows will be:
40 *
41 * [{'lower': 0, 'upper': 499}, {'lower': 500, 'upper': 999}, {'lower': 1000, 'upper': 1499}]
42 *
43 * Using the limit (i.e. the pipelineSize) and offset parameters, the server side request
44 * **MUST** return only the data in the requested cache window **AND** the total number of rows.
45 * To wit, the server side code must use the offset and limit parameters to prepare the response
46 * data.
47 *
48 * On a page change, the new offset is checked if it is within the current cache window. If so,
49 * the requested page data is returned from the cached data set. Otherwise, a new server side
50 * request will be issued for the new cache window.
51 *
52 * The current cached data is only invalidated on these events:
53 * * sorting
54 * * searching
55 * * page size change
56 * * page change moves into a new cache window
57 *
58 * There are two new events:
59 * - cached-data-hit.bs.table: issued when cached data is used on a page change
60 * - cached-data-reset.bs.table: issued when the cached data is invalidated and a
61 * new server side request is issued
62 *
63 **/
64
65 (function ($) {
66
67 'use strict';
68
69 var Utils = $.fn.bootstrapTable.utils;
70
71 $.extend($.fn.bootstrapTable.defaults, {
72 usePipeline: false,
73 pipelineSize: 1000,
74 onCachedDataHit: function onCachedDataHit(data) {
75 return false;
76 },
77 onCachedDataReset: function onCachedDataReset(data) {
78 return false;
79 }
80 });
81
82 $.extend($.fn.bootstrapTable.Constructor.EVENTS, {
83 'cached-data-hit.bs.table': 'onCachedDataHit',
84 'cached-data-reset.bs.table': 'onCachedDataReset'
85 });
86
87 var BootstrapTable = $.fn.bootstrapTable.Constructor,
88 _init = BootstrapTable.prototype.init,
89 _initServer = BootstrapTable.prototype.initServer,
90 _onSearch = BootstrapTable.prototype.onSearch,
91 _onSort = BootstrapTable.prototype.onSort,
92 _onPageListChange = BootstrapTable.prototype.onPageListChange;
93
94 BootstrapTable.prototype.init = function () {
95 // needs to be called before initServer()
96 this.initPipeline();
97 _init.apply(this, Array.prototype.slice.apply(arguments));
98 };
99
100 BootstrapTable.prototype.initPipeline = function () {
101 this.cacheRequestJSON = {};
102 this.cacheWindows = [];
103 this.currWindow = 0;
104 this.resetCache = true;
105 };
106
107 BootstrapTable.prototype.onSearch = function (event) {
108 /* force a cache reset on search */
109 if (this.options.usePipeline) {
110 this.resetCache = true;
111 }
112 _onSearch.apply(this, Array.prototype.slice.apply(arguments));
113 };
114
115 BootstrapTable.prototype.onSort = function (event) {
116 /* force a cache reset on sort */
117 if (this.options.usePipeline) {
118 this.resetCache = true;
119 }
120 _onSort.apply(this, Array.prototype.slice.apply(arguments));
121 };
122
123 BootstrapTable.prototype.onPageListChange = function (event) {
124 /* rebuild cache window on page size change */
125 var target = $(event.currentTarget);
126 var newPageSize = parseInt(target.text());
127 this.options.pipelineSize = this.calculatePipelineSize(this.options.pipelineSize, newPageSize);
128 this.resetCache = true;
129 _onPageListChange.apply(this, Array.prototype.slice.apply(arguments));
130 };
131
132 BootstrapTable.prototype.calculatePipelineSize = function (pipelineSize, pageSize) {
133 /* calculate pipeline size by rounding up to the nearest value evenly divisible
134 * by the pageSize */
135 if (pageSize == 0) return 0;
136 return Math.ceil(pipelineSize / pageSize) * pageSize;
137 };
138
139 BootstrapTable.prototype.setCacheWindows = function () {
140 /* set cache windows based on the total number of rows returned by server side
141 * request and the pipelineSize */
142 this.cacheWindows = [];
143 var numWindows = this.options.totalRows / this.options.pipelineSize;
144 for (var i = 0; i <= numWindows; i++) {
145 var b = i * this.options.pipelineSize;
146 this.cacheWindows[i] = { 'lower': b, 'upper': b + this.options.pipelineSize - 1 };
147 }
148 };
149
150 BootstrapTable.prototype.setCurrWindow = function (offset) {
151 /* set the current cache window index, based on where the current offset falls */
152 this.currWindow = 0;
153 for (var i = 0; i < this.cacheWindows.length; i++) {
154 if (this.cacheWindows[i].lower <= offset && offset <= this.cacheWindows[i].upper) {
155 this.currWindow = i;
156 break;
157 }
158 }
159 };
160
161 BootstrapTable.prototype.drawFromCache = function (offset, limit) {
162 /* draw rows from the cache using offset and limit */
163 var res = $.extend(true, {}, this.cacheRequestJSON);
164 var drawStart = offset - this.cacheWindows[this.currWindow].lower;
165 var drawEnd = drawStart + limit;
166 res.rows = res.rows.slice(drawStart, drawEnd);
167 return res;
168 };
169
170 BootstrapTable.prototype.initServer = function (silent, query, url) {
171 /* determine if requested data is in cache (on paging) or if
172 * a new ajax request needs to be issued (sorting, searching, paging
173 * moving outside of cached data, page size change)
174 * initial version of this extension will entirely override base initServer
175 **/
176
177 var data = {};
178 var index = this.header.fields.indexOf(this.options.sortName);
179
180 var params = {
181 searchText: this.searchText,
182 sortName: this.options.sortName,
183 sortOrder: this.options.sortOrder
184 };
185
186 var request = null;
187
188 if (this.header.sortNames[index]) {
189 params.sortName = this.header.sortNames[index];
190 }
191
192 if (this.options.pagination && this.options.sidePagination === 'server') {
193 params.pageSize = this.options.pageSize === this.options.formatAllRows() ? this.options.totalRows : this.options.pageSize;
194 params.pageNumber = this.options.pageNumber;
195 }
196
197 if (!(url || this.options.url) && !this.options.ajax) {
198 return;
199 }
200
201 var useAjax = true;
202 if (this.options.queryParamsType === 'limit') {
203 params = {
204 searchText: params.searchText,
205 sortName: params.sortName,
206 sortOrder: params.sortOrder
207 };
208 if (this.options.pagination && this.options.sidePagination === 'server') {
209 params.limit = this.options.pageSize === this.options.formatAllRows() ? this.options.totalRows : this.options.pageSize;
210 params.offset = (this.options.pageSize === this.options.formatAllRows() ? this.options.totalRows : this.options.pageSize) * (this.options.pageNumber - 1);
211 if (this.options.usePipeline) {
212 // if cacheWindows is empty, this is the initial request
213 if (!this.cacheWindows.length) {
214 useAjax = true;
215 params.drawOffset = params.offset;
216 // cache exists: determine if the page request is entirely within the current cached window
217 } else {
218 var w = this.cacheWindows[this.currWindow];
219 // case 1: reset cache but stay within current window (e.g. column sort)
220 // case 2: move outside of the current window (e.g. search or paging)
221 // since each cache window is aligned with the current page size
222 // checking if params.offset is outside the current window is sufficient.
223 // need to requery for preceding or succeeding cache window
224 // also handle case
225 if (this.resetCache || params.offset < w.lower || params.offset > w.upper) {
226 useAjax = true;
227 this.setCurrWindow(params.offset);
228 // store the relative offset for drawing the page data afterwards
229 params.drawOffset = params.offset;
230 // now set params.offset to the lower bound of the new cache window
231 // the server will return that whole cache window
232 params.offset = this.cacheWindows[this.currWindow].lower;
233 // within current cache window
234 } else {
235 useAjax = false;
236 }
237 }
238 } else {
239 if (params.limit === 0) {
240 delete params.limit;
241 }
242 }
243 }
244 }
245
246 // force an ajax call - this is on search, sort or page size change
247 if (this.resetCache) {
248 useAjax = true;
249 this.resetCache = false;
250 }
251
252 if (this.options.usePipeline && useAjax) {
253 /* in this scenario limit is used on the server to get the cache window
254 * and drawLimit is used to get the page data afterwards */
255 params.drawLimit = params.limit;
256 params.limit = this.options.pipelineSize;
257 }
258
259 // cached results can be used
260 if (!useAjax) {
261 var res = this.drawFromCache(params.offset, params.limit);
262 this.load(res);
263 this.trigger('load-success', res);
264 this.trigger('cached-data-hit', res);
265 return;
266 }
267 // cached results can't be used
268 // continue base initServer code
269 if (!$.isEmptyObject(this.filterColumnsPartial)) {
270 params.filter = JSON.stringify(this.filterColumnsPartial, null);
271 }
272
273 data = Utils.calculateObjectValue(this.options, this.options.queryParams, [params], data);
274
275 $.extend(data, query || {});
276
277 // false to stop request
278 if (data === false) {
279 return;
280 }
281
282 if (!silent) {
283 this.$tableLoading.show();
284 }
285 var self = this;
286
287 request = $.extend({}, Utils.calculateObjectValue(null, this.options.ajaxOptions), {
288 type: this.options.method,
289 url: url || this.options.url,
290 data: this.options.contentType === 'application/json' && this.options.method === 'post' ? JSON.stringify(data) : data,
291 cache: this.options.cache,
292 contentType: this.options.contentType,
293 dataType: this.options.dataType,
294 success: function success(res) {
295 res = Utils.calculateObjectValue(self.options, self.options.responseHandler, [res], res);
296 // cache results if using pipelining
297 if (self.options.usePipeline) {
298 // store entire request in cache
299 self.cacheRequestJSON = $.extend(true, {}, res);
300 // this gets set in load() also but needs to be set before
301 // setting cacheWindows
302 self.options.totalRows = res[self.options.totalField];
303 // if this is a search, potentially less results will be returned
304 // so cache windows need to be rebuilt. Otherwise it
305 // will come out the same
306 self.setCacheWindows();
307 self.setCurrWindow(params.drawOffset);
308 // just load data for the page
309 res = self.drawFromCache(params.drawOffset, params.drawLimit);
310 self.trigger('cached-data-reset', res);
311 }
312 self.load(res);
313 self.trigger('load-success', res);
314 if (!silent) self.$tableLoading.hide();
315 },
316 error: function error(res) {
317 var data = [];
318 if (self.options.sidePagination === 'server') {
319 data = {};
320 data[self.options.totalField] = 0;
321 data[self.options.dataField] = [];
322 }
323 self.load(data);
324 self.trigger('load-error', res.status, res);
325 if (!silent) self.$tableLoading.hide();
326 }
327 });
328
329 if (this.options.ajax) {
330 Utils.calculateObjectValue(this, this.options.ajax, [request], null);
331 } else {
332 if (this._xhr && this._xhr.readyState !== 4) {
333 this._xhr.abort();
334 }
335 this._xhr = $.ajax(request);
336 }
337 };
338
339 $.fn.bootstrapTable.methods.push();
340 })(jQuery);
341});
\No newline at end of file