UNPKG

99.6 kBJavaScriptView Raw
1/**
2 * @author zhixin wen <wenzhixin2010@gmail.com>
3 * version: 1.13.4
4 * https://github.com/wenzhixin/bootstrap-table/
5 */
6
7($ => {
8 // TOOLS DEFINITION
9 // ======================
10
11 let bootstrapVersion = 3
12 try {
13 const rawVersion = $.fn.dropdown.Constructor.VERSION
14
15 // Only try to parse VERSION if is is defined.
16 // It is undefined in older versions of Bootstrap (tested with 3.1.1).
17 if (rawVersion !== undefined) {
18 bootstrapVersion = parseInt(rawVersion, 10)
19 }
20 } catch (e) {
21 // ignore
22 }
23
24 const bootstrap = {
25 3: {
26 iconsPrefix: 'glyphicon',
27 icons: {
28 paginationSwitchDown: 'glyphicon-collapse-down icon-chevron-down',
29 paginationSwitchUp: 'glyphicon-collapse-up icon-chevron-up',
30 refresh: 'glyphicon-refresh icon-refresh',
31 toggleOff: 'glyphicon-list-alt icon-list-alt',
32 toggleOn: 'glyphicon-list-alt icon-list-alt',
33 columns: 'glyphicon-th icon-th',
34 detailOpen: 'glyphicon-plus icon-plus',
35 detailClose: 'glyphicon-minus icon-minus',
36 fullscreen: 'glyphicon-fullscreen'
37 },
38 classes: {
39 buttons: 'default',
40 pull: 'pull'
41 },
42 html: {
43 toobarDropdow: ['<ul class="dropdown-menu" role="menu">', '</ul>'],
44 toobarDropdowItem: '<li role="menuitem"><label>%s</label></li>',
45 pageDropdown: ['<ul class="dropdown-menu" role="menu">', '</ul>'],
46 pageDropdownItem: '<li role="menuitem" class="%s"><a href="#">%s</a></li>'
47 }
48 },
49 4: {
50 iconsPrefix: 'fa',
51 icons: {
52 paginationSwitchDown: 'fa-caret-square-down',
53 paginationSwitchUp: 'fa-caret-square-up',
54 refresh: 'fa-sync',
55 toggleOff: 'fa-toggle-off',
56 toggleOn: 'fa-toggle-on',
57 columns: 'fa-th-list',
58 detailOpen: 'fa-plus',
59 detailClose: 'fa-minus',
60 fullscreen: 'fa-arrows-alt'
61 },
62 classes: {
63 buttons: 'secondary',
64 pull: 'float'
65 },
66 html: {
67 toobarDropdow: ['<div class="dropdown-menu dropdown-menu-right">', '</div>'],
68 toobarDropdowItem: '<label class="dropdown-item">%s</label>',
69 pageDropdown: ['<div class="dropdown-menu">', '</div>'],
70 pageDropdownItem: '<a class="dropdown-item %s" href="#">%s</a>'
71 }
72 }
73 }[bootstrapVersion]
74
75 const Utils = {
76 bootstrapVersion,
77
78 // it only does '%s', and return '' when arguments are undefined
79 sprintf (_str, ...args) {
80 let flag = true
81 let i = 0
82
83 const str = _str.replace(/%s/g, () => {
84 const arg = args[i++]
85
86 if (typeof arg === 'undefined') {
87 flag = false
88 return ''
89 }
90 return arg
91 })
92 return flag ? str : ''
93 },
94
95 getFieldTitle (list, value) {
96 for (const item of list) {
97 if (item.field === value) {
98 return item.title
99 }
100 }
101 return ''
102 },
103
104 setFieldIndex (columns) {
105 let totalCol = 0
106 const flag = []
107
108 for (const column of columns[0]) {
109 totalCol += column.colspan || 1
110 }
111
112 for (let i = 0; i < columns.length; i++) {
113 flag[i] = []
114 for (let j = 0; j < totalCol; j++) {
115 flag[i][j] = false
116 }
117 }
118
119 for (let i = 0; i < columns.length; i++) {
120 for (const r of columns[i]) {
121 const rowspan = r.rowspan || 1
122 const colspan = r.colspan || 1
123 const index = flag[i].indexOf(false)
124
125 if (colspan === 1) {
126 r.fieldIndex = index
127 // when field is undefined, use index instead
128 if (typeof r.field === 'undefined') {
129 r.field = index
130 }
131 }
132
133 for (let k = 0; k < rowspan; k++) {
134 flag[i + k][index] = true
135 }
136 for (let k = 0; k < colspan; k++) {
137 flag[i][index + k] = true
138 }
139 }
140 }
141 },
142
143 getScrollBarWidth () {
144 if (this.cachedWidth === undefined) {
145 const $inner = $('<div/>').addClass('fixed-table-scroll-inner')
146 const $outer = $('<div/>').addClass('fixed-table-scroll-outer')
147
148 $outer.append($inner)
149 $('body').append($outer)
150
151 const w1 = $inner[0].offsetWidth
152 $outer.css('overflow', 'scroll')
153 let w2 = $inner[0].offsetWidth
154
155 if (w1 === w2) {
156 w2 = $outer[0].clientWidth
157 }
158
159 $outer.remove()
160 this.cachedWidth = w1 - w2
161 }
162 return this.cachedWidth
163 },
164
165 calculateObjectValue (self, name, args, defaultValue) {
166 let func = name
167
168 if (typeof name === 'string') {
169 // support obj.func1.func2
170 const names = name.split('.')
171
172 if (names.length > 1) {
173 func = window
174 for (const f of names) {
175 func = func[f]
176 }
177 } else {
178 func = window[name]
179 }
180 }
181
182 if (func !== null && typeof func === 'object') {
183 return func
184 }
185
186 if (typeof func === 'function') {
187 return func.apply(self, args || [])
188 }
189
190 if (
191 !func &&
192 typeof name === 'string' &&
193 this.sprintf(name, ...args)
194 ) {
195 return this.sprintf(name, ...args)
196 }
197
198 return defaultValue
199 },
200
201 compareObjects (objectA, objectB, compareLength) {
202 const aKeys = Object.keys(objectA)
203 const bKeys = Object.keys(objectB)
204
205 if (compareLength && aKeys.length !== bKeys.length) {
206 return false
207 }
208
209 for (const key of aKeys) {
210 if (bKeys.includes(key) && objectA[key] !== objectB[key]) {
211 return false
212 }
213 }
214
215 return true
216 },
217
218 escapeHTML (text) {
219 if (typeof text === 'string') {
220 return text
221 .replace(/&/g, '&amp;')
222 .replace(/</g, '&lt;')
223 .replace(/>/g, '&gt;')
224 .replace(/"/g, '&quot;')
225 .replace(/'/g, '&#039;')
226 .replace(/`/g, '&#x60;')
227 }
228 return text
229 },
230
231 getRealDataAttr (dataAttr) {
232 for (const [attr, value] of Object.entries(dataAttr)) {
233 const auxAttr = attr.split(/(?=[A-Z])/).join('-').toLowerCase()
234 if (auxAttr !== attr) {
235 dataAttr[auxAttr] = value
236 delete dataAttr[attr]
237 }
238 }
239 return dataAttr
240 },
241
242 getItemField (item, field, escape) {
243 let value = item
244
245 if (typeof field !== 'string' || item.hasOwnProperty(field)) {
246 return escape ? this.escapeHTML(item[field]) : item[field]
247 }
248
249 const props = field.split('.')
250 for (const p of props) {
251 value = value && value[p]
252 }
253 return escape ? this.escapeHTML(value) : value
254 },
255
256 isIEBrowser () {
257 return navigator.userAgent.includes('MSIE ') ||
258 /Trident.*rv:11\./.test(navigator.userAgent)
259 },
260
261 findIndex (items, item) {
262 for (const [i, it] of items.entries()) {
263 if (JSON.stringify(it) === JSON.stringify(item)) {
264 return i
265 }
266 }
267 return -1
268 }
269 }
270
271 // BOOTSTRAP TABLE CLASS DEFINITION
272 // ======================
273
274 const DEFAULTS = {
275 height: undefined,
276 classes: 'table table-bordered table-hover',
277 theadClasses: '',
278 rowStyle (row, index) {
279 return {}
280 },
281 rowAttributes (row, index) {
282 return {}
283 },
284 undefinedText: '-',
285 locale: undefined,
286 sortable: true,
287 sortClass: undefined,
288 silentSort: true,
289 sortName: undefined,
290 sortOrder: 'asc',
291 sortStable: false,
292 rememberOrder: false,
293 customSort: undefined,
294 columns: [
295 []
296 ],
297 data: [],
298 url: undefined,
299 method: 'get',
300 cache: true,
301 contentType: 'application/json',
302 dataType: 'json',
303 ajax: undefined,
304 ajaxOptions: {},
305 queryParams (params) {
306 return params
307 },
308 queryParamsType: 'limit', // 'limit', undefined
309 responseHandler (res) {
310 return res
311 },
312 totalField: 'total',
313 dataField: 'rows',
314 pagination: false,
315 onlyInfoPagination: false,
316 paginationLoop: true,
317 sidePagination: 'client', // client or server
318 totalRows: 0,
319 pageNumber: 1,
320 pageSize: 10,
321 pageList: [10, 25, 50, 100],
322 paginationHAlign: 'right', // right, left
323 paginationVAlign: 'bottom', // bottom, top, both
324 paginationDetailHAlign: 'left', // right, left
325 paginationPreText: '&lsaquo;',
326 paginationNextText: '&rsaquo;',
327 paginationSuccessivelySize: 5, // Maximum successively number of pages in a row
328 paginationPagesBySide: 1, // Number of pages on each side (right, left) of the current page.
329 paginationUseIntermediate: false, // Calculate intermediate pages for quick access
330 search: false,
331 searchOnEnterKey: false,
332 strictSearch: false,
333 trimOnSearch: true,
334 searchAlign: 'right',
335 searchTimeOut: 500,
336 searchText: '',
337 customSearch: undefined,
338 showHeader: true,
339 showFooter: false,
340 footerStyle (row, index) {
341 return {}
342 },
343 showColumns: false,
344 minimumCountColumns: 1,
345 showPaginationSwitch: false,
346 showRefresh: false,
347 showToggle: false,
348 showFullscreen: false,
349 smartDisplay: true,
350 escape: false,
351 idField: undefined,
352 uniqueId: undefined,
353 cardView: false,
354 detailView: false,
355 detailFormatter (index, row) {
356 return ''
357 },
358 detailFilter (index, row) {
359 return true
360 },
361 selectItemName: 'btSelectItem',
362 clickToSelect: false,
363 ignoreClickToSelectOn ({tagName}) {
364 return ['A', 'BUTTON'].includes(tagName)
365 },
366 singleSelect: false,
367 checkboxHeader: true,
368 maintainSelected: false,
369 toolbar: undefined,
370 toolbarAlign: 'left',
371 buttonsToolbar: undefined,
372 buttonsAlign: 'right',
373 buttonsClass: bootstrap.classes.buttons,
374 icons: bootstrap.icons,
375 iconSize: undefined,
376 iconsPrefix: bootstrap.iconsPrefix, // glyphicon or fa(font-awesome)
377 onAll (name, args) {
378 return false
379 },
380 onClickCell (field, value, row, $element) {
381 return false
382 },
383 onDblClickCell (field, value, row, $element) {
384 return false
385 },
386 onClickRow (item, $element) {
387 return false
388 },
389 onDblClickRow (item, $element) {
390 return false
391 },
392 onSort (name, order) {
393 return false
394 },
395 onCheck (row) {
396 return false
397 },
398 onUncheck (row) {
399 return false
400 },
401 onCheckAll (rows) {
402 return false
403 },
404 onUncheckAll (rows) {
405 return false
406 },
407 onCheckSome (rows) {
408 return false
409 },
410 onUncheckSome (rows) {
411 return false
412 },
413 onLoadSuccess (data) {
414 return false
415 },
416 onLoadError (status) {
417 return false
418 },
419 onColumnSwitch (field, checked) {
420 return false
421 },
422 onPageChange (number, size) {
423 return false
424 },
425 onSearch (text) {
426 return false
427 },
428 onToggle (cardView) {
429 return false
430 },
431 onPreBody (data) {
432 return false
433 },
434 onPostBody () {
435 return false
436 },
437 onPostHeader () {
438 return false
439 },
440 onExpandRow (index, row, $detail) {
441 return false
442 },
443 onCollapseRow (index, row) {
444 return false
445 },
446 onRefreshOptions (options) {
447 return false
448 },
449 onRefresh (params) {
450 return false
451 },
452 onResetView () {
453 return false
454 },
455 onScrollBody () {
456 return false
457 }
458 }
459
460 const LOCALES = {}
461 LOCALES['en-US'] = LOCALES.en = {
462 formatLoadingMessage () {
463 return 'Loading, please wait...'
464 },
465 formatRecordsPerPage (pageNumber) {
466 return Utils.sprintf('%s rows per page', pageNumber)
467 },
468 formatShowingRows (pageFrom, pageTo, totalRows) {
469 return Utils.sprintf('Showing %s to %s of %s rows', pageFrom, pageTo, totalRows)
470 },
471 formatDetailPagination (totalRows) {
472 return Utils.sprintf('Showing %s rows', totalRows)
473 },
474 formatSearch () {
475 return 'Search'
476 },
477 formatNoMatches () {
478 return 'No matching records found'
479 },
480 formatPaginationSwitch () {
481 return 'Hide/Show pagination'
482 },
483 formatRefresh () {
484 return 'Refresh'
485 },
486 formatToggle () {
487 return 'Toggle'
488 },
489 formatFullscreen () {
490 return 'Fullscreen'
491 },
492 formatColumns () {
493 return 'Columns'
494 },
495 formatAllRows () {
496 return 'All'
497 }
498 }
499
500 $.extend(DEFAULTS, LOCALES['en-US'])
501
502 const COLUMN_DEFAULTS = {
503 radio: false,
504 checkbox: false,
505 checkboxEnabled: true,
506 field: undefined,
507 title: undefined,
508 titleTooltip: undefined,
509 'class': undefined,
510 align: undefined, // left, right, center
511 halign: undefined, // left, right, center
512 falign: undefined, // left, right, center
513 valign: undefined, // top, middle, bottom
514 width: undefined,
515 sortable: false,
516 order: 'asc', // asc, desc
517 visible: true,
518 switchable: true,
519 clickToSelect: true,
520 formatter: undefined,
521 footerFormatter: undefined,
522 events: undefined,
523 sorter: undefined,
524 sortName: undefined,
525 cellStyle: undefined,
526 searchable: true,
527 searchFormatter: true,
528 cardVisible: true,
529 escape: false,
530 showSelectTitle: false
531 }
532
533 const EVENTS = {
534 'all.bs.table': 'onAll',
535 'click-cell.bs.table': 'onClickCell',
536 'dbl-click-cell.bs.table': 'onDblClickCell',
537 'click-row.bs.table': 'onClickRow',
538 'dbl-click-row.bs.table': 'onDblClickRow',
539 'sort.bs.table': 'onSort',
540 'check.bs.table': 'onCheck',
541 'uncheck.bs.table': 'onUncheck',
542 'check-all.bs.table': 'onCheckAll',
543 'uncheck-all.bs.table': 'onUncheckAll',
544 'check-some.bs.table': 'onCheckSome',
545 'uncheck-some.bs.table': 'onUncheckSome',
546 'load-success.bs.table': 'onLoadSuccess',
547 'load-error.bs.table': 'onLoadError',
548 'column-switch.bs.table': 'onColumnSwitch',
549 'page-change.bs.table': 'onPageChange',
550 'search.bs.table': 'onSearch',
551 'toggle.bs.table': 'onToggle',
552 'pre-body.bs.table': 'onPreBody',
553 'post-body.bs.table': 'onPostBody',
554 'post-header.bs.table': 'onPostHeader',
555 'expand-row.bs.table': 'onExpandRow',
556 'collapse-row.bs.table': 'onCollapseRow',
557 'refresh-options.bs.table': 'onRefreshOptions',
558 'reset-view.bs.table': 'onResetView',
559 'refresh.bs.table': 'onRefresh',
560 'scroll-body.bs.table': 'onScrollBody'
561 }
562
563 class BootstrapTable {
564 constructor (el, options) {
565 this.options = options
566 this.$el = $(el)
567 this.$el_ = this.$el.clone()
568 this.timeoutId_ = 0
569 this.timeoutFooter_ = 0
570
571 this.init()
572 }
573
574 init () {
575 this.initLocale()
576 this.initContainer()
577 this.initTable()
578 this.initHeader()
579 this.initData()
580 this.initHiddenRows()
581 this.initFooter()
582 this.initToolbar()
583 this.initPagination()
584 this.initBody()
585 this.initSearchText()
586 this.initServer()
587 }
588
589 initLocale () {
590 if (this.options.locale) {
591 const locales = $.fn.bootstrapTable.locales
592 const parts = this.options.locale.split(/-|_/)
593
594 parts[0] = parts[0].toLowerCase()
595 if (parts[1]) {
596 parts[1] = parts[1].toUpperCase()
597 }
598
599 if (locales[this.options.locale]) {
600 $.extend(this.options, locales[this.options.locale])
601 } else if (locales[parts.join('-')]) {
602 $.extend(this.options, locales[parts.join('-')])
603 } else if (locales[parts[0]]) {
604 $.extend(this.options, locales[parts[0]])
605 }
606 }
607 }
608
609 initContainer () {
610 const topPagination = ['top', 'both'].includes(this.options.paginationVAlign)
611 ? '<div class="fixed-table-pagination clearfix"></div>' : ''
612 const bottomPagination = ['bottom', 'both'].includes(this.options.paginationVAlign)
613 ? '<div class="fixed-table-pagination"></div>' : ''
614
615 this.$container = $(`
616 <div class="bootstrap-table">
617 <div class="fixed-table-toolbar"></div>
618 ${topPagination}
619 <div class="fixed-table-container">
620 <div class="fixed-table-header"><table></table></div>
621 <div class="fixed-table-body">
622 <div class="fixed-table-loading">
623 ${this.options.formatLoadingMessage()}
624 </div>
625 </div>
626 <div class="fixed-table-footer"><table><tr></tr></table></div>
627 </div>
628 ${bottomPagination}
629 </div>
630 `)
631
632 this.$container.insertAfter(this.$el)
633 this.$tableContainer = this.$container.find('.fixed-table-container')
634 this.$tableHeader = this.$container.find('.fixed-table-header')
635 this.$tableBody = this.$container.find('.fixed-table-body')
636 this.$tableLoading = this.$container.find('.fixed-table-loading')
637 this.$tableFooter = this.$container.find('.fixed-table-footer')
638 // checking if custom table-toolbar exists or not
639 if (this.options.buttonsToolbar) {
640 this.$toolbar = $('body').find(this.options.buttonsToolbar)
641 } else {
642 this.$toolbar = this.$container.find('.fixed-table-toolbar')
643 }
644 this.$pagination = this.$container.find('.fixed-table-pagination')
645
646 this.$tableBody.append(this.$el)
647 this.$container.after('<div class="clearfix"></div>')
648
649 this.$el.addClass(this.options.classes)
650
651 if (this.options.height) {
652 this.$tableContainer.addClass('fixed-height')
653
654 if (this.options.classes.split(' ').includes('table-bordered')) {
655 this.$tableBody.append('<div class="fixed-table-border"></div>')
656 this.$tableBorder = this.$tableBody.find('.fixed-table-border')
657 this.$tableLoading.addClass('fixed-table-border')
658 }
659 }
660 }
661
662 initTable () {
663 const columns = []
664 const data = []
665
666 this.$header = this.$el.find('>thead')
667 if (!this.$header.length) {
668 this.$header = $(`<thead class="${this.options.theadClasses}"></thead>`).appendTo(this.$el)
669 } else if (this.options.theadClasses) {
670 this.$header.addClass(this.options.theadClasses)
671 }
672 this.$header.find('tr').each((i, el) => {
673 const column = []
674
675 $(el).find('th').each((i, el) => {
676 // #2014: getFieldIndex and elsewhere assume this is string, causes issues if not
677 if (typeof $(el).data('field') !== 'undefined') {
678 $(el).data('field', `${$(el).data('field')}`)
679 }
680 column.push($.extend({}, {
681 title: $(el).html(),
682 'class': $(el).attr('class'),
683 titleTooltip: $(el).attr('title'),
684 rowspan: $(el).attr('rowspan') ? +$(el).attr('rowspan') : undefined,
685 colspan: $(el).attr('colspan') ? +$(el).attr('colspan') : undefined
686 }, $(el).data()))
687 })
688 columns.push(column)
689 })
690
691 if (!Array.isArray(this.options.columns[0])) {
692 this.options.columns = [this.options.columns]
693 }
694
695 this.options.columns = $.extend(true, [], columns, this.options.columns)
696 this.columns = []
697 this.fieldsColumnsIndex = []
698
699 Utils.setFieldIndex(this.options.columns)
700
701 this.options.columns.forEach((columns, i) => {
702 columns.forEach((_column, j) => {
703 const column = $.extend({}, BootstrapTable.COLUMN_DEFAULTS, _column)
704
705 if (typeof column.fieldIndex !== 'undefined') {
706 this.columns[column.fieldIndex] = column
707 this.fieldsColumnsIndex[column.field] = column.fieldIndex
708 }
709
710 this.options.columns[i][j] = column
711 })
712 })
713
714 // if options.data is setting, do not process tbody data
715 if (this.options.data.length) {
716 return
717 }
718
719 const m = []
720 this.$el.find('>tbody>tr').each((y, el) => {
721 const row = {}
722
723 // save tr's id, class and data-* attributes
724 row._id = $(el).attr('id')
725 row._class = $(el).attr('class')
726 row._data = Utils.getRealDataAttr($(el).data())
727
728 $(el).find('>td').each((_x, el) => {
729 const cspan = +$(el).attr('colspan') || 1
730 const rspan = +$(el).attr('rowspan') || 1
731 let x = _x
732
733 // skip already occupied cells in current row
734 for (; m[y] && m[y][x]; x++) {
735 // ignore
736 }
737
738 // mark matrix elements occupied by current cell with true
739 for (let tx = x; tx < x + cspan; tx++) {
740 for (let ty = y; ty < y + rspan; ty++) {
741 if (!m[ty]) { // fill missing rows
742 m[ty] = []
743 }
744 m[ty][tx] = true
745 }
746 }
747
748 const field = this.columns[x].field
749
750 row[field] = $(el).html()
751 // save td's id, class and data-* attributes
752 row[`_${field}_id`] = $(el).attr('id')
753 row[`_${field}_class`] = $(el).attr('class')
754 row[`_${field}_rowspan`] = $(el).attr('rowspan')
755 row[`_${field}_colspan`] = $(el).attr('colspan')
756 row[`_${field}_title`] = $(el).attr('title')
757 row[`_${field}_data`] = Utils.getRealDataAttr($(el).data())
758 })
759 data.push(row)
760 })
761 this.options.data = data
762 if (data.length) {
763 this.fromHtml = true
764 }
765 }
766
767 initHeader () {
768 const visibleColumns = {}
769 const html = []
770
771 this.header = {
772 fields: [],
773 styles: [],
774 classes: [],
775 formatters: [],
776 events: [],
777 sorters: [],
778 sortNames: [],
779 cellStyles: [],
780 searchables: []
781 }
782
783 this.options.columns.forEach((columns, i) => {
784 html.push('<tr>')
785
786 if (i === 0 && !this.options.cardView && this.options.detailView) {
787 html.push(`<th class="detail" rowspan="${this.options.columns.length}">
788 <div class="fht-cell"></div>
789 </th>
790 `)
791 }
792
793 columns.forEach((column, j) => {
794 let text = ''
795
796 let halign = '' // header align style
797
798 let align = '' // body align style
799
800 let style = ''
801 const class_ = Utils.sprintf(' class="%s"', column['class'])
802 let unitWidth = 'px'
803 let width = column.width
804
805 if (column.width !== undefined && (!this.options.cardView)) {
806 if (typeof column.width === 'string') {
807 if (column.width.includes('%')) {
808 unitWidth = '%'
809 }
810 }
811 }
812 if (column.width && typeof column.width === 'string') {
813 width = column.width.replace('%', '').replace('px', '')
814 }
815
816 halign = Utils.sprintf('text-align: %s; ', column.halign ? column.halign : column.align)
817 align = Utils.sprintf('text-align: %s; ', column.align)
818 style = Utils.sprintf('vertical-align: %s; ', column.valign)
819 style += Utils.sprintf('width: %s; ', (column.checkbox || column.radio) && !width
820 ? (!column.showSelectTitle ? '36px' : undefined)
821 : (width ? width + unitWidth : undefined))
822
823 if (typeof column.fieldIndex !== 'undefined') {
824 this.header.fields[column.fieldIndex] = column.field
825 this.header.styles[column.fieldIndex] = align + style
826 this.header.classes[column.fieldIndex] = class_
827 this.header.formatters[column.fieldIndex] = column.formatter
828 this.header.events[column.fieldIndex] = column.events
829 this.header.sorters[column.fieldIndex] = column.sorter
830 this.header.sortNames[column.fieldIndex] = column.sortName
831 this.header.cellStyles[column.fieldIndex] = column.cellStyle
832 this.header.searchables[column.fieldIndex] = column.searchable
833
834 if (!column.visible) {
835 return
836 }
837
838 if (this.options.cardView && (!column.cardVisible)) {
839 return
840 }
841
842 visibleColumns[column.field] = column
843 }
844
845 html.push(`<th${Utils.sprintf(' title="%s"', column.titleTooltip)}`,
846 column.checkbox || column.radio
847 ? Utils.sprintf(' class="bs-checkbox %s"', column['class'] || '')
848 : class_,
849 Utils.sprintf(' style="%s"', halign + style),
850 Utils.sprintf(' rowspan="%s"', column.rowspan),
851 Utils.sprintf(' colspan="%s"', column.colspan),
852 Utils.sprintf(' data-field="%s"', column.field),
853 // If `column` is not the first element of `this.options.columns[0]`, then className 'data-not-first-th' should be added.
854 j === 0 && i > 0 ? ' data-not-first-th' : '',
855 '>')
856
857 html.push(Utils.sprintf('<div class="th-inner %s">', this.options.sortable && column.sortable
858 ? 'sortable both' : ''))
859
860 text = this.options.escape ? Utils.escapeHTML(column.title) : column.title
861
862 const title = text
863 if (column.checkbox) {
864 text = ''
865 if (!this.options.singleSelect && this.options.checkboxHeader) {
866 text = '<input name="btSelectAll" type="checkbox" />'
867 }
868 this.header.stateField = column.field
869 }
870 if (column.radio) {
871 text = ''
872 this.header.stateField = column.field
873 this.options.singleSelect = true
874 }
875 if (!text && column.showSelectTitle) {
876 text += title
877 }
878
879 html.push(text)
880 html.push('</div>')
881 html.push('<div class="fht-cell"></div>')
882 html.push('</div>')
883 html.push('</th>')
884 })
885 html.push('</tr>')
886 })
887
888 this.$header.html(html.join(''))
889 this.$header.find('th[data-field]').each((i, el) => {
890 $(el).data(visibleColumns[$(el).data('field')])
891 })
892 this.$container.off('click', '.th-inner').on('click', '.th-inner', e => {
893 const $this = $(e.currentTarget)
894
895 if (this.options.detailView && !$this.parent().hasClass('bs-checkbox')) {
896 if ($this.closest('.bootstrap-table')[0] !== this.$container[0]) {
897 return false
898 }
899 }
900
901 if (this.options.sortable && $this.parent().data().sortable) {
902 this.onSort(e)
903 }
904 })
905
906 this.$header.children().children().off('keypress').on('keypress', e => {
907 if (this.options.sortable && $(e.currentTarget).data().sortable) {
908 const code = e.keyCode || e.which
909 if (code === 13) { // Enter keycode
910 this.onSort(e)
911 }
912 }
913 })
914
915 $(window).off('resize.bootstrap-table')
916 if (!this.options.showHeader || this.options.cardView) {
917 this.$header.hide()
918 this.$tableHeader.hide()
919 this.$tableLoading.css('top', 0)
920 } else {
921 this.$header.show()
922 this.$tableHeader.show()
923 this.$tableLoading.css('top', this.$header.outerHeight() + 1)
924 // Assign the correct sortable arrow
925 this.getCaret()
926 $(window).on('resize.bootstrap-table', $.proxy(this.resetWidth, this))
927 }
928
929 this.$selectAll = this.$header.find('[name="btSelectAll"]')
930 this.$selectAll.off('click').on('click', ({currentTarget}) => {
931 const checked = $(currentTarget).prop('checked')
932 this[checked ? 'checkAll' : 'uncheckAll']()
933 this.updateSelected()
934 })
935 }
936
937 initFooter () {
938 if (!this.options.showFooter || this.options.cardView) {
939 this.$tableFooter.hide()
940 } else {
941 this.$tableFooter.show()
942 }
943 }
944
945 initData (data, type) {
946 if (type === 'append') {
947 this.options.data = this.options.data.concat(data)
948 } else if (type === 'prepend') {
949 this.options.data = [].concat(data).concat(this.options.data)
950 } else {
951 this.options.data = data || this.options.data
952 }
953
954 this.data = this.options.data
955
956 if (this.options.sidePagination === 'server') {
957 return
958 }
959 this.initSort()
960 }
961
962 initSort () {
963 let name = this.options.sortName
964 const order = this.options.sortOrder === 'desc' ? -1 : 1
965 const index = this.header.fields.indexOf(this.options.sortName)
966 let timeoutId = 0
967
968 if (index !== -1) {
969 if (this.options.sortStable) {
970 this.data.forEach((row, i) => {
971 if (!row.hasOwnProperty('_position')) {
972 row._position = i
973 }
974 })
975 }
976
977 if (this.options.customSort) {
978 Utils.calculateObjectValue(this.options, this.options.customSort, [
979 this.options.sortName,
980 this.options.sortOrder,
981 this.data
982 ])
983 } else {
984 this.data.sort((a, b) => {
985 if (this.header.sortNames[index]) {
986 name = this.header.sortNames[index]
987 }
988 let aa = Utils.getItemField(a, name, this.options.escape)
989 let bb = Utils.getItemField(b, name, this.options.escape)
990 const value = Utils.calculateObjectValue(this.header, this.header.sorters[index], [aa, bb, a, b])
991
992 if (value !== undefined) {
993 if (this.options.sortStable && value === 0) {
994 return order * (a._position - b._position)
995 }
996 return order * value
997 }
998
999 // Fix #161: undefined or null string sort bug.
1000 if (aa === undefined || aa === null) {
1001 aa = ''
1002 }
1003 if (bb === undefined || bb === null) {
1004 bb = ''
1005 }
1006
1007 if (this.options.sortStable && aa === bb) {
1008 aa = a._position
1009 bb = b._position
1010 }
1011
1012 // IF both values are numeric, do a numeric comparison
1013 if ($.isNumeric(aa) && $.isNumeric(bb)) {
1014 // Convert numerical values form string to float.
1015 aa = parseFloat(aa)
1016 bb = parseFloat(bb)
1017 if (aa < bb) {
1018 return order * -1
1019 }
1020 if (aa > bb) {
1021 return order
1022 }
1023 return 0
1024 }
1025
1026 if (aa === bb) {
1027 return 0
1028 }
1029
1030 // If value is not a string, convert to string
1031 if (typeof aa !== 'string') {
1032 aa = aa.toString()
1033 }
1034
1035 if (aa.localeCompare(bb) === -1) {
1036 return order * -1
1037 }
1038
1039 return order
1040 })
1041 }
1042
1043 if (this.options.sortClass !== undefined) {
1044 clearTimeout(timeoutId)
1045 timeoutId = setTimeout(() => {
1046 this.$el.removeClass(this.options.sortClass)
1047 const index = this.$header.find(`[data-field="${this.options.sortName}"]`).index()
1048 this.$el.find(`tr td:nth-child(${index + 1})`).addClass(this.options.sortClass)
1049 }, 250)
1050 }
1051 }
1052 }
1053
1054 onSort ({type, currentTarget}) {
1055 const $this = type === 'keypress' ? $(currentTarget) : $(currentTarget).parent()
1056 const $this_ = this.$header.find('th').eq($this.index())
1057
1058 this.$header.add(this.$header_).find('span.order').remove()
1059
1060 if (this.options.sortName === $this.data('field')) {
1061 this.options.sortOrder = this.options.sortOrder === 'asc' ? 'desc' : 'asc'
1062 } else {
1063 this.options.sortName = $this.data('field')
1064 if (this.options.rememberOrder) {
1065 this.options.sortOrder = $this.data('order') === 'asc' ? 'desc' : 'asc'
1066 } else {
1067 this.options.sortOrder = this.columns[this.fieldsColumnsIndex[$this.data('field')]].order
1068 }
1069 }
1070 this.trigger('sort', this.options.sortName, this.options.sortOrder)
1071
1072 $this.add($this_).data('order', this.options.sortOrder)
1073
1074 // Assign the correct sortable arrow
1075 this.getCaret()
1076
1077 if (this.options.sidePagination === 'server') {
1078 this.initServer(this.options.silentSort)
1079 return
1080 }
1081
1082 this.initSort()
1083 this.initBody()
1084 }
1085
1086 initToolbar () {
1087 let html = []
1088 let timeoutId = 0
1089 let $keepOpen
1090 let $search
1091 let switchableCount = 0
1092
1093 if (this.$toolbar.find('.bs-bars').children().length) {
1094 $('body').append($(this.options.toolbar))
1095 }
1096 this.$toolbar.html('')
1097
1098 if (typeof this.options.toolbar === 'string' || typeof this.options.toolbar === 'object') {
1099 $(Utils.sprintf('<div class="bs-bars %s-%s"></div>', bootstrap.classes.pull, this.options.toolbarAlign))
1100 .appendTo(this.$toolbar)
1101 .append($(this.options.toolbar))
1102 }
1103
1104 // showColumns, showToggle, showRefresh
1105 html = [Utils.sprintf('<div class="columns columns-%s btn-group %s-%s">',
1106 this.options.buttonsAlign, bootstrap.classes.pull, this.options.buttonsAlign)]
1107
1108 if (typeof this.options.icons === 'string') {
1109 this.options.icons = Utils.calculateObjectValue(null, this.options.icons)
1110 }
1111
1112 if (this.options.showPaginationSwitch) {
1113 html.push(Utils.sprintf(`<button class="btn${Utils.sprintf(' btn-%s', this.options.buttonsClass)}${Utils.sprintf(' btn-%s', this.options.iconSize)}" type="button" name="paginationSwitch" aria-label="pagination Switch" title="%s">`,
1114 this.options.formatPaginationSwitch()),
1115 Utils.sprintf('<i class="%s %s"></i>', this.options.iconsPrefix, this.options.icons.paginationSwitchDown),
1116 '</button>')
1117 }
1118
1119 if (this.options.showFullscreen) {
1120 this.$toolbar.find('button[name="fullscreen"]')
1121 .off('click').on('click', $.proxy(this.toggleFullscreen, this))
1122 }
1123
1124 if (this.options.showRefresh) {
1125 html.push(Utils.sprintf(`<button class="btn${Utils.sprintf(' btn-%s', this.options.buttonsClass)}${Utils.sprintf(' btn-%s', this.options.iconSize)}" type="button" name="refresh" aria-label="refresh" title="%s">`,
1126 this.options.formatRefresh()),
1127 Utils.sprintf('<i class="%s %s"></i>', this.options.iconsPrefix, this.options.icons.refresh),
1128 '</button>')
1129 }
1130
1131 if (this.options.showToggle) {
1132 html.push(Utils.sprintf(`<button class="btn${Utils.sprintf(' btn-%s', this.options.buttonsClass)}${Utils.sprintf(' btn-%s', this.options.iconSize)}" type="button" name="toggle" aria-label="toggle" title="%s">`,
1133 this.options.formatToggle()),
1134 Utils.sprintf('<i class="%s %s"></i>', this.options.iconsPrefix, this.options.icons.toggleOff),
1135 '</button>')
1136 }
1137
1138 if (this.options.showFullscreen) {
1139 html.push(Utils.sprintf(`<button class="btn${Utils.sprintf(' btn-%s', this.options.buttonsClass)}${Utils.sprintf(' btn-%s', this.options.iconSize)}" type="button" name="fullscreen" aria-label="fullscreen" title="%s">`,
1140 this.options.formatFullscreen()),
1141 Utils.sprintf('<i class="%s %s"></i>', this.options.iconsPrefix, this.options.icons.fullscreen),
1142 '</button>')
1143 }
1144
1145 if (this.options.showColumns) {
1146 html.push(Utils.sprintf('<div class="keep-open btn-group" title="%s">',
1147 this.options.formatColumns()),
1148 `<button type="button" aria-label="columns" class="btn${Utils.sprintf(' btn-%s', this.options.buttonsClass)}${Utils.sprintf(' btn-%s', this.options.iconSize)} dropdown-toggle" data-toggle="dropdown">`,
1149 Utils.sprintf('<i class="%s %s"></i>', this.options.iconsPrefix, this.options.icons.columns),
1150 ' <span class="caret"></span>',
1151 '</button>',
1152 bootstrap.html.toobarDropdow[0])
1153
1154 this.columns.forEach((column, i) => {
1155 if (column.radio || column.checkbox) {
1156 return
1157 }
1158
1159 if (this.options.cardView && !column.cardVisible) {
1160 return
1161 }
1162
1163 const checked = column.visible ? ' checked="checked"' : ''
1164
1165 if (column.switchable) {
1166 html.push(Utils.sprintf(bootstrap.html.toobarDropdowItem,
1167 Utils.sprintf('<input type="checkbox" data-field="%s" value="%s"%s> %s',
1168 column.field, i, checked, column.title)))
1169 switchableCount++
1170 }
1171 })
1172 html.push(bootstrap.html.toobarDropdow[1], '</div>')
1173 }
1174
1175 html.push('</div>')
1176
1177 // Fix #188: this.showToolbar is for extensions
1178 if (this.showToolbar || html.length > 2) {
1179 this.$toolbar.append(html.join(''))
1180 }
1181
1182 if (this.options.showPaginationSwitch) {
1183 this.$toolbar.find('button[name="paginationSwitch"]')
1184 .off('click').on('click', $.proxy(this.togglePagination, this))
1185 }
1186
1187 if (this.options.showRefresh) {
1188 this.$toolbar.find('button[name="refresh"]')
1189 .off('click').on('click', $.proxy(this.refresh, this))
1190 }
1191
1192 if (this.options.showToggle) {
1193 this.$toolbar.find('button[name="toggle"]')
1194 .off('click').on('click', () => {
1195 this.toggleView()
1196 })
1197 }
1198
1199 if (this.options.showColumns) {
1200 $keepOpen = this.$toolbar.find('.keep-open')
1201
1202 if (switchableCount <= this.options.minimumCountColumns) {
1203 $keepOpen.find('input').prop('disabled', true)
1204 }
1205
1206 $keepOpen.find('li').off('click').on('click', e => {
1207 e.stopImmediatePropagation()
1208 })
1209 $keepOpen.find('input').off('click').on('click', ({currentTarget}) => {
1210 const $this = $(currentTarget)
1211
1212 this.toggleColumn($this.val(), $this.prop('checked'), false)
1213 this.trigger('column-switch', $this.data('field'), $this.prop('checked'))
1214 })
1215 }
1216
1217 if (this.options.search) {
1218 html = []
1219 html.push(
1220 Utils.sprintf('<div class="%s-%s search">', bootstrap.classes.pull, this.options.searchAlign),
1221 Utils.sprintf(`<input class="form-control${Utils.sprintf(' input-%s', this.options.iconSize)}" type="text" placeholder="%s">`,
1222 this.options.formatSearch()),
1223 '</div>')
1224
1225 this.$toolbar.append(html.join(''))
1226 $search = this.$toolbar.find('.search input')
1227 $search.off('keyup drop blur').on('keyup drop blur', event => {
1228 if (this.options.searchOnEnterKey && event.keyCode !== 13) {
1229 return
1230 }
1231
1232 if ([37, 38, 39, 40].includes(event.keyCode)) {
1233 return
1234 }
1235
1236 clearTimeout(timeoutId) // doesn't matter if it's 0
1237 timeoutId = setTimeout(() => {
1238 this.onSearch(event)
1239 }, this.options.searchTimeOut)
1240 })
1241
1242 if (Utils.isIEBrowser()) {
1243 $search.off('mouseup').on('mouseup', event => {
1244 clearTimeout(timeoutId) // doesn't matter if it's 0
1245 timeoutId = setTimeout(() => {
1246 this.onSearch(event)
1247 }, this.options.searchTimeOut)
1248 })
1249 }
1250 }
1251 }
1252
1253 onSearch ({currentTarget, firedByInitSearchText}) {
1254 const text = $.trim($(currentTarget).val())
1255
1256 // trim search input
1257 if (this.options.trimOnSearch && $(currentTarget).val() !== text) {
1258 $(currentTarget).val(text)
1259 }
1260
1261 if (text === this.searchText) {
1262 return
1263 }
1264 this.searchText = text
1265 this.options.searchText = text
1266
1267 if (!firedByInitSearchText) {
1268 this.options.pageNumber = 1
1269 }
1270 this.initSearch()
1271 if (firedByInitSearchText) {
1272 if (this.options.sidePagination === 'client') {
1273 this.updatePagination()
1274 }
1275 } else {
1276 this.updatePagination()
1277 }
1278 this.trigger('search', text)
1279 }
1280
1281 initSearch () {
1282 if (this.options.sidePagination !== 'server') {
1283 if (this.options.customSearch) {
1284 Utils.calculateObjectValue(this.options, this.options.customSearch, [this.searchText])
1285 return
1286 }
1287
1288 const s = this.searchText && (this.options.escape
1289 ? Utils.escapeHTML(this.searchText) : this.searchText).toLowerCase()
1290 const f = $.isEmptyObject(this.filterColumns) ? null : this.filterColumns
1291
1292 // Check filter
1293 this.data = f ? this.options.data.filter((item, i) => {
1294 for (const key in f) {
1295 if (
1296 (Array.isArray(f[key]) &&
1297 !f[key].includes(item[key])) ||
1298 (!Array.isArray(f[key]) &&
1299 item[key] !== f[key])
1300 ) {
1301 return false
1302 }
1303 }
1304 return true
1305 }) : this.options.data
1306
1307 this.data = s ? this.data.filter((item, i) => {
1308 for (let j = 0; j < this.header.fields.length; j++) {
1309 if (!this.header.searchables[j]) {
1310 continue
1311 }
1312
1313 const key = $.isNumeric(this.header.fields[j]) ? parseInt(this.header.fields[j], 10) : this.header.fields[j]
1314 const column = this.columns[this.fieldsColumnsIndex[key]]
1315 let value
1316
1317 if (typeof key === 'string') {
1318 value = item
1319 const props = key.split('.')
1320 for (let i = 0; i < props.length; i++) {
1321 if (value[props[i]] !== null) {
1322 value = value[props[i]]
1323 }
1324 }
1325 } else {
1326 value = item[key]
1327 }
1328
1329 // Fix #142: respect searchForamtter boolean
1330 if (column && column.searchFormatter) {
1331 value = Utils.calculateObjectValue(column,
1332 this.header.formatters[j], [value, item, i], value)
1333 }
1334
1335 if (typeof value === 'string' || typeof value === 'number') {
1336 if (this.options.strictSearch) {
1337 if ((`${value}`).toLowerCase() === s) {
1338 return true
1339 }
1340 } else {
1341 if ((`${value}`).toLowerCase().includes(s)) {
1342 return true
1343 }
1344 }
1345 }
1346 }
1347 return false
1348 }) : this.data
1349 }
1350 }
1351
1352 initPagination () {
1353 if (!this.options.pagination) {
1354 this.$pagination.hide()
1355 return
1356 }
1357 this.$pagination.show()
1358
1359
1360 const html = []
1361 let $allSelected = false
1362 let i
1363 let from
1364 let to
1365 let $pageList
1366 let $pre
1367 let $next
1368 let $number
1369 const data = this.getData()
1370 let pageList = this.options.pageList
1371
1372 if (this.options.sidePagination !== 'server') {
1373 this.options.totalRows = data.length
1374 }
1375
1376 this.totalPages = 0
1377 if (this.options.totalRows) {
1378 if (this.options.pageSize === this.options.formatAllRows()) {
1379 this.options.pageSize = this.options.totalRows
1380 $allSelected = true
1381 } else if (this.options.pageSize === this.options.totalRows) {
1382 // Fix #667 Table with pagination,
1383 // multiple pages and a search this matches to one page throws exception
1384 const pageLst = typeof this.options.pageList === 'string'
1385 ? this.options.pageList.replace('[', '').replace(']', '')
1386 .replace(/ /g, '').toLowerCase().split(',') : this.options.pageList
1387 if (pageLst.includes(this.options.formatAllRows().toLowerCase())) {
1388 $allSelected = true
1389 }
1390 }
1391
1392 this.totalPages = ~~((this.options.totalRows - 1) / this.options.pageSize) + 1
1393
1394 this.options.totalPages = this.totalPages
1395 }
1396 if (this.totalPages > 0 && this.options.pageNumber > this.totalPages) {
1397 this.options.pageNumber = this.totalPages
1398 }
1399
1400 this.pageFrom = (this.options.pageNumber - 1) * this.options.pageSize + 1
1401 this.pageTo = this.options.pageNumber * this.options.pageSize
1402 if (this.pageTo > this.options.totalRows) {
1403 this.pageTo = this.options.totalRows
1404 }
1405
1406 html.push(
1407 Utils.sprintf('<div class="%s-%s pagination-detail">', bootstrap.classes.pull, this.options.paginationDetailHAlign),
1408 '<span class="pagination-info">',
1409 this.options.onlyInfoPagination ? this.options.formatDetailPagination(this.options.totalRows)
1410 : this.options.formatShowingRows(this.pageFrom, this.pageTo, this.options.totalRows),
1411 '</span>')
1412
1413 if (!this.options.onlyInfoPagination) {
1414 html.push('<span class="page-list">')
1415
1416 const pageNumber = [
1417 Utils.sprintf('<span class="btn-group %s">',
1418 this.options.paginationVAlign === 'top' || this.options.paginationVAlign === 'both'
1419 ? 'dropdown' : 'dropup'),
1420 `<button type="button" class="btn${Utils.sprintf(' btn-%s', this.options.buttonsClass)}${Utils.sprintf(' btn-%s', this.options.iconSize)} dropdown-toggle" data-toggle="dropdown">`,
1421 '<span class="page-size">',
1422 $allSelected ? this.options.formatAllRows() : this.options.pageSize,
1423 '</span>',
1424 ' <span class="caret"></span>',
1425 '</button>',
1426 bootstrap.html.pageDropdown[0]
1427 ]
1428
1429 if (typeof this.options.pageList === 'string') {
1430 const list = this.options.pageList.replace('[', '').replace(']', '')
1431 .replace(/ /g, '').split(',')
1432
1433 pageList = []
1434 for (const value of list) {
1435 pageList.push(
1436 (value.toUpperCase() === this.options.formatAllRows().toUpperCase() ||
1437 value.toUpperCase() === 'UNLIMITED')
1438 ? this.options.formatAllRows() : +value)
1439 }
1440 }
1441
1442 pageList.forEach((page, i) => {
1443 if (!this.options.smartDisplay || i === 0 || pageList[i - 1] < this.options.totalRows) {
1444 let active
1445 if ($allSelected) {
1446 active = page === this.options.formatAllRows() ? 'active' : ''
1447 } else {
1448 active = page === this.options.pageSize ? 'active' : ''
1449 }
1450 pageNumber.push(Utils.sprintf(bootstrap.html.pageDropdownItem, active, page))
1451 }
1452 })
1453 pageNumber.push(`${bootstrap.html.pageDropdown[1]}</span>`)
1454
1455 html.push(this.options.formatRecordsPerPage(pageNumber.join('')))
1456 html.push('</span>')
1457
1458 html.push('</div>',
1459 Utils.sprintf('<div class="%s-%s pagination">', bootstrap.classes.pull, this.options.paginationHAlign),
1460 `<ul class="pagination${Utils.sprintf(' pagination-%s', this.options.iconSize)}">`,
1461 Utils.sprintf('<li class="page-item page-pre"><a class="page-link" href="#">%s</a></li>',
1462 this.options.paginationPreText))
1463
1464 if (this.totalPages < this.options.paginationSuccessivelySize) {
1465 from = 1
1466 to = this.totalPages
1467 } else {
1468 from = this.options.pageNumber - this.options.paginationPagesBySide
1469 to = from + (this.options.paginationPagesBySide * 2)
1470 }
1471
1472 if (this.options.pageNumber < (this.options.paginationSuccessivelySize - 1)) {
1473 to = this.options.paginationSuccessivelySize
1474 }
1475
1476 if (to > this.totalPages) {
1477 to = this.totalPages
1478 }
1479
1480 if (this.options.paginationSuccessivelySize > this.totalPages - from) {
1481 from = from - (this.options.paginationSuccessivelySize - (this.totalPages - from)) + 1
1482 }
1483
1484 if (from < 1) {
1485 from = 1
1486 }
1487
1488 if (to > this.totalPages) {
1489 to = this.totalPages
1490 }
1491
1492 const middleSize = Math.round(this.options.paginationPagesBySide / 2)
1493 const pageItem = (i, classes = '') => `
1494 <li class="page-item${classes}${i === this.options.pageNumber ? ' active' : ''}">
1495 <a class="page-link" href="#">${i}</a>
1496 </li>
1497 `
1498
1499 if (from > 1) {
1500 let max = this.options.paginationPagesBySide
1501 if (max >= from) max = from - 1
1502 for (i = 1; i <= max; i++) {
1503 html.push(pageItem(i))
1504 }
1505 if ((from - 1) === max + 1) {
1506 i = from - 1
1507 html.push(pageItem(i))
1508 } else {
1509 if ((from - 1) > max) {
1510 if (
1511 (from - this.options.paginationPagesBySide * 2) > this.options.paginationPagesBySide &&
1512 this.options.paginationUseIntermediate
1513 ) {
1514 i = Math.round(((from - middleSize) / 2) + middleSize)
1515 html.push(pageItem(i, ' page-intermediate'))
1516 } else {
1517 html.push(`
1518 <li class="page-item page-first-separator disabled">
1519 <a class="page-link" href="#">...</a>
1520 </li>`
1521 )
1522 }
1523 }
1524 }
1525 }
1526
1527 for (i = from; i <= to; i++) {
1528 html.push(pageItem(i))
1529 }
1530
1531 if (this.totalPages > to) {
1532 let min = this.totalPages - (this.options.paginationPagesBySide - 1)
1533 if (to >= min) min = to + 1
1534 if ((to + 1) === min - 1) {
1535 i = to + 1
1536 html.push(pageItem(i))
1537 } else {
1538 if (min > (to + 1)) {
1539 if (
1540 (this.totalPages - to) > this.options.paginationPagesBySide * 2 &&
1541 this.options.paginationUseIntermediate
1542 ) {
1543 i = Math.round(((this.totalPages - middleSize - to) / 2) + to)
1544 html.push(pageItem(i, ' page-intermediate'))
1545 } else {
1546 html.push(`
1547 <li class="page-item page-last-separator disabled">
1548 <a class="page-link" href="#">...</a>
1549 </li>`
1550 )
1551 }
1552 }
1553 }
1554
1555 for (i = min; i <= this.totalPages; i++) {
1556 html.push(pageItem(i))
1557 }
1558 }
1559
1560 html.push(`
1561 <li class="page-item page-next">
1562 <a class="page-link" href="#">${this.options.paginationNextText}</a>
1563 </li>
1564 </ul>
1565 </div>
1566 `)
1567 }
1568 this.$pagination.html(html.join(''))
1569
1570 if (!this.options.onlyInfoPagination) {
1571 $pageList = this.$pagination.find('.page-list a')
1572 $pre = this.$pagination.find('.page-pre')
1573 $next = this.$pagination.find('.page-next')
1574 $number = this.$pagination.find('.page-item').not('.page-next, .page-pre')
1575
1576 if (this.options.smartDisplay) {
1577 if (this.totalPages <= 1) {
1578 this.$pagination.find('div.pagination').hide()
1579 }
1580 if (pageList.length < 2 || this.options.totalRows <= pageList[0]) {
1581 this.$pagination.find('span.page-list').hide()
1582 }
1583
1584 // when data is empty, hide the pagination
1585 this.$pagination[this.getData().length ? 'show' : 'hide']()
1586 }
1587
1588 if (!this.options.paginationLoop) {
1589 if (this.options.pageNumber === 1) {
1590 $pre.addClass('disabled')
1591 }
1592 if (this.options.pageNumber === this.totalPages) {
1593 $next.addClass('disabled')
1594 }
1595 }
1596
1597 if ($allSelected) {
1598 this.options.pageSize = this.options.formatAllRows()
1599 }
1600 // removed the events for last and first, onPageNumber executeds the same logic
1601 $pageList.off('click').on('click', $.proxy(this.onPageListChange, this))
1602 $pre.off('click').on('click', $.proxy(this.onPagePre, this))
1603 $next.off('click').on('click', $.proxy(this.onPageNext, this))
1604 $number.off('click').on('click', $.proxy(this.onPageNumber, this))
1605 }
1606 }
1607
1608 updatePagination (event) {
1609 // Fix #171: IE disabled button can be clicked bug.
1610 if (event && $(event.currentTarget).hasClass('disabled')) {
1611 return
1612 }
1613
1614 if (!this.options.maintainSelected) {
1615 this.resetRows()
1616 }
1617
1618 this.initPagination()
1619 if (this.options.sidePagination === 'server') {
1620 this.initServer()
1621 } else {
1622 this.initBody()
1623 }
1624
1625 this.trigger('page-change', this.options.pageNumber, this.options.pageSize)
1626 }
1627
1628 onPageListChange (event) {
1629 event.preventDefault()
1630 const $this = $(event.currentTarget)
1631
1632 $this.parent().addClass('active').siblings().removeClass('active')
1633 this.options.pageSize = $this.text().toUpperCase() === this.options.formatAllRows().toUpperCase()
1634 ? this.options.formatAllRows() : +$this.text()
1635 this.$toolbar.find('.page-size').text(this.options.pageSize)
1636
1637 this.updatePagination(event)
1638 return false
1639 }
1640
1641 onPagePre (event) {
1642 event.preventDefault()
1643 if ((this.options.pageNumber - 1) === 0) {
1644 this.options.pageNumber = this.options.totalPages
1645 } else {
1646 this.options.pageNumber--
1647 }
1648 this.updatePagination(event)
1649 return false
1650 }
1651
1652 onPageNext (event) {
1653 event.preventDefault()
1654 if ((this.options.pageNumber + 1) > this.options.totalPages) {
1655 this.options.pageNumber = 1
1656 } else {
1657 this.options.pageNumber++
1658 }
1659 this.updatePagination(event)
1660 return false
1661 }
1662
1663 onPageNumber (event) {
1664 event.preventDefault()
1665 if (this.options.pageNumber === +$(event.currentTarget).text()) {
1666 return
1667 }
1668 this.options.pageNumber = +$(event.currentTarget).text()
1669 this.updatePagination(event)
1670 return false
1671 }
1672
1673 initRow (item, i, data, parentDom) {
1674 const html = []
1675 let style = {}
1676 const csses = []
1677 let data_ = ''
1678 let attributes = {}
1679 const htmlAttributes = []
1680
1681 if (Utils.findIndex(this.hiddenRows, item) > -1) {
1682 return
1683 }
1684
1685 style = Utils.calculateObjectValue(this.options, this.options.rowStyle, [item, i], style)
1686
1687 if (style && style.css) {
1688 for (const [key, value] of Object.entries(style.css)) {
1689 csses.push(`${key}: ${value}`)
1690 }
1691 }
1692
1693 attributes = Utils.calculateObjectValue(this.options,
1694 this.options.rowAttributes, [item, i], attributes)
1695
1696 if (attributes) {
1697 for (const [key, value] of Object.entries(attributes)) {
1698 htmlAttributes.push(`${key}="${Utils.escapeHTML(value)}"`)
1699 }
1700 }
1701
1702 if (item._data && !$.isEmptyObject(item._data)) {
1703 for (const [k, v] of Object.entries(item._data)) {
1704 // ignore data-index
1705 if (k === 'index') {
1706 return
1707 }
1708 data_ += ` data-${k}="${v}"`
1709 }
1710 }
1711
1712 html.push('<tr',
1713 Utils.sprintf(' %s', htmlAttributes.length ? htmlAttributes.join(' ') : undefined),
1714 Utils.sprintf(' id="%s"', Array.isArray(item) ? undefined : item._id),
1715 Utils.sprintf(' class="%s"', style.classes || (Array.isArray(item) ? undefined : item._class)),
1716 ` data-index="${i}"`,
1717 Utils.sprintf(' data-uniqueid="%s"', item[this.options.uniqueId]),
1718 Utils.sprintf('%s', data_),
1719 '>'
1720 )
1721
1722 if (this.options.cardView) {
1723 html.push(`<td colspan="${this.header.fields.length}"><div class="card-views">`)
1724 }
1725
1726 if (!this.options.cardView && this.options.detailView) {
1727 html.push('<td>')
1728
1729 if (Utils.calculateObjectValue(null, this.options.detailFilter, [i, item])) {
1730 html.push(`
1731 <a class="detail-icon" href="#">
1732 <i class="${this.options.iconsPrefix} ${this.options.icons.detailOpen}"></i>
1733 </a>
1734 `)
1735 }
1736
1737 html.push('</td>')
1738 }
1739
1740 this.header.fields.forEach((field, j) => {
1741 let text = ''
1742 let value_ = Utils.getItemField(item, field, this.options.escape)
1743 let value = ''
1744 let type = ''
1745 let cellStyle = {}
1746 let id_ = ''
1747 let class_ = this.header.classes[j]
1748 let style_ = ''
1749 let data_ = ''
1750 let rowspan_ = ''
1751 let colspan_ = ''
1752 let title_ = ''
1753 const column = this.columns[j]
1754
1755 if (this.fromHtml && typeof value_ === 'undefined') {
1756 if ((!column.checkbox) && (!column.radio)) {
1757 return
1758 }
1759 }
1760
1761 if (!column.visible) {
1762 return
1763 }
1764
1765 if (this.options.cardView && (!column.cardVisible)) {
1766 return
1767 }
1768
1769 if (column.escape) {
1770 value_ = Utils.escapeHTML(value_)
1771 }
1772
1773 if (csses.concat([this.header.styles[j]]).length) {
1774 style_ = ` style="${csses.concat([this.header.styles[j]]).join('; ')}"`
1775 }
1776 // handle td's id and class
1777 if (item[`_${field}_id`]) {
1778 id_ = Utils.sprintf(' id="%s"', item[`_${field}_id`])
1779 }
1780 if (item[`_${field}_class`]) {
1781 class_ = Utils.sprintf(' class="%s"', item[`_${field}_class`])
1782 }
1783 if (item[`_${field}_rowspan`]) {
1784 rowspan_ = Utils.sprintf(' rowspan="%s"', item[`_${field}_rowspan`])
1785 }
1786 if (item[`_${field}_colspan`]) {
1787 colspan_ = Utils.sprintf(' colspan="%s"', item[`_${field}_colspan`])
1788 }
1789 if (item[`_${field}_title`]) {
1790 title_ = Utils.sprintf(' title="%s"', item[`_${field}_title`])
1791 }
1792 cellStyle = Utils.calculateObjectValue(this.header,
1793 this.header.cellStyles[j], [value_, item, i, field], cellStyle)
1794 if (cellStyle.classes) {
1795 class_ = ` class="${cellStyle.classes}"`
1796 }
1797 if (cellStyle.css) {
1798 const csses_ = []
1799 for (const [key, value] of Object.entries(cellStyle.css)) {
1800 csses_.push(`${key}: ${value}`)
1801 }
1802 style_ = ` style="${csses_.concat(this.header.styles[j]).join('; ')}"`
1803 }
1804
1805 value = Utils.calculateObjectValue(column,
1806 this.header.formatters[j], [value_, item, i, field], value_)
1807
1808 if (item[`_${field}_data`] && !$.isEmptyObject(item[`_${field}_data`])) {
1809 for (const [k, v] of Object.entries(item[`_${field}_data`])) {
1810 // ignore data-index
1811 if (k === 'index') {
1812 return
1813 }
1814 data_ += ` data-${k}="${v}"`
1815 }
1816 }
1817
1818 if (column.checkbox || column.radio) {
1819 type = column.checkbox ? 'checkbox' : type
1820 type = column.radio ? 'radio' : type
1821
1822 const c = column['class'] || ''
1823 const isChecked = value === true || (value_ || (value && value.checked))
1824 const isDisabled = !column.checkboxEnabled || (value && value.disabled)
1825
1826 text = [
1827 this.options.cardView
1828 ? `<div class="card-view ${c}">`
1829 : `<td class="bs-checkbox ${c}">`,
1830 `<input
1831 data-index="${i}"
1832 name="${this.options.selectItemName}"
1833 type="${type}"
1834 ${Utils.sprintf('value="%s"', item[this.options.idField])}
1835 ${Utils.sprintf('checked="%s"', isChecked ? 'checked' : undefined)}
1836 ${Utils.sprintf('disabled="%s"', isDisabled ? 'disabled' : undefined)} />`,
1837 this.header.formatters[j] && typeof value === 'string' ? value : '',
1838 this.options.cardView ? '</div>' : '</td>'
1839 ].join('')
1840
1841 item[this.header.stateField] = value === true || (!!value_ || (value && value.checked))
1842 } else {
1843 value = typeof value === 'undefined' || value === null
1844 ? this.options.undefinedText : value
1845
1846 if (this.options.cardView) {
1847 const cardTitle = this.options.showHeader
1848 ? `<span class="title"${style}>${Utils.getFieldTitle(this.columns, field)}</span>` : ''
1849
1850 text = `<div class="card-view">${cardTitle}<span class="value">${value}</span></div>`
1851
1852 if (this.options.smartDisplay && value === '') {
1853 text = '<div class="card-view"></div>'
1854 }
1855 } else {
1856 text = `<td${id_}${class_}${style_}${data_}${rowspan_}${colspan_}${title_}>${value}</td>`
1857 }
1858 }
1859
1860 html.push(text)
1861 })
1862
1863 if (this.options.cardView) {
1864 html.push('</div></td>')
1865 }
1866 html.push('</tr>')
1867
1868 return html.join('')
1869 }
1870
1871 initBody (fixedScroll) {
1872 const data = this.getData()
1873
1874 this.trigger('pre-body', data)
1875
1876 this.$body = this.$el.find('>tbody')
1877 if (!this.$body.length) {
1878 this.$body = $('<tbody></tbody>').appendTo(this.$el)
1879 }
1880
1881 // Fix #389 Bootstrap-table-flatJSON is not working
1882 if (!this.options.pagination || this.options.sidePagination === 'server') {
1883 this.pageFrom = 1
1884 this.pageTo = data.length
1885 }
1886
1887 const trFragments = $(document.createDocumentFragment())
1888 let hasTr = false
1889
1890 for (let i = this.pageFrom - 1; i < this.pageTo; i++) {
1891 const item = data[i]
1892 const tr = this.initRow(item, i, data, trFragments)
1893 hasTr = hasTr || !!tr
1894 if (tr && typeof tr === 'string') {
1895 trFragments.append(tr)
1896 }
1897 }
1898
1899 // show no records
1900 if (!hasTr) {
1901 this.$body.html(`<tr class="no-records-found">${Utils.sprintf('<td colspan="%s">%s</td>',
1902 this.$header.find('th').length,
1903 this.options.formatNoMatches())}</tr>`)
1904 } else {
1905 this.$body.html(trFragments)
1906 }
1907
1908 if (!fixedScroll) {
1909 this.scrollTo(0)
1910 }
1911
1912 // click to select by column
1913 this.$body.find('> tr[data-index] > td').off('click dblclick').on('click dblclick', ({currentTarget, type, target}) => {
1914 const $td = $(currentTarget)
1915 const $tr = $td.parent()
1916 const item = this.data[$tr.data('index')]
1917 const index = $td[0].cellIndex
1918 const fields = this.getVisibleFields()
1919 const field = fields[this.options.detailView && !this.options.cardView ? index - 1 : index]
1920 const column = this.columns[this.fieldsColumnsIndex[field]]
1921 const value = Utils.getItemField(item, field, this.options.escape)
1922
1923 if ($td.find('.detail-icon').length) {
1924 return
1925 }
1926
1927 this.trigger(type === 'click' ? 'click-cell' : 'dbl-click-cell', field, value, item, $td)
1928 this.trigger(type === 'click' ? 'click-row' : 'dbl-click-row', item, $tr, field)
1929
1930 // if click to select - then trigger the checkbox/radio click
1931 if (
1932 type === 'click' &&
1933 this.options.clickToSelect &&
1934 column.clickToSelect &&
1935 !this.options.ignoreClickToSelectOn(target)
1936 ) {
1937 const $selectItem = $tr.find(Utils.sprintf('[name="%s"]', this.options.selectItemName))
1938 if ($selectItem.length) {
1939 $selectItem[0].click() // #144: .trigger('click') bug
1940 }
1941 }
1942 })
1943
1944 this.$body.find('> tr[data-index] > td > .detail-icon').off('click').on('click', e => {
1945 e.preventDefault()
1946
1947 const $this = $(e.currentTarget) // Fix #980 Detail view, when searching, returns wrong row
1948 const $tr = $this.parent().parent()
1949 const index = $tr.data('index')
1950 const row = data[index]
1951
1952 // remove and update
1953 if ($tr.next().is('tr.detail-view')) {
1954 $this.find('i').attr('class', Utils.sprintf('%s %s', this.options.iconsPrefix, this.options.icons.detailOpen))
1955 this.trigger('collapse-row', index, row, $tr.next())
1956 $tr.next().remove()
1957 } else {
1958 $this.find('i').attr('class', Utils.sprintf('%s %s', this.options.iconsPrefix, this.options.icons.detailClose))
1959 $tr.after(Utils.sprintf('<tr class="detail-view"><td colspan="%s"></td></tr>', $tr.find('td').length))
1960 const $element = $tr.next().find('td')
1961 const content = Utils.calculateObjectValue(this.options, this.options.detailFormatter, [index, row, $element], '')
1962 if ($element.length === 1) {
1963 $element.append(content)
1964 }
1965 this.trigger('expand-row', index, row, $element)
1966 }
1967 this.resetView()
1968 return false
1969 })
1970
1971 this.$selectItem = this.$body.find(Utils.sprintf('[name="%s"]', this.options.selectItemName))
1972 this.$selectItem.off('click').on('click', e => {
1973 e.stopImmediatePropagation()
1974
1975 const $this = $(e.currentTarget)
1976 this.check_($this.prop('checked'), $this.data('index'))
1977 })
1978
1979 this.header.events.forEach((_events, i) => {
1980 let events = _events
1981 if (!events) {
1982 return
1983 }
1984 // fix bug, if events is defined with namespace
1985 if (typeof events === 'string') {
1986 events = Utils.calculateObjectValue(null, events)
1987 }
1988
1989 const field = this.header.fields[i]
1990 let fieldIndex = this.getVisibleFields().indexOf(field)
1991
1992 if (fieldIndex === -1) {
1993 return
1994 }
1995
1996 if (this.options.detailView && !this.options.cardView) {
1997 fieldIndex += 1
1998 }
1999
2000 for (const [key, event] of Object.entries(events)) {
2001 this.$body.find('>tr:not(.no-records-found)').each((i, tr) => {
2002 const $tr = $(tr)
2003 const $td = $tr.find(this.options.cardView ? '.card-view' : 'td').eq(fieldIndex)
2004 const index = key.indexOf(' ')
2005 const name = key.substring(0, index)
2006 const el = key.substring(index + 1)
2007
2008 $td.find(el).off(name).on(name, e => {
2009 const index = $tr.data('index')
2010 const row = this.data[index]
2011 const value = row[field]
2012
2013 event.apply(this, [e, value, row, index])
2014 })
2015 })
2016 }
2017 })
2018
2019 this.updateSelected()
2020 this.resetView()
2021
2022 this.trigger('post-body', data)
2023 }
2024
2025 initServer (silent, query, url) {
2026 let data = {}
2027 const index = this.header.fields.indexOf(this.options.sortName)
2028
2029 let params = {
2030 searchText: this.searchText,
2031 sortName: this.options.sortName,
2032 sortOrder: this.options.sortOrder
2033 }
2034
2035 if (this.header.sortNames[index]) {
2036 params.sortName = this.header.sortNames[index]
2037 }
2038
2039 if (this.options.pagination && this.options.sidePagination === 'server') {
2040 params.pageSize = this.options.pageSize === this.options.formatAllRows()
2041 ? this.options.totalRows : this.options.pageSize
2042 params.pageNumber = this.options.pageNumber
2043 }
2044
2045 if (!(url || this.options.url) && !this.options.ajax) {
2046 return
2047 }
2048
2049 if (this.options.queryParamsType === 'limit') {
2050 params = {
2051 search: params.searchText,
2052 sort: params.sortName,
2053 order: params.sortOrder
2054 }
2055
2056 if (this.options.pagination && this.options.sidePagination === 'server') {
2057 params.offset = this.options.pageSize === this.options.formatAllRows()
2058 ? 0 : this.options.pageSize * (this.options.pageNumber - 1)
2059 params.limit = this.options.pageSize === this.options.formatAllRows()
2060 ? this.options.totalRows : this.options.pageSize
2061 if (params.limit === 0) {
2062 delete params.limit
2063 }
2064 }
2065 }
2066
2067 if (!($.isEmptyObject(this.filterColumnsPartial))) {
2068 params.filter = JSON.stringify(this.filterColumnsPartial, null)
2069 }
2070
2071 data = Utils.calculateObjectValue(this.options, this.options.queryParams, [params], data)
2072
2073 $.extend(data, query || {})
2074
2075 // false to stop request
2076 if (data === false) {
2077 return
2078 }
2079
2080 if (!silent) {
2081 this.$tableLoading.show()
2082 }
2083 const request = $.extend({}, Utils.calculateObjectValue(null, this.options.ajaxOptions), {
2084 type: this.options.method,
2085 url: url || this.options.url,
2086 data: this.options.contentType === 'application/json' && this.options.method === 'post'
2087 ? JSON.stringify(data) : data,
2088 cache: this.options.cache,
2089 contentType: this.options.contentType,
2090 dataType: this.options.dataType,
2091 success: _res => {
2092 const res = Utils.calculateObjectValue(this.options,
2093 this.options.responseHandler, [_res], _res)
2094
2095 this.load(res)
2096 this.trigger('load-success', res)
2097 if (!silent) {
2098 this.$tableLoading.hide()
2099 }
2100 },
2101 error: jqXHR => {
2102 let data = []
2103 if (this.options.sidePagination === 'server') {
2104 data = {}
2105 data[this.options.totalField] = 0
2106 data[this.options.dataField] = []
2107 }
2108 this.load(data)
2109 this.trigger('load-error', jqXHR.status, jqXHR)
2110 if (!silent) this.$tableLoading.hide()
2111 }
2112 })
2113
2114 if (this.options.ajax) {
2115 Utils.calculateObjectValue(this, this.options.ajax, [request], null)
2116 } else {
2117 if (this._xhr && this._xhr.readyState !== 4) {
2118 this._xhr.abort()
2119 }
2120 this._xhr = $.ajax(request)
2121 }
2122 }
2123
2124 initSearchText () {
2125 if (this.options.search) {
2126 this.searchText = ''
2127 if (this.options.searchText !== '') {
2128 const $search = this.$toolbar.find('.search input')
2129 $search.val(this.options.searchText)
2130 this.onSearch({currentTarget: $search, firedByInitSearchText: true})
2131 }
2132 }
2133 }
2134
2135 getCaret () {
2136 this.$header.find('th').each((i, th) => {
2137 $(th).find('.sortable').removeClass('desc asc')
2138 .addClass($(th).data('field') === this.options.sortName
2139 ? this.options.sortOrder : 'both')
2140 })
2141 }
2142
2143 updateSelected () {
2144 const checkAll = this.$selectItem.filter(':enabled').length &&
2145 this.$selectItem.filter(':enabled').length ===
2146 this.$selectItem.filter(':enabled').filter(':checked').length
2147
2148 this.$selectAll.add(this.$selectAll_).prop('checked', checkAll)
2149
2150 this.$selectItem.each((i, el) => {
2151 $(el).closest('tr')[$(el).prop('checked') ? 'addClass' : 'removeClass']('selected')
2152 })
2153 }
2154
2155 updateRows () {
2156 this.$selectItem.each((i, el) => {
2157 this.data[$(el).data('index')][this.header.stateField] = $(el).prop('checked')
2158 })
2159 }
2160
2161 resetRows () {
2162 for (const row of this.data) {
2163 this.$selectAll.prop('checked', false)
2164 this.$selectItem.prop('checked', false)
2165 if (this.header.stateField) {
2166 row[this.header.stateField] = false
2167 }
2168 }
2169 this.initHiddenRows()
2170 }
2171
2172 trigger (_name, ...args) {
2173 const name = `${_name}.bs.table`
2174 this.options[BootstrapTable.EVENTS[name]](...args)
2175 this.$el.trigger($.Event(name), args)
2176
2177 this.options.onAll(name, args)
2178 this.$el.trigger($.Event('all.bs.table'), [name, args])
2179 }
2180
2181 resetHeader () {
2182 // fix #61: the hidden table reset header bug.
2183 // fix bug: get $el.css('width') error sometime (height = 500)
2184 clearTimeout(this.timeoutId_)
2185 this.timeoutId_ = setTimeout($.proxy(this.fitHeader, this), this.$el.is(':hidden') ? 100 : 0)
2186 }
2187
2188 fitHeader () {
2189 if (this.$el.is(':hidden')) {
2190 this.timeoutId_ = setTimeout($.proxy(this.fitHeader, this), 100)
2191 return
2192 }
2193 const fixedBody = this.$tableBody.get(0)
2194
2195 const scrollWidth = fixedBody.scrollWidth > fixedBody.clientWidth &&
2196 fixedBody.scrollHeight > fixedBody.clientHeight + this.$header.outerHeight()
2197 ? Utils.getScrollBarWidth() : 0
2198
2199 this.$el.css('margin-top', -this.$header.outerHeight())
2200
2201 const focused = $(':focus')
2202 if (focused.length > 0) {
2203 const $th = focused.parents('th')
2204 if ($th.length > 0) {
2205 const dataField = $th.attr('data-field')
2206 if (dataField !== undefined) {
2207 const $headerTh = this.$header.find(`[data-field='${dataField}']`)
2208 if ($headerTh.length > 0) {
2209 $headerTh.find(':input').addClass('focus-temp')
2210 }
2211 }
2212 }
2213 }
2214
2215 this.$header_ = this.$header.clone(true, true)
2216 this.$selectAll_ = this.$header_.find('[name="btSelectAll"]')
2217 this.$tableHeader.css({
2218 'margin-right': scrollWidth
2219 }).find('table').css('width', this.$el.outerWidth())
2220 .html('').attr('class', this.$el.attr('class'))
2221 .append(this.$header_)
2222
2223 const focusedTemp = $('.focus-temp:visible:eq(0)')
2224 if (focusedTemp.length > 0) {
2225 focusedTemp.focus()
2226 this.$header.find('.focus-temp').removeClass('focus-temp')
2227 }
2228
2229 // fix bug: $.data() is not working as expected after $.append()
2230 this.$header.find('th[data-field]').each((i, el) => {
2231 this.$header_.find(Utils.sprintf('th[data-field="%s"]', $(el).data('field'))).data($(el).data())
2232 })
2233
2234 const visibleFields = this.getVisibleFields()
2235 const $ths = this.$header_.find('th')
2236 let $tr = this.$body.find('>tr:first-child:not(.no-records-found)')
2237
2238 while ($tr.length && $tr.find('>td[colspan]:not([colspan="1"])').length) {
2239 $tr = $tr.next()
2240 }
2241
2242 $tr.find('> *').each((i, el) => {
2243 const $this = $(el)
2244 let index = i
2245
2246 if (this.options.detailView && !this.options.cardView) {
2247 if (i === 0) {
2248 const $thDetail = $ths.filter('.detail')
2249 const zoomWidth = $thDetail.width() - $thDetail.find('.fht-cell').width()
2250 $thDetail.find('.fht-cell').width($this.innerWidth() - zoomWidth)
2251 }
2252 index = i - 1
2253 }
2254
2255 if (index === -1) {
2256 return
2257 }
2258
2259 let $th = this.$header_.find(Utils.sprintf('th[data-field="%s"]', visibleFields[index]))
2260 if ($th.length > 1) {
2261 $th = $($ths[$this[0].cellIndex])
2262 }
2263
2264 const zoomWidth = $th.width() - $th.find('.fht-cell').width()
2265 $th.find('.fht-cell').width($this.innerWidth() - zoomWidth)
2266 })
2267
2268 this.horizontalScroll()
2269 this.trigger('post-header')
2270 }
2271
2272 resetFooter () {
2273 const data = this.getData()
2274 const html = []
2275
2276 if (!this.options.showFooter || this.options.cardView) { // do nothing
2277 return
2278 }
2279
2280 if (!this.options.cardView && this.options.detailView) {
2281 html.push('<td><div class="th-inner">&nbsp;</div><div class="fht-cell"></div></td>')
2282 }
2283
2284 for (const column of this.columns) {
2285 let falign = ''
2286
2287 let valign = ''
2288 const csses = []
2289 let style = {}
2290 const class_ = Utils.sprintf(' class="%s"', column['class'])
2291
2292 if (!column.visible) {
2293 return
2294 }
2295
2296 if (this.options.cardView && (!column.cardVisible)) {
2297 return
2298 }
2299
2300 falign = Utils.sprintf('text-align: %s; ', column.falign ? column.falign : column.align)
2301 valign = Utils.sprintf('vertical-align: %s; ', column.valign)
2302
2303 style = Utils.calculateObjectValue(null, this.options.footerStyle)
2304
2305 if (style && style.css) {
2306 for (const [key, value] of Object.keys(style.css)) {
2307 csses.push(`${key}: ${value}`)
2308 }
2309 }
2310
2311 html.push('<td', class_, Utils.sprintf(' style="%s"', falign + valign + csses.concat().join('; ')), '>')
2312 html.push('<div class="th-inner">')
2313
2314 html.push(Utils.calculateObjectValue(column, column.footerFormatter, [data], '&nbsp;') || '&nbsp;')
2315
2316 html.push('</div>')
2317 html.push('<div class="fht-cell"></div>')
2318 html.push('</div>')
2319 html.push('</td>')
2320 }
2321
2322 this.$tableFooter.find('tr').html(html.join(''))
2323 this.$tableFooter.show()
2324 clearTimeout(this.timeoutFooter_)
2325 this.timeoutFooter_ = setTimeout($.proxy(this.fitFooter, this),
2326 this.$el.is(':hidden') ? 100 : 0)
2327 }
2328
2329 fitFooter () {
2330 clearTimeout(this.timeoutFooter_)
2331 if (this.$el.is(':hidden')) {
2332 this.timeoutFooter_ = setTimeout($.proxy(this.fitFooter, this), 100)
2333 return
2334 }
2335
2336 const elWidth = this.$el.css('width')
2337 const scrollWidth = elWidth > this.$tableBody.width() ? Utils.getScrollBarWidth() : 0
2338
2339 this.$tableFooter.css({
2340 'margin-right': scrollWidth
2341 }).find('table').css('width', elWidth)
2342 .attr('class', this.$el.attr('class'))
2343
2344 const $footerTd = this.$tableFooter.find('td')
2345
2346 this.$body.find('>tr:first-child:not(.no-records-found) > *').each((i, el) => {
2347 const $this = $(el)
2348
2349 $footerTd.eq(i).find('.fht-cell').width($this.innerWidth())
2350 })
2351
2352 this.horizontalScroll()
2353 }
2354
2355 horizontalScroll () {
2356 // horizontal scroll event
2357 // TODO: it's probably better improving the layout than binding to scroll event
2358
2359 this.trigger('scroll-body')
2360 this.$tableBody.off('scroll').on('scroll', ({currentTarget}) => {
2361 if (this.options.showHeader && this.options.height) {
2362 this.$tableHeader.scrollLeft($(currentTarget).scrollLeft())
2363 }
2364
2365 if (this.options.showFooter && !this.options.cardView) {
2366 this.$tableFooter.scrollLeft($(currentTarget).scrollLeft())
2367 }
2368 })
2369 }
2370
2371 toggleColumn (index, checked, needUpdate) {
2372 if (index === -1) {
2373 return
2374 }
2375 this.columns[index].visible = checked
2376 this.initHeader()
2377 this.initSearch()
2378 this.initPagination()
2379 this.initBody()
2380
2381 if (this.options.showColumns) {
2382 const $items = this.$toolbar.find('.keep-open input').prop('disabled', false)
2383
2384 if (needUpdate) {
2385 $items.filter(Utils.sprintf('[value="%s"]', index)).prop('checked', checked)
2386 }
2387
2388 if ($items.filter(':checked').length <= this.options.minimumCountColumns) {
2389 $items.filter(':checked').prop('disabled', true)
2390 }
2391 }
2392 }
2393
2394 getVisibleFields () {
2395 const visibleFields = []
2396
2397 for (const field of this.header.fields) {
2398 const column = this.columns[this.fieldsColumnsIndex[field]]
2399
2400 if (!column.visible) {
2401 continue
2402 }
2403 visibleFields.push(field)
2404 }
2405 return visibleFields
2406 }
2407
2408 // PUBLIC FUNCTION DEFINITION
2409 // =======================
2410
2411 resetView (params) {
2412 let padding = 0
2413
2414 if (params && params.height) {
2415 this.options.height = params.height
2416 }
2417
2418 this.$selectAll.prop('checked', this.$selectItem.length > 0 &&
2419 this.$selectItem.length === this.$selectItem.filter(':checked').length)
2420
2421 if (this.options.cardView) {
2422 // remove the element css
2423 this.$el.css('margin-top', '0')
2424 this.$tableContainer.css('padding-bottom', '0')
2425 this.$tableFooter.hide()
2426 return
2427 }
2428
2429 if (this.options.showHeader && this.options.height) {
2430 this.$tableHeader.show()
2431 this.resetHeader()
2432 padding += this.$header.outerHeight()
2433 } else {
2434 this.$tableHeader.hide()
2435 this.trigger('post-header')
2436 }
2437
2438 if (this.options.showFooter) {
2439 this.resetFooter()
2440 if (this.options.height) {
2441 padding += this.$tableFooter.outerHeight() + 1
2442 }
2443 }
2444
2445 if (this.options.height) {
2446 const toolbarHeight = this.$toolbar.outerHeight(true)
2447 const paginationHeight = this.$pagination.outerHeight(true)
2448 const height = this.options.height - toolbarHeight - paginationHeight
2449 const tableHeight = this.$tableBody.find('table').outerHeight(true)
2450 this.$tableContainer.css('height', `${height}px`)
2451 this.$tableBorder && this.$tableBorder.css('height', `${height - tableHeight - padding - 1}px`)
2452 }
2453
2454 // Assign the correct sortable arrow
2455 this.getCaret()
2456 this.$tableContainer.css('padding-bottom', `${padding}px`)
2457 this.trigger('reset-view')
2458 }
2459
2460 getData (useCurrentPage) {
2461 let data = this.options.data
2462 if (this.searchText || this.options.sortName || !$.isEmptyObject(this.filterColumns) || !$.isEmptyObject(this.filterColumnsPartial)) {
2463 data = this.data
2464 }
2465
2466 if (useCurrentPage) {
2467 return data.slice(this.pageFrom - 1, this.pageTo)
2468 }
2469
2470 return data
2471 }
2472
2473 load (_data) {
2474 let fixedScroll = false
2475 let data = _data
2476
2477 // #431: support pagination
2478 if (this.options.pagination && this.options.sidePagination === 'server') {
2479 this.options.totalRows = data[this.options.totalField]
2480 }
2481
2482 fixedScroll = data.fixedScroll
2483 data = Array.isArray(data) ? data : data[this.options.dataField]
2484
2485 this.initData(data)
2486 this.initSearch()
2487 this.initPagination()
2488 this.initBody(fixedScroll)
2489 }
2490
2491 append (data) {
2492 this.initData(data, 'append')
2493 this.initSearch()
2494 this.initPagination()
2495 this.initSort()
2496 this.initBody(true)
2497 }
2498
2499 prepend (data) {
2500 this.initData(data, 'prepend')
2501 this.initSearch()
2502 this.initPagination()
2503 this.initSort()
2504 this.initBody(true)
2505 }
2506
2507 remove (params) {
2508 const len = this.options.data.length
2509 let i
2510 let row
2511
2512 if (!params.hasOwnProperty('field') || !params.hasOwnProperty('values')) {
2513 return
2514 }
2515
2516 for (i = len - 1; i >= 0; i--) {
2517 row = this.options.data[i]
2518
2519 if (!row.hasOwnProperty(params.field)) {
2520 continue
2521 }
2522 if (params.values.includes(row[params.field])) {
2523 this.options.data.splice(i, 1)
2524 if (this.options.sidePagination === 'server') {
2525 this.options.totalRows -= 1
2526 }
2527 }
2528 }
2529
2530 if (len === this.options.data.length) {
2531 return
2532 }
2533
2534 this.initSearch()
2535 this.initPagination()
2536 this.initSort()
2537 this.initBody(true)
2538 }
2539
2540 removeAll () {
2541 if (this.options.data.length > 0) {
2542 this.options.data.splice(0, this.options.data.length)
2543 this.initSearch()
2544 this.initPagination()
2545 this.initBody(true)
2546 }
2547 }
2548
2549 getRowByUniqueId (_id) {
2550 const uniqueId = this.options.uniqueId
2551 const len = this.options.data.length
2552 let id = _id
2553 let dataRow = null
2554 let i
2555 let row
2556 let rowUniqueId
2557
2558 for (i = len - 1; i >= 0; i--) {
2559 row = this.options.data[i]
2560
2561 if (row.hasOwnProperty(uniqueId)) { // uniqueId is a column
2562 rowUniqueId = row[uniqueId]
2563 } else if (row._data && row._data.hasOwnProperty(uniqueId)) { // uniqueId is a row data property
2564 rowUniqueId = row._data[uniqueId]
2565 } else {
2566 continue
2567 }
2568
2569 if (typeof rowUniqueId === 'string') {
2570 id = id.toString()
2571 } else if (typeof rowUniqueId === 'number') {
2572 if ((Number(rowUniqueId) === rowUniqueId) && (rowUniqueId % 1 === 0)) {
2573 id = parseInt(id)
2574 } else if ((rowUniqueId === Number(rowUniqueId)) && (rowUniqueId !== 0)) {
2575 id = parseFloat(id)
2576 }
2577 }
2578
2579 if (rowUniqueId === id) {
2580 dataRow = row
2581 break
2582 }
2583 }
2584
2585 return dataRow
2586 }
2587
2588 removeByUniqueId (id) {
2589 const len = this.options.data.length
2590 const row = this.getRowByUniqueId(id)
2591
2592 if (row) {
2593 this.options.data.splice(this.options.data.indexOf(row), 1)
2594 }
2595
2596 if (len === this.options.data.length) {
2597 return
2598 }
2599
2600 this.initSearch()
2601 this.initPagination()
2602 this.initBody(true)
2603 }
2604
2605 updateByUniqueId (params) {
2606 const allParams = Array.isArray(params) ? params : [params]
2607
2608 for (const params of allParams) {
2609 if (!params.hasOwnProperty('id') || !params.hasOwnProperty('row')) {
2610 continue
2611 }
2612
2613 const rowId = this.options.data.indexOf(this.getRowByUniqueId(params.id))
2614
2615 if (rowId === -1) {
2616 continue
2617 }
2618 $.extend(this.options.data[rowId], params.row)
2619 }
2620
2621 this.initSearch()
2622 this.initPagination()
2623 this.initSort()
2624 this.initBody(true)
2625 }
2626
2627 refreshColumnTitle (params) {
2628 if (!params.hasOwnProperty('field') || !params.hasOwnProperty('title')) {
2629 return
2630 }
2631
2632 this.columns[this.fieldsColumnsIndex[params.field]].title =
2633 this.options.escape ? Utils.escapeHTML(params.title) : params.title
2634
2635 if (this.columns[this.fieldsColumnsIndex[params.field]].visible) {
2636 const header = this.options.height !== undefined ? this.$tableHeader : this.$header
2637 header.find('th[data-field]').each((i, el) => {
2638 if ($(el).data('field') === params.field) {
2639 $($(el).find('.th-inner')[0]).text(params.title)
2640 return false
2641 }
2642 })
2643 }
2644 }
2645
2646 insertRow (params) {
2647 if (!params.hasOwnProperty('index') || !params.hasOwnProperty('row')) {
2648 return
2649 }
2650 this.options.data.splice(params.index, 0, params.row)
2651 this.initSearch()
2652 this.initPagination()
2653 this.initSort()
2654 this.initBody(true)
2655 }
2656
2657 updateRow (params) {
2658 const allParams = Array.isArray(params) ? params : [params]
2659
2660 for (const params of allParams) {
2661 if (!params.hasOwnProperty('index') || !params.hasOwnProperty('row')) {
2662 continue
2663 }
2664 $.extend(this.options.data[params.index], params.row)
2665 }
2666
2667 this.initSearch()
2668 this.initPagination()
2669 this.initSort()
2670 this.initBody(true)
2671 }
2672
2673 initHiddenRows () {
2674 this.hiddenRows = []
2675 }
2676
2677 showRow (params) {
2678 this.toggleRow(params, true)
2679 }
2680
2681 hideRow (params) {
2682 this.toggleRow(params, false)
2683 }
2684
2685 toggleRow (params, visible) {
2686 let row
2687
2688 if (params.hasOwnProperty('index')) {
2689 row = this.getData()[params.index]
2690 } else if (params.hasOwnProperty('uniqueId')) {
2691 row = this.getRowByUniqueId(params.uniqueId)
2692 }
2693
2694 if (!row) {
2695 return
2696 }
2697
2698 const index = Utils.findIndex(this.hiddenRows, row)
2699
2700 if (!visible && index === -1) {
2701 this.hiddenRows.push(row)
2702 } else if (visible && index > -1) {
2703 this.hiddenRows.splice(index, 1)
2704 }
2705 this.initBody(true)
2706 }
2707
2708 getHiddenRows (show) {
2709 if (show) {
2710 this.initHiddenRows()
2711 this.initBody(true)
2712 return
2713 }
2714 const data = this.getData()
2715 const rows = []
2716
2717 for (const row of data) {
2718 if (this.hiddenRows.includes(row)) {
2719 rows.push(row)
2720 }
2721 }
2722 this.hiddenRows = rows
2723 return rows
2724 }
2725
2726 mergeCells (options) {
2727 const row = options.index
2728 let col = this.getVisibleFields().indexOf(options.field)
2729 const rowspan = options.rowspan || 1
2730 const colspan = options.colspan || 1
2731 let i
2732 let j
2733 const $tr = this.$body.find('>tr')
2734
2735 if (this.options.detailView && !this.options.cardView) {
2736 col += 1
2737 }
2738
2739 const $td = $tr.eq(row).find('>td').eq(col)
2740
2741 if (row < 0 || col < 0 || row >= this.data.length) {
2742 return
2743 }
2744
2745 for (i = row; i < row + rowspan; i++) {
2746 for (j = col; j < col + colspan; j++) {
2747 $tr.eq(i).find('>td').eq(j).hide()
2748 }
2749 }
2750
2751 $td.attr('rowspan', rowspan).attr('colspan', colspan).show()
2752 }
2753
2754 updateCell (params) {
2755 if (!params.hasOwnProperty('index') ||
2756 !params.hasOwnProperty('field') ||
2757 !params.hasOwnProperty('value')) {
2758 return
2759 }
2760 this.data[params.index][params.field] = params.value
2761
2762 if (params.reinit === false) {
2763 return
2764 }
2765 this.initSort()
2766 this.initBody(true)
2767 }
2768
2769 updateCellById (params) {
2770 if (!params.hasOwnProperty('id') ||
2771 !params.hasOwnProperty('field') ||
2772 !params.hasOwnProperty('value')) {
2773 return
2774 }
2775 const allParams = Array.isArray(params) ? params : [params]
2776
2777 allParams.forEach(({id, field, value}) => {
2778 const rowId = this.options.data.indexOf(this.getRowByUniqueId(id))
2779
2780 if (rowId === -1) {
2781 return
2782 }
2783 this.data[rowId][field] = value
2784 })
2785
2786 if (params.reinit === false) {
2787 return
2788 }
2789 this.initSort()
2790 this.initBody(true)
2791 }
2792
2793 getOptions () {
2794 // Deep copy: remove data
2795 const options = $.extend({}, this.options)
2796 delete options.data
2797 return $.extend(true, {}, options)
2798 }
2799
2800 getSelections () {
2801 // fix #2424: from html with checkbox
2802 return this.options.data.filter(row =>
2803 row[this.header.stateField] === true)
2804 }
2805
2806 getAllSelections () {
2807 return this.options.data.filter(row => row[this.header.stateField])
2808 }
2809
2810 checkAll () {
2811 this.checkAll_(true)
2812 }
2813
2814 uncheckAll () {
2815 this.checkAll_(false)
2816 }
2817
2818 checkInvert () {
2819 const $items = this.$selectItem.filter(':enabled')
2820 let checked = $items.filter(':checked')
2821 $items.each((i, el) => {
2822 $(el).prop('checked', !$(el).prop('checked'))
2823 })
2824 this.updateRows()
2825 this.updateSelected()
2826 this.trigger('uncheck-some', checked)
2827 checked = this.getSelections()
2828 this.trigger('check-some', checked)
2829 }
2830
2831 checkAll_ (checked) {
2832 let rows
2833 if (!checked) {
2834 rows = this.getSelections()
2835 }
2836 this.$selectAll.add(this.$selectAll_).prop('checked', checked)
2837 this.$selectItem.filter(':enabled').prop('checked', checked)
2838 this.updateRows()
2839 if (checked) {
2840 rows = this.getSelections()
2841 }
2842 this.trigger(checked ? 'check-all' : 'uncheck-all', rows)
2843 }
2844
2845 check (index) {
2846 this.check_(true, index)
2847 }
2848
2849 uncheck (index) {
2850 this.check_(false, index)
2851 }
2852
2853 check_ (checked, index) {
2854 const $el = this.$selectItem.filter(`[data-index="${index}"]`)
2855 const row = this.data[index]
2856
2857 if ($el.is(':radio') || this.options.singleSelect) {
2858 for (const r of this.options.data) {
2859 r[this.header.stateField] = false
2860 }
2861 this.$selectItem.filter(':checked').not($el).prop('checked', false)
2862 }
2863
2864 row[this.header.stateField] = checked
2865 $el.prop('checked', checked)
2866 this.updateSelected()
2867 this.trigger(checked ? 'check' : 'uncheck', this.data[index], $el)
2868 }
2869
2870 checkBy (obj) {
2871 this.checkBy_(true, obj)
2872 }
2873
2874 uncheckBy (obj) {
2875 this.checkBy_(false, obj)
2876 }
2877
2878 checkBy_ (checked, obj) {
2879 if (!obj.hasOwnProperty('field') || !obj.hasOwnProperty('values')) {
2880 return
2881 }
2882
2883 const rows = []
2884 this.options.data.forEach((row, i) => {
2885 if (!row.hasOwnProperty(obj.field)) {
2886 return false
2887 }
2888 if (obj.values.includes(row[obj.field])) {
2889 const $el = this.$selectItem.filter(':enabled')
2890 .filter(Utils.sprintf('[data-index="%s"]', i)).prop('checked', checked)
2891 row[this.header.stateField] = checked
2892 rows.push(row)
2893 this.trigger(checked ? 'check' : 'uncheck', row, $el)
2894 }
2895 })
2896 this.updateSelected()
2897 this.trigger(checked ? 'check-some' : 'uncheck-some', rows)
2898 }
2899
2900 destroy () {
2901 this.$el.insertBefore(this.$container)
2902 $(this.options.toolbar).insertBefore(this.$el)
2903 this.$container.next().remove()
2904 this.$container.remove()
2905 this.$el.html(this.$el_.html())
2906 .css('margin-top', '0')
2907 .attr('class', this.$el_.attr('class') || '') // reset the class
2908 }
2909
2910 showLoading () {
2911 this.$tableLoading.show()
2912 }
2913
2914 hideLoading () {
2915 this.$tableLoading.hide()
2916 }
2917
2918 togglePagination () {
2919 this.options.pagination = !this.options.pagination
2920 const button = this.$toolbar.find('button[name="paginationSwitch"] i')
2921 if (this.options.pagination) {
2922 button.attr('class', `${this.options.iconsPrefix} ${this.options.icons.paginationSwitchDown}`)
2923 } else {
2924 button.attr('class', `${this.options.iconsPrefix} ${this.options.icons.paginationSwitchUp}`)
2925 }
2926 this.updatePagination()
2927 }
2928
2929 toggleFullscreen () {
2930 this.$el.closest('.bootstrap-table').toggleClass('fullscreen')
2931 }
2932
2933 refresh (params) {
2934 if (params && params.url) {
2935 this.options.url = params.url
2936 }
2937 if (params && params.pageNumber) {
2938 this.options.pageNumber = params.pageNumber
2939 }
2940 if (params && params.pageSize) {
2941 this.options.pageSize = params.pageSize
2942 }
2943 this.initServer(params && params.silent,
2944 params && params.query, params && params.url)
2945 this.trigger('refresh', params)
2946 }
2947
2948 resetWidth () {
2949 if (this.options.showHeader && this.options.height) {
2950 this.fitHeader()
2951 }
2952 if (this.options.showFooter && !this.options.cardView) {
2953 this.fitFooter()
2954 }
2955 }
2956
2957 showColumn (field) {
2958 this.toggleColumn(this.fieldsColumnsIndex[field], true, true)
2959 }
2960
2961 hideColumn (field) {
2962 this.toggleColumn(this.fieldsColumnsIndex[field], false, true)
2963 }
2964
2965 getHiddenColumns () {
2966 return this.columns.filter(({visible}) => !visible)
2967 }
2968
2969 getVisibleColumns () {
2970 return this.columns.filter(({visible}) => visible)
2971 }
2972
2973 toggleAllColumns (visible) {
2974 for (const column of this.columns) {
2975 column.visible = visible
2976 }
2977
2978 this.initHeader()
2979 this.initSearch()
2980 this.initPagination()
2981 this.initBody()
2982 if (this.options.showColumns) {
2983 const $items = this.$toolbar.find('.keep-open input').prop('disabled', false)
2984
2985 if ($items.filter(':checked').length <= this.options.minimumCountColumns) {
2986 $items.filter(':checked').prop('disabled', true)
2987 }
2988 }
2989 }
2990
2991 showAllColumns () {
2992 this.toggleAllColumns(true)
2993 }
2994
2995 hideAllColumns () {
2996 this.toggleAllColumns(false)
2997 }
2998
2999 filterBy (columns) {
3000 this.filterColumns = $.isEmptyObject(columns) ? {} : columns
3001 this.options.pageNumber = 1
3002 this.initSearch()
3003 this.updatePagination()
3004 }
3005
3006 scrollTo (_value) {
3007 if (typeof _value === 'undefined') {
3008 return this.$tableBody.scrollTop()
3009 }
3010
3011 let value = _value
3012 if (typeof _value === 'string' && _value === 'bottom') {
3013 value = this.$tableBody[0].scrollHeight
3014 }
3015 this.$tableBody.scrollTop(value)
3016 }
3017
3018 getScrollPosition () {
3019 return this.scrollTo()
3020 }
3021
3022 selectPage (page) {
3023 if (page > 0 && page <= this.options.totalPages) {
3024 this.options.pageNumber = page
3025 this.updatePagination()
3026 }
3027 }
3028
3029 prevPage () {
3030 if (this.options.pageNumber > 1) {
3031 this.options.pageNumber--
3032 this.updatePagination()
3033 }
3034 }
3035
3036 nextPage () {
3037 if (this.options.pageNumber < this.options.totalPages) {
3038 this.options.pageNumber++
3039 this.updatePagination()
3040 }
3041 }
3042
3043 toggleView () {
3044 this.options.cardView = !this.options.cardView
3045 this.initHeader()
3046 // Fixed remove toolbar when click cardView button.
3047 // this.initToolbar();
3048 const $icon = this.$toolbar.find('button[name="toggle"] i')
3049 if (this.options.cardView) {
3050 $icon.removeClass(this.options.icons.toggleOff)
3051 $icon.addClass(this.options.icons.toggleOn)
3052 } else {
3053 $icon.removeClass(this.options.icons.toggleOn)
3054 $icon.addClass(this.options.icons.toggleOff)
3055 }
3056 this.initBody()
3057 this.trigger('toggle', this.options.cardView)
3058 }
3059
3060 refreshOptions (options) {
3061 // If the objects are equivalent then avoid the call of destroy / init methods
3062 if (Utils.compareObjects(this.options, options, true)) {
3063 return
3064 }
3065 this.options = $.extend(this.options, options)
3066 this.trigger('refresh-options', this.options)
3067 this.destroy()
3068 this.init()
3069 }
3070
3071 resetSearch (text) {
3072 const $search = this.$toolbar.find('.search input')
3073 $search.val(text || '')
3074 this.onSearch({currentTarget: $search})
3075 }
3076
3077 expandRow_ (expand, index) {
3078 const $tr = this.$body.find(Utils.sprintf('> tr[data-index="%s"]', index))
3079 if ($tr.next().is('tr.detail-view') === (!expand)) {
3080 $tr.find('> td > .detail-icon').click()
3081 }
3082 }
3083
3084 expandRow (index) {
3085 this.expandRow_(true, index)
3086 }
3087
3088 collapseRow (index) {
3089 this.expandRow_(false, index)
3090 }
3091
3092 expandAllRows (isSubTable) {
3093 if (isSubTable) {
3094 const $tr = this.$body.find(Utils.sprintf('> tr[data-index="%s"]', 0))
3095 let detailIcon = null
3096 let executeInterval = false
3097 let idInterval = -1
3098
3099 if (!$tr.next().is('tr.detail-view')) {
3100 $tr.find('> td > .detail-icon').click()
3101 executeInterval = true
3102 } else if (!$tr.next().next().is('tr.detail-view')) {
3103 $tr.next().find('.detail-icon').click()
3104 executeInterval = true
3105 }
3106
3107 if (executeInterval) {
3108 try {
3109 idInterval = setInterval(() => {
3110 detailIcon = this.$body.find('tr.detail-view').last().find('.detail-icon')
3111 if (detailIcon.length > 0) {
3112 detailIcon.click()
3113 } else {
3114 clearInterval(idInterval)
3115 }
3116 }, 1)
3117 } catch (ex) {
3118 clearInterval(idInterval)
3119 }
3120 }
3121 } else {
3122 const trs = this.$body.children()
3123 for (let i = 0; i < trs.length; i++) {
3124 this.expandRow_(true, $(trs[i]).data('index'))
3125 }
3126 }
3127 }
3128
3129 collapseAllRows (isSubTable) {
3130 if (isSubTable) {
3131 this.expandRow_(false, 0)
3132 } else {
3133 const trs = this.$body.children()
3134 for (let i = 0; i < trs.length; i++) {
3135 this.expandRow_(false, $(trs[i]).data('index'))
3136 }
3137 }
3138 }
3139
3140 updateFormatText (name, text) {
3141 if (this.options[Utils.sprintf('format%s', name)]) {
3142 if (typeof text === 'string') {
3143 this.options[Utils.sprintf('format%s', name)] = () => text
3144 } else if (typeof text === 'function') {
3145 this.options[Utils.sprintf('format%s', name)] = text
3146 }
3147 }
3148 this.initToolbar()
3149 this.initPagination()
3150 this.initBody()
3151 }
3152 }
3153
3154 BootstrapTable.DEFAULTS = DEFAULTS
3155 BootstrapTable.LOCALES = LOCALES
3156 BootstrapTable.COLUMN_DEFAULTS = COLUMN_DEFAULTS
3157 BootstrapTable.EVENTS = EVENTS
3158
3159 // BOOTSTRAP TABLE PLUGIN DEFINITION
3160 // =======================
3161
3162 const allowedMethods = [
3163 'getOptions',
3164 'getSelections', 'getAllSelections', 'getData',
3165 'load', 'append', 'prepend', 'remove', 'removeAll',
3166 'insertRow', 'updateRow', 'updateCell',
3167 'updateByUniqueId', 'removeByUniqueId',
3168 'getRowByUniqueId', 'showRow', 'hideRow', 'getHiddenRows',
3169 'mergeCells', 'refreshColumnTitle',
3170 'checkAll', 'uncheckAll', 'checkInvert',
3171 'check', 'uncheck',
3172 'checkBy', 'uncheckBy',
3173 'refresh',
3174 'resetView',
3175 'resetWidth',
3176 'destroy',
3177 'showLoading', 'hideLoading',
3178 'showColumn', 'hideColumn',
3179 'getHiddenColumns', 'getVisibleColumns',
3180 'showAllColumns', 'hideAllColumns',
3181 'filterBy',
3182 'scrollTo',
3183 'getScrollPosition',
3184 'selectPage', 'prevPage', 'nextPage',
3185 'togglePagination',
3186 'toggleView',
3187 'refreshOptions',
3188 'resetSearch',
3189 'expandRow', 'collapseRow',
3190 'expandAllRows', 'collapseAllRows',
3191 'updateFormatText', 'updateCellById'
3192 ]
3193
3194 $.BootstrapTable = BootstrapTable
3195 $.fn.bootstrapTable = function (option, ...args) {
3196 let value
3197
3198 this.each((i, el) => {
3199 let data = $(el).data('bootstrap.table')
3200 const options = $.extend({}, BootstrapTable.DEFAULTS, $(el).data(),
3201 typeof option === 'object' && option)
3202
3203 if (typeof option === 'string') {
3204 if (!allowedMethods.includes(option)) {
3205 throw new Error(`Unknown method: ${option}`)
3206 }
3207
3208 if (!data) {
3209 return
3210 }
3211
3212 value = data[option](...args)
3213
3214 if (option === 'destroy') {
3215 $(el).removeData('bootstrap.table')
3216 }
3217 }
3218
3219 if (!data) {
3220 $(el).data('bootstrap.table', (data = new $.BootstrapTable(el, options)))
3221 }
3222 })
3223
3224 return typeof value === 'undefined' ? this : value
3225 }
3226
3227 $.fn.bootstrapTable.Constructor = BootstrapTable
3228 $.fn.bootstrapTable.defaults = BootstrapTable.DEFAULTS
3229 $.fn.bootstrapTable.columnDefaults = BootstrapTable.COLUMN_DEFAULTS
3230 $.fn.bootstrapTable.locales = BootstrapTable.LOCALES
3231 $.fn.bootstrapTable.methods = allowedMethods
3232 $.fn.bootstrapTable.utils = Utils
3233
3234 // BOOTSTRAP TABLE INIT
3235 // =======================
3236
3237 $(() => {
3238 $('[data-toggle="table"]').bootstrapTable()
3239 })
3240})(jQuery)