2 * @author zhixin wen <wenzhixin2010@gmail.com>
4 * https://github.com/wenzhixin/bootstrap-table/
11 // ======================
13 var cachedWidth = null;
15 // it only does '%s', and return '' when arguments are undefined
16 var sprintf = function (str) {
21 str = str.replace(/%s/g, function () {
24 if (typeof arg === 'undefined') {
30 return flag ? str : '';
33 var getPropertyFromOther = function (list, from, to, value) {
35 $.each(list, function (i, item) {
36 if (item[from] === value) {
45 var getFieldIndex = function (columns, field) {
48 $.each(columns, function (i, column) {
49 if (column.field === field) {
58 // http://jsfiddle.net/wenyi/47nz7ez9/3/
59 var setFieldIndex = function (columns) {
64 for (i = 0; i < columns[0].length; i++) {
65 totalCol += columns[0][i].colspan || 1;
68 for (i = 0; i < columns.length; i++) {
70 for (j = 0; j < totalCol; j++) {
75 for (i = 0; i < columns.length; i++) {
76 for (j = 0; j < columns[i].length; j++) {
77 var r = columns[i][j],
78 rowspan = r.rowspan || 1,
79 colspan = r.colspan || 1,
80 index = $.inArray(false, flag[i]);
84 // when field is undefined, use index instead
85 if (typeof r.field === 'undefined') {
90 for (k = 0; k < rowspan; k++) {
91 flag[i + k][index] = true;
93 for (k = 0; k < colspan; k++) {
94 flag[i][index + k] = true;
100 var getScrollBarWidth = function () {
101 if (cachedWidth === null) {
102 var inner = $('<p/>').addClass('fixed-table-scroll-inner'),
103 outer = $('<div/>').addClass('fixed-table-scroll-outer'),
107 $('body').append(outer);
109 w1 = inner[0].offsetWidth;
110 outer.css('overflow', 'scroll');
111 w2 = inner[0].offsetWidth;
114 w2 = outer[0].clientWidth;
118 cachedWidth = w1 - w2;
123 var calculateObjectValue = function (self, name, args, defaultValue) {
126 if (typeof name === 'string') {
127 // support obj.func1.func2
128 var names = name.split('.');
130 if (names.length > 1) {
132 $.each(names, function (i, f) {
139 if (typeof func === 'object') {
142 if (typeof func === 'function') {
143 return func.apply(self, args);
145 if (!func && typeof name === 'string' && sprintf.apply(this, [name].concat(args))) {
146 return sprintf.apply(this, [name].concat(args));
151 var compareObjects = function (objectA, objectB, compareLength) {
152 // Create arrays of property names
153 var objectAProperties = Object.getOwnPropertyNames(objectA),
154 objectBProperties = Object.getOwnPropertyNames(objectB),
158 // If number of properties is different, objects are not equivalent
159 if (objectAProperties.length !== objectBProperties.length) {
164 for (var i = 0; i < objectAProperties.length; i++) {
165 propName = objectAProperties[i];
167 // If the property is not in the object B properties, continue with the next property
168 if ($.inArray(propName, objectBProperties) > -1) {
169 // If values of same property are not equal, objects are not equivalent
170 if (objectA[propName] !== objectB[propName]) {
176 // If we made it this far, objects are considered equivalent
180 var escapeHTML = function (text) {
181 if (typeof text === 'string') {
183 .replace(/&/g, "&")
184 .replace(/</g, "<")
185 .replace(/>/g, ">")
186 .replace(/"/g, """)
187 .replace(/'/g, "'");
192 var getRealHeight = function ($el) {
194 $el.children().each(function () {
195 if (height < $(this).outerHeight(true)) {
196 height = $(this).outerHeight(true);
202 var getRealDataAttr = function (dataAttr) {
203 for (var attr in dataAttr) {
204 var auxAttr = attr.split(/(?=[A-Z])/).join('-').toLowerCase();
205 if (auxAttr !== attr) {
206 dataAttr[auxAttr] = dataAttr[attr];
207 delete dataAttr[attr];
214 var getItemField = function (item, field) {
217 if (typeof field !== 'string' || item.hasOwnProperty(field)) {
220 var props = field.split('.');
221 for (var p in props) {
222 value = value[props[p]];
227 // BOOTSTRAP TABLE CLASS DEFINITION
228 // ======================
230 var BootstrapTable = function (el, options) {
231 this.options = options;
233 this.$el_ = this.$el.clone();
235 this.timeoutFooter_ = 0;
240 BootstrapTable.DEFAULTS = {
241 classes: 'table table-hover',
255 contentType: 'application/json',
258 queryParams: function (params) {
261 queryParamsType: 'limit', // undefined
262 responseHandler: function (res) {
266 onlyInfoPagination: false,
267 sidePagination: 'client', // client or server
268 totalRows: 0, // server side need to set
271 pageList: [10, 25, 50, 100],
272 paginationHAlign: 'right', //right, left
273 paginationVAlign: 'bottom', //bottom, top, both
274 paginationDetailHAlign: 'left', //right, left
275 paginationFirstText: '«',
276 paginationPreText: '‹',
277 paginationNextText: '›',
278 paginationLastText: '»',
281 searchAlign: 'right',
282 selectItemName: 'btSelectItem',
286 showPaginationSwitch: false,
289 buttonsAlign: 'right',
291 minimumCountColumns: 1,
296 detailFormatter: function (index, row) {
300 clickToSelect: false,
303 toolbarAlign: 'left',
304 checkboxHeader: true,
307 maintainSelected: false,
311 iconsPrefix: 'glyphicon', // glyphicon of fa (font awesome)
313 paginationSwitchDown: 'glyphicon-collapse-down icon-chevron-down',
314 paginationSwitchUp: 'glyphicon-collapse-up icon-chevron-up',
315 refresh: 'glyphicon-refresh icon-refresh',
316 toggle: 'glyphicon-list-alt icon-list-alt',
317 columns: 'glyphicon-th icon-th',
318 detailOpen: 'glyphicon-plus icon-plus',
319 detailClose: 'glyphicon-minus icon-minus'
322 rowStyle: function (row, index) {
326 rowAttributes: function (row, index) {
330 onAll: function (name, args) {
333 onClickCell: function (field, value, row, $element) {
336 onDblClickCell: function (field, value, row, $element) {
339 onClickRow: function (item, $element) {
342 onDblClickRow: function (item, $element) {
345 onSort: function (name, order) {
348 onCheck: function (row) {
351 onUncheck: function (row) {
354 onCheckAll: function (rows) {
357 onUncheckAll: function (rows) {
360 onCheckSome: function (rows) {
363 onUncheckSome: function (rows) {
366 onLoadSuccess: function (data) {
369 onLoadError: function (status) {
372 onColumnSwitch: function (field, checked) {
375 onPageChange: function (number, size) {
378 onSearch: function (text) {
381 onToggle: function (cardView) {
384 onPreBody: function (data) {
387 onPostBody: function () {
390 onPostHeader: function () {
393 onExpandRow: function (index, row, $detail) {
396 onCollapseRow: function (index, row) {
399 onRefreshOptions: function (options) {
402 onResetView: function () {
407 BootstrapTable.LOCALES = [];
409 BootstrapTable.LOCALES['en-US'] = BootstrapTable.LOCALES['en'] = {
410 formatLoadingMessage: function () {
411 return 'Loading, please wait...';
413 formatRecordsPerPage: function (pageNumber) {
414 return sprintf('%s records per page', pageNumber);
416 formatShowingRows: function (pageFrom, pageTo, totalRows) {
417 return sprintf('Showing %s to %s of %s rows', pageFrom, pageTo, totalRows);
419 formatDetailPagination: function (totalRows) {
420 return sprintf('Showing %s rows', totalRows);
422 formatSearch: function () {
425 formatNoMatches: function () {
426 return 'No matching records found';
428 formatPaginationSwitch: function () {
429 return 'Hide/Show pagination';
431 formatRefresh: function () {
434 formatToggle: function () {
437 formatColumns: function () {
440 formatAllRows: function () {
445 $.extend(BootstrapTable.DEFAULTS, BootstrapTable.LOCALES['en-US']);
447 BootstrapTable.COLUMN_DEFAULTS = {
450 checkboxEnabled: true,
453 titleTooltip: undefined,
455 align: undefined, // left, right, center
456 halign: undefined, // left, right, center
457 falign: undefined, // left, right, center
458 valign: undefined, // top, middle, bottom
461 order: 'asc', // asc, desc
465 formatter: undefined,
466 footerFormatter: undefined,
470 cellStyle: undefined,
472 searchFormatter: true,
476 BootstrapTable.EVENTS = {
477 'all.bs.table': 'onAll',
478 'click-cell.bs.table': 'onClickCell',
479 'dbl-click-cell.bs.table': 'onDblClickCell',
480 'click-row.bs.table': 'onClickRow',
481 'dbl-click-row.bs.table': 'onDblClickRow',
482 'sort.bs.table': 'onSort',
483 'check.bs.table': 'onCheck',
484 'uncheck.bs.table': 'onUncheck',
485 'check-all.bs.table': 'onCheckAll',
486 'uncheck-all.bs.table': 'onUncheckAll',
487 'check-some.bs.table': 'onCheckSome',
488 'uncheck-some.bs.table': 'onUncheckSome',
489 'load-success.bs.table': 'onLoadSuccess',
490 'load-error.bs.table': 'onLoadError',
491 'column-switch.bs.table': 'onColumnSwitch',
492 'page-change.bs.table': 'onPageChange',
493 'search.bs.table': 'onSearch',
494 'toggle.bs.table': 'onToggle',
495 'pre-body.bs.table': 'onPreBody',
496 'post-body.bs.table': 'onPostBody',
497 'post-header.bs.table': 'onPostHeader',
498 'expand-row.bs.table': 'onExpandRow',
499 'collapse-row.bs.table': 'onCollapseRow',
500 'refresh-options.bs.table': 'onRefreshOptions',
501 'reset-view.bs.table': 'onResetView'
504 BootstrapTable.prototype.init = function () {
506 this.initContainer();
512 this.initPagination();
514 this.initSearchText();
518 BootstrapTable.prototype.initLocale = function () {
519 if (this.options.locale) {
520 var parts = this.options.locale.split(/-|_/);
521 parts[0].toLowerCase();
522 parts[1] && parts[1].toUpperCase();
523 if ($.fn.bootstrapTable.locales[this.options.locale]) {
524 // locale as requested
525 $.extend(this.options, $.fn.bootstrapTable.locales[this.options.locale]);
526 } else if ($.fn.bootstrapTable.locales[parts.join('-')]) {
527 // locale with sep set to - (in case original was specified with _)
528 $.extend(this.options, $.fn.bootstrapTable.locales[parts.join('-')]);
529 } else if ($.fn.bootstrapTable.locales[parts[0]]) {
530 // short locale language code (i.e. 'en')
531 $.extend(this.options, $.fn.bootstrapTable.locales[parts[0]]);
536 BootstrapTable.prototype.initContainer = function () {
537 this.$container = $([
538 '<div class="bootstrap-table">',
539 '<div class="fixed-table-toolbar"></div>',
540 this.options.paginationVAlign === 'top' || this.options.paginationVAlign === 'both' ?
541 '<div class="fixed-table-pagination" style="clear: both;"></div>' :
543 '<div class="fixed-table-container">',
544 '<div class="fixed-table-header"><table></table></div>',
545 '<div class="fixed-table-body">',
546 '<div class="fixed-table-loading">',
547 this.options.formatLoadingMessage(),
550 '<div class="fixed-table-footer"><table><tr></tr></table></div>',
551 this.options.paginationVAlign === 'bottom' || this.options.paginationVAlign === 'both' ?
552 '<div class="fixed-table-pagination"></div>' :
558 this.$container.insertAfter(this.$el);
559 this.$tableContainer = this.$container.find('.fixed-table-container');
560 this.$tableHeader = this.$container.find('.fixed-table-header');
561 this.$tableBody = this.$container.find('.fixed-table-body');
562 this.$tableLoading = this.$container.find('.fixed-table-loading');
563 this.$tableFooter = this.$container.find('.fixed-table-footer');
564 this.$toolbar = this.$container.find('.fixed-table-toolbar');
565 this.$pagination = this.$container.find('.fixed-table-pagination');
567 this.$tableBody.append(this.$el);
568 this.$container.after('<div class="clearfix"></div>');
570 this.$el.addClass(this.options.classes);
571 if (this.options.striped) {
572 this.$el.addClass('table-striped');
574 if ($.inArray('table-no-bordered', this.options.classes.split(' ')) !== -1) {
575 this.$tableContainer.addClass('table-no-bordered');
579 BootstrapTable.prototype.initTable = function () {
584 this.$header = this.$el.find('>thead');
585 if (!this.$header.length) {
586 this.$header = $('<thead></thead>').appendTo(this.$el);
588 this.$header.find('tr').each(function () {
591 $(this).find('th').each(function () {
592 column.push($.extend({}, {
593 title: $(this).html(),
594 'class': $(this).attr('class'),
595 titleTooltip: $(this).attr('title'),
596 rowspan: $(this).attr('rowspan') ? +$(this).attr('rowspan') : undefined,
597 colspan: $(this).attr('colspan') ? +$(this).attr('colspan') : undefined
600 columns.push(column);
602 if (!$.isArray(this.options.columns[0])) {
603 this.options.columns = [this.options.columns];
605 this.options.columns = $.extend(true, [], columns, this.options.columns);
608 setFieldIndex(this.options.columns);
609 $.each(this.options.columns, function (i, columns) {
610 $.each(columns, function (j, column) {
611 column = $.extend({}, BootstrapTable.COLUMN_DEFAULTS, column);
613 if (typeof column.fieldIndex !== 'undefined') {
614 that.columns[column.fieldIndex] = column;
617 that.options.columns[i][j] = column;
621 // if options.data is setting, do not process tbody data
622 if (this.options.data.length) {
626 this.$el.find('>tbody>tr').each(function () {
629 // save tr's id, class and data-* attributes
630 row._id = $(this).attr('id');
631 row._class = $(this).attr('class');
632 row._data = getRealDataAttr($(this).data());
634 $(this).find('td').each(function (i) {
635 var field = that.columns[i].field;
637 row[field] = $(this).html();
638 // save td's id, class and data-* attributes
639 row['_' + field + '_id'] = $(this).attr('id');
640 row['_' + field + '_class'] = $(this).attr('class');
641 row['_' + field + '_rowspan'] = $(this).attr('rowspan');
642 row['_' + field + '_title'] = $(this).attr('title');
643 row['_' + field + '_data'] = getRealDataAttr($(this).data());
647 this.options.data = data;
650 BootstrapTable.prototype.initHeader = function () {
667 $.each(this.options.columns, function (i, columns) {
670 if (i == 0 && !that.options.cardView && that.options.detailView) {
671 html.push(sprintf('<th class="detail" rowspan="%s"><div class="fht-cell"></div></th>',
672 that.options.columns.length));
675 $.each(columns, function (j, column) {
677 halign = '', // header align style
678 align = '', // body align style
680 class_ = sprintf(' class="%s"', column['class']),
681 order = that.options.sortOrder || column.order,
683 width = column.width;
685 if (column.width !== undefined && (!that.options.cardView)) {
686 if (typeof column.width === 'string') {
687 if (column.width.indexOf('%') !== -1) {
692 if (column.width && typeof column.width === 'string') {
693 width = column.width.replace('%', '').replace('px', '');
696 halign = sprintf('text-align: %s; ', column.halign ? column.halign : column.align);
697 align = sprintf('text-align: %s; ', column.align);
698 style = sprintf('vertical-align: %s; ', column.valign);
699 style += sprintf('width: %s; ', (column.checkbox || column.radio) && !width ?
700 '36px' : (width ? width + unitWidth : undefined));
702 if (typeof column.fieldIndex !== 'undefined') {
703 that.header.fields[column.fieldIndex] = column.field;
704 that.header.styles[column.fieldIndex] = align + style;
705 that.header.classes[column.fieldIndex] = class_;
706 that.header.formatters[column.fieldIndex] = column.formatter;
707 that.header.events[column.fieldIndex] = column.events;
708 that.header.sorters[column.fieldIndex] = column.sorter;
709 that.header.sortNames[column.fieldIndex] = column.sortName;
710 that.header.cellStyles[column.fieldIndex] = column.cellStyle;
711 that.header.searchables[column.fieldIndex] = column.searchable;
713 if (!column.visible) {
717 if (that.options.cardView && (!column.cardVisible)) {
721 visibleColumns[column.field] = column;
724 html.push('<th' + sprintf(' title="%s"', column.titleTooltip),
725 column.checkbox || column.radio ?
726 sprintf(' class="bs-checkbox %s"', column['class'] || '') :
728 sprintf(' style="%s"', halign + style),
729 sprintf(' rowspan="%s"', column.rowspan),
730 sprintf(' colspan="%s"', column.colspan),
731 sprintf(' data-field="%s"', column.field),
735 html.push(sprintf('<div class="th-inner %s">', that.options.sortable && column.sortable ?
736 'sortable both' : ''));
740 if (column.checkbox) {
741 if (!that.options.singleSelect && that.options.checkboxHeader) {
742 text = '<input name="btSelectAll" type="checkbox" />';
744 that.header.stateField = column.field;
748 that.header.stateField = column.field;
749 that.options.singleSelect = true;
754 html.push('<div class="fht-cell"></div>');
761 this.$header.html(html.join(''));
762 this.$header.find('th[data-field]').each(function (i) {
763 $(this).data(visibleColumns[$(this).data('field')]);
765 this.$container.off('click', '.th-inner').on('click', '.th-inner', function (event) {
766 if (that.options.sortable && $(this).parent().data().sortable) {
771 this.$header.children().children().off('keypress').on('keypress', function (event) {
772 if (that.options.sortable && $(this).data().sortable) {
773 var code = event.keyCode || event.which;
774 if (code == 13) { //Enter keycode
780 if (!this.options.showHeader || this.options.cardView) {
782 this.$tableHeader.hide();
783 this.$tableLoading.css('top', 0);
786 this.$tableHeader.show();
787 this.$tableLoading.css('top', this.$header.outerHeight() + 1);
788 // Assign the correct sortable arrow
792 this.$selectAll = this.$header.find('[name="btSelectAll"]');
793 this.$container.off('click', '[name="btSelectAll"]')
794 .on('click', '[name="btSelectAll"]', function () {
795 var checked = $(this).prop('checked');
796 that[checked ? 'checkAll' : 'uncheckAll']();
797 that.updateSelected();
801 BootstrapTable.prototype.initFooter = function () {
802 if (!this.options.showFooter || this.options.cardView) {
803 this.$tableFooter.hide();
805 this.$tableFooter.show();
811 * @param type: append / prepend
813 BootstrapTable.prototype.initData = function (data, type) {
814 if (type === 'append') {
815 this.data = this.data.concat(data);
816 } else if (type === 'prepend') {
817 this.data = [].concat(data).concat(this.data);
819 this.data = data || this.options.data;
822 // Fix #839 Records deleted when adding new row on filtered table
823 if (type === 'append') {
824 this.options.data = this.options.data.concat(data);
825 } else if (type === 'prepend') {
826 this.options.data = [].concat(data).concat(this.options.data);
828 this.options.data = this.data;
831 if (this.options.sidePagination === 'server') {
837 BootstrapTable.prototype.initSort = function () {
839 name = this.options.sortName,
840 order = this.options.sortOrder === 'desc' ? -1 : 1,
841 index = $.inArray(this.options.sortName, this.header.fields);
844 this.data.sort(function (a, b) {
845 if (that.header.sortNames[index]) {
846 name = that.header.sortNames[index];
848 var aa = getItemField(a, name),
849 bb = getItemField(b, name),
850 value = calculateObjectValue(that.header, that.header.sorters[index], [aa, bb]);
852 if (value !== undefined) {
853 return order * value;
856 // Fix #161: undefined or null string sort bug.
857 if (aa === undefined || aa === null) {
860 if (bb === undefined || bb === null) {
864 // IF both values are numeric, do a numeric comparison
865 if ($.isNumeric(aa) && $.isNumeric(bb)) {
866 // Convert numerical values form string to float.
879 // If value is not a string, convert to string
880 if (typeof aa !== 'string') {
884 if (aa.localeCompare(bb) === -1) {
893 BootstrapTable.prototype.onSort = function (event) {
894 var $this = event.type === "keypress" ? $(event.currentTarget) : $(event.currentTarget).parent(),
895 $this_ = this.$header.find('th').eq($this.index());
897 this.$header.add(this.$header_).find('span.order').remove();
899 if (this.options.sortName === $this.data('field')) {
900 this.options.sortOrder = this.options.sortOrder === 'asc' ? 'desc' : 'asc';
902 this.options.sortName = $this.data('field');
903 this.options.sortOrder = $this.data('order') === 'asc' ? 'desc' : 'asc';
905 this.trigger('sort', this.options.sortName, this.options.sortOrder);
907 $this.add($this_).data('order', this.options.sortOrder);
909 // Assign the correct sortable arrow
912 if (this.options.sidePagination === 'server') {
913 this.initServer(this.options.silentSort);
921 BootstrapTable.prototype.initToolbar = function () {
929 this.$toolbar.html('');
931 if (typeof this.options.toolbar === 'string' || typeof this.options.toolbar === 'object') {
932 $(sprintf('<div class="bars pull-%s"></div>', this.options.toolbarAlign))
933 .appendTo(this.$toolbar)
934 .append($(this.options.toolbar));
937 // showColumns, showToggle, showRefresh
938 html = [sprintf('<div class="columns columns-%s btn-group pull-%s">',
939 this.options.buttonsAlign, this.options.buttonsAlign)];
941 if (typeof this.options.icons === 'string') {
942 this.options.icons = calculateObjectValue(null, this.options.icons);
945 if (this.options.showPaginationSwitch) {
946 html.push(sprintf('<button class="btn btn-default" type="button" name="paginationSwitch" title="%s">',
947 this.options.formatPaginationSwitch()),
948 sprintf('<i class="%s %s"></i>', this.options.iconsPrefix, this.options.icons.paginationSwitchDown),
952 if (this.options.showRefresh) {
953 html.push(sprintf('<button class="btn btn-default' +
954 sprintf(' btn-%s', this.options.iconSize) +
955 '" type="button" name="refresh" title="%s">',
956 this.options.formatRefresh()),
957 sprintf('<i class="%s %s"></i>', this.options.iconsPrefix, this.options.icons.refresh),
961 if (this.options.showToggle) {
962 html.push(sprintf('<button class="btn btn-default' +
963 sprintf(' btn-%s', this.options.iconSize) +
964 '" type="button" name="toggle" title="%s">',
965 this.options.formatToggle()),
966 sprintf('<i class="%s %s"></i>', this.options.iconsPrefix, this.options.icons.toggle),
970 if (this.options.showColumns) {
971 html.push(sprintf('<div class="keep-open btn-group" title="%s">',
972 this.options.formatColumns()),
973 '<button type="button" class="btn btn-default' +
974 sprintf(' btn-%s', this.options.iconSize) +
975 ' dropdown-toggle" data-toggle="dropdown">',
976 sprintf('<i class="%s %s"></i>', this.options.iconsPrefix, this.options.icons.columns),
977 ' <span class="caret"></span>',
979 '<ul class="dropdown-menu" role="menu">');
981 $.each(this.columns, function (i, column) {
982 if (column.radio || column.checkbox) {
986 if (that.options.cardView && (!column.cardVisible)) {
990 var checked = column.visible ? ' checked="checked"' : '';
992 if (column.switchable) {
993 html.push(sprintf('<li>' +
994 '<label><input type="checkbox" data-field="%s" value="%s"%s> %s</label>' +
995 '</li>', column.field, i, checked, column.title));
1003 html.push('</div>');
1005 // Fix #188: this.showToolbar is for extentions
1006 if (this.showToolbar || html.length > 2) {
1007 this.$toolbar.append(html.join(''));
1010 if (this.options.showPaginationSwitch) {
1011 this.$toolbar.find('button[name="paginationSwitch"]')
1012 .off('click').on('click', $.proxy(this.togglePagination, this));
1015 if (this.options.showRefresh) {
1016 this.$toolbar.find('button[name="refresh"]')
1017 .off('click').on('click', $.proxy(this.refresh, this));
1020 if (this.options.showToggle) {
1021 this.$toolbar.find('button[name="toggle"]')
1022 .off('click').on('click', function () {
1027 if (this.options.showColumns) {
1028 $keepOpen = this.$toolbar.find('.keep-open');
1030 if (switchableCount <= this.options.minimumCountColumns) {
1031 $keepOpen.find('input').prop('disabled', true);
1034 $keepOpen.find('li').off('click').on('click', function (event) {
1035 event.stopImmediatePropagation();
1037 $keepOpen.find('input').off('click').on('click', function () {
1038 var $this = $(this);
1040 that.toggleColumn(getFieldIndex(that.columns,
1041 $(this).data('field')), $this.prop('checked'), false);
1042 that.trigger('column-switch', $(this).data('field'), $this.prop('checked'));
1046 if (this.options.search) {
1049 '<div class="pull-' + this.options.searchAlign + ' search">',
1050 sprintf('<input class="form-control' +
1051 sprintf(' input-%s', this.options.iconSize) +
1052 '" type="text" placeholder="%s">',
1053 this.options.formatSearch()),
1056 this.$toolbar.append(html.join(''));
1057 $search = this.$toolbar.find('.search input');
1058 $search.off('keyup drop').on('keyup drop', function (event) {
1059 clearTimeout(timeoutId); // doesn't matter if it's 0
1060 timeoutId = setTimeout(function () {
1061 that.onSearch(event);
1062 }, that.options.searchTimeOut);
1067 BootstrapTable.prototype.onSearch = function (event) {
1068 var text = $.trim($(event.currentTarget).val());
1070 // trim search input
1071 if (this.options.trimOnSearch && $(event.currentTarget).val() !== text) {
1072 $(event.currentTarget).val(text);
1075 if (text === this.searchText) {
1078 this.searchText = text;
1080 this.options.pageNumber = 1;
1082 this.updatePagination();
1083 this.trigger('search', text);
1086 BootstrapTable.prototype.initSearch = function () {
1089 if (this.options.sidePagination !== 'server') {
1090 var s = this.searchText && this.searchText.toLowerCase();
1091 var f = $.isEmptyObject(this.filterColumns) ? null : this.filterColumns;
1094 this.data = f ? $.grep(this.options.data, function (item, i) {
1095 for (var key in f) {
1096 if ($.isArray(f[key])) {
1097 if ($.inArray(item[key], f[key]) === -1) {
1100 } else if (item[key] !== f[key]) {
1105 }) : this.options.data;
1107 this.data = s ? $.grep(this.data, function (item, i) {
1108 for (var key in item) {
1109 key = $.isNumeric(key) ? parseInt(key, 10) : key;
1110 var value = item[key],
1111 column = that.columns[getFieldIndex(that.columns, key)],
1112 j = $.inArray(key, that.header.fields);
1114 // Fix #142: search use formated data
1115 if (column && column.searchFormatter) {
1116 value = calculateObjectValue(column,
1117 that.header.formatters[j], [value, item, i], value);
1120 var index = $.inArray(key, that.header.fields);
1121 if (index !== -1 && that.header.searchables[index] && (typeof value === 'string' || typeof value === 'number')) {
1122 if (that.options.strictSearch) {
1123 if ((value + '').toLowerCase() === s) {
1127 if ((value + '').toLowerCase().indexOf(s) !== -1) {
1138 BootstrapTable.prototype.initPagination = function () {
1139 if (!this.options.pagination) {
1140 this.$pagination.hide();
1143 this.$pagination.show();
1148 $allSelected = false,
1154 data = this.getData();
1156 if (this.options.sidePagination !== 'server') {
1157 this.options.totalRows = data.length;
1160 this.totalPages = 0;
1161 if (this.options.totalRows) {
1162 if (this.options.pageSize === this.options.formatAllRows()) {
1163 this.options.pageSize = this.options.totalRows;
1164 $allSelected = true;
1165 } else if (this.options.pageSize === this.options.totalRows) {
1166 // Fix #667 Table with pagination,
1167 // multiple pages and a search that matches to one page throws exception
1168 var pageLst = typeof this.options.pageList === 'string' ?
1169 this.options.pageList.replace('[', '').replace(']', '')
1170 .replace(/ /g, '').toLowerCase().split(',') : this.options.pageList;
1171 if ($.inArray(this.options.formatAllRows().toLowerCase(), pageLst) > -1) {
1172 $allSelected = true;
1176 this.totalPages = ~~((this.options.totalRows - 1) / this.options.pageSize) + 1;
1178 this.options.totalPages = this.totalPages;
1180 if (this.totalPages > 0 && this.options.pageNumber > this.totalPages) {
1181 this.options.pageNumber = this.totalPages;
1184 this.pageFrom = (this.options.pageNumber - 1) * this.options.pageSize + 1;
1185 this.pageTo = this.options.pageNumber * this.options.pageSize;
1186 if (this.pageTo > this.options.totalRows) {
1187 this.pageTo = this.options.totalRows;
1191 '<div class="pull-' + this.options.paginationDetailHAlign + ' pagination-detail">',
1192 '<span class="pagination-info">',
1193 this.options.onlyInfoPagination ? this.options.formatDetailPagination(this.options.totalRows) :
1194 this.options.formatShowingRows(this.pageFrom, this.pageTo, this.options.totalRows),
1197 if (!this.options.onlyInfoPagination) {
1198 html.push('<span class="page-list">');
1201 sprintf('<span class="btn-group %s">',
1202 this.options.paginationVAlign === 'top' || this.options.paginationVAlign === 'both' ?
1203 'dropdown' : 'dropup'),
1204 '<button type="button" class="btn btn-default ' +
1205 sprintf(' btn-%s', this.options.iconSize) +
1206 ' dropdown-toggle" data-toggle="dropdown">',
1207 '<span class="page-size">',
1208 $allSelected ? this.options.formatAllRows() : this.options.pageSize,
1210 ' <span class="caret"></span>',
1212 '<ul class="dropdown-menu" role="menu">'
1214 pageList = this.options.pageList;
1216 if (typeof this.options.pageList === 'string') {
1217 var list = this.options.pageList.replace('[', '').replace(']', '')
1218 .replace(/ /g, '').split(',');
1221 $.each(list, function (i, value) {
1222 pageList.push(value.toUpperCase() === that.options.formatAllRows().toUpperCase() ?
1223 that.options.formatAllRows() : +value);
1227 $.each(pageList, function (i, page) {
1228 if (!that.options.smartDisplay || i === 0 || pageList[i - 1] <= that.options.totalRows) {
1231 active = page === that.options.formatAllRows() ? ' class="active"' : '';
1233 active = page === that.options.pageSize ? ' class="active"' : '';
1235 pageNumber.push(sprintf('<li%s><a href="javascript:void(0)">%s</a></li>', active, page));
1238 pageNumber.push('</ul></span>');
1240 html.push(this.options.formatRecordsPerPage(pageNumber.join('')));
1241 html.push('</span>');
1244 '<div class="pull-' + this.options.paginationHAlign + ' pagination">',
1245 '<ul class="pagination' + sprintf(' pagination-%s', this.options.iconSize) + '">',
1246 '<li class="page-first"><a href="javascript:void(0)">' + this.options.paginationFirstText + '</a></li>',
1247 '<li class="page-pre"><a href="javascript:void(0)">' + this.options.paginationPreText + '</a></li>');
1249 if (this.totalPages < 5) {
1251 to = this.totalPages;
1253 from = this.options.pageNumber - 2;
1259 if (to > this.totalPages) {
1260 to = this.totalPages;
1264 for (i = from; i <= to; i++) {
1265 html.push('<li class="page-number' + (i === this.options.pageNumber ? ' active' : '') + '">',
1266 '<a href="javascript:void(0)">', i, '</a>',
1271 '<li class="page-next"><a href="javascript:void(0)">' + this.options.paginationNextText + '</a></li>',
1272 '<li class="page-last"><a href="javascript:void(0)">' + this.options.paginationLastText + '</a></li>',
1277 this.$pagination.html(html.join(''));
1279 if (!this.options.onlyInfoPagination) {
1280 $pageList = this.$pagination.find('.page-list a');
1281 $first = this.$pagination.find('.page-first');
1282 $pre = this.$pagination.find('.page-pre');
1283 $next = this.$pagination.find('.page-next');
1284 $last = this.$pagination.find('.page-last');
1285 $number = this.$pagination.find('.page-number');
1287 if (this.options.pageNumber <= 1) {
1288 $first.addClass('disabled');
1289 $pre.addClass('disabled');
1291 if (this.options.pageNumber >= this.totalPages) {
1292 $next.addClass('disabled');
1293 $last.addClass('disabled');
1295 if (this.options.smartDisplay) {
1296 if (this.totalPages <= 1) {
1297 this.$pagination.find('div.pagination').hide();
1299 if (pageList.length < 2 || this.options.totalRows <= pageList[0]) {
1300 this.$pagination.find('span.page-list').hide();
1303 // when data is empty, hide the pagination
1304 this.$pagination[this.getData().length ? 'show' : 'hide']();
1307 this.options.pageSize = this.options.formatAllRows();
1309 $pageList.off('click').on('click', $.proxy(this.onPageListChange, this));
1310 $first.off('click').on('click', $.proxy(this.onPageFirst, this));
1311 $pre.off('click').on('click', $.proxy(this.onPagePre, this));
1312 $next.off('click').on('click', $.proxy(this.onPageNext, this));
1313 $last.off('click').on('click', $.proxy(this.onPageLast, this));
1314 $number.off('click').on('click', $.proxy(this.onPageNumber, this));
1318 BootstrapTable.prototype.updatePagination = function (event) {
1319 // Fix #171: IE disabled button can be clicked bug.
1320 if (event && $(event.currentTarget).hasClass('disabled')) {
1324 if (!this.options.maintainSelected) {
1328 this.initPagination();
1329 if (this.options.sidePagination === 'server') {
1335 this.trigger('page-change', this.options.pageNumber, this.options.pageSize);
1338 BootstrapTable.prototype.onPageListChange = function (event) {
1339 var $this = $(event.currentTarget);
1341 $this.parent().addClass('active').siblings().removeClass('active');
1342 this.options.pageSize = $this.text().toUpperCase() === this.options.formatAllRows().toUpperCase() ?
1343 this.options.formatAllRows() : +$this.text();
1344 this.$toolbar.find('.page-size').text(this.options.pageSize);
1346 this.updatePagination(event);
1349 BootstrapTable.prototype.onPageFirst = function (event) {
1350 this.options.pageNumber = 1;
1351 this.updatePagination(event);
1354 BootstrapTable.prototype.onPagePre = function (event) {
1355 this.options.pageNumber--;
1356 this.updatePagination(event);
1359 BootstrapTable.prototype.onPageNext = function (event) {
1360 this.options.pageNumber++;
1361 this.updatePagination(event);
1364 BootstrapTable.prototype.onPageLast = function (event) {
1365 this.options.pageNumber = this.totalPages;
1366 this.updatePagination(event);
1369 BootstrapTable.prototype.onPageNumber = function (event) {
1370 if (this.options.pageNumber === +$(event.currentTarget).text()) {
1373 this.options.pageNumber = +$(event.currentTarget).text();
1374 this.updatePagination(event);
1377 BootstrapTable.prototype.initBody = function (fixedScroll) {
1380 data = this.getData();
1382 this.trigger('pre-body', data);
1384 this.$body = this.$el.find('>tbody');
1385 if (!this.$body.length) {
1386 this.$body = $('<tbody></tbody>').appendTo(this.$el);
1389 //Fix #389 Bootstrap-table-flatJSON is not working
1391 if (!this.options.pagination || this.options.sidePagination === 'server') {
1393 this.pageTo = data.length;
1396 for (var i = this.pageFrom - 1; i < this.pageTo; i++) {
1403 htmlAttributes = [];
1405 style = calculateObjectValue(this.options, this.options.rowStyle, [item, i], style);
1407 if (style && style.css) {
1408 for (key in style.css) {
1409 csses.push(key + ': ' + style.css[key]);
1413 attributes = calculateObjectValue(this.options,
1414 this.options.rowAttributes, [item, i], attributes);
1417 for (key in attributes) {
1418 htmlAttributes.push(sprintf('%s="%s"', key, escapeHTML(attributes[key])));
1422 if (item._data && !$.isEmptyObject(item._data)) {
1423 $.each(item._data, function (k, v) {
1424 // ignore data-index
1425 if (k === 'index') {
1428 data_ += sprintf(' data-%s="%s"', k, v);
1433 sprintf(' %s', htmlAttributes.join(' ')),
1434 sprintf(' id="%s"', $.isArray(item) ? undefined : item._id),
1435 sprintf(' class="%s"', style.classes || ($.isArray(item) ? undefined : item._class)),
1436 sprintf(' data-index="%s"', i),
1437 sprintf(' data-uniqueid="%s"', item[this.options.uniqueId]),
1438 sprintf('%s', data_),
1442 if (this.options.cardView) {
1443 html.push(sprintf('<td colspan="%s">', this.header.fields.length));
1446 if (!this.options.cardView && this.options.detailView) {
1448 '<a class="detail-icon" href="javascript:">',
1449 sprintf('<i class="%s %s"></i>', this.options.iconsPrefix, this.options.icons.detailOpen),
1454 $.each(this.header.fields, function (j, field) {
1456 value = getItemField(item, field),
1460 class_ = that.header.classes[j],
1464 column = that.columns[getFieldIndex(that.columns, field)];
1466 if (!column.visible) {
1470 style = sprintf('style="%s"', csses.concat(that.header.styles[j]).join('; '));
1472 value = calculateObjectValue(column,
1473 that.header.formatters[j], [value, item, i], value);
1475 // handle td's id and class
1476 if (item['_' + field + '_id']) {
1477 id_ = sprintf(' id="%s"', item['_' + field + '_id']);
1479 if (item['_' + field + '_class']) {
1480 class_ = sprintf(' class="%s"', item['_' + field + '_class']);
1482 if (item['_' + field + '_rowspan']) {
1483 rowspan_ = sprintf(' rowspan="%s"', item['_' + field + '_rowspan']);
1485 if (item['_' + field + '_title']) {
1486 title_ = sprintf(' title="%s"', item['_' + field + '_title']);
1488 cellStyle = calculateObjectValue(that.header,
1489 that.header.cellStyles[j], [value, item, i], cellStyle);
1490 if (cellStyle.classes) {
1491 class_ = sprintf(' class="%s"', cellStyle.classes);
1493 if (cellStyle.css) {
1495 for (var key in cellStyle.css) {
1496 csses_.push(key + ': ' + cellStyle.css[key]);
1498 style = sprintf('style="%s"', csses_.concat(that.header.styles[j]).join('; '));
1501 if (item['_' + field + '_data'] && !$.isEmptyObject(item['_' + field + '_data'])) {
1502 $.each(item['_' + field + '_data'], function (k, v) {
1503 // ignore data-index
1504 if (k === 'index') {
1507 data_ += sprintf(' data-%s="%s"', k, v);
1511 if (column.checkbox || column.radio) {
1512 type = column.checkbox ? 'checkbox' : type;
1513 type = column.radio ? 'radio' : type;
1515 text = [that.options.cardView ?
1516 '<div class="card-view">' : '<td class="bs-checkbox">',
1518 sprintf(' data-index="%s"', i) +
1519 sprintf(' name="%s"', that.options.selectItemName) +
1520 sprintf(' type="%s"', type) +
1521 sprintf(' value="%s"', item[that.options.idField]) +
1522 sprintf(' checked="%s"', value === true ||
1523 (value && value.checked) ? 'checked' : undefined) +
1524 sprintf(' disabled="%s"', !column.checkboxEnabled ||
1525 (value && value.disabled) ? 'disabled' : undefined) +
1527 that.header.formatters[j] && typeof value === 'string' ? value : '',
1528 that.options.cardView ? '</div>' : '</td>'
1531 item[that.header.stateField] = value === true || (value && value.checked);
1533 value = typeof value === 'undefined' || value === null ?
1534 that.options.undefinedText : value;
1536 text = that.options.cardView ? ['<div class="card-view">',
1537 that.options.showHeader ? sprintf('<span class="title" %s>%s</span>', style,
1538 getPropertyFromOther(that.columns, 'field', 'title', field)) : '',
1539 sprintf('<span class="value">%s</span>', value),
1541 ].join('') : [sprintf('<td%s %s %s %s %s %s>', id_, class_, style, data_, rowspan_, title_),
1546 // Hide empty data on Card view when smartDisplay is set to true.
1547 if (that.options.cardView && that.options.smartDisplay && value === '') {
1548 // Should set a placeholder for event binding correct fieldIndex
1549 text = '<div class="card-view"></div>';
1556 if (this.options.cardView) {
1565 html.push('<tr class="no-records-found">',
1566 sprintf('<td colspan="%s">%s</td>',
1567 this.$header.find('th').length, this.options.formatNoMatches()),
1571 this.$body.html(html.join(''));
1577 // click to select by column
1578 this.$body.find('> tr[data-index] > td').off('click dblclick').on('click dblclick', function (e) {
1581 item = that.data[$tr.data('index')],
1582 index = $td[0].cellIndex,
1583 field = that.header.fields[that.options.detailView && !that.options.cardView ? index - 1 : index],
1584 column = that.columns[getFieldIndex(that.columns, field)],
1585 value = getItemField(item, field);
1587 if ($td.find('.detail-icon').length) {
1591 that.trigger(e.type === 'click' ? 'click-cell' : 'dbl-click-cell', field, value, item, $td);
1592 that.trigger(e.type === 'click' ? 'click-row' : 'dbl-click-row', item, $tr);
1594 // if click to select - then trigger the checkbox/radio click
1595 if (e.type === 'click' && that.options.clickToSelect && column.clickToSelect) {
1596 var $selectItem = $tr.find(sprintf('[name="%s"]', that.options.selectItemName));
1597 if ($selectItem.length) {
1598 $selectItem[0].click(); // #144: .trigger('click') bug
1603 this.$body.find('> tr[data-index] > td > .detail-icon').off('click').on('click', function () {
1604 var $this = $(this),
1605 $tr = $this.parent().parent(),
1606 index = $tr.data('index'),
1607 row = data[index]; // Fix #980 Detail view, when searching, returns wrong row
1609 // remove and update
1610 if ($tr.next().is('tr.detail-view')) {
1611 $this.find('i').attr('class', sprintf('%s %s', that.options.iconsPrefix, that.options.icons.detailOpen));
1612 $tr.next().remove();
1613 that.trigger('collapse-row', index, row);
1615 $this.find('i').attr('class', sprintf('%s %s', that.options.iconsPrefix, that.options.icons.detailClose));
1616 $tr.after(sprintf('<tr class="detail-view"><td colspan="%s">%s</td></tr>',
1617 $tr.find('td').length, calculateObjectValue(that.options,
1618 that.options.detailFormatter, [index, row], '')));
1619 that.trigger('expand-row', index, row, $tr.next().find('td'));
1624 this.$selectItem = this.$body.find(sprintf('[name="%s"]', this.options.selectItemName));
1625 this.$selectItem.off('click').on('click', function (event) {
1626 event.stopImmediatePropagation();
1628 var $this = $(this),
1629 checked = $this.prop('checked'),
1630 row = that.data[$this.data('index')];
1632 if (that.options.maintainSelected && $(this).is(':radio')) {
1633 $.each(that.options.data, function (i, row) {
1634 row[that.header.stateField] = false;
1638 row[that.header.stateField] = checked;
1640 if (that.options.singleSelect) {
1641 that.$selectItem.not(this).each(function () {
1642 that.data[$(this).data('index')][that.header.stateField] = false;
1644 that.$selectItem.filter(':checked').not(this).prop('checked', false);
1647 that.updateSelected();
1648 that.trigger(checked ? 'check' : 'uncheck', row, $this);
1651 $.each(this.header.events, function (i, events) {
1655 // fix bug, if events is defined with namespace
1656 if (typeof events === 'string') {
1657 events = calculateObjectValue(null, events);
1660 var field = that.header.fields[i],
1661 fieldIndex = $.inArray(field, that.getVisibleFields());
1663 if (that.options.detailView && !that.options.cardView) {
1667 for (var key in events) {
1668 that.$body.find('>tr:not(.no-records-found)').each(function () {
1670 $td = $tr.find(that.options.cardView ? '.card-view' : 'td').eq(fieldIndex),
1671 index = key.indexOf(' '),
1672 name = key.substring(0, index),
1673 el = key.substring(index + 1),
1676 $td.find(el).off(name).on(name, function (e) {
1677 var index = $tr.data('index'),
1678 row = that.data[index],
1681 func.apply(this, [e, value, row, index]);
1687 this.updateSelected();
1690 this.trigger('post-body');
1693 BootstrapTable.prototype.initServer = function (silent, query) {
1697 pageSize: this.options.pageSize === this.options.formatAllRows() ?
1698 this.options.totalRows : this.options.pageSize,
1699 pageNumber: this.options.pageNumber,
1700 searchText: this.searchText,
1701 sortName: this.options.sortName,
1702 sortOrder: this.options.sortOrder
1706 if (!this.options.url && !this.options.ajax) {
1710 if (this.options.queryParamsType === 'limit') {
1712 search: params.searchText,
1713 sort: params.sortName,
1714 order: params.sortOrder
1716 if (this.options.pagination) {
1717 params.limit = this.options.pageSize === this.options.formatAllRows() ?
1718 this.options.totalRows : this.options.pageSize;
1719 params.offset = this.options.pageSize === this.options.formatAllRows() ?
1720 0 : this.options.pageSize * (this.options.pageNumber - 1);
1724 if (!($.isEmptyObject(this.filterColumnsPartial))) {
1725 params['filter'] = JSON.stringify(this.filterColumnsPartial, null);
1728 data = calculateObjectValue(this.options, this.options.queryParams, [params], data);
1730 $.extend(data, query || {});
1732 // false to stop request
1733 if (data === false) {
1738 this.$tableLoading.show();
1740 request = $.extend({}, calculateObjectValue(null, this.options.ajaxOptions), {
1741 type: this.options.method,
1742 url: this.options.url,
1743 data: this.options.contentType === 'application/json' && this.options.method === 'post' ?
1744 JSON.stringify(data) : data,
1745 cache: this.options.cache,
1746 contentType: this.options.contentType,
1747 dataType: this.options.dataType,
1748 success: function (res) {
1749 res = calculateObjectValue(that.options, that.options.responseHandler, [res], res);
1752 that.trigger('load-success', res);
1754 error: function (res) {
1755 that.trigger('load-error', res.status, res);
1757 complete: function () {
1759 that.$tableLoading.hide();
1764 if (this.options.ajax) {
1765 calculateObjectValue(this, this.options.ajax, [request], null);
1771 BootstrapTable.prototype.initSearchText = function () {
1772 if (this.options.search) {
1773 if (this.options.searchText !== '') {
1774 var $search = this.$toolbar.find('.search input');
1775 $search.val(this.options.searchText);
1776 this.onSearch({currentTarget: $search});
1781 BootstrapTable.prototype.getCaret = function () {
1784 $.each(this.$header.find('th'), function (i, th) {
1785 $(th).find('.sortable').removeClass('desc asc').addClass($(th).data('field') === that.options.sortName ? that.options.sortOrder : 'both');
1789 BootstrapTable.prototype.updateSelected = function () {
1790 var checkAll = this.$selectItem.filter(':enabled').length &&
1791 this.$selectItem.filter(':enabled').length ===
1792 this.$selectItem.filter(':enabled').filter(':checked').length;
1794 this.$selectAll.add(this.$selectAll_).prop('checked', checkAll);
1796 this.$selectItem.each(function () {
1797 $(this).closest('tr')[$(this).prop('checked') ? 'addClass' : 'removeClass']('selected');
1801 BootstrapTable.prototype.updateRows = function () {
1804 this.$selectItem.each(function () {
1805 that.data[$(this).data('index')][that.header.stateField] = $(this).prop('checked');
1809 BootstrapTable.prototype.resetRows = function () {
1812 $.each(this.data, function (i, row) {
1813 that.$selectAll.prop('checked', false);
1814 that.$selectItem.prop('checked', false);
1815 if (that.header.stateField) {
1816 row[that.header.stateField] = false;
1821 BootstrapTable.prototype.trigger = function (name) {
1822 var args = Array.prototype.slice.call(arguments, 1);
1824 name += '.bs.table';
1825 this.options[BootstrapTable.EVENTS[name]].apply(this.options, args);
1826 this.$el.trigger($.Event(name), args);
1828 this.options.onAll(name, args);
1829 this.$el.trigger($.Event('all.bs.table'), [name, args]);
1832 BootstrapTable.prototype.resetHeader = function () {
1833 // fix #61: the hidden table reset header bug.
1834 // fix bug: get $el.css('width') error sometime (height = 500)
1835 clearTimeout(this.timeoutId_);
1836 this.timeoutId_ = setTimeout($.proxy(this.fitHeader, this), this.$el.is(':hidden') ? 100 : 0);
1839 BootstrapTable.prototype.fitHeader = function () {
1846 if (that.$el.is(':hidden')) {
1847 that.timeoutId_ = setTimeout($.proxy(that.fitHeader, that), 100);
1850 fixedBody = this.$tableBody.get(0);
1852 scrollWidth = fixedBody.scrollWidth > fixedBody.clientWidth &&
1853 fixedBody.scrollHeight > fixedBody.clientHeight + this.$header.outerHeight() ?
1854 getScrollBarWidth() : 0;
1856 this.$el.css('margin-top', -this.$header.outerHeight());
1858 focused = $(':focus');
1859 if (focused.length > 0) {
1860 var $th = focused.parents('th');
1861 if ($th.length > 0) {
1862 var dataField = $th.attr('data-field');
1863 if (dataField !== undefined) {
1864 var $headerTh = this.$header.find("[data-field='" + dataField + "']");
1865 if ($headerTh.length > 0) {
1866 $headerTh.find(":input").addClass("focus-temp");
1872 this.$header_ = this.$header.clone(true, true);
1873 this.$selectAll_ = this.$header_.find('[name="btSelectAll"]');
1874 this.$tableHeader.css({
1875 'margin-right': scrollWidth
1876 }).find('table').css('width', this.$el.outerWidth())
1877 .html('').attr('class', this.$el.attr('class'))
1878 .append(this.$header_);
1881 focusedTemp = $('.focus-temp:visible:eq(0)');
1882 if (focusedTemp.length > 0) {
1883 focusedTemp.focus();
1884 this.$header.find('.focus-temp').removeClass('focus-temp');
1887 // fix bug: $.data() is not working as expected after $.append()
1888 this.$header.find('th[data-field]').each(function (i) {
1889 that.$header_.find(sprintf('th[data-field="%s"]', $(this).data('field'))).data($(this).data());
1892 var visibleFields = this.getVisibleFields();
1894 this.$body.find('>tr:first-child:not(.no-records-found) > *').each(function (i) {
1895 var $this = $(this),
1898 if (that.options.detailView && !that.options.cardView) {
1900 that.$header_.find('th.detail').find('.fht-cell').width($this.innerWidth());
1905 that.$header_.find(sprintf('th[data-field="%s"]', visibleFields[index]))
1906 .find('.fht-cell').width($this.innerWidth());
1908 // horizontal scroll event
1909 // TODO: it's probably better improving the layout than binding to scroll event
1910 this.$tableBody.off('scroll').on('scroll', function () {
1911 that.$tableHeader.scrollLeft($(this).scrollLeft());
1913 if (that.options.showFooter && !that.options.cardView) {
1914 that.$tableFooter.scrollLeft($(this).scrollLeft());
1917 that.trigger('post-header');
1920 BootstrapTable.prototype.resetFooter = function () {
1922 data = that.getData(),
1925 if (!this.options.showFooter || this.options.cardView) { //do nothing
1929 if (!this.options.cardView && this.options.detailView) {
1930 html.push('<td><div class="th-inner"> </div><div class="fht-cell"></div></td>');
1933 $.each(this.columns, function (i, column) {
1934 var falign = '', // footer align style
1936 class_ = sprintf(' class="%s"', column['class']);
1938 if (!column.visible) {
1942 if (that.options.cardView && (!column.cardVisible)) {
1946 falign = sprintf('text-align: %s; ', column.falign ? column.falign : column.align);
1947 style = sprintf('vertical-align: %s; ', column.valign);
1949 html.push('<td', class_, sprintf(' style="%s"', falign + style), '>');
1950 html.push('<div class="th-inner">');
1952 html.push(calculateObjectValue(column, column.footerFormatter, [data], ' ') || ' ');
1954 html.push('</div>');
1955 html.push('<div class="fht-cell"></div>');
1956 html.push('</div>');
1960 this.$tableFooter.find('tr').html(html.join(''));
1961 clearTimeout(this.timeoutFooter_);
1962 this.timeoutFooter_ = setTimeout($.proxy(this.fitFooter, this),
1963 this.$el.is(':hidden') ? 100 : 0);
1966 BootstrapTable.prototype.fitFooter = function () {
1972 clearTimeout(this.timeoutFooter_);
1973 if (this.$el.is(':hidden')) {
1974 this.timeoutFooter_ = setTimeout($.proxy(this.fitFooter, this), 100);
1978 elWidth = this.$el.css('width');
1979 scrollWidth = elWidth > this.$tableBody.width() ? getScrollBarWidth() : 0;
1981 this.$tableFooter.css({
1982 'margin-right': scrollWidth
1983 }).find('table').css('width', elWidth)
1984 .attr('class', this.$el.attr('class'));
1986 $footerTd = this.$tableFooter.find('td');
1988 this.$body.find('>tr:first-child:not(.no-records-found) > *').each(function (i) {
1989 var $this = $(this);
1991 $footerTd.eq(i).find('.fht-cell').width($this.innerWidth());
1995 BootstrapTable.prototype.toggleColumn = function (index, checked, needUpdate) {
1999 this.columns[index].visible = checked;
2002 this.initPagination();
2005 if (this.options.showColumns) {
2006 var $items = this.$toolbar.find('.keep-open input').prop('disabled', false);
2009 $items.filter(sprintf('[value="%s"]', index)).prop('checked', checked);
2012 if ($items.filter(':checked').length <= this.options.minimumCountColumns) {
2013 $items.filter(':checked').prop('disabled', true);
2018 BootstrapTable.prototype.toggleRow = function (index, uniqueId, visible) {
2023 this.$body.find(typeof index !== 'undefined' ?
2024 sprintf('tr[data-index="%s"]', index) :
2025 sprintf('tr[data-uniqueid="%s"]', uniqueId))
2026 [visible ? 'show' : 'hide']();
2029 BootstrapTable.prototype.getVisibleFields = function () {
2033 $.each(this.header.fields, function (j, field) {
2034 var column = that.columns[getFieldIndex(that.columns, field)];
2036 if (!column.visible) {
2039 visibleFields.push(field);
2041 return visibleFields;
2044 // PUBLIC FUNCTION DEFINITION
2045 // =======================
2047 BootstrapTable.prototype.resetView = function (params) {
2050 if (params && params.height) {
2051 this.options.height = params.height;
2054 this.$selectAll.prop('checked', this.$selectItem.length > 0 &&
2055 this.$selectItem.length === this.$selectItem.filter(':checked').length);
2057 if (this.options.height) {
2058 var toolbarHeight = getRealHeight(this.$toolbar),
2059 paginationHeight = getRealHeight(this.$pagination),
2060 height = this.options.height - toolbarHeight - paginationHeight;
2062 this.$tableContainer.css('height', height + 'px');
2065 if (this.options.cardView) {
2066 // remove the element css
2067 this.$el.css('margin-top', '0');
2068 this.$tableContainer.css('padding-bottom', '0');
2072 if (this.options.showHeader && this.options.height) {
2073 this.$tableHeader.show();
2075 padding += this.$header.outerHeight();
2077 this.$tableHeader.hide();
2078 this.trigger('post-header');
2081 if (this.options.showFooter) {
2083 if (this.options.height) {
2084 padding += this.$tableFooter.outerHeight() + 1;
2088 // Assign the correct sortable arrow
2090 this.$tableContainer.css('padding-bottom', padding + 'px');
2091 this.trigger('reset-view');
2094 BootstrapTable.prototype.getData = function (useCurrentPage) {
2095 return (this.searchText || !$.isEmptyObject(this.filterColumns) || !$.isEmptyObject(this.filterColumnsPartial)) ?
2096 (useCurrentPage ? this.data.slice(this.pageFrom - 1, this.pageTo) : this.data) :
2097 (useCurrentPage ? this.options.data.slice(this.pageFrom - 1, this.pageTo) : this.options.data);
2100 BootstrapTable.prototype.load = function (data) {
2101 var fixedScroll = false;
2103 // #431: support pagination
2104 if (this.options.sidePagination === 'server') {
2105 this.options.totalRows = data.total;
2106 fixedScroll = data.fixedScroll;
2107 data = data[this.options.dataField];
2108 } else if (!$.isArray(data)) { // support fixedScroll
2109 fixedScroll = data.fixedScroll;
2113 this.initData(data);
2115 this.initPagination();
2116 this.initBody(fixedScroll);
2119 BootstrapTable.prototype.append = function (data) {
2120 this.initData(data, 'append');
2122 this.initPagination();
2123 this.initBody(true);
2126 BootstrapTable.prototype.prepend = function (data) {
2127 this.initData(data, 'prepend');
2129 this.initPagination();
2130 this.initBody(true);
2133 BootstrapTable.prototype.remove = function (params) {
2134 var len = this.options.data.length,
2137 if (!params.hasOwnProperty('field') || !params.hasOwnProperty('values')) {
2141 for (i = len - 1; i >= 0; i--) {
2142 row = this.options.data[i];
2144 if (!row.hasOwnProperty(params.field)) {
2147 if ($.inArray(row[params.field], params.values) !== -1) {
2148 this.options.data.splice(i, 1);
2152 if (len === this.options.data.length) {
2157 this.initPagination();
2158 this.initBody(true);
2161 BootstrapTable.prototype.removeAll = function () {
2162 if (this.options.data.length > 0) {
2163 this.options.data.splice(0, this.options.data.length);
2165 this.initPagination();
2166 this.initBody(true);
2170 BootstrapTable.prototype.getRowByUniqueId = function (id) {
2171 var uniqueId = this.options.uniqueId,
2172 len = this.options.data.length,
2174 i, row, rowUniqueId;
2176 for (i = len - 1; i >= 0; i--) {
2177 row = this.options.data[i];
2179 if (row.hasOwnProperty(uniqueId)) { // uniqueId is a column
2180 rowUniqueId = row[uniqueId];
2181 } else if(row._data.hasOwnProperty(uniqueId)) { // uniqueId is a row data property
2182 rowUniqueId = row._data[uniqueId];
2187 if (typeof rowUniqueId === 'string') {
2189 } else if (typeof rowUniqueId === 'number') {
2190 if ((Number(rowUniqueId) === rowUniqueId) && (rowUniqueId % 1 === 0)) {
2192 } else if ((rowUniqueId === Number(rowUniqueId)) && (rowUniqueId !== 0)) {
2193 id = parseFloat(id);
2197 if (rowUniqueId === id) {
2206 BootstrapTable.prototype.removeByUniqueId = function (id) {
2207 var len = this.options.data.length,
2208 row = this.getRowByUniqueId(id);
2211 this.options.data.splice(this.options.data.indexOf(row), 1);
2214 if (len === this.options.data.length) {
2219 this.initPagination();
2220 this.initBody(true);
2223 BootstrapTable.prototype.updateByUniqueId = function (params) {
2226 if (!params.hasOwnProperty('id') || !params.hasOwnProperty('row')) {
2230 rowId = $.inArray(this.getRowByUniqueId(params.id), this.options.data);
2236 $.extend(this.data[rowId], params.row);
2238 this.initBody(true);
2241 BootstrapTable.prototype.insertRow = function (params) {
2242 if (!params.hasOwnProperty('index') || !params.hasOwnProperty('row')) {
2245 this.data.splice(params.index, 0, params.row);
2247 this.initPagination();
2249 this.initBody(true);
2252 BootstrapTable.prototype.updateRow = function (params) {
2253 if (!params.hasOwnProperty('index') || !params.hasOwnProperty('row')) {
2256 $.extend(this.data[params.index], params.row);
2258 this.initBody(true);
2261 BootstrapTable.prototype.showRow = function (params) {
2262 if (!params.hasOwnProperty('index') || !params.hasOwnProperty('uniqueId')) {
2265 this.toggleRow(params.index, params.uniqueId, true);
2268 BootstrapTable.prototype.hideRow = function (params) {
2269 if (!params.hasOwnProperty('index') || !params.hasOwnProperty('uniqueId')) {
2272 this.toggleRow(params.index, params.uniqueId, false);
2275 BootstrapTable.prototype.getRowsHidden = function (show) {
2276 var rows = $(this.$body[0]).children().filter(':hidden'),
2279 for (; i < rows.length; i++) {
2286 BootstrapTable.prototype.mergeCells = function (options) {
2287 var row = options.index,
2288 col = $.inArray(options.field, this.getVisibleFields()),
2289 rowspan = options.rowspan || 1,
2290 colspan = options.colspan || 1,
2292 $tr = this.$body.find('>tr'),
2295 if (this.options.detailView && !this.options.cardView) {
2299 $td = $tr.eq(row).find('>td').eq(col);
2301 if (row < 0 || col < 0 || row >= this.data.length) {
2305 for (i = row; i < row + rowspan; i++) {
2306 for (j = col; j < col + colspan; j++) {
2307 $tr.eq(i).find('>td').eq(j).hide();
2311 $td.attr('rowspan', rowspan).attr('colspan', colspan).show();
2314 BootstrapTable.prototype.updateCell = function (params) {
2315 if (!params.hasOwnProperty('index') ||
2316 !params.hasOwnProperty('field') ||
2317 !params.hasOwnProperty('value')) {
2320 this.data[params.index][params.field] = params.value;
2322 this.initBody(true);
2325 BootstrapTable.prototype.getOptions = function () {
2326 return this.options;
2329 BootstrapTable.prototype.getSelections = function () {
2332 return $.grep(this.data, function (row) {
2333 return row[that.header.stateField];
2337 BootstrapTable.prototype.getAllSelections = function () {
2340 return $.grep(this.options.data, function (row) {
2341 return row[that.header.stateField];
2345 BootstrapTable.prototype.checkAll = function () {
2346 this.checkAll_(true);
2349 BootstrapTable.prototype.uncheckAll = function () {
2350 this.checkAll_(false);
2353 BootstrapTable.prototype.checkAll_ = function (checked) {
2356 rows = this.getSelections();
2358 this.$selectAll.add(this.$selectAll_).prop('checked', checked);
2359 this.$selectItem.filter(':enabled').prop('checked', checked);
2362 rows = this.getSelections();
2364 this.trigger(checked ? 'check-all' : 'uncheck-all', rows);
2367 BootstrapTable.prototype.check = function (index) {
2368 this.check_(true, index);
2371 BootstrapTable.prototype.uncheck = function (index) {
2372 this.check_(false, index);
2375 BootstrapTable.prototype.check_ = function (checked, index) {
2376 var $el = this.$selectItem.filter(sprintf('[data-index="%s"]', index)).prop('checked', checked);
2377 this.data[index][this.header.stateField] = checked;
2378 this.updateSelected();
2379 this.trigger(checked ? 'check' : 'uncheck', this.data[index], $el);
2382 BootstrapTable.prototype.checkBy = function (obj) {
2383 this.checkBy_(true, obj);
2386 BootstrapTable.prototype.uncheckBy = function (obj) {
2387 this.checkBy_(false, obj);
2390 BootstrapTable.prototype.checkBy_ = function (checked, obj) {
2391 if (!obj.hasOwnProperty('field') || !obj.hasOwnProperty('values')) {
2397 $.each(this.options.data, function (index, row) {
2398 if (!row.hasOwnProperty(obj.field)) {
2401 if ($.inArray(row[obj.field], obj.values) !== -1) {
2402 var $el = that.$selectItem.filter(':enabled')
2403 .filter(sprintf('[data-index="%s"]', index)).prop('checked', checked);
2404 row[that.header.stateField] = checked;
2406 that.trigger(checked ? 'check' : 'uncheck', row, $el);
2409 this.updateSelected();
2410 this.trigger(checked ? 'check-some' : 'uncheck-some', rows);
2413 BootstrapTable.prototype.destroy = function () {
2414 this.$el.insertBefore(this.$container);
2415 $(this.options.toolbar).insertBefore(this.$el);
2416 this.$container.next().remove();
2417 this.$container.remove();
2418 this.$el.html(this.$el_.html())
2419 .css('margin-top', '0')
2420 .attr('class', this.$el_.attr('class') || ''); // reset the class
2423 BootstrapTable.prototype.showLoading = function () {
2424 this.$tableLoading.show();
2427 BootstrapTable.prototype.hideLoading = function () {
2428 this.$tableLoading.hide();
2431 BootstrapTable.prototype.togglePagination = function () {
2432 this.options.pagination = !this.options.pagination;
2433 var button = this.$toolbar.find('button[name="paginationSwitch"] i');
2434 if (this.options.pagination) {
2435 button.attr("class", this.options.iconsPrefix + " " + this.options.icons.paginationSwitchDown);
2437 button.attr("class", this.options.iconsPrefix + " " + this.options.icons.paginationSwitchUp);
2439 this.updatePagination();
2442 BootstrapTable.prototype.refresh = function (params) {
2443 if (params && params.url) {
2444 this.options.url = params.url;
2445 this.options.pageNumber = 1;
2447 this.initServer(params && params.silent, params && params.query);
2450 BootstrapTable.prototype.resetWidth = function () {
2451 if (this.options.showHeader && this.options.height) {
2454 if (this.options.showFooter) {
2459 BootstrapTable.prototype.showColumn = function (field) {
2460 this.toggleColumn(getFieldIndex(this.columns, field), true, true);
2463 BootstrapTable.prototype.hideColumn = function (field) {
2464 this.toggleColumn(getFieldIndex(this.columns, field), false, true);
2467 BootstrapTable.prototype.getHiddenColumns = function () {
2468 return $.grep(this.columns, function (column) {
2469 return !column.visible;
2473 BootstrapTable.prototype.filterBy = function (columns) {
2474 this.filterColumns = $.isEmptyObject(columns) ? {} : columns;
2475 this.options.pageNumber = 1;
2477 this.updatePagination();
2480 BootstrapTable.prototype.scrollTo = function (value) {
2481 if (typeof value === 'string') {
2482 value = value === 'bottom' ? this.$tableBody[0].scrollHeight : 0;
2484 if (typeof value === 'number') {
2485 this.$tableBody.scrollTop(value);
2487 if (typeof value === 'undefined') {
2488 return this.$tableBody.scrollTop();
2492 BootstrapTable.prototype.getScrollPosition = function () {
2493 return this.scrollTo();
2496 BootstrapTable.prototype.selectPage = function (page) {
2497 if (page > 0 && page <= this.options.totalPages) {
2498 this.options.pageNumber = page;
2499 this.updatePagination();
2503 BootstrapTable.prototype.prevPage = function () {
2504 if (this.options.pageNumber > 1) {
2505 this.options.pageNumber--;
2506 this.updatePagination();
2510 BootstrapTable.prototype.nextPage = function () {
2511 if (this.options.pageNumber < this.options.totalPages) {
2512 this.options.pageNumber++;
2513 this.updatePagination();
2517 BootstrapTable.prototype.toggleView = function () {
2518 this.options.cardView = !this.options.cardView;
2520 // Fixed remove toolbar when click cardView button.
2521 //that.initToolbar();
2523 this.trigger('toggle', this.options.cardView);
2526 BootstrapTable.prototype.refreshOptions = function (options) {
2527 //If the objects are equivalent then avoid the call of destroy / init methods
2528 if (compareObjects(this.options, options, false)) {
2531 this.options = $.extend(this.options, options);
2532 this.trigger('refresh-options', this.options);
2537 BootstrapTable.prototype.resetSearch = function (text) {
2538 var $search = this.$toolbar.find('.search input');
2539 $search.val(text || '');
2540 this.onSearch({currentTarget: $search});
2543 BootstrapTable.prototype.expandRow_ = function (expand, index) {
2544 var $tr = this.$body.find(sprintf('> tr[data-index="%s"]', index));
2545 if ($tr.next().is('tr.detail-view') === (expand ? false : true)) {
2546 $tr.find('> td > .detail-icon').click();
2550 BootstrapTable.prototype.expandRow = function (index) {
2551 this.expandRow_(true, index);
2554 BootstrapTable.prototype.collapseRow = function (index) {
2555 this.expandRow_(false, index);
2558 BootstrapTable.prototype.expandAllRows = function (isSubTable) {
2560 var $tr = this.$body.find(sprintf('> tr[data-index="%s"]', 0)),
2563 executeInterval = false,
2566 if (!$tr.next().is('tr.detail-view')) {
2567 $tr.find('> td > .detail-icon').click();
2568 executeInterval = true;
2569 } else if (!$tr.next().next().is('tr.detail-view')) {
2570 $tr.next().find(".detail-icon").click();
2571 executeInterval = true;
2574 if (executeInterval) {
2576 idInterval = setInterval(function () {
2577 detailIcon = that.$body.find("tr.detail-view").last().find(".detail-icon");
2578 if (detailIcon.length > 0) {
2581 clearInterval(idInterval);
2585 clearInterval(idInterval);
2589 var trs = this.$body.children();
2590 for (var i = 0; i < trs.length; i++) {
2591 this.expandRow_(true, $(trs[i]).data("index"));
2596 BootstrapTable.prototype.collapseAllRows = function (isSubTable) {
2598 this.expandRow_(false, 0);
2600 var trs = this.$body.children();
2601 for (var i = 0; i < trs.length; i++) {
2602 this.expandRow_(false, $(trs[i]).data("index"));
2607 // BOOTSTRAP TABLE PLUGIN DEFINITION
2608 // =======================
2610 var allowedMethods = [
2612 'getSelections', 'getAllSelections', 'getData',
2613 'load', 'append', 'prepend', 'remove', 'removeAll',
2614 'insertRow', 'updateRow', 'updateCell', 'updateByUniqueId', 'removeByUniqueId',
2615 'getRowByUniqueId', 'showRow', 'hideRow', 'getRowsHidden',
2617 'checkAll', 'uncheckAll',
2619 'checkBy', 'uncheckBy',
2624 'showLoading', 'hideLoading',
2625 'showColumn', 'hideColumn', 'getHiddenColumns',
2628 'getScrollPosition',
2629 'selectPage', 'prevPage', 'nextPage',
2634 'expandRow', 'collapseRow', 'expandAllRows', 'collapseAllRows'
2637 $.fn.bootstrapTable = function (option) {
2639 args = Array.prototype.slice.call(arguments, 1);
2641 this.each(function () {
2642 var $this = $(this),
2643 data = $this.data('bootstrap.table'),
2644 options = $.extend({}, BootstrapTable.DEFAULTS, $this.data(),
2645 typeof option === 'object' && option);
2647 if (typeof option === 'string') {
2648 if ($.inArray(option, allowedMethods) < 0) {
2649 throw new Error("Unknown method: " + option);
2656 value = data[option].apply(data, args);
2658 if (option === 'destroy') {
2659 $this.removeData('bootstrap.table');
2664 $this.data('bootstrap.table', (data = new BootstrapTable(this, options)));
2668 return typeof value === 'undefined' ? this : value;
2671 $.fn.bootstrapTable.Constructor = BootstrapTable;
2672 $.fn.bootstrapTable.defaults = BootstrapTable.DEFAULTS;
2673 $.fn.bootstrapTable.columnDefaults = BootstrapTable.COLUMN_DEFAULTS;
2674 $.fn.bootstrapTable.locales = BootstrapTable.LOCALES;
2675 $.fn.bootstrapTable.methods = allowedMethods;
2676 $.fn.bootstrapTable.utils = {
2678 getFieldIndex: getFieldIndex,
2679 compareObjects: compareObjects,
2680 calculateObjectValue: calculateObjectValue
2683 // BOOTSTRAP TABLE INIT
2684 // =======================
2687 $('[data-toggle="table"]').bootstrapTable();