Browse Source

Added regex search (#5810)

* Added an option to use regex for the search

* WIP

* Added documentation and the default flags

* Fixed wrong link name

* use the ternary if syntax

* fixed linting

* updated stylelint to 13.13.1 and changed the qoutes of the stylelint
config file

* reverted test entry

Co-authored-by: Dennis Hernández <dennishernandezvargas@gmail.com>
Dustin Utecht 4 years ago
parent
commit
7d18de0ff0

+ 9 - 9
.stylelintrc

@@ -1,12 +1,12 @@
 {
-  'extends': 'stylelint-config-standard',
-  'rules': {
-    'indentation': null,
-    'selector-pseudo-element-colon-notation': null,
-    'function-comma-space-after': null,
-    'no-descending-specificity': null,
-    'declaration-bang-space-before': null,
-    'number-leading-zero': null,
-    'rule-empty-line-before': null
+  "extends": "stylelint-config-standard",
+  "rules": {
+    "indentation": null,
+    "selector-pseudo-element-colon-notation": null,
+    "function-comma-space-after": null,
+    "no-descending-specificity": null,
+    "declaration-bang-space-before": null,
+    "number-leading-zero": null,
+    "rule-empty-line-before": null
   }
 }

+ 1 - 0
index.d.ts

@@ -202,6 +202,7 @@ interface BootstrapTableOptions{
     onColumnSwitch?: (field, checked) => boolean;
     searchSelector?: boolean;
     strictSearch?: boolean;
+    regexSearch?: boolean;
     multipleSelectRow?: boolean;
     onLoadError?: (status) => boolean;
     buttonsToolbar?: any;

+ 1 - 1
package.json

@@ -30,7 +30,7 @@
     "rollup-plugin-node-resolve": "^5.0.4",
     "rollup-plugin-terser": "^7.0.0",
     "rollup-plugin-vue": "5.1.9",
-    "stylelint": "^13.3.3",
+    "stylelint": "^13.13.1",
     "stylelint-config-standard": "^22.0.0",
     "vue-template-compiler": "^2.6.10"
   },

+ 20 - 1
site/docs/api/table-options.md

@@ -1074,6 +1074,23 @@ The table options are defined in `jQuery.fn.bootstrapTable.defaults`.
 
 - **Example:** [Query Params Type](https://examples.bootstrap-table.com/#options/query-params-type.html)
 
+## regexSearch
+
+- **Attribute:** `data-regex-search`
+
+- **Type:** `Boolean`
+
+- **Detail:**
+
+  Set `true` to enable the regex search.
+  Is this option enabled, you can search with regex e.g. `[47a]$` for all items which ends with a `4`, `7` or `a`.     
+  The regex can be entered with `/[47a]$/gim` or without `[47a]$` flags, the default flags are `gim`.
+
+
+- **Default:** `false`
+
+- **Example:** [Regex Search](https://examples.bootstrap-table.com/#options/regex-search.html)
+
 ## rememberOrder
 
 - **Attribute:** `data-remember-order`
@@ -1161,7 +1178,9 @@ The table options are defined in `jQuery.fn.bootstrapTable.defaults`.
   - Comparisons (<, >, <=, =<, >=, =>).
     Example: 4 is larger than 3.
 
-  Note: If you want to use a custom search input use the [searchSelector](https://bootstrap-table.com/docs/api/table-options/#searchSelector).
+  Notes:
+  - If you want to use a custom search input use the [searchSelector](https://bootstrap-table.com/docs/api/table-options/#searchSelector).
+  - You can also search via regex using the [regexSearch](https://bootstrap-table.com/docs/api/table-options/#regexSearch) option.
 
 - **Default:** `false`
 

+ 47 - 45
src/bootstrap-table.js

@@ -946,11 +946,12 @@ class BootstrapTable {
         return
       }
 
-      let s = this.searchText && (this.fromHtml ? Utils.escapeHTML(this.searchText) : this.searchText).toLowerCase()
+      const rawSearchText = this.searchText && (this.fromHtml ? Utils.escapeHTML(this.searchText) : this.searchText)
+      let searchText = rawSearchText.toLowerCase()
       const f = Utils.isEmptyObject(this.filterColumns) ? null : this.filterColumns
 
       if (this.options.searchAccentNeutralise) {
-        s = Utils.normalizeAccent(s)
+        searchText = Utils.normalizeAccent(searchText)
       }
 
       // Check filter
@@ -994,7 +995,7 @@ class BootstrapTable {
 
       const visibleFields = this.getVisibleFields()
 
-      this.data = s ? this.data.filter((item, i) => {
+      this.data = searchText ? this.data.filter((item, i) => {
         for (let j = 0; j < this.header.fields.length; j++) {
           if (!this.header.searchables[j] || (this.options.visibleSearch && visibleFields.indexOf(this.header.fields[j]) === -1)) {
             continue
@@ -1028,51 +1029,52 @@ class BootstrapTable {
           }
 
           if (typeof value === 'string' || typeof value === 'number') {
-            if (this.options.strictSearch) {
-              if ((`${value}`).toLowerCase() === s) {
-                return true
-              }
-            } else {
-              const largerSmallerEqualsRegex = /(?:(<=|=>|=<|>=|>|<)(?:\s+)?(-?\d+)?|(-?\d+)?(\s+)?(<=|=>|=<|>=|>|<))/gm
-              const matches = largerSmallerEqualsRegex.exec(this.searchText)
-              let comparisonCheck = false
-
-              if (matches) {
-                const operator = matches[1] || `${matches[5]}l`
-                const comparisonValue = matches[2] || matches[3]
-                const int = parseInt(value, 10)
-                const comparisonInt = parseInt(comparisonValue, 10)
-
-                switch (operator) {
-                  case '>':
-                  case '<l':
-                    comparisonCheck = int > comparisonInt
-                    break
-                  case '<':
-                  case '>l':
-                    comparisonCheck = int < comparisonInt
-                    break
-                  case '<=':
-                  case '=<':
-                  case '>=l':
-                  case '=>l':
-                    comparisonCheck = int <= comparisonInt
-                    break
-                  case '>=':
-                  case '=>':
-                  case '<=l':
-                  case '=<l':
-                    comparisonCheck = int >= comparisonInt
-                    break
-                  default:
-                    break
-                }
-              }
+            if (
+              this.options.strictSearch && (`${value}`).toLowerCase() === searchText ||
+              (this.options.regexSearch && Utils.regexCompare(value, rawSearchText))
+            ) {
+              return true
+            }
 
-              if (comparisonCheck || (`${value}`).toLowerCase().includes(s)) {
-                return true
+            const largerSmallerEqualsRegex = /(?:(<=|=>|=<|>=|>|<)(?:\s+)?(-?\d+)?|(-?\d+)?(\s+)?(<=|=>|=<|>=|>|<))/gm
+            const matches = largerSmallerEqualsRegex.exec(this.searchText)
+            let comparisonCheck = false
+
+            if (matches) {
+              const operator = matches[1] || `${matches[5]}l`
+              const comparisonValue = matches[2] || matches[3]
+              const int = parseInt(value, 10)
+              const comparisonInt = parseInt(comparisonValue, 10)
+
+              switch (operator) {
+                case '>':
+                case '<l':
+                  comparisonCheck = int > comparisonInt
+                  break
+                case '<':
+                case '>l':
+                  comparisonCheck = int < comparisonInt
+                  break
+                case '<=':
+                case '=<':
+                case '>=l':
+                case '=>l':
+                  comparisonCheck = int <= comparisonInt
+                  break
+                case '>=':
+                case '=>':
+                case '<=l':
+                case '=<l':
+                  comparisonCheck = int >= comparisonInt
+                  break
+                default:
+                  break
               }
             }
+
+            if (comparisonCheck || (`${value}`).toLowerCase().includes(searchText)) {
+              return true
+            }
           }
         }
         return false

+ 1 - 0
src/constants/index.js

@@ -240,6 +240,7 @@ const DEFAULTS = {
   searchHighlight: false,
   searchOnEnterKey: false,
   strictSearch: false,
+  regexSearch: false,
   searchSelector: false,
   visibleSearch: false,
   showButtonIcons: true,

+ 10 - 9
src/extensions/filter-control/bootstrap-table-filter-control.js

@@ -172,29 +172,29 @@ $.BootstrapTable = class extends $.BootstrapTable {
 
   initSearch () {
     const that = this
-    const fp = $.isEmptyObject(that.filterColumnsPartial) ? null : that.filterColumnsPartial
+    const filterPartial = $.isEmptyObject(that.filterColumnsPartial) ? null : that.filterColumnsPartial
 
     super.initSearch()
 
-    if (this.options.sidePagination === 'server' || fp === null) {
+    if (this.options.sidePagination === 'server' || filterPartial === null) {
       return
     }
 
     // Check partial column filter
-    that.data = fp ?
+    that.data = filterPartial ?
       that.data.filter((item, i) => {
         const itemIsExpected = []
         const keys1 = Object.keys(item)
-        const keys2 = Object.keys(fp)
+        const keys2 = Object.keys(filterPartial)
         const keys = keys1.concat(keys2.filter(item => !keys1.includes(item)))
 
         keys.forEach(key => {
           const thisColumn = that.columns[that.fieldsColumnsIndex[key]]
-          const fval = (fp[key] || '').toLowerCase()
+          const filterValue = (filterPartial[key] || '').toLowerCase()
           let value = Utils.unescapeHTML(Utils.getItemField(item, key, false))
           let tmpItemIsExpected
 
-          if (fval === '') {
+          if (filterValue === '') {
             tmpItemIsExpected = true
           } else {
             // Fix #142: search use formatted data
@@ -219,14 +219,13 @@ $.BootstrapTable = class extends $.BootstrapTable {
                   if (this.options.searchAccentNeutralise) {
                     objectValue = Utils.normalizeAccent(objectValue)
                   }
-
-                  tmpItemIsExpected = that.isValueExpected(fval, objectValue, thisColumn, key)
+                  tmpItemIsExpected = that.isValueExpected(filterValue, objectValue, thisColumn, key)
                 })
               } else if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
                 if (this.options.searchAccentNeutralise) {
                   value = Utils.normalizeAccent(value)
                 }
-                tmpItemIsExpected = that.isValueExpected(fval, value, thisColumn, key)
+                tmpItemIsExpected = that.isValueExpected(filterValue, value, thisColumn, key)
               }
             }
           }
@@ -248,6 +247,8 @@ $.BootstrapTable = class extends $.BootstrapTable {
       tmpItemIsExpected = value.toString().toLowerCase() === searchValue.toString().toLowerCase()
     } else if (column.filterStartsWithSearch) {
       tmpItemIsExpected = (`${value}`).toLowerCase().indexOf(searchValue) === 0
+    } else if (this.options.regexSearch) {
+      tmpItemIsExpected = Utils.regexCompare(value, searchValue)
     } else {
       tmpItemIsExpected = (`${value}`).toLowerCase().includes(searchValue)
     }

+ 12 - 0
src/utils/index.js

@@ -193,6 +193,18 @@ export default {
     return true
   },
 
+  regexCompare (value, search) {
+    try {
+      const regexpParts = search.match(/^\/(.*?)\/([gim]*)$/)
+
+      if (value.toString().search(regexpParts ? new RegExp(regexpParts[1], regexpParts[2]) : new RegExp(search, 'gim')) !== -1) {
+        return true
+      }
+    } catch (e) {
+      return false
+    }
+  },
+
   escapeHTML (text) {
     if (typeof text === 'string') {
       return text