Initial commit for OpenECOMP SDN-C OA&M
[sdnc/oam.git] / admportal / public / javascripts / bootstrap-table.js
1 /**
2  * @author zhixin wen <wenzhixin2010@gmail.com>
3  * version: 1.9.1
4  * https://github.com/wenzhixin/bootstrap-table/
5  */
6
7 !function ($) {
8     'use strict';
9
10     // TOOLS DEFINITION
11     // ======================
12
13     var cachedWidth = null;
14
15     // it only does '%s', and return '' when arguments are undefined
16     var sprintf = function (str) {
17         var args = arguments,
18             flag = true,
19             i = 1;
20
21         str = str.replace(/%s/g, function () {
22             var arg = args[i++];
23
24             if (typeof arg === 'undefined') {
25                 flag = false;
26                 return '';
27             }
28             return arg;
29         });
30         return flag ? str : '';
31     };
32
33     var getPropertyFromOther = function (list, from, to, value) {
34         var result = '';
35         $.each(list, function (i, item) {
36             if (item[from] === value) {
37                 result = item[to];
38                 return false;
39             }
40             return true;
41         });
42         return result;
43     };
44
45     var getFieldIndex = function (columns, field) {
46         var index = -1;
47
48         $.each(columns, function (i, column) {
49             if (column.field === field) {
50                 index = i;
51                 return false;
52             }
53             return true;
54         });
55         return index;
56     };
57
58     // http://jsfiddle.net/wenyi/47nz7ez9/3/
59     var setFieldIndex = function (columns) {
60         var i, j, k,
61             totalCol = 0,
62             flag = [];
63
64         for (i = 0; i < columns[0].length; i++) {
65             totalCol += columns[0][i].colspan || 1;
66         }
67
68         for (i = 0; i < columns.length; i++) {
69             flag[i] = [];
70             for (j = 0; j < totalCol; j++) {
71                 flag[i][j] = false;
72             }
73         }
74
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]);
81
82                 if (colspan === 1) {
83                     r.fieldIndex = index;
84                     // when field is undefined, use index instead
85                     if (typeof r.field === 'undefined') {
86                         r.field = index;
87                     }
88                 }
89
90                 for (k = 0; k < rowspan; k++) {
91                     flag[i + k][index] = true;
92                 }
93                 for (k = 0; k < colspan; k++) {
94                     flag[i][index + k] = true;
95                 }
96             }
97         }
98     };
99
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'),
104                 w1, w2;
105
106             outer.append(inner);
107             $('body').append(outer);
108
109             w1 = inner[0].offsetWidth;
110             outer.css('overflow', 'scroll');
111             w2 = inner[0].offsetWidth;
112
113             if (w1 === w2) {
114                 w2 = outer[0].clientWidth;
115             }
116
117             outer.remove();
118             cachedWidth = w1 - w2;
119         }
120         return cachedWidth;
121     };
122
123     var calculateObjectValue = function (self, name, args, defaultValue) {
124         var func = name;
125
126         if (typeof name === 'string') {
127             // support obj.func1.func2
128             var names = name.split('.');
129
130             if (names.length > 1) {
131                 func = window;
132                 $.each(names, function (i, f) {
133                     func = func[f];
134                 });
135             } else {
136                 func = window[name];
137             }
138         }
139         if (typeof func === 'object') {
140             return func;
141         }
142         if (typeof func === 'function') {
143             return func.apply(self, args);
144         }
145         if (!func && typeof name === 'string' && sprintf.apply(this, [name].concat(args))) {
146             return sprintf.apply(this, [name].concat(args));
147         }
148         return defaultValue;
149     };
150
151     var compareObjects = function (objectA, objectB, compareLength) {
152         // Create arrays of property names
153         var objectAProperties = Object.getOwnPropertyNames(objectA),
154             objectBProperties = Object.getOwnPropertyNames(objectB),
155             propName = '';
156
157         if (compareLength) {
158             // If number of properties is different, objects are not equivalent
159             if (objectAProperties.length !== objectBProperties.length) {
160                 return false;
161             }
162         }
163
164         for (var i = 0; i < objectAProperties.length; i++) {
165             propName = objectAProperties[i];
166
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]) {
171                     return false;
172                 }
173             }
174         }
175
176         // If we made it this far, objects are considered equivalent
177         return true;
178     };
179
180     var escapeHTML = function (text) {
181         if (typeof text === 'string') {
182             return text
183                 .replace(/&/g, "&amp;")
184                 .replace(/</g, "&lt;")
185                 .replace(/>/g, "&gt;")
186                 .replace(/"/g, "&quot;")
187                 .replace(/'/g, "&#039;");
188         }
189         return text;
190     };
191
192     var getRealHeight = function ($el) {
193         var height = 0;
194         $el.children().each(function () {
195             if (height < $(this).outerHeight(true)) {
196                 height = $(this).outerHeight(true);
197             }
198         });
199         return height;
200     };
201
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];
208             }
209         }
210
211         return dataAttr;
212     };
213
214     var getItemField = function (item, field) {
215         var value = item;
216
217         if (typeof field !== 'string' || item.hasOwnProperty(field)) {
218             return item[field];
219         }
220         var props = field.split('.');
221         for (var p in props) {
222             value = value[props[p]];
223         }
224         return value;
225     };
226
227     // BOOTSTRAP TABLE CLASS DEFINITION
228     // ======================
229
230     var BootstrapTable = function (el, options) {
231         this.options = options;
232         this.$el = $(el);
233         this.$el_ = this.$el.clone();
234         this.timeoutId_ = 0;
235         this.timeoutFooter_ = 0;
236
237         this.init();
238     };
239
240     BootstrapTable.DEFAULTS = {
241         classes: 'table table-hover',
242         locale: undefined,
243         height: undefined,
244         undefinedText: '-',
245         sortName: undefined,
246         sortOrder: 'asc',
247         striped: false,
248         columns: [[]],
249         data: [],
250         dataField: 'rows',
251         method: 'get',
252         url: undefined,
253         ajax: undefined,
254         cache: true,
255         contentType: 'application/json',
256         dataType: 'json',
257         ajaxOptions: {},
258         queryParams: function (params) {
259             return params;
260         },
261         queryParamsType: 'limit', // undefined
262         responseHandler: function (res) {
263             return res;
264         },
265         pagination: false,
266         onlyInfoPagination: false,
267         sidePagination: 'client', // client or server
268         totalRows: 0, // server side need to set
269         pageNumber: 1,
270         pageSize: 10,
271         pageList: [10, 25, 50, 100],
272         paginationHAlign: 'right', //right, left
273         paginationVAlign: 'bottom', //bottom, top, both
274         paginationDetailHAlign: 'left', //right, left
275         paginationFirstText: '&laquo;',
276         paginationPreText: '&lsaquo;',
277         paginationNextText: '&rsaquo;',
278         paginationLastText: '&raquo;',
279         search: false,
280         strictSearch: false,
281         searchAlign: 'right',
282         selectItemName: 'btSelectItem',
283         showHeader: true,
284         showFooter: false,
285         showColumns: false,
286         showPaginationSwitch: false,
287         showRefresh: false,
288         showToggle: false,
289         buttonsAlign: 'right',
290         smartDisplay: true,
291         minimumCountColumns: 1,
292         idField: undefined,
293         uniqueId: undefined,
294         cardView: false,
295         detailView: false,
296         detailFormatter: function (index, row) {
297             return '';
298         },
299         trimOnSearch: true,
300         clickToSelect: false,
301         singleSelect: false,
302         toolbar: undefined,
303         toolbarAlign: 'left',
304         checkboxHeader: true,
305         sortable: true,
306         silentSort: true,
307         maintainSelected: false,
308         searchTimeOut: 500,
309         searchText: '',
310         iconSize: undefined,
311         iconsPrefix: 'glyphicon', // glyphicon of fa (font awesome)
312         icons: {
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'
320         },
321
322         rowStyle: function (row, index) {
323             return {};
324         },
325
326         rowAttributes: function (row, index) {
327             return {};
328         },
329
330         onAll: function (name, args) {
331             return false;
332         },
333         onClickCell: function (field, value, row, $element) {
334             return false;
335         },
336         onDblClickCell: function (field, value, row, $element) {
337             return false;
338         },
339         onClickRow: function (item, $element) {
340             return false;
341         },
342         onDblClickRow: function (item, $element) {
343             return false;
344         },
345         onSort: function (name, order) {
346             return false;
347         },
348         onCheck: function (row) {
349             return false;
350         },
351         onUncheck: function (row) {
352             return false;
353         },
354         onCheckAll: function (rows) {
355             return false;
356         },
357         onUncheckAll: function (rows) {
358             return false;
359         },
360         onCheckSome: function (rows) {
361             return false;
362         },
363         onUncheckSome: function (rows) {
364             return false;
365         },
366         onLoadSuccess: function (data) {
367             return false;
368         },
369         onLoadError: function (status) {
370             return false;
371         },
372         onColumnSwitch: function (field, checked) {
373             return false;
374         },
375         onPageChange: function (number, size) {
376             return false;
377         },
378         onSearch: function (text) {
379             return false;
380         },
381         onToggle: function (cardView) {
382             return false;
383         },
384         onPreBody: function (data) {
385             return false;
386         },
387         onPostBody: function () {
388             return false;
389         },
390         onPostHeader: function () {
391             return false;
392         },
393         onExpandRow: function (index, row, $detail) {
394             return false;
395         },
396         onCollapseRow: function (index, row) {
397             return false;
398         },
399         onRefreshOptions: function (options) {
400             return false;
401         },
402         onResetView: function () {
403             return false;
404         }
405     };
406
407     BootstrapTable.LOCALES = [];
408
409     BootstrapTable.LOCALES['en-US'] = BootstrapTable.LOCALES['en'] = {
410         formatLoadingMessage: function () {
411             return 'Loading, please wait...';
412         },
413         formatRecordsPerPage: function (pageNumber) {
414             return sprintf('%s records per page', pageNumber);
415         },
416         formatShowingRows: function (pageFrom, pageTo, totalRows) {
417             return sprintf('Showing %s to %s of %s rows', pageFrom, pageTo, totalRows);
418         },
419         formatDetailPagination: function (totalRows) {
420             return sprintf('Showing %s rows', totalRows);
421         },
422         formatSearch: function () {
423             return 'Search';
424         },
425         formatNoMatches: function () {
426             return 'No matching records found';
427         },
428         formatPaginationSwitch: function () {
429             return 'Hide/Show pagination';
430         },
431         formatRefresh: function () {
432             return 'Refresh';
433         },
434         formatToggle: function () {
435             return 'Toggle';
436         },
437         formatColumns: function () {
438             return 'Columns';
439         },
440         formatAllRows: function () {
441             return 'All';
442         }
443     };
444
445     $.extend(BootstrapTable.DEFAULTS, BootstrapTable.LOCALES['en-US']);
446
447     BootstrapTable.COLUMN_DEFAULTS = {
448         radio: false,
449         checkbox: false,
450         checkboxEnabled: true,
451         field: undefined,
452         title: undefined,
453         titleTooltip: undefined,
454         'class': 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
459         width: undefined,
460         sortable: false,
461         order: 'asc', // asc, desc
462         visible: true,
463         switchable: true,
464         clickToSelect: true,
465         formatter: undefined,
466         footerFormatter: undefined,
467         events: undefined,
468         sorter: undefined,
469         sortName: undefined,
470         cellStyle: undefined,
471         searchable: true,
472         searchFormatter: true,
473         cardVisible: true
474     };
475
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'
502     };
503
504     BootstrapTable.prototype.init = function () {
505         this.initLocale();
506         this.initContainer();
507         this.initTable();
508         this.initHeader();
509         this.initData();
510         this.initFooter();
511         this.initToolbar();
512         this.initPagination();
513         this.initBody();
514         this.initSearchText();
515         this.initServer();
516     };
517
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]]);
532             }
533         }
534     };
535
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>' :
542                 '',
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(),
548             '</div>',
549             '</div>',
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>' :
553                 '',
554             '</div>',
555             '</div>'
556         ].join(''));
557
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');
566
567         this.$tableBody.append(this.$el);
568         this.$container.after('<div class="clearfix"></div>');
569
570         this.$el.addClass(this.options.classes);
571         if (this.options.striped) {
572             this.$el.addClass('table-striped');
573         }
574         if ($.inArray('table-no-bordered', this.options.classes.split(' ')) !== -1) {
575             this.$tableContainer.addClass('table-no-bordered');
576         }
577     };
578
579     BootstrapTable.prototype.initTable = function () {
580         var that = this,
581             columns = [],
582             data = [];
583
584         this.$header = this.$el.find('>thead');
585         if (!this.$header.length) {
586             this.$header = $('<thead></thead>').appendTo(this.$el);
587         }
588         this.$header.find('tr').each(function () {
589             var column = [];
590
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
598                 }, $(this).data()));
599             });
600             columns.push(column);
601         });
602         if (!$.isArray(this.options.columns[0])) {
603             this.options.columns = [this.options.columns];
604         }
605         this.options.columns = $.extend(true, [], columns, this.options.columns);
606         this.columns = [];
607
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);
612
613                 if (typeof column.fieldIndex !== 'undefined') {
614                     that.columns[column.fieldIndex] = column;
615                 }
616
617                 that.options.columns[i][j] = column;
618             });
619         });
620
621         // if options.data is setting, do not process tbody data
622         if (this.options.data.length) {
623             return;
624         }
625
626         this.$el.find('>tbody>tr').each(function () {
627             var row = {};
628
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());
633
634             $(this).find('td').each(function (i) {
635                 var field = that.columns[i].field;
636
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());
644             });
645             data.push(row);
646         });
647         this.options.data = data;
648     };
649
650     BootstrapTable.prototype.initHeader = function () {
651         var that = this,
652             visibleColumns = {},
653             html = [];
654
655         this.header = {
656             fields: [],
657             styles: [],
658             classes: [],
659             formatters: [],
660             events: [],
661             sorters: [],
662             sortNames: [],
663             cellStyles: [],
664             searchables: []
665         };
666
667         $.each(this.options.columns, function (i, columns) {
668             html.push('<tr>');
669
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));
673             }
674
675             $.each(columns, function (j, column) {
676                 var text = '',
677                     halign = '', // header align style
678                     align = '', // body align style
679                     style = '',
680                     class_ = sprintf(' class="%s"', column['class']),
681                     order = that.options.sortOrder || column.order,
682                     unitWidth = 'px',
683                     width = column.width;
684
685                 if (column.width !== undefined && (!that.options.cardView)) {
686                     if (typeof column.width === 'string') {
687                         if (column.width.indexOf('%') !== -1) {
688                             unitWidth = '%';
689                         }
690                     }
691                 }
692                 if (column.width && typeof column.width === 'string') {
693                     width = column.width.replace('%', '').replace('px', '');
694                 }
695
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));
701
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;
712
713                     if (!column.visible) {
714                         return;
715                     }
716
717                     if (that.options.cardView && (!column.cardVisible)) {
718                         return;
719                     }
720
721                     visibleColumns[column.field] = column;
722                 }
723
724                 html.push('<th' + sprintf(' title="%s"', column.titleTooltip),
725                     column.checkbox || column.radio ?
726                         sprintf(' class="bs-checkbox %s"', column['class'] || '') :
727                         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),
732                     "tabindex='0'",
733                     '>');
734
735                 html.push(sprintf('<div class="th-inner %s">', that.options.sortable && column.sortable ?
736                     'sortable both' : ''));
737
738                 text = column.title;
739
740                 if (column.checkbox) {
741                     if (!that.options.singleSelect && that.options.checkboxHeader) {
742                         text = '<input name="btSelectAll" type="checkbox" />';
743                     }
744                     that.header.stateField = column.field;
745                 }
746                 if (column.radio) {
747                     text = '';
748                     that.header.stateField = column.field;
749                     that.options.singleSelect = true;
750                 }
751
752                 html.push(text);
753                 html.push('</div>');
754                 html.push('<div class="fht-cell"></div>');
755                 html.push('</div>');
756                 html.push('</th>');
757             });
758             html.push('</tr>');
759         });
760
761         this.$header.html(html.join(''));
762         this.$header.find('th[data-field]').each(function (i) {
763             $(this).data(visibleColumns[$(this).data('field')]);
764         });
765         this.$container.off('click', '.th-inner').on('click', '.th-inner', function (event) {
766             if (that.options.sortable && $(this).parent().data().sortable) {
767                 that.onSort(event);
768             }
769         });
770
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
775                     that.onSort(event);
776                 }
777             }
778         });
779
780         if (!this.options.showHeader || this.options.cardView) {
781             this.$header.hide();
782             this.$tableHeader.hide();
783             this.$tableLoading.css('top', 0);
784         } else {
785             this.$header.show();
786             this.$tableHeader.show();
787             this.$tableLoading.css('top', this.$header.outerHeight() + 1);
788             // Assign the correct sortable arrow
789             this.getCaret();
790         }
791
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();
798             });
799     };
800
801     BootstrapTable.prototype.initFooter = function () {
802         if (!this.options.showFooter || this.options.cardView) {
803             this.$tableFooter.hide();
804         } else {
805             this.$tableFooter.show();
806         }
807     };
808
809     /**
810      * @param data
811      * @param type: append / prepend
812      */
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);
818         } else {
819             this.data = data || this.options.data;
820         }
821
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);
827         } else {
828             this.options.data = this.data;
829         }
830
831         if (this.options.sidePagination === 'server') {
832             return;
833         }
834         this.initSort();
835     };
836
837     BootstrapTable.prototype.initSort = function () {
838         var that = this,
839             name = this.options.sortName,
840             order = this.options.sortOrder === 'desc' ? -1 : 1,
841             index = $.inArray(this.options.sortName, this.header.fields);
842
843         if (index !== -1) {
844             this.data.sort(function (a, b) {
845                 if (that.header.sortNames[index]) {
846                     name = that.header.sortNames[index];
847                 }
848                 var aa = getItemField(a, name),
849                     bb = getItemField(b, name),
850                     value = calculateObjectValue(that.header, that.header.sorters[index], [aa, bb]);
851
852                 if (value !== undefined) {
853                     return order * value;
854                 }
855
856                 // Fix #161: undefined or null string sort bug.
857                 if (aa === undefined || aa === null) {
858                     aa = '';
859                 }
860                 if (bb === undefined || bb === null) {
861                     bb = '';
862                 }
863
864                 // IF both values are numeric, do a numeric comparison
865                 if ($.isNumeric(aa) && $.isNumeric(bb)) {
866                     // Convert numerical values form string to float.
867                     aa = parseFloat(aa);
868                     bb = parseFloat(bb);
869                     if (aa < bb) {
870                         return order * -1;
871                     }
872                     return order;
873                 }
874
875                 if (aa === bb) {
876                     return 0;
877                 }
878
879                 // If value is not a string, convert to string
880                 if (typeof aa !== 'string') {
881                     aa = aa.toString();
882                 }
883
884                 if (aa.localeCompare(bb) === -1) {
885                     return order * -1;
886                 }
887
888                 return order;
889             });
890         }
891     };
892
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());
896
897         this.$header.add(this.$header_).find('span.order').remove();
898
899         if (this.options.sortName === $this.data('field')) {
900             this.options.sortOrder = this.options.sortOrder === 'asc' ? 'desc' : 'asc';
901         } else {
902             this.options.sortName = $this.data('field');
903             this.options.sortOrder = $this.data('order') === 'asc' ? 'desc' : 'asc';
904         }
905         this.trigger('sort', this.options.sortName, this.options.sortOrder);
906
907         $this.add($this_).data('order', this.options.sortOrder);
908
909         // Assign the correct sortable arrow
910         this.getCaret();
911
912         if (this.options.sidePagination === 'server') {
913             this.initServer(this.options.silentSort);
914             return;
915         }
916
917         this.initSort();
918         this.initBody();
919     };
920
921     BootstrapTable.prototype.initToolbar = function () {
922         var that = this,
923             html = [],
924             timeoutId = 0,
925             $keepOpen,
926             $search,
927             switchableCount = 0;
928
929         this.$toolbar.html('');
930
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));
935         }
936
937         // showColumns, showToggle, showRefresh
938         html = [sprintf('<div class="columns columns-%s btn-group pull-%s">',
939             this.options.buttonsAlign, this.options.buttonsAlign)];
940
941         if (typeof this.options.icons === 'string') {
942             this.options.icons = calculateObjectValue(null, this.options.icons);
943         }
944
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),
949                 '</button>');
950         }
951
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),
958                 '</button>');
959         }
960
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),
967                 '</button>');
968         }
969
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>',
978                 '</button>',
979                 '<ul class="dropdown-menu" role="menu">');
980
981             $.each(this.columns, function (i, column) {
982                 if (column.radio || column.checkbox) {
983                     return;
984                 }
985
986                 if (that.options.cardView && (!column.cardVisible)) {
987                     return;
988                 }
989
990                 var checked = column.visible ? ' checked="checked"' : '';
991
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));
996                     switchableCount++;
997                 }
998             });
999             html.push('</ul>',
1000                 '</div>');
1001         }
1002
1003         html.push('</div>');
1004
1005         // Fix #188: this.showToolbar is for extentions
1006         if (this.showToolbar || html.length > 2) {
1007             this.$toolbar.append(html.join(''));
1008         }
1009
1010         if (this.options.showPaginationSwitch) {
1011             this.$toolbar.find('button[name="paginationSwitch"]')
1012                 .off('click').on('click', $.proxy(this.togglePagination, this));
1013         }
1014
1015         if (this.options.showRefresh) {
1016             this.$toolbar.find('button[name="refresh"]')
1017                 .off('click').on('click', $.proxy(this.refresh, this));
1018         }
1019
1020         if (this.options.showToggle) {
1021             this.$toolbar.find('button[name="toggle"]')
1022                 .off('click').on('click', function () {
1023                     that.toggleView();
1024                 });
1025         }
1026
1027         if (this.options.showColumns) {
1028             $keepOpen = this.$toolbar.find('.keep-open');
1029
1030             if (switchableCount <= this.options.minimumCountColumns) {
1031                 $keepOpen.find('input').prop('disabled', true);
1032             }
1033
1034             $keepOpen.find('li').off('click').on('click', function (event) {
1035                 event.stopImmediatePropagation();
1036             });
1037             $keepOpen.find('input').off('click').on('click', function () {
1038                 var $this = $(this);
1039
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'));
1043             });
1044         }
1045
1046         if (this.options.search) {
1047             html = [];
1048             html.push(
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()),
1054                 '</div>');
1055
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);
1063             });
1064         }
1065     };
1066
1067     BootstrapTable.prototype.onSearch = function (event) {
1068         var text = $.trim($(event.currentTarget).val());
1069
1070         // trim search input
1071         if (this.options.trimOnSearch && $(event.currentTarget).val() !== text) {
1072             $(event.currentTarget).val(text);
1073         }
1074
1075         if (text === this.searchText) {
1076             return;
1077         }
1078         this.searchText = text;
1079
1080         this.options.pageNumber = 1;
1081         this.initSearch();
1082         this.updatePagination();
1083         this.trigger('search', text);
1084     };
1085
1086     BootstrapTable.prototype.initSearch = function () {
1087         var that = this;
1088
1089         if (this.options.sidePagination !== 'server') {
1090             var s = this.searchText && this.searchText.toLowerCase();
1091             var f = $.isEmptyObject(this.filterColumns) ? null : this.filterColumns;
1092
1093             // Check filter
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) {
1098                             return false;
1099                         }
1100                     } else if (item[key] !== f[key]) {
1101                         return false;
1102                     }
1103                 }
1104                 return true;
1105             }) : this.options.data;
1106
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);
1113
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);
1118                     }
1119
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) {
1124                                 return true;
1125                             }
1126                         } else {
1127                             if ((value + '').toLowerCase().indexOf(s) !== -1) {
1128                                 return true;
1129                             }
1130                         }
1131                     }
1132                 }
1133                 return false;
1134             }) : this.data;
1135         }
1136     };
1137
1138     BootstrapTable.prototype.initPagination = function () {
1139         if (!this.options.pagination) {
1140             this.$pagination.hide();
1141             return;
1142         } else {
1143             this.$pagination.show();
1144         }
1145
1146         var that = this,
1147             html = [],
1148             $allSelected = false,
1149             i, from, to,
1150             $pageList,
1151             $first, $pre,
1152             $next, $last,
1153             $number,
1154             data = this.getData();
1155
1156         if (this.options.sidePagination !== 'server') {
1157             this.options.totalRows = data.length;
1158         }
1159
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;
1173                 }
1174             }
1175
1176             this.totalPages = ~~((this.options.totalRows - 1) / this.options.pageSize) + 1;
1177
1178             this.options.totalPages = this.totalPages;
1179         }
1180         if (this.totalPages > 0 && this.options.pageNumber > this.totalPages) {
1181             this.options.pageNumber = this.totalPages;
1182         }
1183
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;
1188         }
1189
1190         html.push(
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),
1195             '</span>');
1196
1197         if (!this.options.onlyInfoPagination) {
1198             html.push('<span class="page-list">');
1199
1200             var pageNumber = [
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,
1209                     '</span>',
1210                     ' <span class="caret"></span>',
1211                     '</button>',
1212                     '<ul class="dropdown-menu" role="menu">'
1213                 ],
1214                 pageList = this.options.pageList;
1215
1216             if (typeof this.options.pageList === 'string') {
1217                 var list = this.options.pageList.replace('[', '').replace(']', '')
1218                     .replace(/ /g, '').split(',');
1219
1220                 pageList = [];
1221                 $.each(list, function (i, value) {
1222                     pageList.push(value.toUpperCase() === that.options.formatAllRows().toUpperCase() ?
1223                         that.options.formatAllRows() : +value);
1224                 });
1225             }
1226
1227             $.each(pageList, function (i, page) {
1228                 if (!that.options.smartDisplay || i === 0 || pageList[i - 1] <= that.options.totalRows) {
1229                     var active;
1230                     if ($allSelected) {
1231                         active = page === that.options.formatAllRows() ? ' class="active"' : '';
1232                     } else {
1233                         active = page === that.options.pageSize ? ' class="active"' : '';
1234                     }
1235                     pageNumber.push(sprintf('<li%s><a href="javascript:void(0)">%s</a></li>', active, page));
1236                 }
1237             });
1238             pageNumber.push('</ul></span>');
1239
1240             html.push(this.options.formatRecordsPerPage(pageNumber.join('')));
1241             html.push('</span>');
1242
1243             html.push('</div>',
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>');
1248
1249             if (this.totalPages < 5) {
1250                 from = 1;
1251                 to = this.totalPages;
1252             } else {
1253                 from = this.options.pageNumber - 2;
1254                 to = from + 4;
1255                 if (from < 1) {
1256                     from = 1;
1257                     to = 5;
1258                 }
1259                 if (to > this.totalPages) {
1260                     to = this.totalPages;
1261                     from = to - 4;
1262                 }
1263             }
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>',
1267                     '</li>');
1268             }
1269
1270             html.push(
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>',
1273                 '</ul>',
1274                 '</div>');
1275
1276         }
1277         this.$pagination.html(html.join(''));
1278
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');
1286
1287             if (this.options.pageNumber <= 1) {
1288                 $first.addClass('disabled');
1289                 $pre.addClass('disabled');
1290             }
1291             if (this.options.pageNumber >= this.totalPages) {
1292                 $next.addClass('disabled');
1293                 $last.addClass('disabled');
1294             }
1295             if (this.options.smartDisplay) {
1296                 if (this.totalPages <= 1) {
1297                     this.$pagination.find('div.pagination').hide();
1298                 }
1299                 if (pageList.length < 2 || this.options.totalRows <= pageList[0]) {
1300                     this.$pagination.find('span.page-list').hide();
1301                 }
1302
1303                 // when data is empty, hide the pagination
1304                 this.$pagination[this.getData().length ? 'show' : 'hide']();
1305             }
1306             if ($allSelected) {
1307                 this.options.pageSize = this.options.formatAllRows();
1308             }
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));
1315         }
1316     };
1317
1318     BootstrapTable.prototype.updatePagination = function (event) {
1319         // Fix #171: IE disabled button can be clicked bug.
1320         if (event && $(event.currentTarget).hasClass('disabled')) {
1321             return;
1322         }
1323
1324         if (!this.options.maintainSelected) {
1325             this.resetRows();
1326         }
1327
1328         this.initPagination();
1329         if (this.options.sidePagination === 'server') {
1330             this.initServer();
1331         } else {
1332             this.initBody();
1333         }
1334
1335         this.trigger('page-change', this.options.pageNumber, this.options.pageSize);
1336     };
1337
1338     BootstrapTable.prototype.onPageListChange = function (event) {
1339         var $this = $(event.currentTarget);
1340
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);
1345
1346         this.updatePagination(event);
1347     };
1348
1349     BootstrapTable.prototype.onPageFirst = function (event) {
1350         this.options.pageNumber = 1;
1351         this.updatePagination(event);
1352     };
1353
1354     BootstrapTable.prototype.onPagePre = function (event) {
1355         this.options.pageNumber--;
1356         this.updatePagination(event);
1357     };
1358
1359     BootstrapTable.prototype.onPageNext = function (event) {
1360         this.options.pageNumber++;
1361         this.updatePagination(event);
1362     };
1363
1364     BootstrapTable.prototype.onPageLast = function (event) {
1365         this.options.pageNumber = this.totalPages;
1366         this.updatePagination(event);
1367     };
1368
1369     BootstrapTable.prototype.onPageNumber = function (event) {
1370         if (this.options.pageNumber === +$(event.currentTarget).text()) {
1371             return;
1372         }
1373         this.options.pageNumber = +$(event.currentTarget).text();
1374         this.updatePagination(event);
1375     };
1376
1377     BootstrapTable.prototype.initBody = function (fixedScroll) {
1378         var that = this,
1379             html = [],
1380             data = this.getData();
1381
1382         this.trigger('pre-body', data);
1383
1384         this.$body = this.$el.find('>tbody');
1385         if (!this.$body.length) {
1386             this.$body = $('<tbody></tbody>').appendTo(this.$el);
1387         }
1388
1389         //Fix #389 Bootstrap-table-flatJSON is not working
1390
1391         if (!this.options.pagination || this.options.sidePagination === 'server') {
1392             this.pageFrom = 1;
1393             this.pageTo = data.length;
1394         }
1395
1396         for (var i = this.pageFrom - 1; i < this.pageTo; i++) {
1397             var key,
1398                 item = data[i],
1399                 style = {},
1400                 csses = [],
1401                 data_ = '',
1402                 attributes = {},
1403                 htmlAttributes = [];
1404
1405             style = calculateObjectValue(this.options, this.options.rowStyle, [item, i], style);
1406
1407             if (style && style.css) {
1408                 for (key in style.css) {
1409                     csses.push(key + ': ' + style.css[key]);
1410                 }
1411             }
1412
1413             attributes = calculateObjectValue(this.options,
1414                 this.options.rowAttributes, [item, i], attributes);
1415
1416             if (attributes) {
1417                 for (key in attributes) {
1418                     htmlAttributes.push(sprintf('%s="%s"', key, escapeHTML(attributes[key])));
1419                 }
1420             }
1421
1422             if (item._data && !$.isEmptyObject(item._data)) {
1423                 $.each(item._data, function (k, v) {
1424                     // ignore data-index
1425                     if (k === 'index') {
1426                         return;
1427                     }
1428                     data_ += sprintf(' data-%s="%s"', k, v);
1429                 });
1430             }
1431
1432             html.push('<tr',
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_),
1439                 '>'
1440             );
1441
1442             if (this.options.cardView) {
1443                 html.push(sprintf('<td colspan="%s">', this.header.fields.length));
1444             }
1445
1446             if (!this.options.cardView && this.options.detailView) {
1447                 html.push('<td>',
1448                     '<a class="detail-icon" href="javascript:">',
1449                     sprintf('<i class="%s %s"></i>', this.options.iconsPrefix, this.options.icons.detailOpen),
1450                     '</a>',
1451                     '</td>');
1452             }
1453
1454             $.each(this.header.fields, function (j, field) {
1455                 var text = '',
1456                     value = getItemField(item, field),
1457                     type = '',
1458                     cellStyle = {},
1459                     id_ = '',
1460                     class_ = that.header.classes[j],
1461                     data_ = '',
1462                     rowspan_ = '',
1463                     title_ = '',
1464                     column = that.columns[getFieldIndex(that.columns, field)];
1465
1466                 if (!column.visible) {
1467                     return;
1468                 }
1469
1470                 style = sprintf('style="%s"', csses.concat(that.header.styles[j]).join('; '));
1471
1472                 value = calculateObjectValue(column,
1473                     that.header.formatters[j], [value, item, i], value);
1474
1475                 // handle td's id and class
1476                 if (item['_' + field + '_id']) {
1477                     id_ = sprintf(' id="%s"', item['_' + field + '_id']);
1478                 }
1479                 if (item['_' + field + '_class']) {
1480                     class_ = sprintf(' class="%s"', item['_' + field + '_class']);
1481                 }
1482                 if (item['_' + field + '_rowspan']) {
1483                     rowspan_ = sprintf(' rowspan="%s"', item['_' + field + '_rowspan']);
1484                 }
1485                 if (item['_' + field + '_title']) {
1486                     title_ = sprintf(' title="%s"', item['_' + field + '_title']);
1487                 }
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);
1492                 }
1493                 if (cellStyle.css) {
1494                     var csses_ = [];
1495                     for (var key in cellStyle.css) {
1496                         csses_.push(key + ': ' + cellStyle.css[key]);
1497                     }
1498                     style = sprintf('style="%s"', csses_.concat(that.header.styles[j]).join('; '));
1499                 }
1500
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') {
1505                             return;
1506                         }
1507                         data_ += sprintf(' data-%s="%s"', k, v);
1508                     });
1509                 }
1510
1511                 if (column.checkbox || column.radio) {
1512                     type = column.checkbox ? 'checkbox' : type;
1513                     type = column.radio ? 'radio' : type;
1514
1515                     text = [that.options.cardView ?
1516                         '<div class="card-view">' : '<td class="bs-checkbox">',
1517                         '<input' +
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) +
1526                         ' />',
1527                         that.header.formatters[j] && typeof value === 'string' ? value : '',
1528                         that.options.cardView ? '</div>' : '</td>'
1529                     ].join('');
1530
1531                     item[that.header.stateField] = value === true || (value && value.checked);
1532                 } else {
1533                     value = typeof value === 'undefined' || value === null ?
1534                         that.options.undefinedText : value;
1535
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),
1540                         '</div>'
1541                     ].join('') : [sprintf('<td%s %s %s %s %s %s>', id_, class_, style, data_, rowspan_, title_),
1542                         value,
1543                         '</td>'
1544                     ].join('');
1545
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>';
1550                     }
1551                 }
1552
1553                 html.push(text);
1554             });
1555
1556             if (this.options.cardView) {
1557                 html.push('</td>');
1558             }
1559
1560             html.push('</tr>');
1561         }
1562
1563         // show no records
1564         if (!html.length) {
1565             html.push('<tr class="no-records-found">',
1566                 sprintf('<td colspan="%s">%s</td>',
1567                     this.$header.find('th').length, this.options.formatNoMatches()),
1568                 '</tr>');
1569         }
1570
1571         this.$body.html(html.join(''));
1572
1573         if (!fixedScroll) {
1574             this.scrollTo(0);
1575         }
1576
1577         // click to select by column
1578         this.$body.find('> tr[data-index] > td').off('click dblclick').on('click dblclick', function (e) {
1579             var $td = $(this),
1580                 $tr = $td.parent(),
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);
1586
1587             if ($td.find('.detail-icon').length) {
1588                 return;
1589             }
1590
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);
1593
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
1599                 }
1600             }
1601         });
1602
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
1608
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);
1614             } else {
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'));
1620             }
1621             that.resetView();
1622         });
1623
1624         this.$selectItem = this.$body.find(sprintf('[name="%s"]', this.options.selectItemName));
1625         this.$selectItem.off('click').on('click', function (event) {
1626             event.stopImmediatePropagation();
1627
1628             var $this = $(this),
1629                 checked = $this.prop('checked'),
1630                 row = that.data[$this.data('index')];
1631
1632             if (that.options.maintainSelected && $(this).is(':radio')) {
1633                 $.each(that.options.data, function (i, row) {
1634                     row[that.header.stateField] = false;
1635                 });
1636             }
1637
1638             row[that.header.stateField] = checked;
1639
1640             if (that.options.singleSelect) {
1641                 that.$selectItem.not(this).each(function () {
1642                     that.data[$(this).data('index')][that.header.stateField] = false;
1643                 });
1644                 that.$selectItem.filter(':checked').not(this).prop('checked', false);
1645             }
1646
1647             that.updateSelected();
1648             that.trigger(checked ? 'check' : 'uncheck', row, $this);
1649         });
1650
1651         $.each(this.header.events, function (i, events) {
1652             if (!events) {
1653                 return;
1654             }
1655             // fix bug, if events is defined with namespace
1656             if (typeof events === 'string') {
1657                 events = calculateObjectValue(null, events);
1658             }
1659
1660             var field = that.header.fields[i],
1661                 fieldIndex = $.inArray(field, that.getVisibleFields());
1662
1663             if (that.options.detailView && !that.options.cardView) {
1664                 fieldIndex += 1;
1665             }
1666
1667             for (var key in events) {
1668                 that.$body.find('>tr:not(.no-records-found)').each(function () {
1669                     var $tr = $(this),
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),
1674                         func = events[key];
1675
1676                     $td.find(el).off(name).on(name, function (e) {
1677                         var index = $tr.data('index'),
1678                             row = that.data[index],
1679                             value = row[field];
1680
1681                         func.apply(this, [e, value, row, index]);
1682                     });
1683                 });
1684             }
1685         });
1686
1687         this.updateSelected();
1688         this.resetView();
1689
1690         this.trigger('post-body');
1691     };
1692
1693     BootstrapTable.prototype.initServer = function (silent, query) {
1694         var that = this,
1695             data = {},
1696             params = {
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
1703             },
1704             request;
1705
1706         if (!this.options.url && !this.options.ajax) {
1707             return;
1708         }
1709
1710         if (this.options.queryParamsType === 'limit') {
1711             params = {
1712                 search: params.searchText,
1713                 sort: params.sortName,
1714                 order: params.sortOrder
1715             };
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);
1721             }
1722         }
1723
1724         if (!($.isEmptyObject(this.filterColumnsPartial))) {
1725             params['filter'] = JSON.stringify(this.filterColumnsPartial, null);
1726         }
1727
1728         data = calculateObjectValue(this.options, this.options.queryParams, [params], data);
1729
1730         $.extend(data, query || {});
1731
1732         // false to stop request
1733         if (data === false) {
1734             return;
1735         }
1736
1737         if (!silent) {
1738             this.$tableLoading.show();
1739         }
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);
1750
1751                 that.load(res);
1752                 that.trigger('load-success', res);
1753             },
1754             error: function (res) {
1755                 that.trigger('load-error', res.status, res);
1756             },
1757             complete: function () {
1758                 if (!silent) {
1759                     that.$tableLoading.hide();
1760                 }
1761             }
1762         });
1763
1764         if (this.options.ajax) {
1765             calculateObjectValue(this, this.options.ajax, [request], null);
1766         } else {
1767             $.ajax(request);
1768         }
1769     };
1770
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});
1777             }
1778         }
1779     };
1780
1781     BootstrapTable.prototype.getCaret = function () {
1782         var that = this;
1783
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');
1786         });
1787     };
1788
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;
1793
1794         this.$selectAll.add(this.$selectAll_).prop('checked', checkAll);
1795
1796         this.$selectItem.each(function () {
1797             $(this).closest('tr')[$(this).prop('checked') ? 'addClass' : 'removeClass']('selected');
1798         });
1799     };
1800
1801     BootstrapTable.prototype.updateRows = function () {
1802         var that = this;
1803
1804         this.$selectItem.each(function () {
1805             that.data[$(this).data('index')][that.header.stateField] = $(this).prop('checked');
1806         });
1807     };
1808
1809     BootstrapTable.prototype.resetRows = function () {
1810         var that = this;
1811
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;
1817             }
1818         });
1819     };
1820
1821     BootstrapTable.prototype.trigger = function (name) {
1822         var args = Array.prototype.slice.call(arguments, 1);
1823
1824         name += '.bs.table';
1825         this.options[BootstrapTable.EVENTS[name]].apply(this.options, args);
1826         this.$el.trigger($.Event(name), args);
1827
1828         this.options.onAll(name, args);
1829         this.$el.trigger($.Event('all.bs.table'), [name, args]);
1830     };
1831
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);
1837     };
1838
1839     BootstrapTable.prototype.fitHeader = function () {
1840         var that = this,
1841             fixedBody,
1842             scrollWidth,
1843             focused,
1844             focusedTemp;
1845
1846         if (that.$el.is(':hidden')) {
1847             that.timeoutId_ = setTimeout($.proxy(that.fitHeader, that), 100);
1848             return;
1849         }
1850         fixedBody = this.$tableBody.get(0);
1851
1852         scrollWidth = fixedBody.scrollWidth > fixedBody.clientWidth &&
1853         fixedBody.scrollHeight > fixedBody.clientHeight + this.$header.outerHeight() ?
1854             getScrollBarWidth() : 0;
1855
1856         this.$el.css('margin-top', -this.$header.outerHeight());
1857
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");
1867                     }
1868                 }
1869             }
1870         }
1871
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_);
1879
1880
1881         focusedTemp = $('.focus-temp:visible:eq(0)');
1882         if (focusedTemp.length > 0) {
1883             focusedTemp.focus();
1884             this.$header.find('.focus-temp').removeClass('focus-temp');
1885         }
1886
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());
1890         });
1891
1892         var visibleFields = this.getVisibleFields();
1893
1894         this.$body.find('>tr:first-child:not(.no-records-found) > *').each(function (i) {
1895             var $this = $(this),
1896                 index = i;
1897
1898             if (that.options.detailView && !that.options.cardView) {
1899                 if (i === 0) {
1900                     that.$header_.find('th.detail').find('.fht-cell').width($this.innerWidth());
1901                 }
1902                 index = i - 1;
1903             }
1904
1905             that.$header_.find(sprintf('th[data-field="%s"]', visibleFields[index]))
1906                 .find('.fht-cell').width($this.innerWidth());
1907         });
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());
1912
1913             if (that.options.showFooter && !that.options.cardView) {
1914                 that.$tableFooter.scrollLeft($(this).scrollLeft());
1915             }
1916         });
1917         that.trigger('post-header');
1918     };
1919
1920     BootstrapTable.prototype.resetFooter = function () {
1921         var that = this,
1922             data = that.getData(),
1923             html = [];
1924
1925         if (!this.options.showFooter || this.options.cardView) { //do nothing
1926             return;
1927         }
1928
1929         if (!this.options.cardView && this.options.detailView) {
1930             html.push('<td><div class="th-inner">&nbsp;</div><div class="fht-cell"></div></td>');
1931         }
1932
1933         $.each(this.columns, function (i, column) {
1934             var falign = '', // footer align style
1935                 style = '',
1936                 class_ = sprintf(' class="%s"', column['class']);
1937
1938             if (!column.visible) {
1939                 return;
1940             }
1941
1942             if (that.options.cardView && (!column.cardVisible)) {
1943                 return;
1944             }
1945
1946             falign = sprintf('text-align: %s; ', column.falign ? column.falign : column.align);
1947             style = sprintf('vertical-align: %s; ', column.valign);
1948
1949             html.push('<td', class_, sprintf(' style="%s"', falign + style), '>');
1950             html.push('<div class="th-inner">');
1951
1952             html.push(calculateObjectValue(column, column.footerFormatter, [data], '&nbsp;') || '&nbsp;');
1953
1954             html.push('</div>');
1955             html.push('<div class="fht-cell"></div>');
1956             html.push('</div>');
1957             html.push('</td>');
1958         });
1959
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);
1964     };
1965
1966     BootstrapTable.prototype.fitFooter = function () {
1967         var that = this,
1968             $footerTd,
1969             elWidth,
1970             scrollWidth;
1971
1972         clearTimeout(this.timeoutFooter_);
1973         if (this.$el.is(':hidden')) {
1974             this.timeoutFooter_ = setTimeout($.proxy(this.fitFooter, this), 100);
1975             return;
1976         }
1977
1978         elWidth = this.$el.css('width');
1979         scrollWidth = elWidth > this.$tableBody.width() ? getScrollBarWidth() : 0;
1980
1981         this.$tableFooter.css({
1982             'margin-right': scrollWidth
1983         }).find('table').css('width', elWidth)
1984             .attr('class', this.$el.attr('class'));
1985
1986         $footerTd = this.$tableFooter.find('td');
1987
1988         this.$body.find('>tr:first-child:not(.no-records-found) > *').each(function (i) {
1989             var $this = $(this);
1990
1991             $footerTd.eq(i).find('.fht-cell').width($this.innerWidth());
1992         });
1993     };
1994
1995     BootstrapTable.prototype.toggleColumn = function (index, checked, needUpdate) {
1996         if (index === -1) {
1997             return;
1998         }
1999         this.columns[index].visible = checked;
2000         this.initHeader();
2001         this.initSearch();
2002         this.initPagination();
2003         this.initBody();
2004
2005         if (this.options.showColumns) {
2006             var $items = this.$toolbar.find('.keep-open input').prop('disabled', false);
2007
2008             if (needUpdate) {
2009                 $items.filter(sprintf('[value="%s"]', index)).prop('checked', checked);
2010             }
2011
2012             if ($items.filter(':checked').length <= this.options.minimumCountColumns) {
2013                 $items.filter(':checked').prop('disabled', true);
2014             }
2015         }
2016     };
2017
2018     BootstrapTable.prototype.toggleRow = function (index, uniqueId, visible) {
2019         if (index === -1) {
2020             return;
2021         }
2022
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']();
2027     };
2028
2029     BootstrapTable.prototype.getVisibleFields = function () {
2030         var that = this,
2031             visibleFields = [];
2032
2033         $.each(this.header.fields, function (j, field) {
2034             var column = that.columns[getFieldIndex(that.columns, field)];
2035
2036             if (!column.visible) {
2037                 return;
2038             }
2039             visibleFields.push(field);
2040         });
2041         return visibleFields;
2042     };
2043
2044     // PUBLIC FUNCTION DEFINITION
2045     // =======================
2046
2047     BootstrapTable.prototype.resetView = function (params) {
2048         var padding = 0;
2049
2050         if (params && params.height) {
2051             this.options.height = params.height;
2052         }
2053
2054         this.$selectAll.prop('checked', this.$selectItem.length > 0 &&
2055             this.$selectItem.length === this.$selectItem.filter(':checked').length);
2056
2057         if (this.options.height) {
2058             var toolbarHeight = getRealHeight(this.$toolbar),
2059                 paginationHeight = getRealHeight(this.$pagination),
2060                 height = this.options.height - toolbarHeight - paginationHeight;
2061
2062             this.$tableContainer.css('height', height + 'px');
2063         }
2064
2065         if (this.options.cardView) {
2066             // remove the element css
2067             this.$el.css('margin-top', '0');
2068             this.$tableContainer.css('padding-bottom', '0');
2069             return;
2070         }
2071
2072         if (this.options.showHeader && this.options.height) {
2073             this.$tableHeader.show();
2074             this.resetHeader();
2075             padding += this.$header.outerHeight();
2076         } else {
2077             this.$tableHeader.hide();
2078             this.trigger('post-header');
2079         }
2080
2081         if (this.options.showFooter) {
2082             this.resetFooter();
2083             if (this.options.height) {
2084                 padding += this.$tableFooter.outerHeight() + 1;
2085             }
2086         }
2087
2088         // Assign the correct sortable arrow
2089         this.getCaret();
2090         this.$tableContainer.css('padding-bottom', padding + 'px');
2091         this.trigger('reset-view');
2092     };
2093
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);
2098     };
2099
2100     BootstrapTable.prototype.load = function (data) {
2101         var fixedScroll = false;
2102
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;
2110             data = data.data;
2111         }
2112
2113         this.initData(data);
2114         this.initSearch();
2115         this.initPagination();
2116         this.initBody(fixedScroll);
2117     };
2118
2119     BootstrapTable.prototype.append = function (data) {
2120         this.initData(data, 'append');
2121         this.initSearch();
2122         this.initPagination();
2123         this.initBody(true);
2124     };
2125
2126     BootstrapTable.prototype.prepend = function (data) {
2127         this.initData(data, 'prepend');
2128         this.initSearch();
2129         this.initPagination();
2130         this.initBody(true);
2131     };
2132
2133     BootstrapTable.prototype.remove = function (params) {
2134         var len = this.options.data.length,
2135             i, row;
2136
2137         if (!params.hasOwnProperty('field') || !params.hasOwnProperty('values')) {
2138             return;
2139         }
2140
2141         for (i = len - 1; i >= 0; i--) {
2142             row = this.options.data[i];
2143
2144             if (!row.hasOwnProperty(params.field)) {
2145                 continue;
2146             }
2147             if ($.inArray(row[params.field], params.values) !== -1) {
2148                 this.options.data.splice(i, 1);
2149             }
2150         }
2151
2152         if (len === this.options.data.length) {
2153             return;
2154         }
2155
2156         this.initSearch();
2157         this.initPagination();
2158         this.initBody(true);
2159     };
2160
2161     BootstrapTable.prototype.removeAll = function () {
2162         if (this.options.data.length > 0) {
2163             this.options.data.splice(0, this.options.data.length);
2164             this.initSearch();
2165             this.initPagination();
2166             this.initBody(true);
2167         }
2168     };
2169
2170     BootstrapTable.prototype.getRowByUniqueId = function (id) {
2171         var uniqueId = this.options.uniqueId,
2172             len = this.options.data.length,
2173             dataRow = null,
2174             i, row, rowUniqueId;
2175
2176         for (i = len - 1; i >= 0; i--) {
2177             row = this.options.data[i];
2178
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];
2183             } else {
2184                 continue;
2185             }
2186
2187             if (typeof rowUniqueId === 'string') {
2188                 id = id.toString();
2189             } else if (typeof rowUniqueId === 'number') {
2190                 if ((Number(rowUniqueId) === rowUniqueId) && (rowUniqueId % 1 === 0)) {
2191                     id = parseInt(id);
2192                 } else if ((rowUniqueId === Number(rowUniqueId)) && (rowUniqueId !== 0)) {
2193                     id = parseFloat(id);
2194                 }
2195             }
2196
2197             if (rowUniqueId === id) {
2198                 dataRow = row;
2199                 break;
2200             }
2201         }
2202
2203         return dataRow;
2204     };
2205
2206     BootstrapTable.prototype.removeByUniqueId = function (id) {
2207         var len = this.options.data.length,
2208             row = this.getRowByUniqueId(id);
2209
2210         if (row) {
2211             this.options.data.splice(this.options.data.indexOf(row), 1);
2212         }
2213
2214         if (len === this.options.data.length) {
2215             return;
2216         }
2217
2218         this.initSearch();
2219         this.initPagination();
2220         this.initBody(true);
2221     };
2222
2223     BootstrapTable.prototype.updateByUniqueId = function (params) {
2224         var rowId;
2225
2226         if (!params.hasOwnProperty('id') || !params.hasOwnProperty('row')) {
2227             return;
2228         }
2229
2230         rowId = $.inArray(this.getRowByUniqueId(params.id), this.options.data);
2231
2232         if (rowId === -1) {
2233             return;
2234         }
2235
2236         $.extend(this.data[rowId], params.row);
2237         this.initSort();
2238         this.initBody(true);
2239     };
2240
2241     BootstrapTable.prototype.insertRow = function (params) {
2242         if (!params.hasOwnProperty('index') || !params.hasOwnProperty('row')) {
2243             return;
2244         }
2245         this.data.splice(params.index, 0, params.row);
2246         this.initSearch();
2247         this.initPagination();
2248         this.initSort();
2249         this.initBody(true);
2250     };
2251
2252     BootstrapTable.prototype.updateRow = function (params) {
2253         if (!params.hasOwnProperty('index') || !params.hasOwnProperty('row')) {
2254             return;
2255         }
2256         $.extend(this.data[params.index], params.row);
2257         this.initSort();
2258         this.initBody(true);
2259     };
2260
2261     BootstrapTable.prototype.showRow = function (params) {
2262         if (!params.hasOwnProperty('index') || !params.hasOwnProperty('uniqueId')) {
2263             return;
2264         }
2265         this.toggleRow(params.index, params.uniqueId, true);
2266     };
2267
2268     BootstrapTable.prototype.hideRow = function (params) {
2269         if (!params.hasOwnProperty('index') || !params.hasOwnProperty('uniqueId')) {
2270             return;
2271         }
2272         this.toggleRow(params.index, params.uniqueId, false);
2273     };
2274
2275     BootstrapTable.prototype.getRowsHidden = function (show) {
2276         var rows = $(this.$body[0]).children().filter(':hidden'),
2277             i = 0;
2278         if (show) {
2279             for (; i < rows.length; i++) {
2280                 $(rows[i]).show();
2281             }
2282         }
2283         return rows;
2284     };
2285
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,
2291             i, j,
2292             $tr = this.$body.find('>tr'),
2293             $td;
2294
2295         if (this.options.detailView && !this.options.cardView) {
2296             col += 1;
2297         }
2298
2299         $td = $tr.eq(row).find('>td').eq(col);
2300
2301         if (row < 0 || col < 0 || row >= this.data.length) {
2302             return;
2303         }
2304
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();
2308             }
2309         }
2310
2311         $td.attr('rowspan', rowspan).attr('colspan', colspan).show();
2312     };
2313
2314     BootstrapTable.prototype.updateCell = function (params) {
2315         if (!params.hasOwnProperty('index') ||
2316             !params.hasOwnProperty('field') ||
2317             !params.hasOwnProperty('value')) {
2318             return;
2319         }
2320         this.data[params.index][params.field] = params.value;
2321         this.initSort();
2322         this.initBody(true);
2323     };
2324
2325     BootstrapTable.prototype.getOptions = function () {
2326         return this.options;
2327     };
2328
2329     BootstrapTable.prototype.getSelections = function () {
2330         var that = this;
2331
2332         return $.grep(this.data, function (row) {
2333             return row[that.header.stateField];
2334         });
2335     };
2336
2337     BootstrapTable.prototype.getAllSelections = function () {
2338         var that = this;
2339
2340         return $.grep(this.options.data, function (row) {
2341             return row[that.header.stateField];
2342         });
2343     };
2344
2345     BootstrapTable.prototype.checkAll = function () {
2346         this.checkAll_(true);
2347     };
2348
2349     BootstrapTable.prototype.uncheckAll = function () {
2350         this.checkAll_(false);
2351     };
2352
2353     BootstrapTable.prototype.checkAll_ = function (checked) {
2354         var rows;
2355         if (!checked) {
2356             rows = this.getSelections();
2357         }
2358         this.$selectAll.add(this.$selectAll_).prop('checked', checked);
2359         this.$selectItem.filter(':enabled').prop('checked', checked);
2360         this.updateRows();
2361         if (checked) {
2362             rows = this.getSelections();
2363         }
2364         this.trigger(checked ? 'check-all' : 'uncheck-all', rows);
2365     };
2366
2367     BootstrapTable.prototype.check = function (index) {
2368         this.check_(true, index);
2369     };
2370
2371     BootstrapTable.prototype.uncheck = function (index) {
2372         this.check_(false, index);
2373     };
2374
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);
2380     };
2381
2382     BootstrapTable.prototype.checkBy = function (obj) {
2383         this.checkBy_(true, obj);
2384     };
2385
2386     BootstrapTable.prototype.uncheckBy = function (obj) {
2387         this.checkBy_(false, obj);
2388     };
2389
2390     BootstrapTable.prototype.checkBy_ = function (checked, obj) {
2391         if (!obj.hasOwnProperty('field') || !obj.hasOwnProperty('values')) {
2392             return;
2393         }
2394
2395         var that = this,
2396             rows = [];
2397         $.each(this.options.data, function (index, row) {
2398             if (!row.hasOwnProperty(obj.field)) {
2399                 return false;
2400             }
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;
2405                 rows.push(row);
2406                 that.trigger(checked ? 'check' : 'uncheck', row, $el);
2407             }
2408         });
2409         this.updateSelected();
2410         this.trigger(checked ? 'check-some' : 'uncheck-some', rows);
2411     };
2412
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
2421     };
2422
2423     BootstrapTable.prototype.showLoading = function () {
2424         this.$tableLoading.show();
2425     };
2426
2427     BootstrapTable.prototype.hideLoading = function () {
2428         this.$tableLoading.hide();
2429     };
2430
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);
2436         } else {
2437             button.attr("class", this.options.iconsPrefix + " " + this.options.icons.paginationSwitchUp);
2438         }
2439         this.updatePagination();
2440     };
2441
2442     BootstrapTable.prototype.refresh = function (params) {
2443         if (params && params.url) {
2444             this.options.url = params.url;
2445             this.options.pageNumber = 1;
2446         }
2447         this.initServer(params && params.silent, params && params.query);
2448     };
2449
2450     BootstrapTable.prototype.resetWidth = function () {
2451         if (this.options.showHeader && this.options.height) {
2452             this.fitHeader();
2453         }
2454         if (this.options.showFooter) {
2455             this.fitFooter();
2456         }
2457     };
2458
2459     BootstrapTable.prototype.showColumn = function (field) {
2460         this.toggleColumn(getFieldIndex(this.columns, field), true, true);
2461     };
2462
2463     BootstrapTable.prototype.hideColumn = function (field) {
2464         this.toggleColumn(getFieldIndex(this.columns, field), false, true);
2465     };
2466
2467     BootstrapTable.prototype.getHiddenColumns = function () {
2468         return $.grep(this.columns, function (column) {
2469             return !column.visible;
2470         });
2471     };
2472
2473     BootstrapTable.prototype.filterBy = function (columns) {
2474         this.filterColumns = $.isEmptyObject(columns) ? {} : columns;
2475         this.options.pageNumber = 1;
2476         this.initSearch();
2477         this.updatePagination();
2478     };
2479
2480     BootstrapTable.prototype.scrollTo = function (value) {
2481         if (typeof value === 'string') {
2482             value = value === 'bottom' ? this.$tableBody[0].scrollHeight : 0;
2483         }
2484         if (typeof value === 'number') {
2485             this.$tableBody.scrollTop(value);
2486         }
2487         if (typeof value === 'undefined') {
2488             return this.$tableBody.scrollTop();
2489         }
2490     };
2491
2492     BootstrapTable.prototype.getScrollPosition = function () {
2493         return this.scrollTo();
2494     };
2495
2496     BootstrapTable.prototype.selectPage = function (page) {
2497         if (page > 0 && page <= this.options.totalPages) {
2498             this.options.pageNumber = page;
2499             this.updatePagination();
2500         }
2501     };
2502
2503     BootstrapTable.prototype.prevPage = function () {
2504         if (this.options.pageNumber > 1) {
2505             this.options.pageNumber--;
2506             this.updatePagination();
2507         }
2508     };
2509
2510     BootstrapTable.prototype.nextPage = function () {
2511         if (this.options.pageNumber < this.options.totalPages) {
2512             this.options.pageNumber++;
2513             this.updatePagination();
2514         }
2515     };
2516
2517     BootstrapTable.prototype.toggleView = function () {
2518         this.options.cardView = !this.options.cardView;
2519         this.initHeader();
2520         // Fixed remove toolbar when click cardView button.
2521         //that.initToolbar();
2522         this.initBody();
2523         this.trigger('toggle', this.options.cardView);
2524     };
2525
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)) {
2529             return;
2530         }
2531         this.options = $.extend(this.options, options);
2532         this.trigger('refresh-options', this.options);
2533         this.destroy();
2534         this.init();
2535     };
2536
2537     BootstrapTable.prototype.resetSearch = function (text) {
2538         var $search = this.$toolbar.find('.search input');
2539         $search.val(text || '');
2540         this.onSearch({currentTarget: $search});
2541     };
2542
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();
2547         }
2548     };
2549
2550     BootstrapTable.prototype.expandRow = function (index) {
2551         this.expandRow_(true, index);
2552     };
2553
2554     BootstrapTable.prototype.collapseRow = function (index) {
2555         this.expandRow_(false, index);
2556     };
2557
2558     BootstrapTable.prototype.expandAllRows = function (isSubTable) {
2559         if (isSubTable) {
2560             var $tr = this.$body.find(sprintf('> tr[data-index="%s"]', 0)),
2561                 that = this,
2562                 detailIcon = null,
2563                 executeInterval = false,
2564                 idInterval = -1;
2565
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;
2572             }
2573
2574             if (executeInterval) {
2575                 try {
2576                     idInterval = setInterval(function () {
2577                         detailIcon = that.$body.find("tr.detail-view").last().find(".detail-icon");
2578                         if (detailIcon.length > 0) {
2579                             detailIcon.click();
2580                         } else {
2581                             clearInterval(idInterval);
2582                         }
2583                     }, 1);
2584                 } catch (ex) {
2585                     clearInterval(idInterval);
2586                 }
2587             }
2588         } else {
2589             var trs = this.$body.children();
2590             for (var i = 0; i < trs.length; i++) {
2591                 this.expandRow_(true, $(trs[i]).data("index"));
2592             }
2593         }
2594     };
2595
2596     BootstrapTable.prototype.collapseAllRows = function (isSubTable) {
2597         if (isSubTable) {
2598             this.expandRow_(false, 0);
2599         } else {
2600             var trs = this.$body.children();
2601             for (var i = 0; i < trs.length; i++) {
2602                 this.expandRow_(false, $(trs[i]).data("index"));
2603             }
2604         }
2605     };
2606
2607     // BOOTSTRAP TABLE PLUGIN DEFINITION
2608     // =======================
2609
2610     var allowedMethods = [
2611         'getOptions',
2612         'getSelections', 'getAllSelections', 'getData',
2613         'load', 'append', 'prepend', 'remove', 'removeAll',
2614         'insertRow', 'updateRow', 'updateCell', 'updateByUniqueId', 'removeByUniqueId',
2615         'getRowByUniqueId', 'showRow', 'hideRow', 'getRowsHidden',
2616         'mergeCells',
2617         'checkAll', 'uncheckAll',
2618         'check', 'uncheck',
2619         'checkBy', 'uncheckBy',
2620         'refresh',
2621         'resetView',
2622         'resetWidth',
2623         'destroy',
2624         'showLoading', 'hideLoading',
2625         'showColumn', 'hideColumn', 'getHiddenColumns',
2626         'filterBy',
2627         'scrollTo',
2628         'getScrollPosition',
2629         'selectPage', 'prevPage', 'nextPage',
2630         'togglePagination',
2631         'toggleView',
2632         'refreshOptions',
2633         'resetSearch',
2634         'expandRow', 'collapseRow', 'expandAllRows', 'collapseAllRows'
2635     ];
2636
2637     $.fn.bootstrapTable = function (option) {
2638         var value,
2639             args = Array.prototype.slice.call(arguments, 1);
2640
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);
2646
2647             if (typeof option === 'string') {
2648                 if ($.inArray(option, allowedMethods) < 0) {
2649                     throw new Error("Unknown method: " + option);
2650                 }
2651
2652                 if (!data) {
2653                     return;
2654                 }
2655
2656                 value = data[option].apply(data, args);
2657
2658                 if (option === 'destroy') {
2659                     $this.removeData('bootstrap.table');
2660                 }
2661             }
2662
2663             if (!data) {
2664                 $this.data('bootstrap.table', (data = new BootstrapTable(this, options)));
2665             }
2666         });
2667
2668         return typeof value === 'undefined' ? this : value;
2669     };
2670
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 = {
2677         sprintf: sprintf,
2678         getFieldIndex: getFieldIndex,
2679         compareObjects: compareObjects,
2680         calculateObjectValue: calculateObjectValue
2681     };
2682
2683     // BOOTSTRAP TABLE INIT
2684     // =======================
2685
2686     $(function () {
2687         $('[data-toggle="table"]').bootstrapTable();
2688     });
2689
2690 }(jQuery);