diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 7fe8317d..1131297f 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -3,6 +3,5 @@
- [ ] I read the [guidelines for contributing](https://github.com/mistic100/jQuery-QueryBuilder/blob/master/.github/CONTRIBUTING.md)
- [ ] I created my branch from `dev` and I am issuing the PR to `dev`
- [ ] I didn't pushed the `dist` directory
-- [ ] Unit tests are OK
- [ ] If it's a new feature, I added the necessary unit tests
- [ ] If it's a new language, I filled the `__locale` and `__author` fields
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 81718908..0648b398 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -8,9 +8,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v1
- - name: build
- run: |
- npm install
- npm run test
- npm run build
+ - uses: actions/checkout@v3
+
+ - uses: actions/setup-node@v3
+ with:
+ node-version: '16'
+ cache: 'yarn'
+
+ - name: build
+ run: |
+ yarn install
+ yarn build
diff --git a/.gitignore b/.gitignore
index d0a0ebb9..a50c5eea 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,10 +1,7 @@
-bower_components
node_modules
dist
doc
.sass-cache
-.coverage-results
.idea
*.iml
-yarn.lock
package-lock.json
diff --git a/.jscsrc b/.jscsrc
deleted file mode 100644
index ce1e5220..00000000
--- a/.jscsrc
+++ /dev/null
@@ -1,37 +0,0 @@
-{
- "preset": "idiomatic",
- "validateIndentation": 4,
- "requireCamelCaseOrUpperCaseIdentifiers": false,
- "disallowKeywordsOnNewLine": [],
- "requireKeywordsOnNewLine": [
- "else"
- ],
- "requirePaddingNewLinesAfterBlocks": false,
- "safeContextKeyword": "self",
- "disallowMultipleLineStrings": false,
- "requirePaddingNewLinesBeforeLineComments": false,
- "requireSpaceBeforeBinaryOperators": [
- "=", "+", "-", "*", "/", "%",
- "<<", ">>", ">>>", "&", "|", "^",
- "&&", "||",
- "===", "==", ">=", "<=", "<", ">", "!=", "!=="
- ],
- "requireDotNotation": false,
- "requireSpacesInsideBrackets": false,
- "requireSpacesInsideParentheses": false,
- "maximumLineLength": null,
- "maximumNumberOfLines": null,
- "validateQuoteMarks": {
- "mark": "'",
- "escape": true
- },
- "requireCurlyBraces": [
- "for",
- "while",
- "do",
- "try",
- "catch"
- ],
- "requireEarlyReturn": false,
- "validateCommentPosition": false
-}
\ No newline at end of file
diff --git a/.jshintrc b/.jshintrc
deleted file mode 100644
index 518111a8..00000000
--- a/.jshintrc
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "-W069": true, // accesses to "regional" in language files
- "multistr": true
-}
\ No newline at end of file
diff --git a/.sass-lint.yml b/.sass-lint.yml
deleted file mode 100644
index 2d387551..00000000
--- a/.sass-lint.yml
+++ /dev/null
@@ -1,9 +0,0 @@
-rules:
- property-sort-order: [0]
- force-pseudo-nesting: [0]
- force-element-nesting: [0]
- force-attribute-nesting: [0]
- no-important: [0]
- no-qualifying-elements: [0]
- shorthand-values: [1, {allowed-shorthands: [1, 2, 4]}]
- hex-notation: [1, {style: uppercase}]
diff --git a/Gruntfile.js b/Gruntfile.js
deleted file mode 100644
index 52cc8a99..00000000
--- a/Gruntfile.js
+++ /dev/null
@@ -1,443 +0,0 @@
-var initConfig = require('./build/initConfig');
-var processLang = require('./build/processLang');
-var removeJshint = require('./build/removeJshint');
-var cleanLn = require('./build/cleanLn');
-
-module.exports = function(grunt) {
- require('time-grunt')(grunt);
- require('jit-grunt')(grunt, {
- sasslint: 'grunt-sass-lint',
- sass_injection: 'grunt-sass-injection',
- usebanner: 'grunt-banner'
- });
-
- grunt.util.linefeed = '\n';
-
- var config = initConfig(grunt, {
- js_core_files: [
- 'src/main.js',
- 'src/defaults.js',
- 'src/plugins.js',
- 'src/core.js',
- 'src/public.js',
- 'src/data.js',
- 'src/template.js',
- 'src/utils.js',
- 'src/model.js',
- 'src/jquery.js'
- ],
- js_files_for_standalone: [
- 'node_modules/jquery-extendext/jquery-extendext.js',
- 'node_modules/dot/doT.js',
- 'dist/js/query-builder.js'
- ]
- });
-
- grunt.initConfig({
- pkg: grunt.file.readJSON('package.json'),
-
- banner: '/*!\n' +
- ' * jQuery QueryBuilder <%= pkg.version %>\n' +
- ' * Copyright 2014-<%= grunt.template.today("yyyy") %> Damien "Mistic" Sorel (http://www.strangeplanet.fr)\n' +
- ' * Licensed under MIT (https://opensource.org/licenses/MIT)\n' +
- ' */',
-
- langBanner: '/*!\n' +
- ' * jQuery QueryBuilder <%= pkg.version %>\n' +
- ' * Locale: <%= lang_locale %>\n' +
- '<% if (lang_author) { %> * Author: <%= lang_author %>\n<% } %>' +
- ' * Licensed under MIT (https://opensource.org/licenses/MIT)\n' +
- ' */',
-
- // serve folder content
- connect: {
- dev: {
- options: {
- host: '0.0.0.0',
- port: 9000,
- livereload: true
- }
- },
- test: {
- options: {
- port: 9001
- }
- }
- },
-
- // watchers
- watch: {
- options: {
- livereload: true
- },
- js: {
- files: ['src/*.js', 'src/plugins/**/plugin.js'],
- tasks: ['injector:example']
- },
- css: {
- files: ['src/scss/*.scss', 'src/plugins/**/plugin.scss'],
- tasks: ['build_css']
- },
- lang: {
- files: ['src/i18n/*.json', 'src/plugins/**/i18n/*.json'],
- tasks: ['build_lang']
- },
- example: {
- files: ['examples/**'],
- tasks: []
- }
- },
-
- // open example
- open: {
- dev: {
- path: 'http://localhost:<%= connect.dev.options.port%>/examples/index.html'
- }
- },
-
- // copy SASS files
- copy: {
- sass_core: {
- files: [{
- expand: true,
- flatten: true,
- src: ['src/scss/*.scss'],
- dest: 'dist/scss'
- }]
- },
- sass_plugins: {
- files: config.loaded_plugins.map(function(name) {
- return {
- src: 'src/plugins/' + name + '/plugin.scss',
- dest: 'dist/scss/plugins/_' + name + '.scss'
- };
- })
- },
- doc_script: {
- src: 'build/jsdoc.js',
- dest: 'doc/js/custom.js'
- }
- },
-
- concat: {
- // concat all JS
- js: {
- src: config.js_files_to_load,
- dest: 'dist/js/query-builder.js',
- options: {
- stripBanners: false,
- separator: '\n\n',
- process: function(src) {
- return cleanLn(removeJshint(src));
- }
- }
- },
- // create standalone version
- js_standalone: {
- src: config.js_files_for_standalone,
- dest: 'dist/js/query-builder.standalone.js',
- options: {
- stripBanners: false,
- separator: '\n\n',
- process: function(src, file) {
- var name = file.match(/([^\/]+?).js$/)[1];
-
- return cleanLn(removeJshint(src))
- .replace(/define\((.*?)\);/, 'define(\'' + name + '\', $1);');
- }
- }
- },
- // compile language files with AMD wrapper
- lang: {
- files: Object.keys(config.all_langs).map(function(name) {
- return {
- src: 'src/i18n/' + name + '.json',
- dest: 'dist/i18n/query-builder.' + name + '.js'
- };
- }),
- options: {
- process: function(src, file) {
- var wrapper = cleanLn(grunt.file.read('src/i18n/.wrapper.js')).split(/@@js\n/);
- return processLang(grunt, config.loaded_plugins)(file, src, wrapper);
- }
- }
- },
- // compile language files without AMD wrapper
- lang_temp: {
- files: Object.keys(config.all_langs).map(function(name) {
- return {
- src: 'src/i18n/' + name + '.json',
- dest: '.temp/i18n/' + name + '.js'
- };
- }),
- options: {
- process: function(src, file) {
- return processLang(grunt, config.loaded_plugins)(file, src);
- }
- }
- }
- },
-
- // add AMD wrapper
- wrap: {
- js: {
- src: ['dist/js/query-builder.js'],
- dest: 'dist/js/query-builder.js',
- options: {
- separator: '',
- wrapper: function() {
- return cleanLn(grunt.file.read('src/.wrapper.js')).split(/@@js\n/);
- }
- }
- }
- },
-
- // add banners
- usebanner: {
- options: {
- banner: '<%= banner %>'
- },
- js: {
- src: ['dist/js/*.js']
- },
- css: {
- src: ['dist/css/*.css', 'dist/scss/*.scss']
- }
- },
-
- // add plugins SASS imports
- sass_injection: {
- dist: {
- options: {
- replacePath: {
- pattern: 'dist/scss/',
- replace: ''
- }
- },
- src: ['dist/scss/plugins/*.scss'],
- target: 'dist/scss/default.scss'
- }
- },
-
- // parse scss
- sass: {
- options: {
- sourceMap: false,
- outputStyle: 'expanded'
- },
- dist: {
- files: [{
- expand: true,
- flatten: true,
- src: ['dist/scss/*.scss'],
- dest: 'dist/css',
- ext: '.css',
- rename: function(dest, src) {
- return dest + '/query-builder.' + src;
- }
- }]
- }
- },
-
- // compress js
- uglify: {
- options: {
- banner: '<%= banner %>\n',
- mangle: { reserved: ['$'] },
- sourceMap: true,
- },
- dist: {
- files: [{
- expand: true,
- flatten: true,
- src: ['dist/js/*.js', '!dist/js/*.min.js'],
- dest: 'dist/js',
- ext: '.min.js',
- extDot: 'last'
- }]
- }
- },
-
- // compress css
- cssmin: {
- dist: {
- files: [{
- expand: true,
- flatten: true,
- src: ['dist/css/*.css', '!dist/css/*.min.css'],
- dest: 'dist/css',
- ext: '.min.css',
- extDot: 'last'
- }]
- }
- },
-
- // clean build dir
- clean: {
- temp: ['.temp'],
- doc: ['doc']
- },
-
- // jshint tests
- jshint: {
- lib: {
- options: {
- jshintrc: '.jshintrc'
- },
- src: ['src/**/*.js', '!src/**/.wrapper.js']
- }
- },
-
- // jscs tests
- jscs: {
- lib: {
- options: {
- config: '.jscsrc'
- },
- src: ['src/**/*.js', '!src/**/.wrapper.js']
- }
- },
-
- // scss tests
- sasslint: {
- lib: {
- options: {
- configFile: '.sass-lint.yml'
- },
- src: ['src/**/*.scss']
- }
- },
-
- // jsDoc generation
- jsdoc: {
- lib: {
- src: ['src/**/*.js', '!src/**/.wrapper.js'],
- options: {
- destination: 'doc',
- config: '.jsdoc.json'
- }
- }
- },
-
- // inject sources files and tests modules in demo and test
- injector: {
- options: {
- relative: true,
- addRootSlash: false
- },
- example: {
- src: config.all_js_files.concat(['dist/i18n/query-builder.en.js']),
- dest: 'examples/index.html'
- },
- testSrc: {
- options: {
- starttag: '',
- transform: function(filepath) {
- return '';
- }
- },
- src: config.all_js_files,
- dest: 'tests/index.html'
- },
- testModules: {
- options: {
- starttag: ''
- },
- src: ['tests/*.module.js'],
- dest: 'tests/index.html'
- }
- },
-
- // qunit test suite
- qunit: {
- all: {
- options: {
- urls: ['http://localhost:<%= connect.test.options.port %>/tests/index.html?coverage=true'],
- noGlobals: true
- }
- }
- },
-
- // save LCOV files
- qunit_blanket_lcov: {
- all: {
- files: [{
- expand: true,
- src: ['src/*.js', 'src/plugins/**/plugin.js']
- }],
- options: {
- dest: '.coverage-results/all.lcov',
- prefix: 'http://localhost:<%= connect.test.options.port %>/'
- }
- }
- },
-
- // coveralls data
- coveralls: {
- options: {
- force: true
- },
- all: {
- src: '.coverage-results/all.lcov'
- }
- }
- });
-
-
- grunt.registerTask('build_js', [
- 'concat:lang_temp',
- 'concat:js',
- 'wrap:js',
- 'usebanner:js',
- 'concat:js_standalone',
- 'uglify',
- 'clean:temp'
- ]);
-
- grunt.registerTask('build_css', [
- 'copy:sass_core',
- 'copy:sass_plugins',
- 'sass_injection',
- 'sass',
- 'cssmin',
- 'usebanner:css'
- ]);
-
- grunt.registerTask('build_lang', [
- 'concat:lang'
- ]);
-
- grunt.registerTask('default', [
- 'build_lang',
- 'build_js',
- 'build_css'
- ]);
-
- grunt.registerTask('test', [
- 'jshint',
- 'jscs',
- 'sasslint',
- 'build_lang',
- 'build_css',
- 'injector:testSrc',
- 'injector:testModules',
- 'connect:test',
- 'qunit_blanket_lcov',
- 'qunit'
- ]);
-
- grunt.registerTask('serve', [
- 'build_lang',
- 'build_css',
- 'injector:example',
- 'open',
- 'connect:dev',
- 'watch'
- ]);
-
- grunt.registerTask('doc', [
- 'clean:doc',
- 'jsdoc',
- 'copy:doc_script'
- ]);
-};
diff --git a/README.md b/README.md
index 84e74403..f0fd8ab7 100644
--- a/README.md
+++ b/README.md
@@ -3,8 +3,6 @@
[](https://www.npmjs.com/package/jQuery-QueryBuilder)
[](https://www.jsdelivr.com/package/npm/jQuery-QueryBuilder)
[](https://github.com/mistic100/jQuery-QueryBuilder/actions)
-[](https://coveralls.io/r/mistic100/jQuery-QueryBuilder)
-[](https://david-dm.org/mistic100/jQuery-QueryBuilder)
[](https://gitlocalize.com/repo/5259/whole_project?utm_source=badge)
jQuery plugin offering an simple interface to create complex queries.
@@ -35,55 +33,28 @@ $ npm install jQuery-QueryBuilder
jQuery-QueryBuilder is available on [jsDelivr](https://www.jsdelivr.com/package/npm/jQuery-QueryBuilder).
### Dependencies
* [jQuery 3](https://jquery.com)
- * [Bootstrap 3](https://getbootstrap.com/docs/3.3) (CSS only)
+ * [Bootstrap 5](https://getbootstrap.com/docs/5.3/) CSS and bundle.js which includes `Popper` for tooltips and popovers
+ * [Bootstrap Icons](https://icons.getbootstrap.com/)
* [jQuery.extendext](https://github.com/mistic100/jQuery.extendext)
- * [doT.js](https://olado.github.io/doT)
* [MomentJS](https://momentjs.com) (optional, for Date/Time validation)
* [SQL Parser](https://github.com/mistic100/sql-parser) (optional, for SQL methods)
* Other Bootstrap/jQuery plugins used by plugins
-($.extendext and doT.js are directly included in the [standalone](https://github.com/mistic100/jQuery-QueryBuilder/blob/master/dist/js/query-builder.standalone.js) file)
+($.extendext is directly included in the [standalone](https://github.com/mistic100/jQuery-QueryBuilder/blob/master/dist/js/query-builder.standalone.js) file)
-### Browser support
- * Internet Explorer >= 11
- * All other recent browsers
+## Developement
-## Build
+Install Node dependencies with `npm install`.
-#### Prerequisites
+#### Build
- * NodeJS + NPM: `apt-get install nodejs-legacy npm`
- * Grunt CLI: `npm install -g grunt-cli`
+Run `npm run build` in the root directory to generate production files inside `dist`.
-#### Run
+#### Serve
-Install Node dependencies `npm install` then run `grunt` in the root directory to generate production files inside `dist`.
-
-#### Options
-
-You can choose which plugins to include with `--plugins` :
-```bash
-# include "sql-support" and "mongodb-support" plugins
-grunt --plugins=sql-support,mongodb-support
-
-# disable all plugins
-grunt --plugins=false
-```
-All plugins are included by default.
-
-You can also include language files with `--languages` :
-```bash
-# include French & Italian translation
-grunt --languages=fr,it
-```
-
-#### Other commands
-
- * `grunt test` to run jshint/jscs/scsslint and the QUnit test suite.
- * `grunt serve` to open the example page with automatic build and livereload.
- * `grunt doc` to generate the documentation.
+Run `npm run serve` to open the example page with automatic build and livereload.
## License
diff --git a/build/cleanLn.js b/build/cleanLn.js
deleted file mode 100644
index f93432e8..00000000
--- a/build/cleanLn.js
+++ /dev/null
@@ -1,3 +0,0 @@
-module.exports = function(src) {
- return src.replace(/\r\n/g, '\n');
-};
diff --git a/build/dist.mjs b/build/dist.mjs
new file mode 100644
index 00000000..e413853b
--- /dev/null
+++ b/build/dist.mjs
@@ -0,0 +1,210 @@
+import fs from 'fs';
+import path from 'path';
+import { globSync } from 'glob';
+import * as sass from 'sass';
+import pkg from '../package.json' assert { type: 'json' };
+
+const DEV = process.argv[2] === '--dev';
+
+const DIST = 'dist/';
+
+const CORE_JS = [
+ 'src/main.js',
+ 'src/defaults.js',
+ 'src/plugins.js',
+ 'src/core.js',
+ 'src/public.js',
+ 'src/data.js',
+ 'src/template.js',
+ 'src/utils.js',
+ 'src/model.js',
+ 'src/jquery.js',
+];
+
+const CORE_SASS = [
+ 'src/scss/dark.scss',
+ 'src/scss/default.scss',
+];
+
+const STANDALONE_JS = {
+ 'jquery-extendext': 'node_modules/jquery-extendext/jquery-extendext.js',
+ 'query-builder': `${DIST}js/query-builder.js`,
+};
+
+const BANNER = () => `/*!
+ * jQuery QueryBuilder ${pkg.version}
+ * Copyright 2014-${new Date().getFullYear()} Damien "Mistic" Sorel (http://www.strangeplanet.fr)
+ * Licensed under MIT (https://opensource.org/licenses/MIT)
+ */`;
+
+const LANG_BANNER = (locale, author) => `/*!
+ * jQuery QueryBuilder ${pkg.version}
+ * Locale: ${locale}
+ * Author: ${author}
+ * Licensed under MIT (https://opensource.org/licenses/MIT)
+ */`;
+
+const ALL_PLUGINS_JS = glob('src/plugins/*/plugin.js')
+ .sort()
+ .reduce((all, p) => {
+ const n = p.split('/')[2];
+ all[n] = p;
+ return all;
+ }, {});
+
+const ALL_PLUGINS_SASS = glob('src/plugins/*/plugin.scss')
+ .sort()
+ .reduce((all, p) => {
+ const n = p.split('/')[2];
+ all[n] = p;
+ return all;
+ }, {});
+
+const ALL_LANGS = glob('src/i18n/*.json')
+ .map(p => p.split(/[\/\.]/)[2])
+ .sort();
+
+function glob(pattern) {
+ return globSync(pattern)
+ .map(p => p.split(path.sep).join('/'));
+}
+
+/**
+ * Build lang files
+ */
+function buildLangs() {
+ const wrapper = fs.readFileSync('src/i18n/.wrapper.js', { encoding: 'utf8' })
+ .split('@@js\n');
+
+ ALL_LANGS.forEach(lang => {
+ const outpath = `${DIST}i18n/query-builder.${lang}.js`;
+ console.log(`LANG ${lang} (${outpath})`);
+ fs.writeFileSync(outpath, getLang(lang, wrapper));
+ });
+}
+
+/**
+ * Get the content of a single lang
+ */
+function getLang(lang, wrapper = ['', '']) {
+ const corepath = `src/i18n/${lang}.json`;
+ const content = JSON.parse(fs.readFileSync(corepath, { encoding: 'utf8' }));
+
+ Object.keys(ALL_PLUGINS_JS).forEach(plugin => {
+ const pluginpath = `src/plugins/${plugin}/i18n/${lang}.json`;
+ try {
+ const plugincontent = JSON.parse(fs.readFileSync(pluginpath, { encoding: 'utf8' }));
+ Object.assign(content, plugincontent);
+ } catch { }
+ });
+
+ return LANG_BANNER(content.__locale || lang, content.__author || '')
+ + '\n\n'
+ + wrapper[0]
+ + `QueryBuilder.regional['${lang}'] = `
+ + JSON.stringify(content, null, 2)
+ + ';\n\n'
+ + `QueryBuilder.defaults({ lang_code: '${lang}' });`
+ + wrapper[1];
+}
+
+/**
+ * Build main JS file
+ */
+function buildMain() {
+ const wrapper = fs.readFileSync('src/.wrapper.js', { encoding: 'utf8' })
+ .split('@@js\n');
+
+ const files_to_load = [
+ ...CORE_JS,
+ ...Object.values(ALL_PLUGINS_JS),
+ ];
+
+ const output = BANNER()
+ + '\n\n'
+ + wrapper[0]
+ + files_to_load.map(f => fs.readFileSync(f, { encoding: 'utf8' })).join('\n\n')
+ + '\n\n'
+ + getLang('en')
+ + wrapper[1];
+
+ const outpath = `${DIST}js/query-builder.js`;
+ console.log(`MAIN (${outpath})`);
+ fs.writeFileSync(outpath, output);
+}
+
+/**
+ * Build standalone JS file
+ */
+function buildStandalone() {
+ const output = Object.entries(STANDALONE_JS)
+ .map(([name, file]) => {
+ return fs.readFileSync(file, { encoding: 'utf8' })
+ .replace(/define\((.*?)\);/, `define('${name}', $1);`);
+ })
+ .join('\n\n');
+
+ const outpath = `${DIST}js/query-builder.standalone.js`;
+ console.log(`STANDALONE (${outpath})`);
+ fs.writeFileSync(outpath, output);
+}
+
+/**
+ * Copy SASS files
+ */
+function copySass() {
+ Object.entries(ALL_PLUGINS_SASS).forEach(([plugin, path]) => {
+ const outpath = `${DIST}scss/plugins/${plugin}.scss`;
+ console.log(`SASS ${plugin} (${path})`);
+ fs.copyFileSync(path, outpath);
+ });
+
+ CORE_SASS.forEach(path => {
+ const name = path.split('/').pop();
+
+ const content = fs.readFileSync(path, { encoding: 'utf8' });
+
+ let output = BANNER()
+ + '\n'
+ + content;
+ if (name === 'default.scss') {
+ output += '\n'
+ + Object.keys(ALL_PLUGINS_SASS).map(p => `@import "plugins/${p}";`).join('\n');
+ }
+
+ const outpath = `${DIST}scss/${name}`;
+ console.log(`SASS (${path})`);
+ fs.writeFileSync(outpath, output);
+ });
+}
+
+/**
+ * Build CSS files
+ */
+function buildCss() {
+ CORE_SASS.forEach(p => {
+ const path = p.replace('src/', DIST);
+ const name = path.split('/').pop();
+
+ const output = sass.compile(path);
+
+ const outpath = `${DIST}css/query-builder.${name.split('.').shift()}.css`;
+ console.log(`CSS (${path})`);
+ fs.writeFileSync(outpath, output.css);
+ });
+}
+
+if (!DEV) {
+ fs.rmSync(DIST, { recursive: true, force: true });
+}
+fs.mkdirSync(DIST + 'css', { recursive: true });
+fs.mkdirSync(DIST + 'i18n', { recursive: true });
+fs.mkdirSync(DIST + 'js', { recursive: true });
+fs.mkdirSync(DIST + 'scss', { recursive: true });
+fs.mkdirSync(DIST + 'scss/plugins', { recursive: true });
+
+buildLangs();
+buildMain();
+buildStandalone();
+copySass();
+buildCss();
diff --git a/build/initConfig.js b/build/initConfig.js
deleted file mode 100644
index 5bb333ee..00000000
--- a/build/initConfig.js
+++ /dev/null
@@ -1,68 +0,0 @@
-module.exports = function(grunt, config) {
- config.all_plugins = {};
- config.all_langs = {};
- config.loaded_plugins = [];
- config.loaded_langs = [];
- config.js_files_to_load = config.js_core_files.slice();
- config.all_js_files = config.js_core_files.slice();
-
- // list available plugins and languages
- grunt.file.expand('src/plugins/**/plugin.js')
- .forEach(function(f) {
- var n = f.split('/')[2];
- config.all_plugins[n] = f;
- });
-
- grunt.file.expand('src/i18n/*.json')
- .forEach(function(f) {
- var n = f.split(/[\/\.]/)[2];
- config.all_langs[n] = f;
- });
-
- // fill all js files
- for (var p in config.all_plugins) {
- config.all_js_files.push(config.all_plugins[p]);
- }
-
- // parse 'plugins' parameter
- var arg_plugins = grunt.option('plugins');
- if (typeof arg_plugins === 'string') {
- arg_plugins.replace(/ /g, '').split(',').forEach(function(p) {
- if (config.all_plugins[p]) {
- config.js_files_to_load.push(config.all_plugins[p]);
- config.loaded_plugins.push(p);
- }
- else {
- grunt.fail.warn('Plugin ' + p + ' unknown');
- }
- });
- }
- else if (arg_plugins === undefined) {
- for (var p in config.all_plugins) {
- config.js_files_to_load.push(config.all_plugins[p]);
- config.loaded_plugins.push(p);
- }
- }
-
- // default language
- config.js_files_to_load.push('.temp/i18n/en.js');
- config.loaded_langs.push('en');
-
- // parse 'lang' parameter
- var arg_langs = grunt.option('languages');
- if (typeof arg_langs === 'string') {
- arg_langs.replace(/ /g, '').split(',').forEach(function(l) {
- if (config.all_langs[l]) {
- if (l !== 'en') {
- config.js_files_to_load.push(config.all_langs[l].replace(/^src/, '.temp').replace(/json$/, 'js'));
- config.loaded_langs.push(l);
- }
- }
- else {
- grunt.fail.warn('Language ' + l + ' unknown');
- }
- });
- }
-
- return config;
-};
diff --git a/build/jsdoc.md b/build/jsdoc.md
index 029fecb1..e329af92 100644
--- a/build/jsdoc.md
+++ b/build/jsdoc.md
@@ -1,4 +1,4 @@
-# [Main documentation](..)
+# [Main documentation](..)
# Entry point: [$.fn.QueryBuilder](external-_jQuery.fn_.html)
diff --git a/build/liveserver.mjs b/build/liveserver.mjs
new file mode 100644
index 00000000..3d60e23c
--- /dev/null
+++ b/build/liveserver.mjs
@@ -0,0 +1,20 @@
+import liveServer from 'alive-server';
+import path from 'path';
+
+const rootDir = process.cwd();
+
+const EXAMPLES_DIR = 'examples';
+const DIST_DIR = 'dist';
+
+liveServer.start({
+ open: true,
+ root: path.join(rootDir, EXAMPLES_DIR),
+ watch: [
+ path.join(rootDir, EXAMPLES_DIR),
+ path.join(rootDir, DIST_DIR),
+ ],
+ mount: [
+ ['/node_modules', path.join(rootDir, 'node_modules')],
+ ['/dist', path.join(rootDir, DIST_DIR)],
+ ],
+});
diff --git a/build/processLang.js b/build/processLang.js
deleted file mode 100644
index 33c6b2c7..00000000
--- a/build/processLang.js
+++ /dev/null
@@ -1,30 +0,0 @@
-var deepmerge = require('deepmerge');
-
-module.exports = function(grunt, loaded_plugins) {
- return function(file, src, wrapper) {
- var lang = file.split(/[\/\.]/)[2];
- var content = JSON.parse(src);
- wrapper = wrapper || ['', ''];
-
- grunt.config.set('lang_locale', content.__locale || lang);
- grunt.config.set('lang_author', content.__author);
- var header = grunt.template.process('<%= langBanner %>');
-
- loaded_plugins.forEach(function(p) {
- var plugin_file = 'src/plugins/' + p + '/i18n/' + lang + '.json';
-
- if (grunt.file.exists(plugin_file)) {
- content = deepmerge(content, grunt.file.readJSON(plugin_file));
- }
- });
-
- return header
- + '\n\n'
- + wrapper[0]
- + 'QueryBuilder.regional[\'' + lang + '\'] = '
- + JSON.stringify(content, null, 2)
- + ';\n\n'
- + 'QueryBuilder.defaults({ lang_code: \'' + lang + '\' });'
- + wrapper[1];
- };
-};
diff --git a/build/removeJshint.js b/build/removeJshint.js
deleted file mode 100644
index 4ff583ac..00000000
--- a/build/removeJshint.js
+++ /dev/null
@@ -1,5 +0,0 @@
-module.exports = function(src) {
- return src
- .replace(/\/\*jshint [a-z:]+ \*\/\r?\n\r?\n?/g, '')
- .replace(/\/\*jshint -[EWI]{1}[0-9]{3} \*\/\r?\n\r?\n?/g, '');
-};
diff --git a/examples/index.html b/examples/index.html
index 8fc91c18..180652b8 100644
--- a/examples/index.html
+++ b/examples/index.html
@@ -1,35 +1,27 @@
-
+
jQuery QueryBuilder Example
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
diff --git a/package.json b/package.json
index 926ae20f..4111f3a0 100644
--- a/package.json
+++ b/package.json
@@ -13,50 +13,28 @@
"src/"
],
"dependencies": {
- "bootstrap": "^3.4.1",
- "dot": "^1.1.3",
+ "bootstrap": "^5.3.0",
+ "@popperjs/core": "^2.11.8",
+ "bootstrap-icons": "^1.11.3",
"jquery": "^3.5.1",
"jquery-extendext": "^1.0.0",
"moment": "^2.29.1",
"sql-parser-mistic": "^1.2.3"
},
"devDependencies": {
+ "alive-server": "^1.3.0",
"awesome-bootstrap-checkbox": "^0.3.7",
- "blanket": "^1.2.3",
- "bootbox": "^4.4.0",
- "bootstrap-select": "^1.12.4",
+ "bootbox": "^6.0.0",
"bootstrap-slider": "^10.0.0",
- "bootswatch-dist": "git+https://github.com/dbtek/bootswatch-dist.git#slate",
"chosenjs": "^1.4.3",
+ "concurrently": "^8.2.0",
"deepmerge": "^2.1.0",
"foodoc": "^0.0.9",
- "grunt": "^1.0.2",
- "grunt-banner": "^0.6.0",
- "grunt-cli": "^1.3.2",
- "grunt-contrib-clean": "^1.0.0",
- "grunt-contrib-concat": "^1.0.0",
- "grunt-contrib-connect": "^1.0.0",
- "grunt-contrib-copy": "^1.0.0",
- "grunt-contrib-cssmin": "^2.2.1",
- "grunt-contrib-jshint": "^1.0.0",
- "grunt-contrib-qunit": "^2.0.0",
- "grunt-contrib-uglify": "^3.3.0",
- "grunt-contrib-watch": "^1.0.0",
- "grunt-coveralls": "^1.0.0",
- "grunt-injector": "^1.1.0",
- "grunt-jscs": "^3.0.1",
- "grunt-jsdoc": "^2.2.1",
- "grunt-open": "^0.2.3",
- "grunt-qunit-blanket-lcov": "^1.0.0",
- "grunt-sass": "^2.1.0",
- "grunt-sass-injection": "^1.0.3",
- "grunt-sass-lint": "^0.2.2",
- "grunt-wrap": "^0.3.0",
+ "glob": "^10.3.1",
"interactjs": "^1.3.3",
- "jit-grunt": "^0.10.0",
- "qunit": "^2.5.1",
- "selectize": "^0.12.4",
- "time-grunt": "^1.3.0"
+ "nodemon": "^2.0.22",
+ "sass": "^1.63.6",
+ "@selectize/selectize": "^0.15.2"
},
"keywords": [
"jquery",
@@ -74,8 +52,9 @@
"url": "https://github.com/mistic100/jQuery-QueryBuilder/issues"
},
"scripts": {
- "build": "grunt",
- "serve": "grunt serve",
- "test": "grunt test"
+ "build": "node ./build/dist.mjs",
+ "watch:build": "nodemon --watch src -e js,scss,json ./build/dist.mjs --dev",
+ "watch:serve": "node ./build/liveserver.mjs",
+ "serve": "concurrently \"npm:watch:build\" \"npm:watch:serve\""
}
}
diff --git a/src/.wrapper.js b/src/.wrapper.js
index 732ae480..2b326090 100644
--- a/src/.wrapper.js
+++ b/src/.wrapper.js
@@ -1,14 +1,14 @@
(function(root, factory) {
if (typeof define == 'function' && define.amd) {
- define(['jquery', 'dot/doT', 'jquery-extendext'], factory);
+ define(['jquery', 'jquery-extendext'], factory);
}
else if (typeof module === 'object' && module.exports) {
- module.exports = factory(require('jquery'), require('dot/doT'), require('jquery-extendext'));
+ module.exports = factory(require('jquery'), require('jquery-extendext'));
}
else {
- factory(root.jQuery, root.doT);
+ factory(root.jQuery);
}
-}(this, function($, doT) {
+}(this, function($) {
"use strict";
@@js
diff --git a/src/core.js b/src/core.js
index 6944c9a3..bcb7c912 100644
--- a/src/core.js
+++ b/src/core.js
@@ -700,7 +700,7 @@ QueryBuilder.prototype.createRuleInput = function(rule) {
var filter = rule.filter;
for (var i = 0; i < rule.operator.nb_inputs; i++) {
- var $ruleInput = $($.parseHTML(this.getRuleInput(rule, i)));
+ var $ruleInput = $($.parseHTML($.trim(this.getRuleInput(rule, i))));
if (i > 0) $valueContainer.append(this.settings.inputs_separator);
$valueContainer.append($ruleInput);
$inputs = $inputs.add($ruleInput);
diff --git a/src/data.js b/src/data.js
index 304f8341..7f466940 100644
--- a/src/data.js
+++ b/src/data.js
@@ -386,22 +386,18 @@ QueryBuilder.prototype.getRuleInputValue = function(rule) {
case 'checkbox':
tmp = [];
- // jshint loopfunc:true
$value.find('[name=' + name + ']:checked').each(function() {
tmp.push($(this).val());
});
- // jshint loopfunc:false
value.push(tmp);
break;
case 'select':
if (filter.multiple) {
tmp = [];
- // jshint loopfunc:true
$value.find('[name=' + name + '] option:selected').each(function() {
tmp.push($(this).val());
});
- // jshint loopfunc:false
value.push(tmp);
}
else {
@@ -488,11 +484,9 @@ QueryBuilder.prototype.setRuleInputValue = function(rule, value) {
if (!$.isArray(value[i])) {
value[i] = [value[i]];
}
- // jshint loopfunc:true
value[i].forEach(function(value) {
$value.find('[name=' + name + '][value="' + value + '"]').prop('checked', true).trigger('change');
});
- // jshint loopfunc:false
break;
default:
diff --git a/src/defaults.js b/src/defaults.js
index b6c5a5eb..b63b1251 100644
--- a/src/defaults.js
+++ b/src/defaults.js
@@ -187,10 +187,10 @@ QueryBuilder.DEFAULTS = {
],
icons: {
- add_group: 'glyphicon glyphicon-plus-sign',
- add_rule: 'glyphicon glyphicon-plus',
- remove_group: 'glyphicon glyphicon-remove',
- remove_rule: 'glyphicon glyphicon-remove',
- error: 'glyphicon glyphicon-warning-sign'
+ add_group: 'bi-plus-circle-fill',
+ add_rule: 'bi-plus-lg',
+ remove_group: 'bi-x-lg',
+ remove_rule: 'bi-x-lg',
+ error: 'bi-exclamation-triangle'
}
};
diff --git a/src/i18n/ro.json b/src/i18n/ro.json
index a0e1fc06..b2a6eae3 100644
--- a/src/i18n/ro.json
+++ b/src/i18n/ro.json
@@ -1,6 +1,6 @@
{
"__locale": "Romanian (ro)",
- "__author": "ArianServ",
+ "__author": "ArianServ, totpero",
"add_rule": "Adaugă regulă",
"add_group": "Adaugă grup",
@@ -17,10 +17,12 @@
"not_equal": "diferit",
"in": "în",
"not_in": "nu în",
- "less": "mai puţin",
- "less_or_equal": "mai puţin sau egal",
+ "less": "mai mic",
+ "less_or_equal": "mai mic sau egal",
"greater": "mai mare",
"greater_or_equal": "mai mare sau egal",
+ "between": "între",
+ "not_between": "nu între",
"begins_with": "începe cu",
"not_begins_with": "nu începe cu",
"contains": "conţine",
@@ -31,5 +33,31 @@
"is_not_empty": "nu este gol",
"is_null": "e nul",
"is_not_null": "nu e nul"
+ },
+
+ "errors": {
+ "no_filter": "Nici un filtru selectat",
+ "empty_group": "Grupul este gol",
+ "radio_empty": "Nici o valoare nu este selectată",
+ "checkbox_empty": "Nici o valoare nu este selectată",
+ "select_empty": "Nici o valoare nu este selectată",
+ "string_empty": "Valoare goală",
+ "string_exceed_min_length": "Trebuie să conţină mai puţin de {0} caractere",
+ "string_exceed_max_length": "Trebuie să conţină mai mult de {0} caractere",
+ "string_invalid_format": "Format invalid ({0})",
+ "number_nan": "Nu este număr",
+ "number_not_integer": "Nu este număr întreg",
+ "number_not_double": "Nu este număr real",
+ "number_exceed_min": "Trebuie să fie mai mare decât {0}",
+ "number_exceed_max": "Trebuie să fie mai mic decât {0}",
+ "number_wrong_step": "Trebuie să fie multiplu de {0}",
+ "number_between_invalid": "Valori invalide, {0} este mai mare decât {1}",
+ "datetime_empty": "Valoare goală",
+ "datetime_invalid": "Format dată invalid ({0})",
+ "datetime_exceed_min": "Trebuie să fie după {0}",
+ "datetime_exceed_max": "Trebuie să fie înainte {0}",
+ "datetime_between_invalid": "Valori invalide, {0} este mai mare decât {1}",
+ "boolean_not_valid": "Nu este boolean",
+ "operator_not_multiple": "Operatorul \"{1}\" nu poate accepta mai multe valori"
}
-}
\ No newline at end of file
+}
diff --git a/src/main.js b/src/main.js
index f799af6f..0659d288 100644
--- a/src/main.js
+++ b/src/main.js
@@ -121,8 +121,8 @@ var QueryBuilder = function($el, options) {
if (!this.templates[tpl]) {
this.templates[tpl] = QueryBuilder.templates[tpl];
}
- if (typeof this.templates[tpl] == 'string') {
- this.templates[tpl] = doT.template(this.templates[tpl]);
+ if (typeof this.templates[tpl] !== 'function') {
+ throw new Error(`Template ${tpl} must be a function`);
}
}, this);
@@ -134,7 +134,7 @@ var QueryBuilder = function($el, options) {
this.status.id = this.$el.attr('id');
// INIT
- this.$el.addClass('query-builder form-inline');
+ this.$el.addClass('query-builder');
this.filters = this.checkFilters(this.filters);
this.operators = this.checkOperators(this.operators);
diff --git a/src/plugins/bt-checkbox/plugin.js b/src/plugins/bt-checkbox/plugin.js
index 7a7311c2..26b0998f 100644
--- a/src/plugins/bt-checkbox/plugin.js
+++ b/src/plugins/bt-checkbox/plugin.js
@@ -3,12 +3,12 @@
* @memberof module:plugins
* @description Applies Awesome Bootstrap Checkbox for checkbox and radio inputs.
* @param {object} [options]
- * @param {string} [options.font='glyphicons']
+ * @param {string} [options.font='bootstrap-icons']
* @param {string} [options.color='default']
*/
QueryBuilder.define('bt-checkbox', function(options) {
- if (options.font == 'glyphicons') {
- this.$el.addClass('bt-checkbox-glyphicons');
+ if (options.font === 'bootstrap-icons') {
+ this.$el.addClass('bt-checkbox-bootstrap-icons');
}
this.on('getRuleInput.filter', function(h, rule, name) {
@@ -31,15 +31,11 @@ QueryBuilder.define('bt-checkbox', function(options) {
var color = filter.colors[key] || filter.colors._def_ || options.color;
var id = name + '_' + (i++);
- h.value+= '\
-
\
- \
- \
-
';
+ h.value += `
`;
});
}
});
}, {
- font: 'glyphicons',
+ font: 'bootstrap-icons',
color: 'default'
});
diff --git a/src/plugins/bt-checkbox/plugin.scss b/src/plugins/bt-checkbox/plugin.scss
index 324f610f..22e21eed 100644
--- a/src/plugins/bt-checkbox/plugin.scss
+++ b/src/plugins/bt-checkbox/plugin.scss
@@ -1,12 +1,10 @@
-.query-builder.bt-checkbox-glyphicons {
- .checkbox input[type='checkbox']:checked + label::after {
- font-family: 'Glyphicons Halflings';
- content: '\e013';
+.query-builder.bt-checkbox-bootstrap-icons {
+ .checkbox input[type='checkbox'] + label::before {
+ outline: 0;
}
- .checkbox label::after {
- padding-left: 4px;
- padding-top: 2px;
- font-size: 9px;
+ .checkbox input[type='checkbox']:checked + label::after {
+ font-family: 'bootstrap-icons';
+ content: '\F633'; // https://icons.getbootstrap.com/icons/check-lg/
}
}
diff --git a/src/plugins/bt-selectpicker/plugin.js b/src/plugins/bt-selectpicker/plugin.js
deleted file mode 100644
index d9ef2812..00000000
--- a/src/plugins/bt-selectpicker/plugin.js
+++ /dev/null
@@ -1,46 +0,0 @@
-/**
- * @class BtSelectpicker
- * @memberof module:plugins
- * @descriptioon Applies Bootstrap Select on filters and operators combo-boxes.
- * @param {object} [options]
- * @param {string} [options.container='body']
- * @param {string} [options.style='btn-inverse btn-xs']
- * @param {int|string} [options.width='auto']
- * @param {boolean} [options.showIcon=false]
- * @throws MissingLibraryError
- */
-QueryBuilder.define('bt-selectpicker', function(options) {
- if (!$.fn.selectpicker || !$.fn.selectpicker.Constructor) {
- Utils.error('MissingLibrary', 'Bootstrap Select is required to use "bt-selectpicker" plugin. Get it here: http://silviomoreto.github.io/bootstrap-select');
- }
-
- var Selectors = QueryBuilder.selectors;
-
- // init selectpicker
- this.on('afterCreateRuleFilters', function(e, rule) {
- rule.$el.find(Selectors.rule_filter).removeClass('form-control').selectpicker(options);
- });
-
- this.on('afterCreateRuleOperators', function(e, rule) {
- rule.$el.find(Selectors.rule_operator).removeClass('form-control').selectpicker(options);
- });
-
- // update selectpicker on change
- this.on('afterUpdateRuleFilter', function(e, rule) {
- rule.$el.find(Selectors.rule_filter).selectpicker('render');
- });
-
- this.on('afterUpdateRuleOperator', function(e, rule) {
- rule.$el.find(Selectors.rule_operator).selectpicker('render');
- });
-
- this.on('beforeDeleteRule', function(e, rule) {
- rule.$el.find(Selectors.rule_filter).selectpicker('destroy');
- rule.$el.find(Selectors.rule_operator).selectpicker('destroy');
- });
-}, {
- container: 'body',
- style: 'btn-inverse btn-xs',
- width: 'auto',
- showIcon: false
-});
diff --git a/src/plugins/bt-tooltip-errors/plugin.js b/src/plugins/bt-tooltip-errors/plugin.js
index 68423252..52f4830d 100644
--- a/src/plugins/bt-tooltip-errors/plugin.js
+++ b/src/plugins/bt-tooltip-errors/plugin.js
@@ -7,8 +7,9 @@
* @throws MissingLibraryError
*/
QueryBuilder.define('bt-tooltip-errors', function(options) {
- if (!$.fn.tooltip || !$.fn.tooltip.Constructor || !$.fn.tooltip.Constructor.prototype.fixTitle) {
- Utils.error('MissingLibrary', 'Bootstrap Tooltip is required to use "bt-tooltip-errors" plugin. Get it here: http://getbootstrap.com');
+ if (! typeof bootstrap.Tooltip === "function") {
+ alert(typeof bootstrap.Tooltip );
+ Utils.error('MissingLibrary', 'Bootstrap Popper is required to use "bt-tooltip-errors" plugin. Get it here: http://getbootstrap.com');
}
var self = this;
@@ -16,7 +17,7 @@ QueryBuilder.define('bt-tooltip-errors', function(options) {
// add BT Tooltip data
this.on('getRuleTemplate.filter getGroupTemplate.filter', function(h) {
var $h = $($.parseHTML(h.value));
- $h.find(QueryBuilder.selectors.error_container).attr('data-toggle', 'tooltip');
+ $h.find(QueryBuilder.selectors.error_container).attr('data-bs-toggle', 'tooltip');
h.value = $h.prop('outerHTML');
});
@@ -24,9 +25,7 @@ QueryBuilder.define('bt-tooltip-errors', function(options) {
this.model.on('update', function(e, node, field) {
if (field == 'error' && self.settings.display_errors) {
node.$el.find(QueryBuilder.selectors.error_container).eq(0)
- .tooltip(options)
- .tooltip('hide')
- .tooltip('fixTitle');
+ .attr('data-bs-original-title',options).attr('data-bs-title',options).tooltip();
}
});
}, {
diff --git a/src/plugins/filter-description/plugin.js b/src/plugins/filter-description/plugin.js
index 472dd328..3027e761 100644
--- a/src/plugins/filter-description/plugin.js
+++ b/src/plugins/filter-description/plugin.js
@@ -3,7 +3,7 @@
* @memberof module:plugins
* @description Provides three ways to display a description about a filter: inline, Bootsrap Popover or Bootbox.
* @param {object} [options]
- * @param {string} [options.icon='glyphicon glyphicon-info-sign']
+ * @param {string} [options.icon='bi-info-circle-fill']
* @param {string} [options.mode='popover'] - inline, popover or bootbox
* @throws ConfigError
*/
@@ -43,30 +43,28 @@ QueryBuilder.define('filter-description', function(options) {
if (!description) {
$b.hide();
- if ($b.data('bs.popover')) {
+ if ($b.data('bs-popover')) {
$b.popover('hide');
}
}
else {
if ($b.length === 0) {
- $b = $($.parseHTML('
'));
+ $b = $($.parseHTML('
'));
$b.prependTo(rule.$el.find(QueryBuilder.selectors.rule_actions));
-
- $b.popover({
+ const popover = new bootstrap.Popover($b.get(0), {
placement: 'left',
container: 'body',
html: true
- });
-
+ })
$b.on('mouseout', function() {
- $b.popover('hide');
+ popover('hide');
});
}
else {
$b.css('display', '');
}
- $b.data('bs.popover').options.content = description;
+ $b.data('bs-popover').options.content = description;
if ($b.attr('aria-describedby')) {
$b.popover('show');
@@ -89,7 +87,7 @@ QueryBuilder.define('filter-description', function(options) {
}
else {
if ($b.length === 0) {
- $b = $($.parseHTML('
'));
+ $b = $($.parseHTML('
'));
$b.prependTo(rule.$el.find(QueryBuilder.selectors.rule_actions));
$b.on('click', function() {
@@ -105,7 +103,7 @@ QueryBuilder.define('filter-description', function(options) {
});
}
}, {
- icon: 'glyphicon glyphicon-info-sign',
+ icon: 'bi-info-circle-fill',
mode: 'popover'
});
diff --git a/src/plugins/filter-description/plugin.scss b/src/plugins/filter-description/plugin.scss
index 80a2af71..41498718 100644
--- a/src/plugins/filter-description/plugin.scss
+++ b/src/plugins/filter-description/plugin.scss
@@ -16,6 +16,6 @@ $description-border: 1px solid $description-border-color;
border: $description-border;
color: $description-text-color;
border-radius: $item-border-radius;
- padding: #{$rule-padding / 2} $rule-padding;
+ padding: #{$rule-padding * .5} $rule-padding;
font-size: .8em;
}
diff --git a/src/plugins/invert/plugin.js b/src/plugins/invert/plugin.js
index c0294e84..8b0ab7ef 100644
--- a/src/plugins/invert/plugin.js
+++ b/src/plugins/invert/plugin.js
@@ -3,7 +3,7 @@
* @memberof module:plugins
* @description Allows to invert a rule operator, a group condition or the entire builder.
* @param {object} [options]
- * @param {string} [options.icon='glyphicon glyphicon-random']
+ * @param {string} [options.icon='bi-shuffle']
* @param {boolean} [options.recursive=true]
* @param {boolean} [options.invert_rules=true]
* @param {boolean} [options.display_rules_button=false]
@@ -33,7 +33,7 @@ QueryBuilder.define('invert', function(options) {
this.on('getGroupTemplate.filter', function(h) {
var $h = $($.parseHTML(h.value));
$h.find(Selectors.condition_container).after(
- '