diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index fe882f7e..be3320fd 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -3,6 +3,8 @@
## Work on `dev`
Any merge request should be created from and issued to the `dev` branch.
+Do not add the `dist` files to your pull request. The directory is ignored for a reason: it is generated and pushed only when doing a release on `master`.
+
## Core vs Plugins
I want to keep the core clean of extra (and certainly awesome) functionalities. That includes, but is not limited to, export/import plugins, visual aids, etc.
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
index 931f097a..7a8c011b 100644
--- a/.github/ISSUE_TEMPLATE.md
+++ b/.github/ISSUE_TEMPLATE.md
@@ -3,4 +3,4 @@
- Please search in the [documentation](http://querybuilder.js.org) before asking.
- Any issue without enough details won't get any answer and will be closed.
- Help requests must be exhaustive, precise and come with some code explaining the need (use Markdown code highlight).
-- Bug reports must come with a simple test case, preferably on jsFiddle, Plunker, etc. (QueryBuilder is available on [unpkg](https://unpkg.com/jQuery-QueryBuilder/dist/) to be used on such platforms).
+- Bug reports must come with a simple test case, preferably on jsFiddle, Plunker, etc. (QueryBuilder is available on [jsDelivr](https://cdn.jsdelivr.net/npm/jQuery-QueryBuilder/dist/) and [unpkg](https://unpkg.com/jQuery-QueryBuilder/dist/) to be used on such platforms).
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 25399e11..1131297f 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -2,6 +2,6 @@
- [ ] 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`
-- [ ] Unit tests are OK
+- [ ] I didn't pushed the `dist` directory
- [ ] 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
new file mode 100644
index 00000000..0648b398
--- /dev/null
+++ b/.github/workflows/main.yml
@@ -0,0 +1,21 @@
+name: CI
+
+on: [push, pull_request]
+
+jobs:
+ build:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - 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 2b7141a4..a50c5eea 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,8 +1,7 @@
-bower_components
node_modules
dist
doc
.sass-cache
-.coverage-results
.idea
*.iml
+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/.npmignore b/.npmignore
deleted file mode 100644
index 4dec3eec..00000000
--- a/.npmignore
+++ /dev/null
@@ -1,5 +0,0 @@
-.*
-build
-composer.json
-Gruntfile.js
-bower_components
diff --git a/.scss-lint.yml b/.scss-lint.yml
deleted file mode 100644
index db8d4f79..00000000
--- a/.scss-lint.yml
+++ /dev/null
@@ -1,31 +0,0 @@
-linters:
- PropertySortOrder:
- enabled: false
-
- SingleLinePerSelector:
- enabled: false
-
- SelectorDepth:
- max_depth: 4
-
- NestingDepth:
- max_depth: 4
-
- HexLength:
- enabled: false
-
- HexNotation:
- style: uppercase
-
- Shorthand:
- allowed_shorthands: [1, 2, 4]
-
- QualifyingElement:
- enabled: false
-
- ImportantRule:
- enabled: false
-
- VendorPrefix:
- exclude:
- - src/scss/_mixins.scss
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 59e0af84..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,11 +0,0 @@
-language: node_js
-node_js:
- - "5"
-before_install:
- - gem install sass
- - gem install scss_lint -v 0.49.0
- - npm install -g grunt-cli
- - npm install -g bower
-before_script:
- - bower install
-after_success: grunt coveralls
diff --git a/Gruntfile.js b/Gruntfile.js
deleted file mode 100644
index cfb31c00..00000000
--- a/Gruntfile.js
+++ /dev/null
@@ -1,436 +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, {
- scsslint: 'grunt-scss-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: [
- 'bower_components/jquery-extendext/jQuery.extendext.js',
- 'bower_components/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 (http://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 (http://opensource.org/licenses/MIT)\n' +
- ' */',
-
- // serve folder content
- connect: {
- dev: {
- options: {
- host: '0.0.0.0',
- port: 9000,
- livereload: true
- }
- }
- },
-
- // 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: '',
- 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: 'none',
- style: '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: { except: ['$'] }
- },
- 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
- scsslint: {
- lib: {
- options: {
- colorizeOutput: true,
- config: '.scss-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: ['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'
- }
- }
- },
-
- // 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',
- 'scsslint',
- 'build_lang',
- 'build_css',
- 'injector:testSrc',
- 'injector:testModules',
- 'qunit_blanket_lcov',
- 'qunit'
- ]);
-
- grunt.registerTask('serve', [
- 'build_lang',
- 'build_css',
- 'injector:example',
- 'open',
- 'connect',
- 'watch'
- ]);
-
- grunt.registerTask('doc', [
- 'clean:doc',
- 'jsdoc',
- 'copy:doc_script'
- ]);
-};
diff --git a/LICENSE b/LICENSE
index 99070e3d..2558fa6a 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2014-2015 Damien Sorel
+Copyright (c) 2014-2018 Damien Sorel
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
\ No newline at end of file
+SOFTWARE.
diff --git a/README.md b/README.md
index e48cf2e2..f0fd8ab7 100644
--- a/README.md
+++ b/README.md
@@ -1,19 +1,18 @@
# jQuery QueryBuilder
-[](http://querybuilder.js.org)
[](https://www.npmjs.com/package/jQuery-QueryBuilder)
-[](http://www.jsdelivr.com/projects/jquery.query-builder)
-[](https://travis-ci.org/mistic100/jQuery-QueryBuilder)
-[](https://coveralls.io/r/mistic100/jQuery-QueryBuilder)
+[](https://www.jsdelivr.com/package/npm/jQuery-QueryBuilder)
+[](https://github.com/mistic100/jQuery-QueryBuilder/actions)
+[](https://gitlocalize.com/repo/5259/whole_project?utm_source=badge)
jQuery plugin offering an simple interface to create complex queries.
-[](http://querybuilder.js.org)
+[](https://querybuilder.js.org)
## Documentation
-[http://querybuilder.js.org](http://querybuilder.js.org)
+[querybuilder.js.org](https://querybuilder.js.org)
@@ -23,77 +22,40 @@ jQuery plugin offering an simple interface to create complex queries.
[Download the latest release](https://github.com/mistic100/jQuery-QueryBuilder/releases)
-#### With Bower
-
-```bash
-$ bower install jQuery-QueryBuilder
-```
-
#### With npm
```bash
$ npm install jQuery-QueryBuilder
```
+#### Via CDN
+
+jQuery-QueryBuilder is available on [jsDelivr](https://www.jsdelivr.com/package/npm/jQuery-QueryBuilder).
### Dependencies
- * jQuery >= 1.10
- * Bootstrap >= 3.1 (CSS only)
+ * [jQuery 3](https://jquery.com)
+ * [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 >= 1.0.3](http://olado.github.io/doT)
- * [MomentJS](http://momentjs.com) (optional, for Date/Time validation)
+ * [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 >= 9
- * All other recent browsers
+## Developement
-## Build
+Install Node dependencies with `npm install`.
-#### Prerequisites
+#### Build
- * NodeJS + NPM: `apt-get install nodejs-legacy npm`
- * Ruby Dev: `apt-get install ruby-dev`
- * Grunt CLI: `npm install -g grunt-cli`
- * Bower: `npm install -g bower`
- * SASS: `gem install sass`
+Run `npm run build` in the root directory to generate production files inside `dist`.
-#### Run
-
-Install Node and Bower dependencies `npm install & bower 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.
+#### Serve
+Run `npm run serve` to open the example page with automatic build and livereload.
## License
This library is available under the MIT license.
-
-#### Inspirations
- * [Knockout Query Builder](http://kindohm.github.io/knockout-query-builder/)
- * [jui_filter_rules](http://www.pontikis.net/labs/jui_filter_rules/)
diff --git a/bower.json b/bower.json
deleted file mode 100644
index 9f597a38..00000000
--- a/bower.json
+++ /dev/null
@@ -1,56 +0,0 @@
-{
- "name": "jQuery-QueryBuilder",
- "authors": [
- {
- "name": "Damien \"Mistic\" Sorel",
- "email": "contact@git.strangeplanet.fr",
- "homepage": "http://www.strangeplanet.fr"
- }
- ],
- "description": "jQuery plugin for user friendly query/filter creator",
- "main": [
- "dist/js/query-builder.js",
- "dist/css/query-builder.default.css"
- ],
- "dependencies": {
- "jquery": ">=1.9.0",
- "bootstrap": ">=3.1.0",
- "moment": ">=2.6.0",
- "jquery-extendext": ">=0.1.2",
- "doT": ">=1.0.3"
- },
- "devDependencies": {
- "blanket": "^1.1.0",
- "qunit": "^1.23.0",
- "bootstrap-select": "^1.10.0",
- "bootbox": "^4.4.0",
- "awesome-bootstrap-checkbox": "^0.3.0",
- "sql-parser": "^1.1.0",
- "bind-polyfill": "~1.0.0",
- "interact": "^1.2.6"
- },
- "keywords": [
- "jquery",
- "query",
- "builder",
- "filter"
- ],
- "license": "MIT",
- "homepage": "https://github.com/mistic100/jQuery-QueryBuilder",
- "repository": {
- "type": "git",
- "url": "git://github.com/mistic100/jQuery-QueryBuilder.git"
- },
- "ignore": [
- "**/.*",
- "node_modules",
- "bower_components",
- "build",
- "src",
- "tests",
- "composer.json",
- "package.json",
- "Gruntfile.js",
- "CONTRIBUTING.md"
- ]
-}
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/composer.json b/composer.json
deleted file mode 100644
index 6cbe2f11..00000000
--- a/composer.json
+++ /dev/null
@@ -1,46 +0,0 @@
-{
- "name": "mistic100/jquery-querybuilder",
- "version": "2.4.0",
- "authors": [{
- "name": "Damien \"Mistic\" Sorel",
- "email": "contact@git.strangeplanet.fr",
- "homepage": "http://www.strangeplanet.fr"
- }],
- "description": "jQuery plugin for user friendly query/filter creator",
- "require": {
- "robloach/component-installer": "*",
- "components/jquery": ">=1.9.0",
- "moment/moment": ">=2.6.0",
- "components/bootstrap": ">=3.1.0"
- },
- "keywords": [
- "jquery",
- "query",
- "builder",
- "filter"
- ],
- "license": "MIT",
- "homepage": "https://github.com/mistic100/jQuery-QueryBuilder",
- "support": {
- "issues": "https://github.com/mistic100/jQuery-QueryBuilder/issues"
- },
- "extra": {
- "component": {
- "styles": [
- "dist/css/query-builder.default.css"
- ],
- "scripts": [
- "dist/js/query-builder.standalone.js"
- ],
- "files": [
- "dist/css/query-builder.default.min.css",
- "dist/css/query-builder.dark.css",
- "dist/css/query-builder.dark.min.css",
- "dist/js/query-builder.js",
- "dist/js/query-builder.min.js",
- "dist/js/query-builder.standalone.min.js",
- "dist/i18n/*.js"
- ]
- }
- }
-}
diff --git a/examples/bower.json b/examples/bower.json
deleted file mode 100644
index 85233d7e..00000000
--- a/examples/bower.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "name": "jQuery-QueryBuilder-example",
- "dependencies" : {
- "seiyria-bootstrap-slider": "latest",
- "bootswatch-dist": "#slate",
- "selectize": "latest"
- }
-}
diff --git a/examples/index.html b/examples/index.html
index 369c9eda..180652b8 100644
--- a/examples/index.html
+++ b/examples/index.html
@@ -1,84 +1,70 @@
-
+
jQuery QueryBuilder Example
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
+
-
-
- You must execute bower install in the example directory to run this demo.
-
-
-
-
@@ -86,7 +72,7 @@
jQuery QueryBuilder
Reset
- Set filters
@@ -112,46 +98,23 @@ Output
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
diff --git a/package.json b/package.json
index f9cc0c5e..4111f3a0 100644
--- a/package.json
+++ b/package.json
@@ -4,43 +4,37 @@
"author": {
"name": "Damien \"Mistic\" Sorel",
"email": "contact@git.strangeplanet.fr",
- "url": "http://www.strangeplanet.fr"
+ "url": "https://www.strangeplanet.fr"
},
"description": "jQuery plugin for user friendly query/filter creator",
"main": "dist/js/query-builder.js",
+ "files": [
+ "dist/",
+ "src/"
+ ],
"dependencies": {
- "jquery": ">=1.9.0",
- "bootstrap": ">=3.1.0",
- "moment": ">=2.6.0",
- "jquery-extendext": ">=0.1.2",
- "dot": ">=1.0.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": {
- "deepmerge": "^0.2.0",
- "foodoc": "git://github.com/mistic100/foodoc.git#custom",
- "grunt": "^1.0.0",
- "grunt-banner": "^0.6.0",
- "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": "^1.0.0",
- "grunt-contrib-jshint": "^1.0.0",
- "grunt-contrib-qunit": "^0.7.0",
- "grunt-contrib-sass": "^1.0.0",
- "grunt-contrib-uglify": "^1.0.0",
- "grunt-contrib-watch": "^1.0.0",
- "grunt-coveralls": "^1.0.0",
- "grunt-injector": "^1.1.0",
- "grunt-jscs": "^2.8.0",
- "grunt-jsdoc": "^2.1.0",
- "grunt-open": "^0.2.3",
- "grunt-qunit-blanket-lcov": "^0.3.0",
- "grunt-sass-injection": "^1.0.3",
- "grunt-scss-lint": "^0.3.8",
- "grunt-wrap": "^0.3.0",
- "jit-grunt": "^0.10.0",
- "time-grunt": "^1.3.0"
+ "alive-server": "^1.3.0",
+ "awesome-bootstrap-checkbox": "^0.3.7",
+ "bootbox": "^6.0.0",
+ "bootstrap-slider": "^10.0.0",
+ "chosenjs": "^1.4.3",
+ "concurrently": "^8.2.0",
+ "deepmerge": "^2.1.0",
+ "foodoc": "^0.0.9",
+ "glob": "^10.3.1",
+ "interactjs": "^1.3.3",
+ "nodemon": "^2.0.22",
+ "sass": "^1.63.6",
+ "@selectize/selectize": "^0.15.2"
},
"keywords": [
"jquery",
@@ -49,7 +43,7 @@
"filter"
],
"license": "MIT",
- "homepage": "https://github.com/mistic100/jQuery-QueryBuilder",
+ "homepage": "https://querybuilder.js.org",
"repository": {
"type": "git",
"url": "git://github.com/mistic100/jQuery-QueryBuilder.git"
@@ -58,6 +52,9 @@
"url": "https://github.com/mistic100/jQuery-QueryBuilder/issues"
},
"scripts": {
- "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 b3e2d2ec..bcb7c912 100644
--- a/src/core.js
+++ b/src/core.js
@@ -1,3 +1,26 @@
+/**
+ * Final initialisation of the builder
+ * @param {object} [rules]
+ * @fires QueryBuilder.afterInit
+ * @private
+ */
+QueryBuilder.prototype.init = function(rules) {
+ /**
+ * When the initilization is done, just before creating the root group
+ * @event afterInit
+ * @memberof QueryBuilder
+ */
+ this.trigger('afterInit');
+
+ if (rules) {
+ this.setRules(rules);
+ delete this.settings.rules;
+ }
+ else {
+ this.setRoot(true);
+ }
+};
+
/**
* Checks the configuration of each filter
* @param {QueryBuilder.Filter[]} filters
@@ -70,12 +93,40 @@ QueryBuilder.prototype.checkFilters = function(filters) {
break;
case 'select':
+ var cleanValues = [];
+ filter.has_optgroup = false;
+
+ Utils.iterateOptions(filter.values, function(value, label, optgroup) {
+ cleanValues.push({
+ value: value,
+ label: label,
+ optgroup: optgroup || null
+ });
+
+ if (optgroup) {
+ filter.has_optgroup = true;
+
+ // register optgroup if needed
+ if (!this.settings.optgroups[optgroup]) {
+ this.settings.optgroups[optgroup] = optgroup;
+ }
+ }
+ }.bind(this));
+
+ if (filter.has_optgroup) {
+ filter.values = Utils.groupSort(cleanValues, 'optgroup');
+ }
+ else {
+ filter.values = cleanValues;
+ }
+
if (filter.placeholder) {
if (filter.placeholder_value === undefined) {
filter.placeholder_value = -1;
}
- Utils.iterateOptions(filter.values, function(key) {
- if (key == filter.placeholder_value) {
+
+ filter.values.forEach(function(entry) {
+ if (entry.value == filter.placeholder_value) {
Utils.error('Config', 'Placeholder of filter "{0}" overlaps with one of its values', filter.id);
}
});
@@ -259,7 +310,7 @@ QueryBuilder.prototype.bindEvents = function() {
break;
case 'value':
- self.updateRuleValue(node);
+ self.updateRuleValue(node, oldValue);
break;
}
}
@@ -274,7 +325,7 @@ QueryBuilder.prototype.bindEvents = function() {
break;
case 'condition':
- self.updateGroupCondition(node);
+ self.updateGroupCondition(node, oldValue);
break;
}
}
@@ -294,19 +345,18 @@ QueryBuilder.prototype.setRoot = function(addRule, data, flags) {
addRule = (addRule === undefined || addRule === true);
var group_id = this.nextGroupId();
- var $group = $(this.getGroupTemplate(group_id, 1));
+ var $group = $($.parseHTML(this.getGroupTemplate(group_id, 1)));
this.$el.append($group);
this.model.root = new Group(null, $group);
this.model.root.model = this.model;
this.model.root.data = data;
- this.model.root.__.flags = $.extend({}, this.settings.default_group_flags, flags);
+ this.model.root.flags = $.extend({}, this.settings.default_group_flags, flags);
+ this.model.root.condition = this.settings.default_condition;
this.trigger('afterAddGroup', this.model.root);
- this.model.root.condition = this.settings.default_condition;
-
if (addRule) {
this.addRule(this.model.root);
}
@@ -347,7 +397,8 @@ QueryBuilder.prototype.addGroup = function(parent, addRule, data, flags) {
var model = parent.addGroup($group);
model.data = data;
- model.__.flags = $.extend({}, this.settings.default_group_flags, flags);
+ model.flags = $.extend({}, this.settings.default_group_flags, flags);
+ model.condition = this.settings.default_condition;
/**
* Just after adding a group
@@ -357,7 +408,12 @@ QueryBuilder.prototype.addGroup = function(parent, addRule, data, flags) {
*/
this.trigger('afterAddGroup', model);
- model.condition = this.settings.default_condition;
+ /**
+ * After any change in the rules
+ * @event rulesChanged
+ * @memberof QueryBuilder
+ */
+ this.trigger('rulesChanged');
if (addRule) {
this.addRule(model);
@@ -406,6 +462,8 @@ QueryBuilder.prototype.deleteGroup = function(group) {
* @memberof QueryBuilder
*/
this.trigger('afterDeleteGroup');
+
+ this.trigger('rulesChanged');
}
return del;
@@ -414,10 +472,11 @@ QueryBuilder.prototype.deleteGroup = function(group) {
/**
* Performs actions when a group's condition changes
* @param {Group} group
+ * @param {object} previousCondition
* @fires QueryBuilder.afterUpdateGroupCondition
* @private
*/
-QueryBuilder.prototype.updateGroupCondition = function(group) {
+QueryBuilder.prototype.updateGroupCondition = function(group, previousCondition) {
group.$el.find('>' + QueryBuilder.selectors.group_condition).each(function() {
var $this = $(this);
$this.prop('checked', $this.val() === group.condition);
@@ -429,8 +488,11 @@ QueryBuilder.prototype.updateGroupCondition = function(group) {
* @event afterUpdateGroupCondition
* @memberof QueryBuilder
* @param {Group} group
+ * @param {object} previousCondition
*/
- this.trigger('afterUpdateGroupCondition', group);
+ this.trigger('afterUpdateGroupCondition', group, previousCondition);
+
+ this.trigger('rulesChanged');
};
/**
@@ -473,14 +535,11 @@ QueryBuilder.prototype.addRule = function(parent, data, flags) {
}
var rule_id = this.nextRuleId();
- var $rule = $(this.getRuleTemplate(rule_id));
+ var $rule = $($.parseHTML(this.getRuleTemplate(rule_id)));
var model = parent.addRule($rule);
- if (data !== undefined) {
- model.data = data;
- }
-
- model.__.flags = $.extend({}, this.settings.default_rule_flags, flags);
+ model.data = data;
+ model.flags = $.extend({}, this.settings.default_rule_flags, flags);
/**
* Just after adding a rule
@@ -490,6 +549,8 @@ QueryBuilder.prototype.addRule = function(parent, data, flags) {
*/
this.trigger('afterAddRule', model);
+ this.trigger('rulesChanged');
+
this.createRuleFilters(model);
if (this.settings.default_filter || !this.settings.display_empty_filter) {
@@ -542,6 +603,8 @@ QueryBuilder.prototype.deleteRule = function(rule) {
*/
this.trigger('afterDeleteRule');
+ this.trigger('rulesChanged');
+
return true;
};
@@ -562,7 +625,7 @@ QueryBuilder.prototype.createRuleFilters = function(rule) {
* @returns {QueryBuilder.Filter[]}
*/
var filters = this.change('getRuleFilters', this.filters, rule);
- var $filterSelect = $(this.getRuleFilterSelect(rule, filters));
+ var $filterSelect = $($.parseHTML(this.getRuleFilterSelect(rule, filters)));
rule.$el.find(QueryBuilder.selectors.filter_container).html($filterSelect);
@@ -573,6 +636,8 @@ QueryBuilder.prototype.createRuleFilters = function(rule) {
* @param {Rule} rule
*/
this.trigger('afterCreateRuleFilters', rule);
+
+ this.applyRuleFlags(rule);
};
/**
@@ -589,12 +654,19 @@ QueryBuilder.prototype.createRuleOperators = function(rule) {
}
var operators = this.getOperators(rule.filter);
- var $operatorSelect = $(this.getRuleOperatorSelect(rule, operators));
+ var $operatorSelect = $($.parseHTML(this.getRuleOperatorSelect(rule, operators)));
$operatorContainer.html($operatorSelect);
// set the operator without triggering update event
- rule.__.operator = operators[0];
+ if (rule.filter.default_operator) {
+ rule.__.operator = this.getOperatorByType(rule.filter.default_operator);
+ }
+ else {
+ rule.__.operator = operators[0];
+ }
+
+ rule.$el.find(QueryBuilder.selectors.rule_operator).val(rule.operator.type);
/**
* After creating the dropdown for operators
@@ -604,6 +676,8 @@ QueryBuilder.prototype.createRuleOperators = function(rule) {
* @param {QueryBuilder.Operator[]} operators - allowed operators for this rule
*/
this.trigger('afterCreateRuleOperators', rule, operators);
+
+ this.applyRuleFlags(rule);
};
/**
@@ -626,16 +700,16 @@ QueryBuilder.prototype.createRuleInput = function(rule) {
var filter = rule.filter;
for (var i = 0; i < rule.operator.nb_inputs; i++) {
- var $ruleInput = $(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);
}
- $valueContainer.show();
+ $valueContainer.css('display', '');
$inputs.on('change ' + (filter.input_event || ''), function() {
- if (!this._updating_input) {
+ if (!rule._updating_input) {
rule._updating_value = true;
rule.value = self.getRuleInputValue(rule);
rule._updating_value = false;
@@ -662,6 +736,8 @@ QueryBuilder.prototype.createRuleInput = function(rule) {
rule.value = self.getRuleInputValue(rule);
rule._updating_value = false;
}
+
+ this.applyRuleFlags(rule);
};
/**
@@ -687,8 +763,11 @@ QueryBuilder.prototype.updateRuleFilter = function(rule, previousFilter) {
* @event afterUpdateRuleFilter
* @memberof QueryBuilder
* @param {Rule} rule
+ * @param {object} previousFilter
*/
- this.trigger('afterUpdateRuleFilter', rule);
+ this.trigger('afterUpdateRuleFilter', rule, previousFilter);
+
+ this.trigger('rulesChanged');
};
/**
@@ -707,7 +786,7 @@ QueryBuilder.prototype.updateRuleOperator = function(rule, previousOperator) {
rule.__.value = undefined;
}
else {
- $valueContainer.show();
+ $valueContainer.css('display', '');
if ($valueContainer.is(':empty') || !previousOperator ||
rule.operator.nb_inputs !== previousOperator.nb_inputs ||
@@ -719,6 +798,9 @@ QueryBuilder.prototype.updateRuleOperator = function(rule, previousOperator) {
if (rule.operator) {
rule.$el.find(QueryBuilder.selectors.rule_operator).val(rule.operator.type);
+
+ // refresh value if the format changed for this operator
+ rule.__.value = this.getRuleInputValue(rule);
}
/**
@@ -726,19 +808,21 @@ QueryBuilder.prototype.updateRuleOperator = function(rule, previousOperator) {
* @event afterUpdateRuleOperator
* @memberof QueryBuilder
* @param {Rule} rule
+ * @param {object} previousOperator
*/
- this.trigger('afterUpdateRuleOperator', rule);
+ this.trigger('afterUpdateRuleOperator', rule, previousOperator);
- this.updateRuleValue(rule);
+ this.trigger('rulesChanged');
};
/**
* Performs actions when rule's value changes
* @param {Rule} rule
+ * @param {object} previousValue
* @fires QueryBuilder.afterUpdateRuleValue
* @private
*/
-QueryBuilder.prototype.updateRuleValue = function(rule) {
+QueryBuilder.prototype.updateRuleValue = function(rule, previousValue) {
if (!rule._updating_value) {
this.setRuleInputValue(rule, rule.value);
}
@@ -748,8 +832,11 @@ QueryBuilder.prototype.updateRuleValue = function(rule) {
* @event afterUpdateRuleValue
* @memberof QueryBuilder
* @param {Rule} rule
+ * @param {*} previousValue
*/
- this.trigger('afterUpdateRuleValue', rule);
+ this.trigger('afterUpdateRuleValue', rule, previousValue);
+
+ this.trigger('rulesChanged');
};
/**
@@ -762,15 +849,10 @@ QueryBuilder.prototype.applyRuleFlags = function(rule) {
var flags = rule.flags;
var Selectors = QueryBuilder.selectors;
- if (flags.filter_readonly) {
- rule.$el.find(Selectors.rule_filter).prop('disabled', true);
- }
- if (flags.operator_readonly) {
- rule.$el.find(Selectors.rule_operator).prop('disabled', true);
- }
- if (flags.value_readonly) {
- rule.$el.find(Selectors.rule_value).prop('disabled', true);
- }
+ rule.$el.find(Selectors.rule_filter).prop('disabled', flags.filter_readonly);
+ rule.$el.find(Selectors.rule_operator).prop('disabled', flags.operator_readonly);
+ rule.$el.find(Selectors.rule_value).prop('disabled', flags.value_readonly);
+
if (flags.no_delete) {
rule.$el.find(Selectors.delete_rule).remove();
}
@@ -794,10 +876,9 @@ QueryBuilder.prototype.applyGroupFlags = function(group) {
var flags = group.flags;
var Selectors = QueryBuilder.selectors;
- if (flags.condition_readonly) {
- group.$el.find('>' + Selectors.group_condition).prop('disabled', true)
- .parent().addClass('readonly');
- }
+ group.$el.find('>' + Selectors.group_condition).prop('disabled', flags.condition_readonly)
+ .parent().toggleClass('readonly', flags.condition_readonly);
+
if (flags.no_add_rule) {
group.$el.find(Selectors.add_rule).remove();
}
diff --git a/src/data.js b/src/data.js
index 1594f334..7f466940 100644
--- a/src/data.js
+++ b/src/data.js
@@ -221,6 +221,29 @@ QueryBuilder.prototype._validateValue = function(rule, value) {
}
}
+ if ((rule.operator.type === 'between' || rule.operator.type === 'not_between') && value.length === 2) {
+ switch (QueryBuilder.types[filter.type]) {
+ case 'number':
+ if (value[0] > value[1]) {
+ result = ['number_between_invalid', value[0], value[1]];
+ }
+ break;
+
+ case 'datetime':
+ // we need MomentJS
+ if (validation.format) {
+ if (!('moment' in window)) {
+ Utils.error('MissingLibrary', 'MomentJS is required for Date/Time validation. Get it here http://momentjs.com');
+ }
+
+ if (moment(value[0], validation.format).isAfter(moment(value[1], validation.format))) {
+ result = ['datetime_between_invalid', value[0], value[1]];
+ }
+ }
+ break;
+ }
+ }
+
return result;
};
@@ -247,7 +270,6 @@ QueryBuilder.prototype.nextRuleId = function() {
* @param {string|object} filter - filter id or filter object
* @returns {object[]}
* @fires QueryBuilder.changer:getOperators
- * @private
*/
QueryBuilder.prototype.getOperators = function(filter) {
if (typeof filter == 'string') {
@@ -295,7 +317,6 @@ QueryBuilder.prototype.getOperators = function(filter) {
* @param {boolean} [doThrow=true]
* @returns {object|null}
* @throws UndefinedFilterError
- * @private
*/
QueryBuilder.prototype.getFilterById = function(id, doThrow) {
if (id == '-1') {
@@ -319,7 +340,6 @@ QueryBuilder.prototype.getFilterById = function(id, doThrow) {
* @param {boolean} [doThrow=true]
* @returns {object|null}
* @throws UndefinedOperatorError
- * @private
*/
QueryBuilder.prototype.getOperatorByType = function(type, doThrow) {
if (type == '-1') {
@@ -366,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 {
@@ -394,11 +410,20 @@ QueryBuilder.prototype.getRuleInputValue = function(rule) {
}
}
- if (operator.multiple && filter.value_separator) {
- value = value.map(function(val) {
- return val.split(filter.value_separator);
- });
- }
+ value = value.map(function(val) {
+ if (operator.multiple && filter.value_separator && typeof val == 'string') {
+ val = val.split(filter.value_separator);
+ }
+
+ if ($.isArray(val)) {
+ return val.map(function(subval) {
+ return Utils.changeType(subval, filter.type);
+ });
+ }
+ else {
+ return Utils.changeType(val, filter.type);
+ }
+ });
if (operator.nb_inputs === 1) {
value = value[0];
@@ -435,7 +460,7 @@ QueryBuilder.prototype.setRuleInputValue = function(rule, value) {
return;
}
- this._updating_input = true;
+ rule._updating_input = true;
if (filter.valueSetter) {
filter.valueSetter.call(this, rule, value);
@@ -459,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:
@@ -476,7 +499,7 @@ QueryBuilder.prototype.setRuleInputValue = function(rule, value) {
}
}
- this._updating_input = false;
+ rule._updating_input = false;
};
/**
diff --git a/src/defaults.js b/src/defaults.js
index 56f1201d..b63b1251 100644
--- a/src/defaults.js
+++ b/src/defaults.js
@@ -156,7 +156,8 @@ QueryBuilder.DEFAULTS = {
group: null,
rule: null,
filterSelect: null,
- operatorSelect: null
+ operatorSelect: null,
+ ruleValueSelect: null
},
lang_code: 'en',
@@ -186,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/en.json b/src/i18n/en.json
index a0e9fd12..15bbb139 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -51,11 +51,13 @@
"number_exceed_min": "Must be greater than {0}",
"number_exceed_max": "Must be lower than {0}",
"number_wrong_step": "Must be a multiple of {0}",
+ "number_between_invalid": "Invalid values, {0} is greater than {1}",
"datetime_empty": "Empty value",
"datetime_invalid": "Invalid date format ({0})",
"datetime_exceed_min": "Must be after {0}",
"datetime_exceed_max": "Must be before {0}",
+ "datetime_between_invalid": "Invalid values, {0} is greater than {1}",
"boolean_not_valid": "Not a boolean",
"operator_not_multiple": "Operator \"{1}\" cannot accept multiple values"
}
-}
\ No newline at end of file
+}
diff --git a/src/i18n/eo.json b/src/i18n/eo.json
new file mode 100644
index 00000000..b3c9166d
--- /dev/null
+++ b/src/i18n/eo.json
@@ -0,0 +1,63 @@
+{
+ "__locale": "Esperanto (eo)",
+ "__author": "Robin van der Vliet, https://robinvandervliet.com/",
+
+ "add_rule": "Aldoni regulon",
+ "add_group": "Aldoni grupon",
+ "delete_rule": "Forigi",
+ "delete_group": "Forigi",
+
+ "conditions": {
+ "AND": "KAJ",
+ "OR": "AŬ"
+ },
+
+ "operators": {
+ "equal": "estas egala al",
+ "not_equal": "ne estas egala al",
+ "in": "estas en",
+ "not_in": "ne estas en",
+ "less": "estas malpli ol",
+ "less_or_equal": "estas malpli ol aŭ egala al",
+ "greater": "estas pli ol",
+ "greater_or_equal": "estas pli ol aŭ egala al",
+ "between": "estas inter",
+ "not_between": "ne estas inter",
+ "begins_with": "komenciĝas per",
+ "not_begins_with": "ne komenciĝas per",
+ "contains": "enhavas",
+ "not_contains": "ne enhavas",
+ "ends_with": "finiĝas per",
+ "not_ends_with": "ne finiĝas per",
+ "is_empty": "estas malplena",
+ "is_not_empty": "ne estas malplena",
+ "is_null": "estas senvalora",
+ "is_not_null": "ne estas senvalora"
+ },
+
+ "errors": {
+ "no_filter": "Neniu filtrilo elektita",
+ "empty_group": "La grupo estas malplena",
+ "radio_empty": "Neniu valoro elektita",
+ "checkbox_empty": "Neniu valoro elektita",
+ "select_empty": "Neniu valoro elektita",
+ "string_empty": "Malplena valoro",
+ "string_exceed_min_length": "Devas enhavi pli ol {0} signojn",
+ "string_exceed_max_length": "Devas ne enhavi pli ol {0} signojn",
+ "string_invalid_format": "Nevalida strukturo ({0})",
+ "number_nan": "Ne estas nombro",
+ "number_not_integer": "Ne estas entjera nombro",
+ "number_not_double": "Ne estas reela nombro",
+ "number_exceed_min": "Devas esti pli ol {0}",
+ "number_exceed_max": "Devas esti malpli ol {0}",
+ "number_wrong_step": "Devas esti oblo de {0}",
+ "number_between_invalid": "Nevalidaj valoroj, {0} estas pli ol {1}",
+ "datetime_empty": "Malplena valoro",
+ "datetime_invalid": "Nevalida dato ({0})",
+ "datetime_exceed_min": "Devas esti post {0}",
+ "datetime_exceed_max": "Devas esti antaŭ {0}",
+ "datetime_between_invalid": "Nevalidaj valoroj, {0} estas post {1}",
+ "boolean_not_valid": "Ne estas bulea valoro",
+ "operator_not_multiple": "La operacio \"{1}\" ne akceptas plurajn valorojn"
+ }
+}
diff --git a/src/i18n/es.json b/src/i18n/es.json
index 3db3e098..3374aae1 100644
--- a/src/i18n/es.json
+++ b/src/i18n/es.json
@@ -22,6 +22,7 @@
"greater": "mayor",
"greater_or_equal": "mayor o igual",
"between": "entre",
+ "not_between": "no está entre",
"begins_with": "empieza por",
"not_begins_with": "no empieza por",
"contains": "contiene",
@@ -52,6 +53,11 @@
"number_wrong_step": "Debe ser múltiplo de {0}",
"datetime_invalid": "Formato de fecha inválido ({0})",
"datetime_exceed_min": "Debe ser posterior a {0}",
- "datetime_exceed_max": "Debe ser anterior a {0}"
+ "datetime_exceed_max": "Debe ser anterior a {0}",
+ "number_between_invalid": "Valores Inválidos, {0} es mayor que {1}",
+ "datetime_empty": "Campo vacio",
+ "datetime_between_invalid": "Valores Inválidos, {0} es mayor que {1}",
+ "boolean_not_valid": "No es booleano",
+ "operator_not_multiple": "El operador \"{1}\" no puede aceptar valores multiples"
}
}
diff --git a/src/i18n/fa-IR.json b/src/i18n/fa-IR.json
index 2900248c..55a6e456 100644
--- a/src/i18n/fa-IR.json
+++ b/src/i18n/fa-IR.json
@@ -22,6 +22,7 @@
"greater": "بزرگتر از",
"greater_or_equal": "بزرگتر یا مساوی با",
"between": "مابین",
+ "not_between": "مابین نباشد",
"begins_with": "شروع شود با",
"not_begins_with": "شروع نشود با",
"contains": "شامل شود",
@@ -57,4 +58,4 @@
"boolean_not_valid": "مقدار دودویی وارد کنید",
"operator_not_multiple": "اپراتور \"{1}\" نمی تواند چند مقدار قبول کند"
}
-}
\ No newline at end of file
+}
diff --git a/src/i18n/fr.json b/src/i18n/fr.json
index 56cbad52..33b27bb6 100644
--- a/src/i18n/fr.json
+++ b/src/i18n/fr.json
@@ -13,14 +13,14 @@
},
"operators": {
- "equal": "égal",
- "not_equal": "non égal",
- "in": "dans",
- "not_in": "pas dans",
- "less": "inférieur",
- "less_or_equal": "inférieur ou égal",
- "greater": "supérieur",
- "greater_or_equal": "supérieur ou égal",
+ "equal": "est égal à",
+ "not_equal": "n'est pas égal à",
+ "in": "est compris dans",
+ "not_in": "n'est pas compris dans",
+ "less": "est inférieur à",
+ "less_or_equal": "est inférieur ou égal à",
+ "greater": "est supérieur à",
+ "greater_or_equal": "est supérieur ou égal à",
"between": "est entre",
"not_between": "n'est pas entre",
"begins_with": "commence par",
@@ -51,11 +51,13 @@
"number_exceed_min": "Doit être plus grand que {0}",
"number_exceed_max": "Doit être plus petit que {0}",
"number_wrong_step": "Doit être un multiple de {0}",
+ "number_between_invalid": "Valeurs invalides, {0} est plus grand que {1}",
"datetime_empty": "Valeur vide",
"datetime_invalid": "Fomat de date invalide ({0})",
"datetime_exceed_min": "Doit être après {0}",
"datetime_exceed_max": "Doit être avant {0}",
+ "datetime_between_invalid": "Valeurs invalides, {0} est plus grand que {1}",
"boolean_not_valid": "N'est pas un booléen",
"operator_not_multiple": "L'opérateur \"{1}\" ne peut utiliser plusieurs valeurs"
}
-}
\ No newline at end of file
+}
diff --git a/src/i18n/hu.json b/src/i18n/hu.json
new file mode 100644
index 00000000..22fa5e4c
--- /dev/null
+++ b/src/i18n/hu.json
@@ -0,0 +1,63 @@
+{
+ "__locale": "Hungarian - Magyar (hu)",
+ "__author": "Szabó Attila \"Tailor993\", https://www.tailor993.hu",
+
+ "add_rule": "Feltétel hozzáadása",
+ "add_group": "Csoport hozzáadása",
+ "delete_rule": "Feltétel törlése",
+ "delete_group": "Csoport törlése",
+
+ "conditions": {
+ "AND": "ÉS",
+ "OR": "VAGY"
+ },
+
+ "operators": {
+ "equal": "egyenlő",
+ "not_equal": "nem egyenlő",
+ "in": "bennevan",
+ "not_in": "nincs benne",
+ "less": "kisebb",
+ "less_or_equal": "kisebb vagy egyenlő",
+ "greater": "nagyobb",
+ "greater_or_equal": "nagyobb vagy egyenlő",
+ "between": "közötte",
+ "not_between": "nincs közötte",
+ "begins_with": "ezzel kezdődik",
+ "not_begins_with": "ezzel nem kezdődik",
+ "contains": "tartalmazza",
+ "not_contains": "nem tartalmazza",
+ "ends_with": "erre végződik",
+ "not_ends_with": "errre nem végződik",
+ "is_empty": "üres",
+ "is_not_empty": "nem üres",
+ "is_null": "null",
+ "is_not_null": "nem null"
+ },
+
+ "errors": {
+ "no_filter": "Nincs kiválasztott feltétel",
+ "empty_group": "A csoport üres",
+ "radio_empty": "Nincs kiválasztott érték",
+ "checkbox_empty": "Nincs kiválasztott érték",
+ "select_empty": "Nincs kiválasztott érték",
+ "string_empty": "Üres érték",
+ "string_exceed_min_length": "A megadott szöveg rövidebb a várt {0} karakternél",
+ "string_exceed_max_length": "A megadott szöveg nem tartalmazhat többet, mint {0} karaktert",
+ "string_invalid_format": "Nem megfelelő formátum ({0})",
+ "number_nan": "Nem szám",
+ "number_not_integer": "Nem egész szám (integer)",
+ "number_not_double": "Nem valós szám",
+ "number_exceed_min": "Nagyobbnak kell lennie, mint {0}",
+ "number_exceed_max": "Kisebbnek kell lennie, mint {0}",
+ "number_wrong_step": "{0} többszörösének kell lennie.",
+ "number_between_invalid": "INem megfelelő érték, {0} nagyobb, mint {1}",
+ "datetime_empty": "Üres érték",
+ "datetime_invalid": "nem megfelelő dátum formátum ({0})",
+ "datetime_exceed_min": "A dátumnak későbbinek kell lennie, mint{0}",
+ "datetime_exceed_max": "A dátumnak korábbinak kell lennie, mint {0}",
+ "datetime_between_invalid": "Nem megfelelő értékek, {0} nagyobb, mint {1}",
+ "boolean_not_valid": "Nem igaz/hamis (boolean)",
+ "operator_not_multiple": "Ez a művelet: \"{1}\" nem fogadhat el több értéket"
+ }
+}
diff --git a/src/i18n/it.json b/src/i18n/it.json
index 015cf3aa..076cc172 100644
--- a/src/i18n/it.json
+++ b/src/i18n/it.json
@@ -1,5 +1,6 @@
{
"__locale": "Italian (it)",
+ "__author": "davegraziosi, Giuseppe Lodi Rizzini",
"add_rule": "Aggiungi regola",
"add_group": "Aggiungi gruppo",
@@ -20,6 +21,8 @@
"less_or_equal": "minore o uguale",
"greater": "maggiore",
"greater_or_equal": "maggiore o uguale",
+ "between" : "compreso tra",
+ "not_between" : "non compreso tra",
"begins_with": "inizia con",
"not_begins_with": "non inizia con",
"contains": "contiene",
@@ -30,5 +33,31 @@
"is_not_empty": "non è vuoto",
"is_null": "è nullo",
"is_not_null": "non è nullo"
+ },
+
+ "errors": {
+ "no_filter": "Nessun filtro selezionato",
+ "empty_group": "Il gruppo è vuoto",
+ "radio_empty": "No value selected",
+ "checkbox_empty": "Nessun valore selezionato",
+ "select_empty": "Nessun valore selezionato",
+ "string_empty": "Valore vuoto",
+ "string_exceed_min_length": "Deve contenere almeno {0} caratteri",
+ "string_exceed_max_length": "Non deve contenere più di {0} caratteri",
+ "string_invalid_format": "Formato non valido ({0})",
+ "number_nan": "Non è un numero",
+ "number_not_integer": "Non è un intero",
+ "number_not_double": "Non è un numero con la virgola",
+ "number_exceed_min": "Deve essere maggiore di {0}",
+ "number_exceed_max": "Deve essere minore di {0}",
+ "number_wrong_step": "Deve essere multiplo di {0}",
+ "number_between_invalid": "Valori non validi, {0} è maggiore di {1}",
+ "datetime_empty": "Valore vuoto",
+ "datetime_invalid": "Formato data non valido ({0})",
+ "datetime_exceed_min": "Deve essere successivo a {0}",
+ "datetime_exceed_max": "Deve essere precedente a {0}",
+ "datetime_between_invalid": "Valori non validi, {0} è maggiore di {1}",
+ "boolean_not_valid": "Non è un booleano",
+ "operator_not_multiple": "L'Operatore {0} non può accettare valori multipli"
}
-}
\ No newline at end of file
+}
diff --git a/src/i18n/lt.json b/src/i18n/lt.json
new file mode 100644
index 00000000..c95c4f08
--- /dev/null
+++ b/src/i18n/lt.json
@@ -0,0 +1,63 @@
+{
+ "__locale": "Lithuanian (lt)",
+ "__author": "Dalius Guzauskas (aka Tichij), https://lt.linkedin.com/in/daliusg",
+
+ "add_rule": "Pridėti taisyklę",
+ "add_group": "Pridėti grupę",
+ "delete_rule": "Ištrinti",
+ "delete_group": "Ištrinti",
+
+ "conditions": {
+ "AND": "IR",
+ "OR": "ARBA"
+ },
+
+ "operators": {
+ "equal": "lygu",
+ "not_equal": "nėra lygu",
+ "in": "iš nurodytų",
+ "not_in": "ne iš nurodytų",
+ "less": "mažiau",
+ "less_or_equal": "mažiau arba lygu",
+ "greater": "daugiau",
+ "greater_or_equal": "daugiau arba lygu",
+ "between": "tarp",
+ "not_between": "nėra tarp",
+ "begins_with": "prasideda",
+ "not_begins_with": "neprasideda",
+ "contains": "turi",
+ "not_contains": "neturi",
+ "ends_with": "baigiasi",
+ "not_ends_with": "nesibaigia",
+ "is_empty": "tuščia",
+ "is_not_empty": "ne tuščia",
+ "is_null": "neapibrėžta",
+ "is_not_null": "nėra neapibrėžta"
+ },
+
+ "errors": {
+ "no_filter": "Nepasirinktas filtras",
+ "empty_group": "Grupė tuščia",
+ "radio_empty": "Nepasirinkta reikšmė",
+ "checkbox_empty": "Nepasirinkta reikšmė",
+ "select_empty": "Nepasirinkta reikšmė",
+ "string_empty": "Tuščia reikšmė",
+ "string_exceed_min_length": "Turi būti bent {0} simbolių",
+ "string_exceed_max_length": "Turi būti ne daugiau kaip {0} simbolių",
+ "string_invalid_format": "Klaidingas formatas ({0})",
+ "number_nan": "Nėra skaičius",
+ "number_not_integer": "Ne sveikasis skaičius",
+ "number_not_double": "Ne realusis skaičius",
+ "number_exceed_min": "Turi būti daugiau už {0}",
+ "number_exceed_max": "Turi būti mažiau už {0}",
+ "number_wrong_step": "Turi būti {0} kartotinis",
+ "number_between_invalid": "Klaidingos reikšmės, {0} yra daugiau už {1}",
+ "datetime_empty": "Tuščia reikšmė",
+ "datetime_invalid": "Klaidingas datos formatas ({0})",
+ "datetime_exceed_min": "Turi būti po {0}",
+ "datetime_exceed_max": "Turi būti prieš {0}",
+ "datetime_between_invalid": "Klaidingos reikšmės, {0} yra daugiau už {1}",
+ "boolean_not_valid": "Nėra loginis tipas",
+ "operator_not_multiple": "Operatorius \"{1}\" negali priimti kelių reikšmių"
+ }
+}
diff --git a/src/i18n/pl.json b/src/i18n/pl.json
index c27069f4..9cddba15 100644
--- a/src/i18n/pl.json
+++ b/src/i18n/pl.json
@@ -8,8 +8,8 @@
"delete_group": "Usuń",
"conditions": {
- "AND": "AND",
- "OR": "OR"
+ "AND": "ORAZ",
+ "OR": "LUB"
},
"operators": {
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/i18n/ru.json b/src/i18n/ru.json
index 33a343e8..9ccfefcc 100644
--- a/src/i18n/ru.json
+++ b/src/i18n/ru.json
@@ -21,6 +21,7 @@
"greater": "больше",
"greater_or_equal": "больше или равно",
"between": "между",
+ "not_between": "не между",
"begins_with": "начинается с",
"not_begins_with": "не начинается с",
"contains": "содержит",
@@ -36,10 +37,10 @@
"errors": {
"no_filter": "Фильтр не выбран",
"empty_group": "Группа пуста",
- "radio_empty": "Не выбранно значение",
- "checkbox_empty": "Не выбранно значение",
- "select_empty": "Не выбранно значение",
- "string_empty": "Не заполненно",
+ "radio_empty": "Не выбрано значение",
+ "checkbox_empty": "Не выбрано значение",
+ "select_empty": "Не выбрано значение",
+ "string_empty": "Не заполнено",
"string_exceed_min_length": "Должен содержать больше {0} символов",
"string_exceed_max_length": "Должен содержать меньше {0} символов",
"string_invalid_format": "Неверный формат ({0})",
@@ -49,10 +50,12 @@
"number_exceed_min": "Должно быть больше {0}",
"number_exceed_max": "Должно быть меньше, чем {0}",
"number_wrong_step": "Должно быть кратно {0}",
- "datetime_empty": "Не заполненно",
+ "number_between_invalid": "Недопустимые значения, {0} больше {1}",
+ "datetime_empty": "Не заполнено",
"datetime_invalid": "Неверный формат даты ({0})",
"datetime_exceed_min": "Должно быть, после {0}",
"datetime_exceed_max": "Должно быть, до {0}",
+ "datetime_between_invalid": "Недопустимые значения, {0} больше {1}",
"boolean_not_valid": "Не логическое",
"operator_not_multiple": "Оператор \"{1}\" не поддерживает много значений"
}
diff --git a/src/i18n/sk.json b/src/i18n/sk.json
new file mode 100644
index 00000000..0a5926c4
--- /dev/null
+++ b/src/i18n/sk.json
@@ -0,0 +1,63 @@
+{
+ "__locale": "Slovensky (sk)",
+ "__author": "k2s",
+
+ "add_rule": "Pridať podmienku",
+ "add_group": "Pridať skupinu",
+ "delete_rule": "Zmazať",
+ "delete_group": "Zmazať",
+
+ "conditions": {
+ "AND": "A",
+ "OR": "ALEBO"
+ },
+
+ "operators": {
+ "equal": "rovné",
+ "not_equal": "nerovné",
+ "in": "v",
+ "not_in": "nie v",
+ "less": "menej",
+ "less_or_equal": "menej alebo rovné",
+ "greater": "väčšie",
+ "greater_or_equal": "väčšie alebo rovné",
+ "between": "medzi",
+ "not_between": "nie medzi",
+ "begins_with": "začína na",
+ "not_begins_with": "nezačína na",
+ "contains": "obsahuje",
+ "not_contains": "neobsahuje",
+ "ends_with": "končí na",
+ "not_ends_with": "nekončí na",
+ "is_empty": "je prázdne",
+ "is_not_empty": "nie je prázdne",
+ "is_null": "je null",
+ "is_not_null": "nie je null"
+ },
+
+ "errors": {
+ "no_filter": "Nie je zvolený filter",
+ "empty_group": "Skupina je prázdna",
+ "radio_empty": "Nie je označená hodnota",
+ "checkbox_empty": "Nie je označená hodnota",
+ "select_empty": "Nie je označená hodnota",
+ "string_empty": "Prázdna hodnota",
+ "string_exceed_min_length": "Musí obsahovať aspon {0} znakov",
+ "string_exceed_max_length": "Nesmie obsahovať viac ako {0} znakov",
+ "string_invalid_format": "Chybný formát ({0})",
+ "number_nan": "Nie je číslo",
+ "number_not_integer": "Nie je celé číslo",
+ "number_not_double": "Nie je desatinné číslo",
+ "number_exceed_min": "Musí byť väčšie ako {0}",
+ "number_exceed_max": "Musí byť menšie ako {0}",
+ "number_wrong_step": "Musí byť násobkom čísla {0}",
+ "number_between_invalid": "Chybné hodnoty, {0} je väčšie ako {1}",
+ "datetime_empty": "Prázdna hodnota",
+ "datetime_invalid": "Chybný formát dátumu ({0})",
+ "datetime_exceed_min": "Musí byť neskôr ako {0}",
+ "datetime_exceed_max": "Musí byť skôr ako {0}",
+ "datetime_between_invalid": "Chybné hodnoty, {0} je neskôr ako {1}",
+ "boolean_not_valid": "Neplatné áno/nie",
+ "operator_not_multiple": "Operátor '{1}' nepodporuje viacero hodnôt"
+ }
+}
diff --git a/src/i18n/sv.json b/src/i18n/sv.json
new file mode 100644
index 00000000..a3b06e84
--- /dev/null
+++ b/src/i18n/sv.json
@@ -0,0 +1,63 @@
+{
+ "__locale": "Svenska (sv)",
+ "__author": "hekin1",
+
+ "add_rule": "Lägg till regel",
+ "add_group": "Lägg till grupp",
+ "delete_rule": "Ta bort",
+ "delete_group": "Ta bort",
+
+ "conditions": {
+ "AND": "OCH",
+ "OR": "ELLER"
+ },
+
+ "operators": {
+ "equal": "lika med",
+ "not_equal": "ej lika med",
+ "in": "en av",
+ "not_in": "ej en av",
+ "less": "mindre",
+ "less_or_equal": "mindre eller lika med",
+ "greater": "större",
+ "greater_or_equal": "större eller lika med",
+ "between": "mellan",
+ "not_between": "ej mellan",
+ "begins_with": "börjar med",
+ "not_begins_with": "börjar inte med",
+ "contains": "innehåller",
+ "not_contains": "innehåller inte",
+ "ends_with": "slutar med",
+ "not_ends_with": "slutar inte med",
+ "is_empty": "är tom",
+ "is_not_empty": "är inte tom",
+ "is_null": "är null",
+ "is_not_null": "är inte null"
+ },
+
+ "errors": {
+ "no_filter": "Inget filter valt",
+ "empty_group": "Gruppen är tom",
+ "radio_empty": "Inget värde valt",
+ "checkbox_empty": "Inget värde valt",
+ "select_empty": "Inget värde valt",
+ "string_empty": "Tomt värde",
+ "string_exceed_min_length": "Måste innehålla minst {0} tecken",
+ "string_exceed_max_length": "Får ej innehålla fler än {0} tecken",
+ "string_invalid_format": "Felaktigt format ({0})",
+ "number_nan": "Inte numeriskt",
+ "number_not_integer": "Inte en siffra",
+ "number_not_double": "Inte ett decimaltal",
+ "number_exceed_min": "Måste vara större än {0}",
+ "number_exceed_max": "Måste vara lägre än {0}",
+ "number_wrong_step": "Måste vara en mutipel av {0}",
+ "number_between_invalid": "Felaktiga värden, {0} är större än {1}",
+ "datetime_empty": "Tomt värde",
+ "datetime_invalid": "Felaktigt datumformat ({0})",
+ "datetime_exceed_min": "Måste vara efter {0}",
+ "datetime_exceed_max": "Måste vara före {0}",
+ "datetime_between_invalid": "Felaktiga värden, {0} är större än {1}",
+ "boolean_not_valid": "Inte en boolean",
+ "operator_not_multiple": "Operatorn \"{1}\" accepterar inte flera värden"
+ }
+}
diff --git a/src/i18n/sw.json b/src/i18n/sw.json
new file mode 100644
index 00000000..e215a84f
--- /dev/null
+++ b/src/i18n/sw.json
@@ -0,0 +1,63 @@
+{
+ "__locale": "Swahili (sw)",
+ "__author": "Timothy Anyona",
+
+ "add_rule": "Ongeza kanuni",
+ "add_group": "Ongeza kikundi",
+ "delete_rule": "Futa",
+ "delete_group": "Futa",
+
+ "conditions": {
+ "AND": "NA",
+ "OR": "AU"
+ },
+
+ "operators": {
+ "equal": "ni",
+ "not_equal": "sio",
+ "in": "mojawapo ya",
+ "not_in": "sio mojawapo ya",
+ "less": "isiyozidi",
+ "less_or_equal": "isiyozidi au ni sawa na",
+ "greater": "inayozidi",
+ "greater_or_equal": "inayozidi au ni sawa na",
+ "between": "kati ya",
+ "not_between": "isiyo kati ya",
+ "begins_with": "inaanza na",
+ "not_begins_with": "isiyoanza na",
+ "contains": "ina",
+ "not_contains": "haina",
+ "ends_with": "inaisha na",
+ "not_ends_with": "isiyoisha na",
+ "is_empty": "ni tupu",
+ "is_not_empty": "sio tupu",
+ "is_null": "ni batili",
+ "is_not_null": "sio batili"
+ },
+
+ "errors": {
+ "no_filter": "Chujio halijachaguliwa",
+ "empty_group": "Kikundi ki tupu",
+ "radio_empty": "Thamani haijachaguliwa",
+ "checkbox_empty": "Thamani haijachaguliwa",
+ "select_empty": "Thamani haijachaguliwa",
+ "string_empty": "Thamani tupu",
+ "string_exceed_min_length": "Lazima iwe na vibambo visiopungua {0}",
+ "string_exceed_max_length": "Haifai kuwa na vibambo zaidi ya {0}",
+ "string_invalid_format": "Fomati batili ({0})",
+ "number_nan": "Sio nambari",
+ "number_not_integer": "Sio namba kamili",
+ "number_not_double": "Sio namba desimali",
+ "number_exceed_min": "Lazima iwe zaidi ya {0}",
+ "number_exceed_max": "Lazima iwe chini ya {0}",
+ "number_wrong_step": "Lazima iwe kigawe cha {0}",
+ "number_between_invalid": "Thamani batili, {0} ni kubwa kuliko {1}",
+ "datetime_empty": "Thamani tupu",
+ "datetime_invalid": "Fomati tarehe batili ({0})",
+ "datetime_exceed_min": "Lazima iwe baada ya {0}",
+ "datetime_exceed_max": "Lazima iwe kabla ya {0}",
+ "datetime_between_invalid": "Thamani batili, {0} ni baada ya {1}",
+ "boolean_not_valid": "Sio buleani",
+ "operator_not_multiple": "Opereta \"{1}\" haikubali thamani nyingi"
+ }
+}
diff --git a/src/i18n/tr.json b/src/i18n/tr.json
index 2b2d0295..25ecbf78 100644
--- a/src/i18n/tr.json
+++ b/src/i18n/tr.json
@@ -38,23 +38,25 @@
"errors": {
"no_filter": "Bir filtre seçili değil",
"empty_group": "Grup bir eleman içermiyor",
- "radio_empty": "seçim yapılmalı",
- "checkbox_empty": "seçim yapılmalı",
- "select_empty": "seçim yapılmalı",
+ "radio_empty": "Seçim yapılmalı",
+ "checkbox_empty": "Seçim yapılmalı",
+ "select_empty": "Seçim yapılmalı",
"string_empty": "Bir metin girilmeli",
"string_exceed_min_length": "En az {0} karakter girilmeli",
"string_exceed_max_length": "En fazla {0} karakter girilebilir",
"string_invalid_format": "Uyumsuz format ({0})",
"number_nan": "Sayı değil",
- "number_not_integer": "Tam sayı değilr",
+ "number_not_integer": "Tam sayı değil",
"number_not_double": "Ondalıklı sayı değil",
"number_exceed_min": "Sayı {0}'den/dan daha büyük olmalı",
"number_exceed_max": "Sayı {0}'den/dan daha küçük olmalı",
"number_wrong_step": "{0} veya katı olmalı",
+ "number_between_invalid": "Geçersiz değerler, {0} değeri {1} değerinden büyük",
"datetime_empty": "Tarih Seçilmemiş",
"datetime_invalid": "Uygun olmayan tarih formatı ({0})",
"datetime_exceed_min": "{0} Tarihinden daha sonrası olmalı.",
"datetime_exceed_max": "{0} Tarihinden daha öncesi olmalı.",
+ "datetime_between_invalid": "Geçersiz değerler, {0} değeri {1} değerinden büyük",
"boolean_not_valid": "Değer Doğru/Yanlış(bool) olmalı",
"operator_not_multiple": "Operatör \"{1}\" birden fazla değer kabul etmiyor"
}
diff --git a/src/jquery.js b/src/jquery.js
index 62742019..941ea675 100644
--- a/src/jquery.js
+++ b/src/jquery.js
@@ -30,7 +30,9 @@ $.fn.queryBuilder = function(option) {
return this;
}
if (!data) {
- this.data('queryBuilder', new QueryBuilder(this, options));
+ var builder = new QueryBuilder(this, options);
+ this.data('queryBuilder', builder);
+ builder.init(options.rules);
}
if (typeof option == 'string') {
return data[option].apply(data, Array.prototype.slice.call(arguments, 1));
diff --git a/src/main.js b/src/main.js
index 0ebfc940..0659d288 100644
--- a/src/main.js
+++ b/src/main.js
@@ -14,7 +14,6 @@
* @param {jQuery} $el
* @param {object} options - see {@link http://querybuilder.js.org/#options}
* @constructor
- * @fires QueryBuilder.afterInit
*/
var QueryBuilder = function($el, options) {
$el[0].queryBuilder = this;
@@ -122,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);
@@ -135,27 +134,12 @@ 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);
this.bindEvents();
this.initPlugins();
-
- /**
- * When the initilization is done, just before creating the root group
- * @event afterInit
- * @memberof QueryBuilder
- */
- this.trigger('afterInit');
-
- if (options.rules) {
- this.setRules(options.rules);
- delete this.settings.rules;
- }
- else {
- this.setRoot(true);
- }
};
$.extend(QueryBuilder.prototype, /** @lends QueryBuilder.prototype */ {
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 9b259265..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 e9bc935c..52f4830d 100644
--- a/src/plugins/bt-tooltip-errors/plugin.js
+++ b/src/plugins/bt-tooltip-errors/plugin.js
@@ -7,16 +7,17 @@
* @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;
// add BT Tooltip data
this.on('getRuleTemplate.filter getGroupTemplate.filter', function(h) {
- var $h = $(h.value);
- $h.find(QueryBuilder.selectors.error_container).attr('data-toggle', 'tooltip');
+ var $h = $($.parseHTML(h.value));
+ $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/change-filters/plugin.js b/src/plugins/change-filters/plugin.js
index 823edac1..f44934ab 100644
--- a/src/plugins/change-filters/plugin.js
+++ b/src/plugins/change-filters/plugin.js
@@ -59,6 +59,8 @@ QueryBuilder.extend(/** @lends module:plugins.ChangeFilters.prototype */ {
function(rule) {
if (rule.filter && filtersIds.indexOf(rule.filter.id) === -1) {
rule.drop();
+
+ self.trigger('rulesChanged');
}
else {
self.createRuleFilters(rule);
diff --git a/src/plugins/chosen-selectpicker/plugin.js b/src/plugins/chosen-selectpicker/plugin.js
new file mode 100644
index 00000000..e18c8573
--- /dev/null
+++ b/src/plugins/chosen-selectpicker/plugin.js
@@ -0,0 +1,44 @@
+/**
+ * @class ChosenSelectpicker
+ * @memberof module:plugins
+ * @descriptioon Applies chosen-js Select on filters and operators combo-boxes.
+ * @param {object} [options] Supports all the options for chosen
+ * @throws MissingLibraryError
+ */
+QueryBuilder.define('chosen-selectpicker', function(options) {
+
+ if (!$.fn.chosen) {
+ Utils.error('MissingLibrary', 'chosen is required to use "chosen-selectpicker" plugin. Get it here: https://github.com/harvesthq/chosen');
+ }
+
+ if (this.settings.plugins['bt-selectpicker']) {
+ Utils.error('Conflict', 'bt-selectpicker is already selected as the dropdown plugin. Please remove chosen-selectpicker from the plugin list');
+ }
+
+ var Selectors = QueryBuilder.selectors;
+
+ // init selectpicker
+ this.on('afterCreateRuleFilters', function(e, rule) {
+ rule.$el.find(Selectors.rule_filter).removeClass('form-control').chosen(options);
+ });
+
+ this.on('afterCreateRuleOperators', function(e, rule) {
+ if (e.builder.getOperators(rule.filter).length > 1) {
+ rule.$el.find(Selectors.rule_operator).removeClass('form-control').chosen(options);
+ }
+ });
+
+ // update selectpicker on change
+ this.on('afterUpdateRuleFilter', function(e, rule) {
+ rule.$el.find(Selectors.rule_filter).trigger('chosen:updated');
+ });
+
+ this.on('afterUpdateRuleOperator', function(e, rule) {
+ rule.$el.find(Selectors.rule_operator).trigger('chosen:updated');
+ });
+
+ this.on('beforeDeleteRule', function(e, rule) {
+ rule.$el.find(Selectors.rule_filter).chosen('destroy');
+ rule.$el.find(Selectors.rule_operator).chosen('destroy');
+ });
+});
diff --git a/src/plugins/filter-description/plugin.js b/src/plugins/filter-description/plugin.js
index 91762b6b..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
*/
@@ -19,11 +19,11 @@ QueryBuilder.define('filter-description', function(options) {
}
else {
if ($p.length === 0) {
- $p = $('');
+ $p = $($.parseHTML(''));
$p.appendTo(rule.$el);
}
else {
- $p.show();
+ $p.css('display', '');
}
$p.html(' ' + description);
@@ -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 = $('');
+ $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.show();
+ $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,20 +87,23 @@ QueryBuilder.define('filter-description', function(options) {
}
else {
if ($b.length === 0) {
- $b = $('');
+ $b = $($.parseHTML(''));
$b.prependTo(rule.$el.find(QueryBuilder.selectors.rule_actions));
$b.on('click', function() {
bootbox.alert($b.data('description'));
});
}
+ else {
+ $b.css('display', '');
+ }
$b.data('description', description);
}
});
}
}, {
- 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/i18n/eo.json b/src/plugins/invert/i18n/eo.json
new file mode 100644
index 00000000..e5ddde54
--- /dev/null
+++ b/src/plugins/invert/i18n/eo.json
@@ -0,0 +1,3 @@
+{
+ "invert": "Inversigi"
+}
diff --git a/src/plugins/invert/i18n/hu.json b/src/plugins/invert/i18n/hu.json
new file mode 100644
index 00000000..83b08887
--- /dev/null
+++ b/src/plugins/invert/i18n/hu.json
@@ -0,0 +1,3 @@
+{
+ "invert": "Megfordítás (Invertálás)"
+}
diff --git a/src/plugins/invert/i18n/lt.json b/src/plugins/invert/i18n/lt.json
new file mode 100644
index 00000000..6913901d
--- /dev/null
+++ b/src/plugins/invert/i18n/lt.json
@@ -0,0 +1,3 @@
+{
+ "invert": "Invertuoti"
+}
diff --git a/src/plugins/invert/i18n/sk.json b/src/plugins/invert/i18n/sk.json
new file mode 100644
index 00000000..b9d3274b
--- /dev/null
+++ b/src/plugins/invert/i18n/sk.json
@@ -0,0 +1,3 @@
+{
+ "invert": "Invertný"
+}
diff --git a/src/plugins/invert/i18n/sv.json b/src/plugins/invert/i18n/sv.json
new file mode 100644
index 00000000..aa932882
--- /dev/null
+++ b/src/plugins/invert/i18n/sv.json
@@ -0,0 +1,3 @@
+{
+ "invert": "Invertera"
+}
diff --git a/src/plugins/invert/i18n/sw.json b/src/plugins/invert/i18n/sw.json
new file mode 100644
index 00000000..7ee2e3fd
--- /dev/null
+++ b/src/plugins/invert/i18n/sw.json
@@ -0,0 +1,3 @@
+{
+ "invert": "Pindua"
+}
diff --git a/src/plugins/invert/plugin.js b/src/plugins/invert/plugin.js
index 8928d4be..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]
@@ -29,25 +29,36 @@ QueryBuilder.define('invert', function(options) {
});
// Modify templates
- this.on('getGroupTemplate.filter', function(h, level) {
- var $h = $(h.value);
- $h.find(Selectors.condition_container).after(' ' + self.translate('invert') + '');
- h.value = $h.prop('outerHTML');
- });
-
- if (options.display_rules_button && options.invert_rules) {
- this.on('getRuleTemplate.filter', function(h) {
- var $h = $(h.value);
- $h.find(Selectors.rule_actions).prepend(' ' + self.translate('invert') + '');
+ if (!options.disable_template) {
+ this.on('getGroupTemplate.filter', function(h) {
+ var $h = $($.parseHTML(h.value));
+ $h.find(Selectors.condition_container).after(
+ '' +
+ ' ' + self.translate('invert') +
+ ''
+ );
h.value = $h.prop('outerHTML');
});
+
+ if (options.display_rules_button && options.invert_rules) {
+ this.on('getRuleTemplate.filter', function(h) {
+ var $h = $($.parseHTML(h.value));
+ $h.find(Selectors.rule_actions).prepend(
+ '' +
+ ' ' + self.translate('invert') +
+ ''
+ );
+ h.value = $h.prop('outerHTML');
+ });
+ }
}
}, {
- icon: 'glyphicon glyphicon-random',
+ icon: 'bi-shuffle',
recursive: true,
invert_rules: true,
display_rules_button: false,
- silent_fail: false
+ silent_fail: false,
+ disable_template: false
});
QueryBuilder.defaults({
@@ -147,6 +158,8 @@ QueryBuilder.extend(/** @lends module:plugins.Invert.prototype */ {
* @param {object} options
*/
this.trigger('afterInvert', node, options);
+
+ this.trigger('rulesChanged');
}
}
});
diff --git a/src/plugins/mongodb-support/plugin.js b/src/plugins/mongodb-support/plugin.js
index 93a17348..5df30db4 100644
--- a/src/plugins/mongodb-support/plugin.js
+++ b/src/plugins/mongodb-support/plugin.js
@@ -31,17 +31,17 @@ QueryBuilder.defaults({
},
mongoRuleOperators: {
- $ne: function(v) {
- v = v.$ne;
+ $eq: function(v) {
return {
'val': v,
- 'op': v === null ? 'is_not_null' : (v === '' ? 'is_not_empty' : 'not_equal')
+ 'op': v === null ? 'is_null' : (v === '' ? 'is_empty' : 'equal')
};
},
- eq: function(v) {
+ $ne: function(v) {
+ v = v.$ne;
return {
'val': v,
- 'op': v === null ? 'is_null' : (v === '' ? 'is_empty' : 'equal')
+ 'op': v === null ? 'is_not_null' : (v === '' ? 'is_not_empty' : 'not_equal')
};
},
$regex: function(v) {
@@ -105,6 +105,10 @@ QueryBuilder.extend(/** @lends module:plugins.MongoDbSupport.prototype */ {
getMongo: function(data) {
data = (data === undefined) ? this.getRules() : data;
+ if (!data) {
+ return null;
+ }
+
var self = this;
return (function parse(group) {
@@ -128,7 +132,6 @@ QueryBuilder.extend(/** @lends module:plugins.MongoDbSupport.prototype */ {
else {
var mdb = self.settings.mongoOperators[rule.operator];
var ope = self.getOperatorByType(rule.operator);
- var values = [];
if (mdb === undefined) {
Utils.error('UndefinedMongoOperator', 'Unknown MongoDB operation for operator "{0}"', rule.operator);
@@ -138,10 +141,6 @@ QueryBuilder.extend(/** @lends module:plugins.MongoDbSupport.prototype */ {
if (!(rule.value instanceof Array)) {
rule.value = [rule.value];
}
-
- rule.value.forEach(function(v) {
- values.push(Utils.changeType(v, rule.type, false));
- });
}
/**
@@ -155,7 +154,7 @@ QueryBuilder.extend(/** @lends module:plugins.MongoDbSupport.prototype */ {
var field = self.change('getMongoDBField', rule.field, rule);
var ruleExpression = {};
- ruleExpression[field] = mdb.call(self, values);
+ ruleExpression[field] = mdb.call(self, rule.value);
/**
* Modifies the MongoDB expression generated for a rul
@@ -167,7 +166,7 @@ QueryBuilder.extend(/** @lends module:plugins.MongoDbSupport.prototype */ {
* @param {function} valueWrapper - function that takes the value and adds the operator
* @returns {object}
*/
- parts.push(self.change('ruleToMongo', ruleExpression, rule, values, mdb));
+ parts.push(self.change('ruleToMongo', ruleExpression, rule, rule.value, mdb));
}
});
@@ -225,7 +224,7 @@ QueryBuilder.extend(/** @lends module:plugins.MongoDbSupport.prototype */ {
};
}
- var key = andOr(query);
+ var key = self.getMongoCondition(query);
if (!key) {
Utils.error('MongoParse', 'Invalid MongoDB query format');
}
@@ -250,7 +249,7 @@ QueryBuilder.extend(/** @lends module:plugins.MongoDbSupport.prototype */ {
return;
}
- var key = andOr(data);
+ var key = self.getMongoCondition(data);
if (key) {
parts.push(parse(data, key));
}
@@ -258,7 +257,7 @@ QueryBuilder.extend(/** @lends module:plugins.MongoDbSupport.prototype */ {
var field = Object.keys(data)[0];
var value = data[field];
- var operator = determineMongoOperator(value, field);
+ var operator = self.getMongoOperator(value);
if (operator === undefined) {
Utils.error('MongoParse', 'Invalid MongoDB query format');
}
@@ -345,58 +344,50 @@ QueryBuilder.extend(/** @lends module:plugins.MongoDbSupport.prototype */ {
}
return id;
- }
-});
-
-/**
- * Finds which operator is used in a MongoDB sub-object
- * @memberof module:plugins.MongoDbSupport
- * @param {*} value
- * @returns {string|undefined}
- * @private
- */
-function determineMongoOperator(value) {
- if (value !== null && typeof value == 'object') {
- var subkeys = Object.keys(value);
+ },
- if (subkeys.length === 1) {
- return subkeys[0];
- }
- else {
- if (value.$gte !== undefined && value.$lte !== undefined) {
+ /**
+ * Finds which operator is used in a MongoDB sub-object
+ * @param {*} data
+ * @returns {string|undefined}
+ * @private
+ */
+ getMongoOperator: function(data) {
+ if (data !== null && typeof data === 'object') {
+ if (data.$gte !== undefined && data.$lte !== undefined) {
return 'between';
}
- if (value.$lt !== undefined && value.$gt !== undefined) {
+ if (data.$lt !== undefined && data.$gt !== undefined) {
return 'not_between';
}
- else if (value.$regex !== undefined) { // optional $options
- return '$regex';
- }
- else {
- return;
+
+ var knownKeys = Object.keys(data).filter(function(key) {
+ return !!this.settings.mongoRuleOperators[key];
+ }.bind(this));
+
+ if (knownKeys.length === 1) {
+ return knownKeys[0];
}
}
- }
- else {
- return 'eq';
- }
-}
+ else {
+ return '$eq';
+ }
+ },
-/**
- * Returns the key corresponding to "$or" or "$and"
- * @memberof module:plugins.MongoDbSupport
- * @param {object} data
- * @returns {string}
- * @private
- */
-function andOr(data) {
- var keys = Object.keys(data);
- for (var i = 0, l = keys.length; i < l; i++) {
- if (keys[i].toLowerCase() == '$or' || keys[i].toLowerCase() == '$and') {
- return keys[i];
+ /**
+ * Returns the key corresponding to "$or" or "$and"
+ * @param {object} data
+ * @returns {string|undefined}
+ * @private
+ */
+ getMongoCondition: function(data) {
+ var keys = Object.keys(data);
+
+ for (var i = 0, l = keys.length; i < l; i++) {
+ if (keys[i].toLowerCase() === '$or' || keys[i].toLowerCase() === '$and') {
+ return keys[i];
+ }
}
}
-
- return undefined;
-}
+});
diff --git a/src/plugins/not-group/i18n/eo.json b/src/plugins/not-group/i18n/eo.json
new file mode 100644
index 00000000..8025e4e8
--- /dev/null
+++ b/src/plugins/not-group/i18n/eo.json
@@ -0,0 +1,3 @@
+{
+ "NOT": "NE"
+}
diff --git a/src/plugins/not-group/i18n/hu.json b/src/plugins/not-group/i18n/hu.json
new file mode 100644
index 00000000..4399728c
--- /dev/null
+++ b/src/plugins/not-group/i18n/hu.json
@@ -0,0 +1,3 @@
+{
+ "NOT": "NEM"
+}
diff --git a/src/plugins/not-group/i18n/lt.json b/src/plugins/not-group/i18n/lt.json
new file mode 100644
index 00000000..8025e4e8
--- /dev/null
+++ b/src/plugins/not-group/i18n/lt.json
@@ -0,0 +1,3 @@
+{
+ "NOT": "NE"
+}
diff --git a/src/plugins/not-group/i18n/ru.json b/src/plugins/not-group/i18n/ru.json
new file mode 100644
index 00000000..923d6069
--- /dev/null
+++ b/src/plugins/not-group/i18n/ru.json
@@ -0,0 +1,3 @@
+{
+ "NOT": "НЕ"
+}
diff --git a/src/plugins/not-group/i18n/sk.json b/src/plugins/not-group/i18n/sk.json
new file mode 100644
index 00000000..9da29bd5
--- /dev/null
+++ b/src/plugins/not-group/i18n/sk.json
@@ -0,0 +1,3 @@
+{
+ "NOT": "NIE"
+}
diff --git a/src/plugins/not-group/i18n/sv.json b/src/plugins/not-group/i18n/sv.json
new file mode 100644
index 00000000..2778b4da
--- /dev/null
+++ b/src/plugins/not-group/i18n/sv.json
@@ -0,0 +1,3 @@
+{
+ "NOT": "INTE"
+}
diff --git a/src/plugins/not-group/i18n/sw.json b/src/plugins/not-group/i18n/sw.json
new file mode 100644
index 00000000..36f9902b
--- /dev/null
+++ b/src/plugins/not-group/i18n/sw.json
@@ -0,0 +1,3 @@
+{
+ "NOT": "SIO"
+}
diff --git a/src/plugins/not-group/plugin.js b/src/plugins/not-group/plugin.js
index da6f98b0..be98bb00 100644
--- a/src/plugins/not-group/plugin.js
+++ b/src/plugins/not-group/plugin.js
@@ -3,8 +3,8 @@
* @memberof module:plugins
* @description Adds a "Not" checkbox in front of group conditions.
* @param {object} [options]
- * @param {string} [options.icon_checked='glyphicon glyphicon-checked']
- * @param {string} [options.icon_unchecked='glyphicon glyphicon-unchecked']
+ * @param {string} [options.icon_checked='bi-check2-square']
+ * @param {string} [options.icon_unchecked='bi-square']
*/
QueryBuilder.define('not-group', function(options) {
var self = this;
@@ -30,15 +30,17 @@ QueryBuilder.define('not-group', function(options) {
});
// Modify templates
- this.on('getGroupTemplate.filter', function(h, level) {
- var $h = $(h.value);
- $h.find(QueryBuilder.selectors.condition_container).prepend(
- '' +
- ' ' + self.translate('NOT') +
- ''
- );
- h.value = $h.prop('outerHTML');
- });
+ if (!options.disable_template) {
+ this.on('getGroupTemplate.filter', function(h) {
+ var $h = $($.parseHTML(h.value));
+ $h.find(QueryBuilder.selectors.condition_container).prepend(
+ '' +
+ ' ' + self.translate('NOT') +
+ ''
+ );
+ h.value = $h.prop('outerHTML');
+ });
+ }
// Export "not" to JSON
this.on('groupToJson.filter', function(e, group) {
@@ -61,10 +63,27 @@ QueryBuilder.define('not-group', function(options) {
this.on('parseSQLNode.filter', function(e) {
if (e.value.name && e.value.name.toUpperCase() == 'NOT') {
e.value = e.value.arguments.value[0];
+
+ // if the there is no sub-group, create one
+ if (['AND', 'OR'].indexOf(e.value.operation.toUpperCase()) === -1) {
+ e.value = new SQLParser.nodes.Op(
+ self.settings.default_condition,
+ e.value,
+ null
+ );
+ }
+
e.value.not = true;
}
});
+ // Request to create sub-group if the "not" flag is set
+ this.on('sqlGroupsDistinct.filter', function(e, group, data, i) {
+ if (data.not && i > 0) {
+ e.value = true;
+ }
+ });
+
// Read "not" from parsed SQL
this.on('sqlToGroup.filter', function(e, data) {
e.value.not = !!data.not;
@@ -93,8 +112,9 @@ QueryBuilder.define('not-group', function(options) {
e.value.not = !!data.not;
});
}, {
- icon_unchecked: 'glyphicon glyphicon-unchecked',
- icon_checked: 'glyphicon glyphicon-check'
+ icon_unchecked: 'bi-square',
+ icon_checked: 'bi-check2-square',
+ disable_template: false
});
/**
@@ -128,5 +148,7 @@ QueryBuilder.extend(/** @lends module:plugins.NotGroup.prototype */ {
* @param {Group} group
*/
this.trigger('afterUpdateGroupNot', group);
+
+ this.trigger('rulesChanged');
}
});
diff --git a/src/plugins/sortable/plugin.js b/src/plugins/sortable/plugin.js
index 83f0b4df..84b29e1f 100644
--- a/src/plugins/sortable/plugin.js
+++ b/src/plugins/sortable/plugin.js
@@ -5,7 +5,7 @@
* @param {object} [options]
* @param {boolean} [options.inherit_no_drop=true]
* @param {boolean} [options.inherit_no_sortable=true]
- * @param {string} [options.icon='glyphicon glyphicon-sort']
+ * @param {string} [options.icon='bi-sort-down']
* @throws MissingLibraryError, ConfigError
*/
QueryBuilder.define('sortable', function(options) {
@@ -27,6 +27,7 @@ QueryBuilder.define('sortable', function(options) {
var placeholder;
var ghost;
var src;
+ var moved;
// Init drag and drop
this.on('afterAddRule afterAddGroup', function(e, node) {
@@ -47,9 +48,11 @@ QueryBuilder.define('sortable', function(options) {
// Configure drag
if (!node.flags.no_sortable) {
interact(node.$el[0])
- .allowFrom(QueryBuilder.selectors.drag_handle)
.draggable({
+ allowFrom: QueryBuilder.selectors.drag_handle,
onstart: function(event) {
+ moved = false;
+
// get model of dragged element
src = self.getModel(event.target);
@@ -60,7 +63,7 @@ QueryBuilder.define('sortable', function(options) {
.addClass('dragging');
// create drop placeholder
- var ph = $('
')
+ var ph = $($.parseHTML('
'))
.height(src.$el.outerHeight());
placeholder = src.parent.addRule(ph, src.getPos());
@@ -73,7 +76,13 @@ QueryBuilder.define('sortable', function(options) {
ghost[0].style.top = event.clientY - 15 + 'px';
ghost[0].style.left = event.clientX - 15 + 'px';
},
- onend: function() {
+ onend: function(event) {
+ // starting from Interact 1.3.3, onend is called before ondrop
+ if (event.dropzone) {
+ moveSortableToTarget(src, $(event.relatedTarget), self);
+ moved = true;
+ }
+
// remove ghost
ghost.remove();
ghost = undefined;
@@ -83,7 +92,7 @@ QueryBuilder.define('sortable', function(options) {
placeholder = undefined;
// show element
- src.$el.show();
+ src.$el.css('display', '');
/**
* After a node has been moved with {@link module:plugins.Sortable}
@@ -92,6 +101,8 @@ QueryBuilder.define('sortable', function(options) {
* @param {Node} node
*/
self.trigger('afterMove', src);
+
+ self.trigger('rulesChanged');
}
});
}
@@ -105,7 +116,9 @@ QueryBuilder.define('sortable', function(options) {
moveSortableToTarget(placeholder, $(event.target), self);
},
ondrop: function(event) {
- moveSortableToTarget(src, $(event.target), self);
+ if (!moved) {
+ moveSortableToTarget(src, $(event.target), self);
+ }
}
});
@@ -118,7 +131,9 @@ QueryBuilder.define('sortable', function(options) {
moveSortableToTarget(placeholder, $(event.target), self);
},
ondrop: function(event) {
- moveSortableToTarget(src, $(event.target), self);
+ if (!moved) {
+ moveSortableToTarget(src, $(event.target), self);
+ }
}
});
}
@@ -144,23 +159,26 @@ QueryBuilder.define('sortable', function(options) {
});
// Modify templates
- this.on('getGroupTemplate.filter', function(h, level) {
- if (level > 1) {
- var $h = $(h.value);
- $h.find(QueryBuilder.selectors.condition_container).after('
');
- h.value = $h.prop('outerHTML');
- }
- });
+ if (!options.disable_template) {
+ this.on('getGroupTemplate.filter', function(h, level) {
+ if (level > 1) {
+ var $h = $($.parseHTML(h.value));
+ $h.find(QueryBuilder.selectors.condition_container).after('
');
+ h.value = $h.prop('outerHTML');
+ }
+ });
- this.on('getRuleTemplate.filter', function(h) {
- var $h = $(h.value);
- $h.find(QueryBuilder.selectors.rule_header).after('
');
- h.value = $h.prop('outerHTML');
- });
+ this.on('getRuleTemplate.filter', function(h) {
+ var $h = $($.parseHTML(h.value));
+ $h.find(QueryBuilder.selectors.rule_header).after('
');
+ h.value = $h.prop('outerHTML');
+ });
+ }
}, {
inherit_no_sortable: true,
inherit_no_drop: true,
- icon: 'glyphicon glyphicon-sort'
+ icon: 'bi-sort-down',
+ disable_template: false
});
QueryBuilder.selectors.rule_and_group_containers = QueryBuilder.selectors.rule_container + ', ' + QueryBuilder.selectors.group_container;
diff --git a/src/plugins/sortable/plugin.scss b/src/plugins/sortable/plugin.scss
index cf57c795..ac902fe1 100644
--- a/src/plugins/sortable/plugin.scss
+++ b/src/plugins/sortable/plugin.scss
@@ -14,7 +14,8 @@ $placeholder-border: 1px dashed $placeholder-border-color;
opacity: .5;
z-index: 100;
- &::before, &::after {
+ &::before,
+ &::after {
display: none;
}
}
diff --git a/src/plugins/sql-support/plugin.js b/src/plugins/sql-support/plugin.js
index 6abc101a..4bfb996a 100644
--- a/src/plugins/sql-support/plugin.js
+++ b/src/plugins/sql-support/plugin.js
@@ -24,12 +24,12 @@ QueryBuilder.defaults({
greater_or_equal: { op: '>= ?' },
between: { op: 'BETWEEN ?', sep: ' AND ' },
not_between: { op: 'NOT BETWEEN ?', sep: ' AND ' },
- begins_with: { op: 'LIKE(?)', mod: '{0}%' },
- not_begins_with: { op: 'NOT LIKE(?)', mod: '{0}%' },
- contains: { op: 'LIKE(?)', mod: '%{0}%' },
- not_contains: { op: 'NOT LIKE(?)', mod: '%{0}%' },
- ends_with: { op: 'LIKE(?)', mod: '%{0}' },
- not_ends_with: { op: 'NOT LIKE(?)', mod: '%{0}' },
+ begins_with: { op: 'LIKE ?', mod: '{0}%', escape: '%_' },
+ not_begins_with: { op: 'NOT LIKE ?', mod: '{0}%', escape: '%_' },
+ contains: { op: 'LIKE ?', mod: '%{0}%', escape: '%_' },
+ not_contains: { op: 'NOT LIKE ?', mod: '%{0}%', escape: '%_' },
+ ends_with: { op: 'LIKE ?', mod: '%{0}', escape: '%_' },
+ not_ends_with: { op: 'NOT LIKE ?', mod: '%{0}', escape: '%_' },
is_empty: { op: '= \'\'' },
is_not_empty: { op: '!= \'\'' },
is_null: { op: 'IS NULL' },
@@ -214,7 +214,7 @@ QueryBuilder.defaults({
'named': function(values, char) {
if (!char || char.length > 1) char = ':';
var regex1 = new RegExp('^\\' + char);
- var regex2 = new RegExp('\\' + char + '(' + Object.keys(values).join('|') + ')', 'g');
+ var regex2 = new RegExp('\\' + char + '(' + Object.keys(values).join('|') + ')\\b', 'g');
return {
parse: function(v) {
return regex1.test(v) ? values[v.slice(1)] : v;
@@ -248,10 +248,17 @@ QueryBuilder.extend(/** @lends module:plugins.SqlSupport.prototype */ {
*/
getSQL: function(stmt, nl, data) {
data = (data === undefined) ? this.getRules() : data;
+
+ if (!data) {
+ return null;
+ }
+
nl = !!nl ? '\n' : ' ';
var boolean_as_integer = this.getPluginOptions('sql-support', 'boolean_as_integer');
- if (stmt === true) stmt = 'question_mark';
+ if (stmt === true) {
+ stmt = 'question_mark';
+ }
if (typeof stmt == 'string') {
var config = getStmtConfig(stmt);
stmt = this.settings.sqlStatements[config[1]](config[2]);
@@ -296,11 +303,11 @@ QueryBuilder.extend(/** @lends module:plugins.SqlSupport.prototype */ {
value += sql.sep;
}
- if (rule.type == 'integer' || rule.type == 'double' || rule.type == 'boolean') {
- v = Utils.changeType(v, rule.type, boolean_as_integer);
+ if (rule.type == 'boolean' && boolean_as_integer) {
+ v = v ? 1 : 0;
}
- else if (!stmt) {
- v = Utils.escapeString(v);
+ else if (!stmt && rule.type !== 'integer' && rule.type !== 'double' && rule.type !== 'boolean') {
+ v = Utils.escapeString(v, sql.escape);
}
if (sql.mod) {
@@ -321,7 +328,9 @@ QueryBuilder.extend(/** @lends module:plugins.SqlSupport.prototype */ {
}
var sqlFn = function(v) {
- return sql.op.replace(/\?/, v);
+ return sql.op.replace('?', function() {
+ return v;
+ });
};
/**
@@ -450,6 +459,10 @@ QueryBuilder.extend(/** @lends module:plugins.SqlSupport.prototype */ {
var curr = out;
(function flatten(data, i) {
+ if (data === null) {
+ return;
+ }
+
// allow plugins to manually parse or handle special cases
data = self.change('parseSQLNode', data);
@@ -473,7 +486,20 @@ QueryBuilder.extend(/** @lends module:plugins.SqlSupport.prototype */ {
// it's a node
if (['AND', 'OR'].indexOf(data.operation.toUpperCase()) !== -1) {
// create a sub-group if the condition is not the same and it's not the first level
- if (i > 0 && curr.condition != data.operation.toUpperCase()) {
+
+ /**
+ * Given an existing group and an AST node, determines if a sub-group must be created
+ * @event changer:sqlGroupsDistinct
+ * @memberof module:plugins.SqlSupport
+ * @param {boolean} create - true by default if the group condition is different
+ * @param {object} group
+ * @param {object} AST
+ * @param {int} current group level
+ * @returns {boolean}
+ */
+ var createGroup = self.change('sqlGroupsDistinct', i > 0 && curr.condition != data.operation.toUpperCase(), curr, data, i);
+
+ if (createGroup) {
/**
* Modifies the group generated from the SQL expression (this is called before the group is filled with rules)
* @event changer:sqlToGroup
@@ -553,6 +579,19 @@ QueryBuilder.extend(/** @lends module:plugins.SqlSupport.prototype */ {
Utils.error('SQLParse', 'Cannot find field name in {0}', JSON.stringify(data.left));
}
+ // unescape chars declared by the operator
+ var finalValue = opVal.val;
+ var sql = self.settings.sqlOperators[opVal.op];
+ if (!stmt && sql && sql.escape) {
+ var searchChars = sql.escape.split('').map(function(c) {
+ return '\\\\' + c;
+ }).join('|');
+ finalValue = finalValue
+ .replace(new RegExp('(' + searchChars + ')', 'g'), function(s) {
+ return s[1];
+ });
+ }
+
var id = self.getSQLFieldID(field, value);
/**
@@ -567,7 +606,7 @@ QueryBuilder.extend(/** @lends module:plugins.SqlSupport.prototype */ {
id: id,
field: field,
operator: opVal.op,
- value: opVal.val
+ value: finalValue
}, data);
curr.rules.push(rule);
@@ -596,7 +635,7 @@ QueryBuilder.extend(/** @lends module:plugins.SqlSupport.prototype */ {
*/
getSQLFieldID: function(field, value) {
var matchingFilters = this.filters.filter(function(filter) {
- return filter.field === field;
+ return filter.field.toLowerCase() === field.toLowerCase();
});
var id;
diff --git a/src/public.js b/src/public.js
index 610a4adf..8e8117af 100644
--- a/src/public.js
+++ b/src/public.js
@@ -46,6 +46,10 @@ QueryBuilder.prototype.reset = function() {
this.model.root.empty();
+ this.model.root.data = undefined;
+ this.model.root.flags = $.extend({}, this.settings.default_group_flags);
+ this.model.root.condition = this.settings.default_condition;
+
this.addRule(this.model.root);
/**
@@ -54,6 +58,8 @@ QueryBuilder.prototype.reset = function() {
* @memberof QueryBuilder
*/
this.trigger('afterReset');
+
+ this.trigger('rulesChanged');
};
/**
@@ -86,6 +92,8 @@ QueryBuilder.prototype.clear = function() {
* @memberof QueryBuilder
*/
this.trigger('afterClear');
+
+ this.trigger('rulesChanged');
};
/**
@@ -264,7 +272,7 @@ QueryBuilder.prototype.getRules = function(options) {
};
if (rule.filter && rule.filter.data || rule.data) {
- ruleData.data = $.extendext(true, 'replace', {}, rule.filter.data, rule.data);
+ ruleData.data = $.extendext(true, 'replace', {}, rule.filter ? rule.filter.data : {}, rule.data);
}
if (options.get_flags) {
@@ -344,7 +352,6 @@ QueryBuilder.prototype.setRules = function(data, options) {
this.clear();
this.setRoot(false, data.data, this.parseGroupFlags(data));
- this.applyGroupFlags(this.model.root);
/**
* Modifies data before the {@link QueryBuilder#setRules} method
@@ -387,8 +394,6 @@ QueryBuilder.prototype.setRules = function(data, options) {
return;
}
- self.applyGroupFlags(model);
-
add(item, model);
}
}
@@ -410,21 +415,24 @@ QueryBuilder.prototype.setRules = function(data, options) {
if (!item.empty) {
model.filter = self.getFilterById(item.id, !options.allow_invalid);
+ }
- if (model.filter) {
- model.operator = self.getOperatorByType(item.operator, !options.allow_invalid);
-
- if (!model.operator) {
- model.operator = self.getOperators(model.filter)[0];
- }
+ if (model.filter) {
+ model.operator = self.getOperatorByType(item.operator, !options.allow_invalid);
- if (model.operator && model.operator.nb_inputs !== 0 && item.value !== undefined) {
- model.value = item.value;
- }
+ if (!model.operator) {
+ model.operator = self.getOperators(model.filter)[0];
}
}
- self.applyRuleFlags(model);
+ if (model.operator && model.operator.nb_inputs !== 0) {
+ if (item.value !== undefined) {
+ model.value = item.value;
+ }
+ else if (model.filter.default_value !== undefined) {
+ model.value = model.filter.default_value;
+ }
+ }
/**
* Modifies the Rule object generated from the JSON
diff --git a/src/scss/default.scss b/src/scss/default.scss
index ace2f2fb..9091634c 100644
--- a/src/scss/default.scss
+++ b/src/scss/default.scss
@@ -62,7 +62,7 @@ $ticks-position: 5px, 10px !default;
.group-conditions {
.btn.readonly:not(.active),
- input[name$=_cond] {
+ input[name$='_cond'] {
border: 0;
clip: rect(0 0 0 0);
height: 1px;
@@ -109,10 +109,6 @@ $ticks-position: 5px, 10px !default;
display: block;
}
}
-
- select, input[type=text], input[type=number] {
- padding: 1px;
- }
}
// ERRORS
@@ -169,6 +165,3 @@ $ticks-position: 5px, 10px !default;
}
}
}
-
-// import
-// endimport
diff --git a/src/template.js b/src/template.js
index b1379f47..219aa06e 100644
--- a/src/template.js
+++ b/src/template.js
@@ -1,91 +1,119 @@
-QueryBuilder.templates.group = '\
- \
- \
- - \
- \
-
\
-
';
+QueryBuilder.templates.group = ({ group_id, level, conditions, icons, settings, translate, builder }) => {
+ return `
+`;
+};
+
+QueryBuilder.templates.rule = ({ rule_id, icons, settings, translate, builder }) => {
+ return `
+
+
+ ${settings.display_errors ? `
+
+ ` : ''}
+
+
+
+
`;
+};
-QueryBuilder.templates.rule = '\
- \
- \
- {{? it.settings.display_errors }} \
-
\
- {{?}} \
- \
- \
- \
-';
+QueryBuilder.templates.filterSelect = ({ rule, filters, icons, settings, translate, builder }) => {
+ let optgroup = null;
+ return `
+`;
+};
-QueryBuilder.templates.filterSelect = '\
-{{ var optgroup = null; }} \
-';
+QueryBuilder.templates.operatorSelect = ({ rule, operators, icons, settings, translate, builder }) => {
+ let optgroup = null;
+ return `
+${operators.length === 1 ? `
+
+${translate("operators", operators[0].type)}
+
+` : ''}
+`;
+};
-QueryBuilder.templates.operatorSelect = '\
-{{? it.operators.length === 1 }} \
- \
-{{= it.translate("operators", it.operators[0].type) }} \
- \
-{{?}} \
-{{ var optgroup = null; }} \
-';
+QueryBuilder.templates.ruleValueSelect = ({ name, rule, icons, settings, translate, builder }) => {
+ let optgroup = null;
+ return `
+`;
+};
/**
* Returns group's HTML
@@ -95,26 +123,26 @@ QueryBuilder.templates.operatorSelect = '\
* @fires QueryBuilder.changer:getGroupTemplate
* @private
*/
-QueryBuilder.prototype.getGroupTemplate = function(group_id, level) {
- var h = this.templates.group({
- builder: this,
- group_id: group_id,
- level: level,
- conditions: this.settings.conditions,
- icons: this.icons,
- settings: this.settings,
- translate: this.translate.bind(this)
- });
+QueryBuilder.prototype.getGroupTemplate = function (group_id, level) {
+ var h = this.templates.group({
+ builder: this,
+ group_id: group_id,
+ level: level,
+ conditions: this.settings.conditions,
+ icons: this.icons,
+ settings: this.settings,
+ translate: this.translate.bind(this)
+ }).trim();
- /**
- * Modifies the raw HTML of a group
- * @event changer:getGroupTemplate
- * @memberof QueryBuilder
- * @param {string} html
- * @param {int} level
- * @returns {string}
- */
- return this.change('getGroupTemplate', h, level);
+ /**
+ * Modifies the raw HTML of a group
+ * @event changer:getGroupTemplate
+ * @memberof QueryBuilder
+ * @param {string} html
+ * @param {int} level
+ * @returns {string}
+ */
+ return this.change('getGroupTemplate', h, level);
};
/**
@@ -124,23 +152,23 @@ QueryBuilder.prototype.getGroupTemplate = function(group_id, level) {
* @fires QueryBuilder.changer:getRuleTemplate
* @private
*/
-QueryBuilder.prototype.getRuleTemplate = function(rule_id) {
- var h = this.templates.rule({
- builder: this,
- rule_id: rule_id,
- icons: this.icons,
- settings: this.settings,
- translate: this.translate.bind(this)
- });
+QueryBuilder.prototype.getRuleTemplate = function (rule_id) {
+ var h = this.templates.rule({
+ builder: this,
+ rule_id: rule_id,
+ icons: this.icons,
+ settings: this.settings,
+ translate: this.translate.bind(this)
+ }).trim();
- /**
- * Modifies the raw HTML of a rule
- * @event changer:getRuleTemplate
- * @memberof QueryBuilder
- * @param {string} html
- * @returns {string}
- */
- return this.change('getRuleTemplate', h);
+ /**
+ * Modifies the raw HTML of a rule
+ * @event changer:getRuleTemplate
+ * @memberof QueryBuilder
+ * @param {string} html
+ * @returns {string}
+ */
+ return this.change('getRuleTemplate', h);
};
/**
@@ -151,26 +179,26 @@ QueryBuilder.prototype.getRuleTemplate = function(rule_id) {
* @fires QueryBuilder.changer:getRuleFilterTemplate
* @private
*/
-QueryBuilder.prototype.getRuleFilterSelect = function(rule, filters) {
- var h = this.templates.filterSelect({
- builder: this,
- rule: rule,
- filters: filters,
- icons: this.icons,
- settings: this.settings,
- translate: this.translate.bind(this)
- });
+QueryBuilder.prototype.getRuleFilterSelect = function (rule, filters) {
+ var h = this.templates.filterSelect({
+ builder: this,
+ rule: rule,
+ filters: filters,
+ icons: this.icons,
+ settings: this.settings,
+ translate: this.translate.bind(this)
+ }).trim();
- /**
- * Modifies the raw HTML of the rule's filter dropdown
- * @event changer:getRuleFilterTemplate
- * @memberof QueryBuilder
- * @param {string} html
- * @param {Rule} rule
- * @param {QueryBuilder.Filter[]} filters
- * @returns {string}
- */
- return this.change('getRuleFilterSelect', h, rule, filters);
+ /**
+ * Modifies the raw HTML of the rule's filter dropdown
+ * @event changer:getRuleFilterSelect
+ * @memberof QueryBuilder
+ * @param {string} html
+ * @param {Rule} rule
+ * @param {QueryBuilder.Filter[]} filters
+ * @returns {string}
+ */
+ return this.change('getRuleFilterSelect', h, rule, filters);
};
/**
@@ -181,26 +209,56 @@ QueryBuilder.prototype.getRuleFilterSelect = function(rule, filters) {
* @fires QueryBuilder.changer:getRuleOperatorTemplate
* @private
*/
-QueryBuilder.prototype.getRuleOperatorSelect = function(rule, operators) {
- var h = this.templates.operatorSelect({
- builder: this,
- rule: rule,
- operators: operators,
- icons: this.icons,
- settings: this.settings,
- translate: this.translate.bind(this)
- });
+QueryBuilder.prototype.getRuleOperatorSelect = function (rule, operators) {
+ var h = this.templates.operatorSelect({
+ builder: this,
+ rule: rule,
+ operators: operators,
+ icons: this.icons,
+ settings: this.settings,
+ translate: this.translate.bind(this)
+ }).trim();
- /**
- * Modifies the raw HTML of the rule's operator dropdown
- * @event changer:getRuleOperatorTemplate
- * @memberof QueryBuilder
- * @param {string} html
- * @param {Rule} rule
- * @param {QueryBuilder.Operator[]} operators
- * @returns {string}
- */
- return this.change('getRuleOperatorSelect', h, rule, operators);
+ /**
+ * Modifies the raw HTML of the rule's operator dropdown
+ * @event changer:getRuleOperatorSelect
+ * @memberof QueryBuilder
+ * @param {string} html
+ * @param {Rule} rule
+ * @param {QueryBuilder.Operator[]} operators
+ * @returns {string}
+ */
+ return this.change('getRuleOperatorSelect', h, rule, operators);
+};
+
+/**
+ * Returns the rule's value select HTML
+ * @param {string} name
+ * @param {Rule} rule
+ * @returns {string}
+ * @fires QueryBuilder.changer:getRuleValueSelect
+ * @private
+ */
+QueryBuilder.prototype.getRuleValueSelect = function (name, rule) {
+ var h = this.templates.ruleValueSelect({
+ builder: this,
+ name: name,
+ rule: rule,
+ icons: this.icons,
+ settings: this.settings,
+ translate: this.translate.bind(this)
+ }).trim();
+
+ /**
+ * Modifies the raw HTML of the rule's value dropdown (in case of a "select filter)
+ * @event changer:getRuleValueSelect
+ * @memberof QueryBuilder
+ * @param {string} html
+ * @param [string} name
+ * @param {Rule} rule
+ * @returns {string}
+ */
+ return this.change('getRuleValueSelect', h, name, rule);
};
/**
@@ -211,74 +269,68 @@ QueryBuilder.prototype.getRuleOperatorSelect = function(rule, operators) {
* @fires QueryBuilder.changer:getRuleInput
* @private
*/
-QueryBuilder.prototype.getRuleInput = function(rule, value_id) {
- var filter = rule.filter;
- var validation = rule.filter.validation || {};
- var name = rule.id + '_value_' + value_id;
- var c = filter.vertical ? ' class=block' : '';
- var h = '';
+QueryBuilder.prototype.getRuleInput = function (rule, value_id) {
+ var filter = rule.filter;
+ var validation = rule.filter.validation || {};
+ var name = rule.id + '_value_' + value_id;
+ var c = filter.vertical ? ' class=block' : '';
+ var h = '';
+ var placeholder = Array.isArray(filter.placeholder) ? filter.placeholder[value_id] : filter.placeholder;
- if (typeof filter.input == 'function') {
- h = filter.input.call(this, rule, name);
- }
- else {
- switch (filter.input) {
- case 'radio':
- case 'checkbox':
- Utils.iterateOptions(filter.values, function(key, val) {
- h += ' ';
- });
- break;
+ if (typeof filter.input == 'function') {
+ h = filter.input.call(this, rule, name);
+ }
+ else {
+ switch (filter.input) {
+ case 'radio':
+ case 'checkbox':
+ Utils.iterateOptions(filter.values, function (key, val) {
+ h += ' ';
+ });
+ break;
- case 'select':
- h += '';
- break;
+ case 'select':
+ h = this.getRuleValueSelect(name, rule);
+ break;
- case 'textarea':
- h += '" ; - break + case textarea : + h +='" ; + break - case number : - h +='" ; - break + case number : + h +='" ; + break - default - h +='" ; - } + default + h +='" ; } + } - - * Modifies the raw HTML of the rule s input - * @event changer:getRuleInput - * @memberof QueryBuilder - * @param {string html - * @param {Rule rule - * @param {string name - the name that the input must have - * @returns {string - * - return this.change getRuleInput , h rule name + + * Modifies the raw HTML of the rule s input + * @event changer:getRuleInput + * @memberof QueryBuilder + * @param {string html + * @param {Rule rule + * @param {string name - the name that the input must have + * @returns {string + * + return this.change getRuleInput , h rule name } diff --git a/src/utils.js b/src/utils.js index 94af0956..d74f0817 100644 - a/src/utils.js + b/src/utils.js @ -14,10 +14,11 @ QueryBuilder.utils="Utils;" * @callback Utils#OptionsIteratee * @param {string key * @param {string value + * @param {string [optgroup * - * Iterates over radio/checkbox/selection options it accept three formats + * Iterates over radio/checkbox/selection options it accept four formats * * @example * array of values @ -28,6 +29,9 @ QueryBuilder.utils="Utils;" * @example * array of 1-element maps * options="[{1:" one } {2 two } {3 three } + * @example + * array of elements + * options="[{value:" 1 label one , optgroup group } {value 2 label two } * * @param {object|array options * @param {Utils#OptionsIteratee tpl @ -36,12 +40,18 @ Utils.iterateOptions="function(options," tpl { if (options { if ($.isArray(options { options.forEach(function(entry { - array of one-element maps if ($.isPlainObject(entry { - $.each(entry function(key val { - tpl(key val - return false break after first entry - } + array of elements + if ( value in entry { + tpl(entry.value entry.label | entry.value entry.optgroup + } + array of one-element maps + else { + $.each(entry function(key val { + tpl(key val + return false break after first entry + } + } } array of values else { @ -103,47 +113,71 @ Utils.error="function()" { * Changes the type of a value to int float or bool * @param { value * @param {string type - integer , double , boolean or anything else (passthrough - * @param {boolean [boolAsInt="false]" - return 0 or 1 for booleans * @returns { * -Utils.changeType="function(value," type boolAsInt { +Utils.changeType="function(value," type { + if (value="==" | value="==" undefined { + return undefined + } + switch (type { @formatter:off - case integer : return parseInt(value - case double : return parseFloat(value - case boolean : - var bool="value.trim().toLowerCase()" true | value.trim="==" 1 | value="==" 1 - return boolAsInt ? (bool ? 1 : 0 : bool - default return value - @formatter:on + case integer : + if (typeof value="==" string & !/^-?\d+$/.test(value { + return value + } + return parseInt(value + case double : + if (typeof value="==" string & !/^-?\d+\.?\d*$/.test(value { + return value + } + return parseFloat(value + case boolean : + if (typeof value="==" string & !/^(0|1|true|false){1}$/i.test(value { + return value + } + return value="==" true | value="==" 1 | value.toLowerCase="==" true | value="==" 1 ; + default return value + @formatter:on } } * Escapes a string like PHP s mysql_real_escape_string does * @param {string value + * @param {string [additionalEscape additionnal chars to escape * @returns {string * -Utils.escapeString="function(value)" { +Utils.escapeString="function(value," additionalEscape { if (typeof value !="string" ) { return value } - return value + var escaped="value" .replace(/[\0\n\r\b \ ]/g function(s { switch (s { @formatter:off - case \0 : return \\0 ; - case \n : return \\n ; - case \r : return \\r ; - case \b : return \\b ; - default return \ + s - @formatter:off + case \0 : return \\0 ; + case \n : return \\n ; + case \r : return \\r ; + case \b : return \\b ; + case \ : return \ \ ; + default return \ + s + @formatter:off } } uglify compliant .replace(/\t/g \\t ) .replace(/\x1a/g \\Z ) + + if (additionalEscape { + escaped="escaped" + .replace(new RegExp [ + additionalEscape + ] , g ) function(s { + return \ + s + } + } + + return escaped } diff --git a/tests/common.js b/tests/common.js index 91a3ffb5..c4473ed7 100644 - a/tests/common.js + b/tests/common.js @ -15,7 +15,7 @ QUnit.begin(function { * QUnit.begin(function $ #qunit-header ).append - />' +
+ '
' +
'
' +
'
' +
'' +
@@ -104,7 +104,12 @@ QUnit.assert.rulesMatch = function(actual, expected, message) {
return ok;
}(actual, expected));
- this.push(ok, actual, expected, message);
+ this.pushResult({
+ result: ok,
+ actual: actual,
+ expected: expected,
+ message: message
+ });
};
/**
@@ -158,7 +163,12 @@ QUnit.assert.optionsMatch = function($target, expected, message) {
* Custom assert to test a regex
*/
QUnit.assert.match = function(actual, regex, message) {
- this.push(regex.test(actual), actual, regex, message);
+ this.pushResult({
+ result: regex.test(actual),
+ actual: actual,
+ expected: regex,
+ message: message
+ });
};
@@ -301,7 +311,8 @@ var basic_filters = [{
label: 'Age',
type: 'integer',
input: 'text',
- value_separator: '|'
+ value_separator: '|',
+ default_operator: 'in'
}];
var basic_rules = {
diff --git a/tests/data.module.js b/tests/data.module.js
index 358df511..41e7b48d 100644
--- a/tests/data.module.js
+++ b/tests/data.module.js
@@ -170,6 +170,11 @@ $(function() {
/number_wrong_step/
);
+ assert.validationError($b,
+ { id: 'integer', operator: 'between', value: [5, 1] },
+ /number_between_invalid/
+ );
+
assert.validationError($b,
{ id: 'date' },
/datetime_empty/
@@ -190,6 +195,11 @@ $(function() {
/datetime_exceed_max/
);
+ assert.validationError($b,
+ { id: 'date', operator: 'between', value: ['2015/01/01', '2014/01/01'] },
+ /datetime_between_invalid/
+ );
+
assert.validationError($b,
{ id: 'boolean', value: 'oui' },
/boolean_not_valid/
@@ -404,13 +414,30 @@ $(function() {
}, {
id: 'age',
operator: 'not_in',
- value: ['16', '17', '18']
+ value: [16, 17, 18]
}]
},
'Should split values on comma and pipe'
);
});
+ /**
+ * Test default operator
+ */
+ QUnit.test('default operator', function(assert) {
+ $b.queryBuilder({
+ filters: basic_filters
+ });
+
+ $('[name=builder_rule_0_filter]').val('age').trigger('change');
+
+ assert.equal(
+ $('[name=builder_rule_0_operator]').val(),
+ 'in',
+ 'Should set "age" operator to "in" by default'
+ );
+ });
+
/**
* Test allow_invalid option
*/
@@ -490,6 +517,35 @@ $(function() {
);
});
+ QUnit.test('apply default value', function(assert) {
+ $b.queryBuilder({
+ filters: [
+ {
+ id: 'name',
+ default_value: 'Mistic'
+ }
+ ],
+ rules: [
+ {
+ id: 'name'
+ }
+ ]
+ });
+
+ assert.rulesMatch(
+ $b.queryBuilder('getRules'),
+ {
+ condition: 'AND',
+ rules: [{
+ id: 'name',
+ operator: 'equal',
+ value: 'Mistic'
+ }]
+ },
+ 'Should have used the filter default value'
+ );
+ });
+
/**
* Test allow_empty_value option
*/
diff --git a/tests/index.html b/tests/index.html
index 3af96d4d..e322dcfd 100644
--- a/tests/index.html
+++ b/tests/index.html
@@ -4,10 +4,10 @@
jQuery-QueryBuilder
-
-
-
-
+
+
+
+