Browse Source

Added the ability to add custom Buttons to the "buttonbar"

Dustin Utecht 5 years ago
parent
commit
93c4b0f5e6
78 changed files with 888 additions and 324 deletions
  1. 21 0
      cypress/plugins/index.js
  2. 25 0
      cypress/support/commands.js
  3. 20 0
      cypress/support/index.js
  4. 86 0
      site/docs/api/table-options.md
  5. 1 1
      site/docs/extensions/auto-refresh.md
  6. 8 0
      site/docs/extensions/copy-rows.md
  7. 1 1
      site/docs/extensions/custom-view.md
  8. 1 1
      site/docs/extensions/export.md
  9. 4 4
      site/docs/extensions/filter-control.md
  10. 14 12
      site/docs/extensions/multiple-sort.md
  11. 1 1
      site/docs/extensions/page-jump-to.md
  12. 8 0
      site/docs/extensions/print.md
  13. 2 2
      site/docs/extensions/toolbar.md
  14. 191 131
      src/bootstrap-table.js
  15. 1 0
      src/constants/index.js
  16. 17 19
      src/extensions/auto-refresh/bootstrap-table-auto-refresh.js
  17. 23 12
      src/extensions/copy-rows/bootstrap-table-copy-rows.js
  18. 13 16
      src/extensions/custom-view/bootstrap-table-custom-view.js
  19. 46 41
      src/extensions/export/bootstrap-table-export.js
  20. 13 15
      src/extensions/filter-control/bootstrap-table-filter-control.js
  21. 9 7
      src/extensions/multiple-sort/bootstrap-table-multiple-sort.js
  22. 22 19
      src/extensions/print/bootstrap-table-print.js
  23. 13 16
      src/extensions/toolbar/bootstrap-table-toolbar.js
  24. 6 0
      src/locale/bootstrap-table-af-ZA.js
  25. 6 0
      src/locale/bootstrap-table-ar-SA.js
  26. 6 0
      src/locale/bootstrap-table-bg-BG.js
  27. 6 0
      src/locale/bootstrap-table-ca-ES.js
  28. 6 0
      src/locale/bootstrap-table-cs-CZ.js
  29. 6 0
      src/locale/bootstrap-table-da-DK.js
  30. 9 3
      src/locale/bootstrap-table-de-DE.js
  31. 6 0
      src/locale/bootstrap-table-el-GR.js
  32. 6 0
      src/locale/bootstrap-table-en-US.js
  33. 6 0
      src/locale/bootstrap-table-es-AR.js
  34. 6 0
      src/locale/bootstrap-table-es-CL.js
  35. 6 0
      src/locale/bootstrap-table-es-CR.js
  36. 6 0
      src/locale/bootstrap-table-es-ES.js
  37. 6 0
      src/locale/bootstrap-table-es-MX.js
  38. 6 0
      src/locale/bootstrap-table-es-NI.js
  39. 6 0
      src/locale/bootstrap-table-es-SP.js
  40. 6 0
      src/locale/bootstrap-table-et-EE.js
  41. 6 0
      src/locale/bootstrap-table-eu-EU.js
  42. 6 0
      src/locale/bootstrap-table-fa-IR.js
  43. 6 0
      src/locale/bootstrap-table-fi-FI.js
  44. 6 0
      src/locale/bootstrap-table-fr-BE.js
  45. 6 0
      src/locale/bootstrap-table-fr-CH.js
  46. 6 0
      src/locale/bootstrap-table-fr-FR.js
  47. 6 0
      src/locale/bootstrap-table-fr-LU.js
  48. 6 0
      src/locale/bootstrap-table-he-IL.js
  49. 6 0
      src/locale/bootstrap-table-hr-HR.js
  50. 6 0
      src/locale/bootstrap-table-hu-HU.js
  51. 6 0
      src/locale/bootstrap-table-id-ID.js
  52. 6 0
      src/locale/bootstrap-table-it-IT.js
  53. 6 0
      src/locale/bootstrap-table-ja-JP.js
  54. 6 0
      src/locale/bootstrap-table-ka-GE.js
  55. 6 0
      src/locale/bootstrap-table-ko-KR.js
  56. 6 0
      src/locale/bootstrap-table-ms-MY.js
  57. 6 0
      src/locale/bootstrap-table-nb-NO.js
  58. 6 0
      src/locale/bootstrap-table-nl-BE.js
  59. 6 0
      src/locale/bootstrap-table-nl-NL.js
  60. 6 0
      src/locale/bootstrap-table-pl-PL.js
  61. 6 0
      src/locale/bootstrap-table-pt-BR.js
  62. 6 0
      src/locale/bootstrap-table-pt-PT.js
  63. 6 0
      src/locale/bootstrap-table-ro-RO.js
  64. 6 0
      src/locale/bootstrap-table-ru-RU.js
  65. 6 0
      src/locale/bootstrap-table-sk-SK.js
  66. 6 0
      src/locale/bootstrap-table-sr-Cyrl-RS.js
  67. 6 0
      src/locale/bootstrap-table-sr-Latn-RS.js
  68. 6 0
      src/locale/bootstrap-table-sv-SE.js
  69. 6 0
      src/locale/bootstrap-table-th-TH.js
  70. 6 0
      src/locale/bootstrap-table-tr-TR.js
  71. 6 0
      src/locale/bootstrap-table-uk-UA.js
  72. 6 0
      src/locale/bootstrap-table-ur-PK.js
  73. 6 0
      src/locale/bootstrap-table-uz-Latn-UZ.js
  74. 6 0
      src/locale/bootstrap-table-vi-VN.js
  75. 6 0
      src/locale/bootstrap-table-zh-CN.js
  76. 6 0
      src/locale/bootstrap-table-zh-TW.js
  77. 1 1
      src/utils/index.js
  78. 26 22
      tools/check-api.js

+ 21 - 0
cypress/plugins/index.js

@@ -0,0 +1,21 @@
+/// <reference types="cypress" />
+// ***********************************************************
+// This example plugins/index.js can be used to load plugins
+//
+// You can change the location of this file or turn off loading
+// the plugins file with the 'pluginsFile' configuration option.
+//
+// You can read more here:
+// https://on.cypress.io/plugins-guide
+// ***********************************************************
+
+// This function is called when a project is opened or re-opened (e.g. due to
+// the project's config changing)
+
+/**
+ * @type {Cypress.PluginConfig}
+ */
+module.exports = (on, config) => {
+  // `on` is used to hook into various events Cypress emits
+  // `config` is the resolved Cypress config
+}

+ 25 - 0
cypress/support/commands.js

@@ -0,0 +1,25 @@
+// ***********************************************
+// This example commands.js shows you how to
+// create various custom commands and overwrite
+// existing commands.
+//
+// For more comprehensive examples of custom
+// commands please read more here:
+// https://on.cypress.io/custom-commands
+// ***********************************************
+//
+//
+// -- This is a parent command --
+// Cypress.Commands.add("login", (email, password) => { ... })
+//
+//
+// -- This is a child command --
+// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
+//
+//
+// -- This is a dual command --
+// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
+//
+//
+// -- This will overwrite an existing command --
+// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })

+ 20 - 0
cypress/support/index.js

@@ -0,0 +1,20 @@
+// ***********************************************************
+// This example support/index.js is processed and
+// loaded automatically before your test files.
+//
+// This is a great place to put global configuration and
+// behavior that modifies Cypress.
+//
+// You can change the location of this file or turn off
+// automatically serving support files with the
+// 'supportFile' configuration option.
+//
+// You can read more here:
+// https://on.cypress.io/configuration
+// ***********************************************************
+
+// Import commands.js using ES2015 syntax:
+import './commands'
+
+// Alternatively you can use CommonJS syntax:
+// require('./commands')

+ 86 - 0
site/docs/api/table-options.md

@@ -55,6 +55,92 @@ The table options are defined in `jQuery.fn.bootstrapTable.defaults`.
 
 - **Example:** [AJAX Options](https://examples.bootstrap-table.com/#options/ajax-options.html)
 
+## buttons
+
+- **Attribute:** `data-buttons`
+
+- **Type:** `Function`
+
+- **Detail:**
+
+  This option allows creating/adding custom button(s) to "buttonbar" (top right of the table).  
+  This buttons can be sorted with the table option [buttonsOrder](https://bootstrap-table.com/docs/api/table-options/#buttonsorder), the used key/name for the event should be used for that!
+  
+  The custom button is highly configurable, the following options exists:
+  - `text`
+    - Description: This options is used for the [showButtonText](https://bootstrap-table.com/docs/api/table-options/#showbuttontext) table option.
+    - Type: `String`
+  - `icon`
+    - Description: This option is used for the [showButtonIcons](https://bootstrap-table.com/docs/api/table-options/#showbuttonicons) table option.
+    - Type: `String` - Only needs the icon class e.g. `fa-users`
+  - `render`
+    - Description: Set this option to `false` to hide the button by default, the button is visible again when you add the data attribute `data-show-BUTTONNAME="true"`.    
+  - `attributes`
+    - Description: This option allows adding additional html attributes e.g. `title`
+    - Type: `Object`
+    - Example: `{title: 'Button title'}`
+  - `html`
+    - Description: If you don't want to autogenerate the html, you can use this option to insert your custom html.   
+      The `event` option is only working if you custom html contains `name="BUTTONNAME"`.   
+      If this option is used the following options will be ignored:
+      - `text`
+      - `icon`
+      - `attributes`   
+    - Type: `Function|String`
+  - `event`
+    - Description: Should be used if you want to add an event to the button
+    - Type: `Function|Object|String`
+
+   The `event` option can be configured in three ways.     
+   One event with `click` event:  
+   ```javascript
+   {
+     'event': () => { }
+   }
+   ```
+  
+  One event with a self defined event type:   
+  ```javascript
+     {
+       'event': {
+         'mouseenter': () => { }
+       }
+     }
+  ```
+  
+  Multiple events with self defined event types:   
+    ```javascript
+       {
+         'event': {
+           'click': () => { },
+           'mouseenter': () => { },
+           'mouseleave': () => { }
+         }
+       }
+    ```
+  
+  **Hint:** Instead of inline functions you also can use function names.
+  
+  A configured custom button could look like this:
+  ``` javascript 
+  {
+    btnRemoveEvenRows: {
+      'text': 'Remove even Rows',
+      'icon': 'fa-trash',
+      'event': () => {
+        //DO STUFF TO REMOVE EVEN ROWS
+      },
+      'attributes': {
+        'title': 'Remove all rows which has a even id'
+      }
+    }
+  }
+  ```
+
+- **Default:** `{}`
+
+- **Example:** [Buttons](https://examples.bootstrap-table.com/#options/buttons.html)
+
 ## buttonsAlign
 
 - **Attribute:** `data-buttons-align`

+ 1 - 1
site/docs/extensions/auto-refresh.md

@@ -76,4 +76,4 @@ toc: true
 
 - **Parameter:** `undefined`
 
-- **Default:** `'Auto Refresh'`
+- **Default:** `function () { return "Auto Refresh" }`

+ 8 - 0
site/docs/extensions/copy-rows.md

@@ -77,3 +77,11 @@ This extension adds functionality for copying selected rows to the clipboard. Cu
 ### copyColumnsToClipboard
 
 * Copy the contents of the selected rows to the clipboard.
+
+## Localizations
+
+### formatCopyRows
+
+- **type:** `Function`
+
+- **Default:** `function () { return "Copy Rows" }`

+ 1 - 1
site/docs/extensions/custom-view.md

@@ -90,4 +90,4 @@ This extension adds the ability to create a custom view to display the data.
 
 - **type:** `Function`
 
-- **Default:** `function () { return "Toggle custom view;}`
+- **Default:** `function () { return "Toggle custom view" }`

+ 1 - 1
site/docs/extensions/export.md

@@ -152,4 +152,4 @@ This is an important link to check out as some file types may require extra step
 
 - **Parameter:** `undefined`
 
-- **Default:** `'Export data'`
+- **Default:** `function () { return "Export data" }`

+ 4 - 4
site/docs/extensions/filter-control.md

@@ -290,22 +290,22 @@ Dependence if you use the datepicker option: [bootstrap-datepicker](https://gith
 
 - **type:** `Function`
 
-- **Default:** `function () { return "Clear Filters";}`
+- **Default:** `function () { return "Clear Filters" }`
 
 ### formatFilterControlSwitch
 
 - **type:** `Function`
 
-- **Default:** `function () { return "Hide/Show controls";}`
+- **Default:** `function () { return "Hide/Show controls" }`
 
 ### formatFilterControlSwitchHide
 
 - **type:** `Function`
 
-- **Default:** `function () { return "Hide controls";}`
+- **Default:** `function () { return "Hide controls" }`
 
 ### formatFilterControlSwitchShow
 
 - **type:** `Function`
 
-- **Default:** `function () { return "Show controls";}`
+- **Default:** `function () { return "Show controls" }`

+ 14 - 12
site/docs/extensions/multiple-sort.md

@@ -112,7 +112,7 @@ toc: true
 
   Text of the add level button
 
-- **Default:** `Add Level`
+- **Default:** `function () { return "Add Level" }`
 
 ### formatCancel
 
@@ -120,7 +120,7 @@ toc: true
 
   Text of the delete level button
 
-- **Default:** `Cancel`
+- **Default:** `function () { return "Cancel" }`
 
 ### formatColumn
 
@@ -128,7 +128,7 @@ toc: true
 
   Text of Column header
 
-- **Default:** `Column`
+- **Default:** `function () { return "Column" }`
 
 ### formatDeleteLevel
 
@@ -136,7 +136,7 @@ toc: true
 
   Text of the delete level button
 
-- **Default:** `Delete Level`
+- **Default:** `function () { return "Delete Level" }`
 
 ### formatDuplicateAlertTitle
 
@@ -144,7 +144,7 @@ toc: true
 
   Title of the duplicate alert
 
-- **Default:** `Duplicate(s) detected!`
+- **Default:** `function () { return "Duplicate(s) detected!" }`
 
 ### formatDuplicateAlertDescription
 
@@ -152,7 +152,7 @@ toc: true
 
   Text of the duplicate alert
 
-- **Default:** `Please remove or change any duplicate column.`
+- **Default:** `function () { return "Please remove or change any duplicate column." }`
 
 ### formatMultipleSort
 
@@ -160,7 +160,7 @@ toc: true
 
   Title of the advanced search modal
 
-- **Default:** `Multiple Sort`
+- **Default:** `function () { return "Multiple Sort" }`
 
 ### formatOrder
 
@@ -168,7 +168,7 @@ toc: true
 
   Text of the delete level button
 
-- **Default:** `Order`
+- **Default:** `function () { return "Order" }`
 
 ### formatSort
 
@@ -176,7 +176,7 @@ toc: true
 
   Text of the delete level button
 
-- **Default:** `Sort`
+- **Default:** `function () { return "Sort" }`
 
 ### formatSortBy
 
@@ -184,7 +184,7 @@ toc: true
 
   Text of the delete level button
 
-- **Default:** `Sort by`
+- **Default:** `function () { return "Sort by" }`
 
 ### formatSortOrders
 
@@ -192,7 +192,9 @@ toc: true
 
   Text of the sort orders
 
-- **Default:**asc : `Ascending` and desc : `Descending`
+- **Default:**
+  - asc : `function () { return "Ascending" }`
+  - desc : `function () { return "Descending" }`
 
 ### formatThenBy
 
@@ -200,7 +202,7 @@ toc: true
 
   Text of the delete level button
 
-- **Default:** `Then by`
+- **Default:** `function () { return "Then by" }`
 
 ## Events
 

+ 1 - 1
site/docs/extensions/page-jump-to.md

@@ -37,4 +37,4 @@ toc: true
 
 - **Parameter:** `undefined`
 
-- **Default:** `'GO'`
+- **Default:** `function () { return "GO" }`

+ 8 - 0
site/docs/extensions/print.md

@@ -121,3 +121,11 @@ Adds a button to the toolbar for printing the table in a predefined configurable
    set true to hide this column in the printed page.
 
 - **Default:** `false`
+
+## Localizations
+
+### formatPrint
+
+- **type:** `Function`
+
+- **Default:** `function () { return "Print" }`

+ 2 - 2
site/docs/extensions/toolbar.md

@@ -80,7 +80,7 @@ toc: true
 
    Text of the close button
 
-- **Default:** `Close`
+- **Default:** `function () { return "Close" }`
 
 ### formatAdvancedSearch
 
@@ -88,4 +88,4 @@ toc: true
 
    Title of the advanced search modal
 
-- **Default:** `Advanced search`
+- **Default:** `function () { return "Advanced search" }`

+ 191 - 131
src/bootstrap-table.js

@@ -43,6 +43,8 @@ class BootstrapTable {
       buttonsPrefix + opts.buttonsClass,
       Utils.sprintf(`${buttonsPrefix}%s`, opts.iconSize)
     ].join(' ').trim()
+
+    this.buttons = Utils.calculateObjectValue(this, opts.buttons, [], [])
   }
 
   initLocale () {
@@ -525,129 +527,190 @@ class BootstrapTable {
   }
 
   initToolbar () {
-    const opts = this.options
     let html = []
     let timeoutId = 0
     let $keepOpen
-    let $search
     let switchableCount = 0
+    const opts = this.options
 
     if (this.$toolbar.find('.bs-bars').children().length) {
-      $('body').append($(opts.toolbar))
+      $('body').append($(this.options.toolbar))
     }
     this.$toolbar.html('')
 
-    if (typeof opts.toolbar === 'string' || typeof opts.toolbar === 'object') {
-      $(Utils.sprintf('<div class="bs-bars %s-%s"></div>', this.constants.classes.pull, opts.toolbarAlign))
+    if (typeof this.options.toolbar === 'string' || typeof this.options.toolbar === 'object') {
+      $(Utils.sprintf('<div class="bs-bars %s-%s"></div>', this.constants.classes.pull, this.options.toolbarAlign))
         .appendTo(this.$toolbar)
-        .append($(opts.toolbar))
+        .append($(this.options.toolbar))
     }
 
     // showColumns, showToggle, showRefresh
     html = [`<div class="${[
       'columns',
-      `columns-${opts.buttonsAlign}`,
+      `columns-${this.options.buttonsAlign}`,
       this.constants.classes.buttonsGroup,
-      `${this.constants.classes.pull}-${opts.buttonsAlign}`
+      `${this.constants.classes.pull}-${this.options.buttonsAlign}`
     ].join(' ')}">`]
 
-    if (typeof opts.icons === 'string') {
-      opts.icons = Utils.calculateObjectValue(null, opts.icons)
-    }
-
-    const buttonsHtml = {
-      paginationSwitch: `<button class="${this.constants.buttonsClass}" type="button" name="paginationSwitch"
-        aria-label="Pagination Switch" title="${opts.formatPaginationSwitch()}">
-        ${opts.showButtonIcons ? Utils.sprintf(this.constants.html.icon, opts.iconsPrefix, opts.icons.paginationSwitchDown) : ''}
-        ${opts.showButtonText ? opts.formatPaginationSwitchUp() : ''}
-        </button>`,
-
-      refresh: `<button class="${this.constants.buttonsClass}" type="button" name="refresh"
-        aria-label="Refresh" title="${opts.formatRefresh()}">
-        ${opts.showButtonIcons ? Utils.sprintf(this.constants.html.icon, opts.iconsPrefix, opts.icons.refresh) : ''}
-        ${opts.showButtonText ? opts.formatRefresh() : ''}
-        </button>`,
-
-      toggle: `<button class="${this.constants.buttonsClass}" type="button" name="toggle"
-        aria-label="Toggle" title="${opts.formatToggle()}">
-        ${opts.showButtonIcons ? Utils.sprintf(this.constants.html.icon, opts.iconsPrefix, opts.icons.toggleOff) : ''}
-        ${opts.showButtonText ? opts.formatToggleOn() : ''}
-        </button>`,
-
-      fullscreen: `<button class="${this.constants.buttonsClass}" type="button" name="fullscreen"
-        aria-label="Fullscreen" title="${opts.formatFullscreen()}">
-        ${opts.showButtonIcons ? Utils.sprintf(this.constants.html.icon, opts.iconsPrefix, opts.icons.fullscreen) : ''}
-        ${opts.showButtonText ? opts.formatFullscreen() : ''}
-        </button>`,
-
-      columns: (() => {
-        const html = []
-        html.push(`<div class="keep-open ${this.constants.classes.buttonsDropdown}" title="${opts.formatColumns()}">
-          <button class="${this.constants.buttonsClass} dropdown-toggle" type="button" data-toggle="dropdown"
-          aria-label="Columns" title="${opts.formatColumns()}">
-          ${opts.showButtonIcons ? Utils.sprintf(this.constants.html.icon, opts.iconsPrefix, opts.icons.columns) : ''}
-          ${opts.showButtonText ? opts.formatColumns() : ''}
-          ${this.constants.html.dropdownCaret}
-          </button>
-          ${this.constants.html.toolbarDropdown[0]}`)
-
-        if (opts.showColumnsSearch) {
-          html.push(
-            Utils.sprintf(this.constants.html.toolbarDropdownItem,
-              Utils.sprintf('<input type="text" class="%s" name="columnsSearch" placeholder="%s" autocomplete="off">', this.constants.classes.input, opts.formatSearch())
-            )
-          )
-          html.push(this.constants.html.toolbarDropdownSeparator)
-        }
+    if (typeof this.options.icons === 'string') {
+      this.options.icons = Utils.calculateObjectValue(null, this.options.icons)
+    }
 
-        if (opts.showColumnsToggleAll) {
-          const allFieldsVisible = this.getVisibleColumns().length === this.columns.filter(column => !this.isSelectionColumn(column)).length
-          html.push(
-            Utils.sprintf(this.constants.html.toolbarDropdownItem,
-              Utils.sprintf('<input type="checkbox" class="toggle-all" %s> <span>%s</span>',
-                allFieldsVisible ? 'checked="checked"' : '', opts.formatColumnsToggleAll())
-            )
-          )
+    if (typeof this.options.buttonsOrder === 'string') {
+      this.options.buttonsOrder = this.options.buttonsOrder.replace(/\[|\]| |'/g, '').toLowerCase().split(',')
+    }
 
-          html.push(this.constants.html.toolbarDropdownSeparator)
+    this.buttons = Object.assign(this.buttons, {
+      paginationSwitch: {
+        'text': this.options.formatPaginationSwitchUp(),
+        'icon': this.options.icons.paginationSwitchDown,
+        'render': false,
+        'event': this.togglePagination,
+        'attributes': {
+          'aria-label': this.options.formatPaginationSwitch(),
+          'title': this.options.formatPaginationSwitch()
+        }
+      },
+      refresh: {
+        'text': this.options.formatRefresh(),
+        'icon': this.options.icons.refresh,
+        'render': false,
+        'event': 'this.refresh',
+        'attributes': {
+          'aria-label': this.options.formatRefresh(),
+          'title': this.options.formatRefresh()
+        }
+      },
+      toggle: {
+        'text': this.options.formatToggle(),
+        'icon': this.options.icons.toggleOff,
+        'render': false,
+        'event': this.toggleView,
+        'attributes': {
+          'aria-label': this.options.formatToggleOn(),
+          'title': this.options.formatToggleOn()
+        }
+      },
+      fullscreen: {
+        'text': this.options.formatFullscreen(),
+        'icon': this.options.icons.fullscreen,
+        'render': false,
+        'event': this.toggleFullscreen,
+        'attributes': {
+          'aria-label': this.options.formatFullscreen(),
+          'title': this.options.formatFullscreen()
         }
+      },
+      columns: {
+        'render': false,
+        'html': (() => {
+          const html = []
+          html.push(`<div class="keep-open ${this.constants.classes.buttonsDropdown}" title="${this.options.formatColumns()}">
+            <button class="${this.constants.buttonsClass} dropdown-toggle" type="button" data-toggle="dropdown"
+            aria-label="Columns" title="${this.options.formatColumns()}">
+            ${this.options.showButtonIcons ? Utils.sprintf(this.constants.html.icon, this.options.iconsPrefix, this.options.icons.columns) : ''}
+            ${this.options.showButtonText ? this.options.formatColumns() : ''}
+            ${this.constants.html.dropdownCaret}
+            </button>
+            ${this.constants.html.toolbarDropdown[0]}`)
+
+          if (this.options.showColumnsSearch) {
+            html.push(
+              Utils.sprintf(this.constants.html.toolbarDropdownItem,
+                Utils.sprintf('<input type="text" class="%s" name="columnsSearch" placeholder="%s" autocomplete="off">', this.constants.classes.input, this.options.formatSearch())
+              )
+            )
+            html.push(this.constants.html.toolbarDropdownSeparator)
+          }
+
+          if (this.options.showColumnsToggleAll) {
+            const allFieldsVisible = this.getVisibleColumns().length === this.columns.filter(column => !this.isSelectionColumn(column)).length
+            html.push(
+              Utils.sprintf(this.constants.html.toolbarDropdownItem,
+                Utils.sprintf('<input type="checkbox" class="toggle-all" %s> <span>%s</span>',
+                  allFieldsVisible ? 'checked="checked"' : '', this.options.formatColumnsToggleAll())
+              )
+            )
 
-        let visibleColumns = 0
-        this.columns.forEach((column, i) => {
-          if (column.visible) {
-            visibleColumns++
+            html.push(this.constants.html.toolbarDropdownSeparator)
           }
+
+          let visibleColumns = 0
+          this.columns.forEach((column, i) => {
+            if (column.visible) {
+              visibleColumns++
+            }
+          })
+
+          this.columns.forEach((column, i) => {
+            if (this.isSelectionColumn(column)) {
+              return
+            }
+
+            if (this.options.cardView && !column.cardVisible) {
+              return
+            }
+
+            const checked = column.visible ? ' checked="checked"' : ''
+            const disabled = (visibleColumns <= this.options.minimumCountColumns) && checked ? ' disabled="disabled"' : ''
+            if (column.switchable) {
+              html.push(Utils.sprintf(this.constants.html.toolbarDropdownItem,
+                Utils.sprintf('<input type="checkbox" data-field="%s" value="%s"%s%s> <span>%s</span>',
+                  column.field, i, checked, disabled, column.title)))
+              switchableCount++
+            }
+          })
+          html.push(this.constants.html.toolbarDropdown[1], '</div>')
+          return html.join('')
         })
+      }
+    })
 
-        this.columns.forEach((column, i) => {
-          if (this.isSelectionColumn(column)) {
-            return
-          }
+    const buttonsHtml = {}
+    for (const [buttonName, buttonConfig] of Object.entries(this.buttons)) {
+      let buttonHtml
+      if (buttonConfig.hasOwnProperty('html')) {
+        buttonHtml = Utils.calculateObjectValue(this.options, buttonConfig.html)
+      } else {
+        buttonHtml = `<button class="${this.constants.buttonsClass}" type="button" name="${buttonName}"`
 
-          if (opts.cardView && !column.cardVisible) {
-            return
+        if (buttonConfig.hasOwnProperty('attributes')) {
+          for (const [attributeName, value] of Object.entries(buttonConfig.attributes)) {
+            const attributeValue = Utils.calculateObjectValue(this.options, value)
+            buttonHtml += ` ${attributeName}="${attributeValue}"`
           }
+        }
 
-          const checked = column.visible ? ' checked="checked"' : ''
-          const disabled = (visibleColumns <= this.options.minimumCountColumns) && checked ? ' disabled="disabled"' : ''
-          if (column.switchable) {
-            html.push(Utils.sprintf(this.constants.html.toolbarDropdownItem,
-              Utils.sprintf('<input type="checkbox" data-field="%s" value="%s"%s%s> <span>%s</span>',
-                column.field, i, checked, disabled, column.title)))
-            switchableCount++
-          }
-        })
-        html.push(this.constants.html.toolbarDropdown[1], '</div>')
-        return html.join('')
-      })()
-    }
+        buttonHtml += '>'
+
+        if (this.options.showButtonIcons && buttonConfig.hasOwnProperty('icon')) {
+          const icon = Utils.calculateObjectValue(this.options, buttonConfig.icon)
+          buttonHtml += Utils.sprintf(this.constants.html.icon, this.options.iconsPrefix, icon) + ' '
+        }
+
+        if (this.options.showButtonText && buttonConfig.hasOwnProperty('text')) {
+          buttonHtml += Utils.calculateObjectValue(this.options, buttonConfig.text)
+        }
+
+        buttonHtml += '</button>'
+      }
+
+      buttonsHtml[buttonName] = buttonHtml
+      const optionName = `show${buttonName.charAt(0).toUpperCase()}${buttonName.substring(1)}`
+      const showOption = this.options[optionName]
+      if ((!buttonConfig.hasOwnProperty('render') || buttonConfig.hasOwnProperty('render') && buttonConfig.render) && (showOption === undefined || showOption === true)) {
+        this.options[optionName] = true
+      }
 
-    if (typeof opts.buttonsOrder === 'string') {
-      opts.buttonsOrder = opts.buttonsOrder.replace(/\[|\]| |'/g, '').toLowerCase().split(',')
+      if (!this.options.buttonsOrder.includes(buttonName)) {
+        this.options.buttonsOrder.push(buttonName)
+      }
     }
 
-    for (const button of opts.buttonsOrder) {
-      if (opts['show' + button.charAt(0).toUpperCase() + button.substring(1)]) {
+    // Adding the button html to the final toolbar html when the showOption is true
+    for (const button of this.options.buttonsOrder) {
+      const showOption = this.options[`show${button.charAt(0).toUpperCase()}${button.substring(1)}`]
+      if (showOption) {
         html.push(buttonsHtml[button])
       }
     }
@@ -659,34 +722,31 @@ class BootstrapTable {
       this.$toolbar.append(html.join(''))
     }
 
-    if (opts.showPaginationSwitch) {
-      this.$toolbar.find('button[name="paginationSwitch"]')
-        .off('click').on('click', () => this.togglePagination())
-    }
-
-    if (opts.showFullscreen) {
-      this.$toolbar.find('button[name="fullscreen"]')
-        .off('click').on('click', () => this.toggleFullscreen())
-    }
-
-    if (opts.showRefresh) {
-      this.$toolbar.find('button[name="refresh"]')
-        .off('click').on('click', () => this.refresh())
-    }
+    for (const [buttonName, buttonConfig] of Object.entries(this.buttons)) {
+      if (buttonConfig.hasOwnProperty('event')) {
+        if (typeof buttonConfig.event === 'function' || typeof buttonConfig.event === 'string') {
+          const event = typeof buttonConfig.event === 'string' ? window[buttonConfig.event] : buttonConfig.event
+          this.$toolbar.find(`button[name="${buttonName}"]`)
+            .off('click')
+            .on('click', () => event.call(this))
+          continue
+        }
 
-    if (opts.showToggle) {
-      this.$toolbar.find('button[name="toggle"]')
-        .off('click').on('click', () => {
-          this.toggleView()
-        })
+        for (const [eventType, eventFunction] of Object.entries(buttonConfig.event)) {
+          const event = typeof eventFunction === 'string' ? window[eventFunction] : eventFunction
+          this.$toolbar.find(`button[name="${buttonName}"]`)
+            .off(eventType)
+            .on(eventType, () => event.call(this))
+        }
+      }
     }
 
-    if (opts.showColumns) {
+    if (this.options.showColumns) {
       $keepOpen = this.$toolbar.find('.keep-open')
       const $checkboxes = $keepOpen.find('input[type="checkbox"]:not(".toggle-all")')
       const $toggleAll = $keepOpen.find('input[type="checkbox"].toggle-all')
 
-      if (switchableCount <= opts.minimumCountColumns) {
+      if (switchableCount <= this.options.minimumCountColumns) {
         $keepOpen.find('input').prop('disabled', true)
       }
 
@@ -706,7 +766,7 @@ class BootstrapTable {
         this._toggleAllColumns($(currentTarget).prop('checked'))
       })
 
-      if (opts.showColumnsSearch) {
+      if (this.options.showColumnsSearch) {
         const $columnsSearch = $keepOpen.find('[name="columnsSearch"]')
         const $listItems = $keepOpen.find('.dropdown-item-marker')
         $columnsSearch.on('keyup paste change', ({currentTarget}) => {
@@ -749,31 +809,31 @@ class BootstrapTable {
       html = []
       const showSearchButton = Utils.sprintf(this.constants.html.searchButton,
         this.constants.buttonsClass,
-        opts.formatSearch(),
-        opts.showButtonIcons ? Utils.sprintf(this.constants.html.icon, opts.iconsPrefix, opts.icons.search) : '',
-        opts.showButtonText ? opts.formatSearch() : ''
+        this.options.formatSearch(),
+        this.options.showButtonIcons ? Utils.sprintf(this.constants.html.icon, this.options.iconsPrefix, this.options.icons.search) : '',
+        this.options.showButtonText ? this.options.formatSearch() : ''
       )
       const showSearchClearButton = Utils.sprintf(this.constants.html.searchClearButton,
         this.constants.buttonsClass,
-        opts.formatClearSearch(),
-        opts.showButtonIcons ? Utils.sprintf(this.constants.html.icon, opts.iconsPrefix, opts.icons.clearSearch) : '',
-        opts.showButtonText ? opts.formatClearSearch() : ''
+        this.options.formatClearSearch(),
+        this.options.showButtonIcons ? Utils.sprintf(this.constants.html.icon, this.options.iconsPrefix, this.options.icons.clearSearch) : '',
+        this.options.showButtonText ? this.options.formatClearSearch() : ''
       )
       const searchInputHtml = `<input class="${this.constants.classes.input}
-        ${Utils.sprintf(' %s%s', this.constants.classes.inputPrefix, opts.iconSize)}
-        search-input" type="search" placeholder="${opts.formatSearch()}" autocomplete="off">`
+        ${Utils.sprintf(' %s%s', this.constants.classes.inputPrefix, this.options.iconSize)}
+        search-input" type="search" placeholder="${this.options.formatSearch()}" autocomplete="off">`
       let searchInputFinalHtml = searchInputHtml
 
-      if (opts.showSearchButton || opts.showSearchClearButton) {
-        const buttonsHtml = (opts.showSearchButton ? showSearchButton : '') +
-          (opts.showSearchClearButton ? showSearchClearButton : '')
+      if (this.options.showSearchButton || this.options.showSearchClearButton) {
+        const buttonsHtml = (this.options.showSearchButton ? showSearchButton : '') +
+          (this.options.showSearchClearButton ? showSearchClearButton : '')
 
-        searchInputFinalHtml = opts.search ? Utils.sprintf(this.constants.html.inputGroup,
+        searchInputFinalHtml = this.options.search ? Utils.sprintf(this.constants.html.inputGroup,
           searchInputHtml, buttonsHtml) : buttonsHtml
       }
 
       html.push(Utils.sprintf(`
-        <div class="${this.constants.classes.pull}-${opts.searchAlign} search ${this.constants.classes.inputGroup}">
+        <div class="${this.constants.classes.pull}-${this.options.searchAlign} search ${this.constants.classes.inputGroup}">
           %s
         </div>
       `, searchInputFinalHtml))
@@ -785,7 +845,7 @@ class BootstrapTable {
           clearTimeout(timeoutId) // doesn't matter if it's 0
           timeoutId = setTimeout(() => {
             this.onSearch({currentTarget: $searchInput})
-          }, opts.searchTimeOut)
+          }, this.options.searchTimeOut)
         })
 
         if (opts.searchOnEnterKey) {
@@ -795,7 +855,7 @@ class BootstrapTable {
         handleInputEvent($searchInput)
       }
 
-      if (opts.showSearchClearButton) {
+      if (this.options.showSearchClearButton) {
         this.$toolbar.find('.search button[name=clearSearch]').click(() => {
           this.resetSearch()
         })
@@ -1890,10 +1950,10 @@ class BootstrapTable {
   trigger (_name, ...args) {
     const name = `${_name}.bs.table`
     this.options[BootstrapTable.EVENTS[name]](...[...args, this])
-    this.$el.trigger($.Event(name, { sender: this }), args)
+    this.$el.trigger($.Event(name, {sender: this}), args)
 
     this.options.onAll(name, ...[...args, this])
-    this.$el.trigger($.Event('all.bs.table', { sender: this }), [name, args])
+    this.$el.trigger($.Event('all.bs.table', {sender: this}), [name, args])
   }
 
   resetHeader () {

+ 1 - 0
src/constants/index.js

@@ -167,6 +167,7 @@ const CONSTANTS = {
 const DEFAULTS = {
   height: undefined,
   classes: 'table table-bordered table-hover',
+  buttons: {},
   theadClasses: '',
   headerStyle (column) {
     return {}

+ 17 - 19
src/extensions/auto-refresh/bootstrap-table-auto-refresh.js

@@ -42,38 +42,36 @@ $.BootstrapTable = class extends $.BootstrapTable {
   }
 
   initToolbar (...args) {
-    super.initToolbar(...args)
-
     if (this.options.autoRefresh) {
-      const $btnGroup = this.$toolbar.find('>.columns')
-      let $btnAutoRefresh = $btnGroup.find('.auto-refresh')
-
-      if (!$btnAutoRefresh.length) {
-        $btnAutoRefresh = $(`
-          <button class="auto-refresh ${this.constants.buttonsClass}
-          ${this.options.autoRefreshStatus ? ` ${this.constants.classes.buttonActive}` : ''}"
-          type="button" title="${this.options.formatAutoRefresh()}">
-          ${ this.options.showButtonIcons ? Utils.sprintf(this.constants.html.icon, this.options.iconsPrefix, this.options.icons.autoRefresh) : ''}
-          ${ this.options.showButtonText ? this.options.formatAutoRefresh() : ''}
-          </button>
-        `).appendTo($btnGroup)
-
-        $btnAutoRefresh.on('click', $.proxy(this.toggleAutoRefresh, this))
-      }
+      this.buttons = Object.assign(this.buttons, {
+        autoRefresh: {
+          'html': `
+            <button class="auto-refresh ${this.constants.buttonsClass}
+              ${this.options.autoRefreshStatus ? ` ${this.constants.classes.buttonActive}` : ''}"
+              type="button" name="autoRefresh" title="${this.options.formatAutoRefresh()}">
+              ${ this.options.showButtonIcons ? Utils.sprintf(this.constants.html.icon, this.options.iconsPrefix, this.options.icons.autoRefresh) : ''}
+              ${ this.options.showButtonText ? this.options.formatAutoRefresh() : ''}
+            </button>
+           `,
+          'event': this.toggleAutoRefresh
+        }
+      })
     }
+
+    super.initToolbar(...args)
   }
 
   toggleAutoRefresh () {
     if (this.options.autoRefresh) {
       if (this.options.autoRefreshStatus) {
         clearInterval(this.options.autoRefreshFunction)
-        this.$toolbar.find('>.columns').find('.auto-refresh')
+        this.$toolbar.find('>.columns .auto-refresh')
           .removeClass(this.constants.classes.buttonActive)
       } else {
         this.options.autoRefreshFunction = setInterval(() => {
           this.refresh({silent: this.options.autoRefreshSilent})
         }, this.options.autoRefreshInterval * 1000)
-        this.$toolbar.find('>.columns').find('.auto-refresh')
+        this.$toolbar.find('>.columns .auto-refresh')
           .addClass(this.constants.classes.buttonActive)
       }
       this.options.autoRefreshStatus = !this.options.autoRefreshStatus

+ 23 - 12
src/extensions/copy-rows/bootstrap-table-copy-rows.js

@@ -5,6 +5,13 @@
 
 const Utils = $.fn.bootstrapTable.utils
 
+$.extend($.fn.bootstrapTable.locales, {
+  formatCopyRows () {
+    return 'Copy Rows'
+  }
+})
+$.extend($.fn.bootstrapTable.defaults, $.fn.bootstrapTable.locales)
+
 $.extend($.fn.bootstrapTable.defaults.icons, {
   copy: {
     bootstrap3: 'glyphicon-copy icon-pencil',
@@ -42,20 +49,24 @@ $.fn.bootstrapTable.methods.push(
 $.BootstrapTable = class extends $.BootstrapTable {
 
   initToolbar (...args) {
-    super.initToolbar(...args)
+    if (this.options.showCopyRows && this.header.stateField) {
+      this.buttons = Object.assign(this.buttons, {
+        copyRows: {
+          'text': this.options.formatCopyRows(),
+          'icon': this.options.icons.copy,
+          'event': this.copyColumnsToClipboard,
+          'attributes': {
+            'aria-label': this.options.formatCopyRows(),
+            'title': this.options.formatCopyRows()
+          }
+        }
+      })
+    }
 
-    const $btnGroup = this.$toolbar.find('>.columns')
+    super.initToolbar(...args)
+    this.$copyButton = this.$toolbar.find('>.columns [name="copyRows"]')
 
     if (this.options.showCopyRows && this.header.stateField) {
-      this.$copyButton = $(`
-        <button class="${this.constants.buttonsClass}">
-        ${Utils.sprintf(this.constants.html.icon, this.options.iconsPrefix, this.options.icons.copy)}
-        </button>
-      `)
-      $btnGroup.append(this.$copyButton)
-      this.$copyButton.click(() => {
-        this.copyColumnsToClipboard()
-      })
       this.updateCopyButton()
     }
   }
@@ -90,7 +101,7 @@ $.BootstrapTable = class extends $.BootstrapTable {
   }
 
   updateCopyButton () {
-    if (this.options.showCopyRows) {
+    if (this.options.showCopyRows && this.header.stateField && this.$copyButton) {
       this.$copyButton.prop('disabled', !this.getSelections().length)
     }
   }

+ 13 - 16
src/extensions/custom-view/bootstrap-table-custom-view.js

@@ -54,24 +54,21 @@ $.BootstrapTable = class extends $.BootstrapTable {
   }
 
   initToolbar (...args) {
-    super.initToolbar(...args)
-
     if (this.options.customView && this.options.showCustomViewButton) {
-      const $btnGroup = this.$toolbar.find('>.' + this.constants.classes.buttonsGroup.split(' ').join('.')).first()
-      let $btnToggleCustomView = $btnGroup.find('.toggle-custom-view')
-
-      if (!$btnToggleCustomView.length) {
-        $btnToggleCustomView = $(`
-          <button class="toggle-custom-view ${this.constants.buttonsClass}"
-          type="button" title="${this.options.formatToggleCustomView()}">
-          ${this.options.showButtonIcons ? Utils.sprintf(this.constants.html.icon, this.options.iconsPrefix, this.options.icons.customView) : ''}
-          ${this.options.showButtonText ? this.options.formatToggleCustomView() : ''}
-          </button>
-        `).appendTo($btnGroup)
-
-        $btnToggleCustomView.on('click', $.proxy(this.toggleCustomView, this))
-      }
+      this.buttons = Object.assign(this.buttons, {
+        customView: {
+          'text': this.options.formatToggleCustomView(),
+          'icon': this.options.icons.customView,
+          'event': this.toggleCustomView,
+          'attributes': {
+            'aria-label': this.options.formatToggleCustomView(),
+            'title': this.options.formatToggleCustomView()
+          }
+        }
+      })
     }
+
+    super.initToolbar(...args)
   }
 
   initBody () {

+ 46 - 41
src/extensions/export/bootstrap-table-export.js

@@ -72,56 +72,61 @@ $.BootstrapTable = class extends $.BootstrapTable {
     const o = this.options
 
     this.showToolbar = this.showToolbar || o.showExport
-
-    super.initToolbar(...args)
-
-    if (!this.options.showExport) {
-      return
-    }
     const $btnGroup = this.$toolbar.find('>.columns')
-    this.$export = $btnGroup.find('div.export')
 
-    if (this.$export.length) {
-      this.updateExportButton()
-      return
-    }
+    if (this.options.showExport) {
+      var exportTypes = o.exportTypes
 
-    let $menu = $(this.constants.html.toolbarDropdown.join(''))
+      if (typeof exportTypes === 'string') {
+        const types = exportTypes.slice(1, -1).replace(/ /g, '').split(',')
+        exportTypes = types.map(t => t.slice(1, -1))
+      }
 
-    let exportTypes = o.exportTypes
+      this.$export = this.$toolbar.find('>.columns div.export')
+      if (this.$export.length) {
+        this.updateExportButton()
+        return
+      }
 
-    if (typeof exportTypes === 'string') {
-      const types = exportTypes.slice(1, -1).replace(/ /g, '').split(',')
-      exportTypes = types.map(t => t.slice(1, -1))
+      this.buttons = Object.assign(this.buttons, {
+        'export': {
+          'html': exportTypes.length === 1 ? `
+            <div class="export ${this.constants.classes.buttonsDropdown}"
+            data-type="${exportTypes[0]}">
+            <button class="${this.constants.buttonsClass}"
+            aria-label="Export"
+            type="button"
+            title="${o.formatExport()}">
+            ${o.showButtonIcons ? Utils.sprintf(this.constants.html.icon, o.iconsPrefix, o.icons.export) : ''}
+            ${o.showButtonText ? o.formatExport() : ''}
+            </button>
+            </div>
+          ` : `
+            <div class="export ${this.constants.classes.buttonsDropdown}">
+            <button class="${this.constants.buttonsClass} dropdown-toggle"
+            aria-label="Export"
+            data-toggle="dropdown"
+            type="button"
+            title="${o.formatExport()}">
+            ${o.showButtonIcons ? Utils.sprintf(this.constants.html.icon, o.iconsPrefix, o.icons.export) : ''}
+            ${o.showButtonText ? o.formatExport() : ''}
+            ${this.constants.html.dropdownCaret}
+            </button>
+            </div>
+          `
+        }
+      })
     }
 
-    this.$export = $(exportTypes.length === 1 ? `
-      <div class="export ${this.constants.classes.buttonsDropdown}"
-      data-type="${exportTypes[0]}">
-      <button class="${this.constants.buttonsClass}"
-      aria-label="Export"
-      type="button"
-      title="${o.formatExport()}">
-      ${o.showButtonIcons ? Utils.sprintf(this.constants.html.icon, o.iconsPrefix, o.icons.export) : ''}
-      ${o.showButtonText ? o.formatExport() : ''}
-      </button>
-      </div>
-    ` : `
-      <div class="export ${this.constants.classes.buttonsDropdown}">
-      <button class="${this.constants.buttonsClass} dropdown-toggle"
-      aria-label="Export"
-      data-toggle="dropdown"
-      type="button"
-      title="${o.formatExport()}">
-      ${o.showButtonIcons ? Utils.sprintf(this.constants.html.icon, o.iconsPrefix, o.icons.export) : ''}
-      ${o.showButtonText ? o.formatExport() : ''}
-      ${this.constants.html.dropdownCaret}
-      </button>
-      </div>
-    `).appendTo($btnGroup)
+    super.initToolbar(...args)
+    this.$export = this.$toolbar.find('>.columns div.export')
 
-    let $items = this.$export
+    if (!this.options.showExport) {
+      return
+    }
 
+    let $menu = $(this.constants.html.toolbarDropdown.join(''))
+    let $items = this.$export
     if (exportTypes.length > 1) {
       this.$export.append($menu)
 

+ 13 - 15
src/extensions/filter-control/bootstrap-table-filter-control.js

@@ -333,24 +333,22 @@ $.BootstrapTable = class extends $.BootstrapTable {
   initToolbar () {
     this.showToolbar = this.showToolbar || this.options.showFilterControlSwitch
     this.showSearchClearButton = this.options.filterControl && this.options.showSearchClearButton
-    super.initToolbar()
 
     if (this.options.showFilterControlSwitch) {
-      const $btnGroup = this.$toolbar.find('>.columns')
-      let $btnFilterControlSwitch = $btnGroup.find('.filter-control-switch')
-
-      if (!$btnFilterControlSwitch.length) {
-        $btnFilterControlSwitch = $(`
-          <button class="filter-control-switch ${this.constants.buttonsClass}"
-          type="button" title="${this.options.formatFilterControlSwitch()}">
-          ${this.options.showButtonIcons ? Utils.sprintf(this.constants.html.icon, this.options.iconsPrefix, this.options.filterControlVisible ? this.options.icons.filterControlSwitchHide : this.options.icons.filterControlSwitchShow) : ''}
-          ${this.options.showButtonText ? this.options.filterControlVisible ? this.options.formatFilterControlSwitchHide() : this.options.formatFilterControlSwitchShow() : ''}
-          </button>
-        `).appendTo($btnGroup)
-
-        $btnFilterControlSwitch.on('click', $.proxy(this.toggleFilterControl, this))
-      }
+      this.buttons = Object.assign(this.buttons, {
+        filterControlSwitch: {
+          'text': this.options.filterControlVisible ? this.options.formatFilterControlSwitchHide() : this.options.formatFilterControlSwitchShow(),
+          'icon': this.options.filterControlVisible ? this.options.icons.filterControlSwitchHide : this.options.icons.filterControlSwitchShow,
+          'event': this.toggleFilterControl,
+          'attributes': {
+            'aria-label': this.options.formatFilterControlSwitch(),
+            'title': this.options.formatFilterControlSwitch()
+          }
+        }
+      })
     }
+
+    super.initToolbar()
   }
 
   resetSearch (text) {

+ 9 - 7
src/extensions/multiple-sort/bootstrap-table-multiple-sort.js

@@ -582,6 +582,9 @@ BootstrapTable.prototype.initToolbar = function (...args) {
   const that = this
   const sortModalSelector = `sortModal_${this.$el.attr('id')}`
   const sortModalId = `#${sortModalSelector}`
+  const $multiSortBtn = this.$toolbar.find('div.multi-sort')
+  const o = this.options
+
   this.$sortModal = $(sortModalId)
   this.sortModalSelector = sortModalSelector
 
@@ -589,6 +592,12 @@ BootstrapTable.prototype.initToolbar = function (...args) {
     that.onMultipleSort()
   }
 
+  this.buttons = Object.assign(this.buttons, {
+    multipleSort: {
+      'html': Utils.sprintf(theme.html.multipleSortButton, that.sortModalSelector, this.options.formatMultipleSort(), Utils.sprintf(that.constants.html.icon, o.iconsPrefix, theme.icons.sort))
+    }
+  })
+
   _initToolbar.apply(this, Array.prototype.slice.apply(args))
 
   if (that.options.sidePagination === 'server' && !isSingleSort && that.options.sortPriority !== null) {
@@ -600,14 +609,7 @@ BootstrapTable.prototype.initToolbar = function (...args) {
   }
 
   if (this.options.showMultiSort) {
-    const $btnGroup = this.$toolbar.find('>.' + that.constants.classes.buttonsGroup.split(' ').join('.')).first()
-    let $multiSortBtn = this.$toolbar.find('div.multi-sort')
-    const o = that.options
-
     if (!$multiSortBtn.length && this.options.showMultiSortButton) {
-      $multiSortBtn = Utils.sprintf(theme.html.multipleSortButton, that.sortModalSelector, this.options.formatMultipleSort(), Utils.sprintf(that.constants.html.icon, o.iconsPrefix, theme.icons.sort))
-      $btnGroup.append($multiSortBtn)
-
       if ($.fn.bootstrapTable.theme === 'semantic') {
         this.$toolbar.find('.multi-sort').on('click', () => {
           $(sortModalId).modal('show')

+ 22 - 19
src/extensions/print/bootstrap-table-print.js

@@ -48,6 +48,13 @@ function printPageBuilderDefault (table) {
   </html>`
 }
 
+$.extend($.fn.bootstrapTable.locales, {
+  formatPrint () {
+    return 'Print'
+  }
+})
+$.extend($.fn.bootstrapTable.defaults, $.fn.bootstrapTable.locales)
+
 $.extend($.fn.bootstrapTable.defaults, {
   showPrint: false,
   printAsFilteredAndSortedOnUI: true,
@@ -85,27 +92,23 @@ $.BootstrapTable = class extends $.BootstrapTable {
   initToolbar (...args) {
     this.showToolbar = this.showToolbar || this.options.showPrint
 
-    super.initToolbar(...args)
-
-    if (!this.options.showPrint) {
-      return
-    }
-
-    const $btnGroup = this.$toolbar.find('>.columns')
-    let $print = $btnGroup.find('button.bs-print')
-
-    if (!$print.length) {
-      $print = $(`
-        <button class="${this.constants.buttonsClass} bs-print" type="button">
-        <i class="${this.options.iconsPrefix} ${this.options.icons.print}"></i>
-        </button>`
-      ).appendTo($btnGroup)
+    if (this.options.showPrint) {
+      this.buttons = Object.assign(this.buttons, {
+        print: {
+          'text': this.options.formatPrint(),
+          'icon': this.options.icons.print,
+          'event': () => {
+            this.doPrint(this.options.printAsFilteredAndSortedOnUI ? this.getData() : this.options.data.slice(0))
+          },
+          'attributes': {
+            'aria-label': this.options.formatPrint(),
+            'title': this.options.formatPrint()
+          }
+        }
+      })
     }
 
-    $print.off('click').on('click', () => {
-      this.doPrint(this.options.printAsFilteredAndSortedOnUI ?
-        this.getData() : this.options.data.slice(0))
-    })
+    super.initToolbar(...args)
   }
 
   mergeCells (options) {

+ 13 - 16
src/extensions/toolbar/bootstrap-table-toolbar.js

@@ -217,24 +217,21 @@ $.BootstrapTable = class extends $.BootstrapTable {
       o.advancedSearch &&
       o.idTable)
 
-    super.initToolbar()
-
-    if (!o.search || !o.advancedSearch || !o.idTable) {
-      return
+    if (o.search && o.advancedSearch && o.idTable) {
+      this.buttons = Object.assign(this.buttons, {
+        advancedSearch: {
+          'text': this.options.formatAdvancedSearch(),
+          'icon': this.options.icons.advancedSearchIcon,
+          'event': this.showAvdSearch,
+          'attributes': {
+            'aria-label': this.options.formatAdvancedSearch(),
+            'title': this.options.formatAdvancedSearch()
+          }
+        }
+      })
     }
 
-    this.$toolbar.find('>.columns').append(`
-      <button class="${this.constants.buttonsClass} "
-        type="button"
-        name="advancedSearch"
-        aria-label="advanced search"
-        title="${o.formatAdvancedSearch()}">
-        ${ this.options.showButtonIcons ? Utils.sprintf(this.constants.html.icon, o.iconsPrefix, o.icons.advancedSearchIcon) : ''}
-        ${ this.options.showButtonText ? this.options.formatAdvancedSearch() : ''}
-      </button>
-    `)
-
-    this.$toolbar.find('button[name="advancedSearch"]').off('click').on('click', () => this.showAvdSearch())
+    super.initToolbar()
   }
 
   showAvdSearch () {

+ 6 - 0
src/locale/bootstrap-table-af-ZA.js

@@ -4,6 +4,12 @@
  */
 
 $.fn.bootstrapTable.locales['af-ZA'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'Besig om te laai, wag asseblief'
   },

+ 6 - 0
src/locale/bootstrap-table-ar-SA.js

@@ -4,6 +4,12 @@
  */
 
 $.fn.bootstrapTable.locales['ar-SA'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'جاري التحميل, يرجى الإنتظار'
   },

+ 6 - 0
src/locale/bootstrap-table-bg-BG.js

@@ -4,6 +4,12 @@
  */
 
 $.fn.bootstrapTable.locales['bg-BG'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'Зареждане, моля изчакайте'
   },

+ 6 - 0
src/locale/bootstrap-table-ca-ES.js

@@ -5,6 +5,12 @@
  */
 
 $.fn.bootstrapTable.locales['ca-ES'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'Espereu, si us plau'
   },

+ 6 - 0
src/locale/bootstrap-table-cs-CZ.js

@@ -5,6 +5,12 @@
  */
 
 $.fn.bootstrapTable.locales['cs-CZ'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'Čekejte, prosím'
   },

+ 6 - 0
src/locale/bootstrap-table-da-DK.js

@@ -4,6 +4,12 @@
  */
 
 $.fn.bootstrapTable.locales['da-DK'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'Indlæser, vent venligst'
   },

+ 9 - 3
src/locale/bootstrap-table-de-DE.js

@@ -4,6 +4,12 @@
 */
 
 $.fn.bootstrapTable.locales['de-DE'] = {
+  formatCopyRows () {
+    return 'Zeilen kopieren'
+  },
+  formatPrint () {
+    return 'Drucken'
+  },
   formatLoadingMessage () {
     return 'Lade, bitte warten'
   },
@@ -87,13 +93,13 @@ $.fn.bootstrapTable.locales['de-DE'] = {
     return 'Schließen'
   },
   formatFilterControlSwitch () {
-    return 'Verstecke/Zeige controls'
+    return 'Verstecke/Zeige Filter'
   },
   formatFilterControlSwitchHide () {
-    return 'Verstecke controls'
+    return 'Verstecke Filter'
   },
   formatFilterControlSwitchShow () {
-    return 'Zeige controls'
+    return 'Zeige Filter'
   }
 }
 

+ 6 - 0
src/locale/bootstrap-table-el-GR.js

@@ -4,6 +4,12 @@
  */
 
 $.fn.bootstrapTable.locales['el-GR'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'Φορτώνει, παρακαλώ περιμένετε'
   },

+ 6 - 0
src/locale/bootstrap-table-en-US.js

@@ -4,6 +4,12 @@
  */
 
 $.fn.bootstrapTable.locales['en-US'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'Loading, please wait'
   },

+ 6 - 0
src/locale/bootstrap-table-es-AR.js

@@ -5,6 +5,12 @@
  */
 
 $.fn.bootstrapTable.locales['es-AR'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'Cargando, espere por favor'
   },

+ 6 - 0
src/locale/bootstrap-table-es-CL.js

@@ -5,6 +5,12 @@
  */
 
 $.fn.bootstrapTable.locales['es-CL'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'Cargando, espere por favor'
   },

+ 6 - 0
src/locale/bootstrap-table-es-CR.js

@@ -4,6 +4,12 @@
  */
 
 $.fn.bootstrapTable.locales['es-CR'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'Cargando, por favor espere'
   },

+ 6 - 0
src/locale/bootstrap-table-es-ES.js

@@ -4,6 +4,12 @@
  */
 
 $.fn.bootstrapTable.locales['es-ES'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'Por favor espere'
   },

+ 6 - 0
src/locale/bootstrap-table-es-MX.js

@@ -6,6 +6,12 @@
  */
 
 $.fn.bootstrapTable.locales['es-MX'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'Cargando, espere por favor'
   },

+ 6 - 0
src/locale/bootstrap-table-es-NI.js

@@ -4,6 +4,12 @@
  */
 
 $.fn.bootstrapTable.locales['es-NI'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'Cargando, por favor espere'
   },

+ 6 - 0
src/locale/bootstrap-table-es-SP.js

@@ -4,6 +4,12 @@
  */
 
 $.fn.bootstrapTable.locales['es-SP'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'Cargando, por favor espera'
   },

+ 6 - 0
src/locale/bootstrap-table-et-EE.js

@@ -4,6 +4,12 @@
  */
 
 $.fn.bootstrapTable.locales['et-EE'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'Päring käib, palun oota'
   },

+ 6 - 0
src/locale/bootstrap-table-eu-EU.js

@@ -4,6 +4,12 @@
  */
 
 $.fn.bootstrapTable.locales['eu-EU'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'Itxaron mesedez'
   },

+ 6 - 0
src/locale/bootstrap-table-fa-IR.js

@@ -4,6 +4,12 @@
  */
 
 $.fn.bootstrapTable.locales['fa-IR'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'در حال بارگذاری, لطفا صبر کنید'
   },

+ 6 - 0
src/locale/bootstrap-table-fi-FI.js

@@ -4,6 +4,12 @@
  */
 
 $.fn.bootstrapTable.locales['fi-FI'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'Ladataan, ole hyvä ja odota'
   },

+ 6 - 0
src/locale/bootstrap-table-fr-BE.js

@@ -5,6 +5,12 @@
  */
 
 $.fn.bootstrapTable.locales['fr-BE'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'Chargement en cours'
   },

+ 6 - 0
src/locale/bootstrap-table-fr-CH.js

@@ -4,6 +4,12 @@
  */
 
 $.fn.bootstrapTable.locales['fr-CH'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'Chargement en cours'
   },

+ 6 - 0
src/locale/bootstrap-table-fr-FR.js

@@ -6,6 +6,12 @@
  */
 
 $.fn.bootstrapTable.locales['fr-FR'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'Chargement en cours'
   },

+ 6 - 0
src/locale/bootstrap-table-fr-LU.js

@@ -4,6 +4,12 @@
  */
 
 $.fn.bootstrapTable.locales['fr-LU'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'Chargement en cours'
   },

+ 6 - 0
src/locale/bootstrap-table-he-IL.js

@@ -4,6 +4,12 @@
  */
 
 $.fn.bootstrapTable.locales['he-IL'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'טוען, נא להמתין'
   },

+ 6 - 0
src/locale/bootstrap-table-hr-HR.js

@@ -5,6 +5,12 @@
  */
 
 $.fn.bootstrapTable.locales['hr-HR'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'Molimo pričekajte'
   },

+ 6 - 0
src/locale/bootstrap-table-hu-HU.js

@@ -4,6 +4,12 @@
  */
 
 $.fn.bootstrapTable.locales['hu-HU'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'Betöltés, kérem várjon'
   },

+ 6 - 0
src/locale/bootstrap-table-id-ID.js

@@ -4,6 +4,12 @@
  */
 
 $.fn.bootstrapTable.locales['id-ID'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'Memuat, mohon tunggu'
   },

+ 6 - 0
src/locale/bootstrap-table-it-IT.js

@@ -6,6 +6,12 @@
  */
 
 $.fn.bootstrapTable.locales['it-IT'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'Caricamento in corso'
   },

+ 6 - 0
src/locale/bootstrap-table-ja-JP.js

@@ -4,6 +4,12 @@
  */
 
 $.fn.bootstrapTable.locales['ja-JP'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return '読み込み中です。少々お待ちください。'
   },

+ 6 - 0
src/locale/bootstrap-table-ka-GE.js

@@ -4,6 +4,12 @@
  */
 
 $.fn.bootstrapTable.locales['ka-GE'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'იტვირთება, გთხოვთ მოიცადოთ'
   },

+ 6 - 0
src/locale/bootstrap-table-ko-KR.js

@@ -4,6 +4,12 @@
  */
 
 $.fn.bootstrapTable.locales['ko-KR'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return '데이터를 불러오는 중입니다'
   },

+ 6 - 0
src/locale/bootstrap-table-ms-MY.js

@@ -4,6 +4,12 @@
  */
 
 $.fn.bootstrapTable.locales['ms-MY'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'Permintaan sedang dimuatkan. Sila tunggu sebentar'
   },

+ 6 - 0
src/locale/bootstrap-table-nb-NO.js

@@ -4,6 +4,12 @@
  */
 
 $.fn.bootstrapTable.locales['nb-NO'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'Oppdaterer, vennligst vent'
   },

+ 6 - 0
src/locale/bootstrap-table-nl-BE.js

@@ -4,6 +4,12 @@
  */
 
 $.fn.bootstrapTable.locales['nl-BE'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'Laden, even geduld'
   },

+ 6 - 0
src/locale/bootstrap-table-nl-NL.js

@@ -5,6 +5,12 @@
  */
 
 $.fn.bootstrapTable.locales['nl-NL'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'Laden, even geduld'
   },

+ 6 - 0
src/locale/bootstrap-table-pl-PL.js

@@ -4,6 +4,12 @@
  */
 
 $.fn.bootstrapTable.locales['pl-PL'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'Ładowanie, proszę czekać'
   },

+ 6 - 0
src/locale/bootstrap-table-pt-BR.js

@@ -7,6 +7,12 @@
  */
 
 $.fn.bootstrapTable.locales['pt-BR'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'Carregando, aguarde'
   },

+ 6 - 0
src/locale/bootstrap-table-pt-PT.js

@@ -4,6 +4,12 @@
  */
 
 $.fn.bootstrapTable.locales['pt-PT'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'A carregar, por favor aguarde'
   },

+ 6 - 0
src/locale/bootstrap-table-ro-RO.js

@@ -4,6 +4,12 @@
  */
 
 $.fn.bootstrapTable.locales['ro-RO'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'Se incarca, va rugam asteptati'
   },

+ 6 - 0
src/locale/bootstrap-table-ru-RU.js

@@ -4,6 +4,12 @@
  */
 
 $.fn.bootstrapTable.locales['ru-RU'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'Пожалуйста, подождите, идёт загрузка'
   },

+ 6 - 0
src/locale/bootstrap-table-sk-SK.js

@@ -4,6 +4,12 @@
  */
 
 $.fn.bootstrapTable.locales['sk-SK'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'Prosím čakajte'
   },

+ 6 - 0
src/locale/bootstrap-table-sr-Cyrl-RS.js

@@ -4,6 +4,12 @@
  */
 
 $.fn.bootstrapTable.locales['sr-Cyrl-RS'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'Молим сачекај'
   },

+ 6 - 0
src/locale/bootstrap-table-sr-Latn-RS.js

@@ -4,6 +4,12 @@
  */
 
 $.fn.bootstrapTable.locales['sr-Latn-RS'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'Molim sačekaj'
   },

+ 6 - 0
src/locale/bootstrap-table-sv-SE.js

@@ -4,6 +4,12 @@
  */
 
 $.fn.bootstrapTable.locales['sv-SE'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'Laddar, vänligen vänta'
   },

+ 6 - 0
src/locale/bootstrap-table-th-TH.js

@@ -4,6 +4,12 @@
  */
 
 $.fn.bootstrapTable.locales['th-TH'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'กำลังโหลดข้อมูล, กรุณารอสักครู่'
   },

+ 6 - 0
src/locale/bootstrap-table-tr-TR.js

@@ -5,6 +5,12 @@
  */
 
 $.fn.bootstrapTable.locales['tr-TR'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'Yükleniyor, lütfen bekleyin'
   },

+ 6 - 0
src/locale/bootstrap-table-uk-UA.js

@@ -4,6 +4,12 @@
  */
 
 $.fn.bootstrapTable.locales['uk-UA'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'Завантаження, будь ласка, зачекайте'
   },

+ 6 - 0
src/locale/bootstrap-table-ur-PK.js

@@ -4,6 +4,12 @@
  */
 
 $.fn.bootstrapTable.locales['ur-PK'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'براۓ مہربانی انتظار کیجئے'
   },

+ 6 - 0
src/locale/bootstrap-table-uz-Latn-UZ.js

@@ -4,6 +4,12 @@
  */
 
 $.fn.bootstrapTable.locales['uz-Latn-UZ'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'Yuklanyapti, iltimos kuting'
   },

+ 6 - 0
src/locale/bootstrap-table-vi-VN.js

@@ -4,6 +4,12 @@
  */
 
 $.fn.bootstrapTable.locales['vi-VN'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return 'Đang tải'
   },

+ 6 - 0
src/locale/bootstrap-table-zh-CN.js

@@ -4,6 +4,12 @@
  */
 
 $.fn.bootstrapTable.locales['zh-CN'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return '正在努力地加载数据中,请稍候'
   },

+ 6 - 0
src/locale/bootstrap-table-zh-TW.js

@@ -4,6 +4,12 @@
  */
 
 $.fn.bootstrapTable.locales['zh-TW'] = {
+  formatCopyRows () {
+    return 'Copy Rows'
+  },
+  formatPrint () {
+    return 'Print'
+  },
   formatLoadingMessage () {
     return '正在努力地載入資料,請稍候'
   },

+ 1 - 1
src/utils/index.js

@@ -136,7 +136,7 @@ export default {
     return this.cachedWidth
   },
 
-  calculateObjectValue (self, name, args, defaultValue) {
+  calculateObjectValue (self, name, args = [], defaultValue) {
     let func = name
 
     if (typeof name === 'string') {

+ 26 - 22
tools/check-api.js

@@ -45,35 +45,39 @@ class API {
 
     const mds = Object.keys(md)
     for (const [i, key] of this.options.entries()) {
-      if (md[key]) {
-        outLines.push(md[key])
-        const details = md[key].split('\n\n- ')
-
-        for (let i = 0; i < this.attributes.length; i++) {
-          const name = this.attributes[i]
-          if (this.ignore && this.ignore[key] && this.ignore[key].includes(name)) {
-            continue
-          }
-
-          const tmpDetails = details[i + 1].trim()
-          if (name === 'Example' && exampleFilesFound) {
-            const matches = exampleRegex.exec(tmpDetails)
-            if (!matches) {
-              errors.push(chalk.red(`[${key}] missing or wrong formatted example`, `"${tmpDetails}"`))
+      try {
+        if (md[key]) {
+          outLines.push(md[key])
+          const details = md[key].split('\n\n- ')
+
+          for (let i = 0; i < this.attributes.length; i++) {
+            const name = this.attributes[i]
+            if (this.ignore && this.ignore[key] && this.ignore[key].includes(name)) {
               continue
             }
 
-            if (!exampleFiles.includes(matches[1])) {
-              errors.push(chalk.red(`[${key}] example '${matches[1]}' could not be found`))
+            const tmpDetails = details[i + 1].trim()
+            if (name === 'Example' && exampleFilesFound) {
+              const matches = exampleRegex.exec(tmpDetails)
+              if (!matches) {
+                errors.push(chalk.red(`[${key}] missing or wrong formatted example`, `"${tmpDetails}"`))
+                continue
+              }
+
+              if (!exampleFiles.includes(matches[1])) {
+                errors.push(chalk.red(`[${key}] example '${matches[1]}' could not be found`))
+              }
             }
-          }
 
-          if (!tmpDetails || tmpDetails.indexOf(`**${name}:**`) === -1) {
-            errors.push(chalk.red(`[${key}] missing '${name}'`))
+            if (!tmpDetails || tmpDetails.indexOf(`**${name}:**`) === -1) {
+              errors.push(chalk.red(`[${key}] missing '${name}'`))
+            }
           }
+        } else {
+          errors.push(chalk.red(`[${key}] option could not be found`))
         }
-      } else {
-        errors.push(chalk.red(`[${key}] option could not be found`))
+      } catch (ex) {
+        console.log(ex)
       }
     }