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 a9007708..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 [jsDelivr](https://www.jsdelivr.com/projects/jquery.query-builder) 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/.jsdoc.json b/.jsdoc.json new file mode 100644 index 00000000..d8a3fd67 --- /dev/null +++ b/.jsdoc.json @@ -0,0 +1,39 @@ +{ + "opts": { + "private": false, + "template": "node_modules/foodoc/template", + "readme": "build/jsdoc.md" + }, + "plugins": [ + "plugins/markdown" + ], + "templates": { + "systemName": "jQuery QueryBuilder API", + "systemSummary": "jQuery plugin for user friendly query/filter creator", + "systemColor": "#004482", + "copyright": "Licensed under MIT License, documentation under CC BY 3.0.", + "includeDate": false, + "inverseNav": false, + "cleverLinks": true, + "sort": "longname, version, since", + "analytics": { + "ua": "UA-28192323-3", + "domain": "auto" + }, + "navMembers": [ + {"kind": "class", "title": "Classes", "summary": "All documented classes."}, + {"kind": "external", "title": "Externals", "summary": "All documented external members."}, + {"kind": "global", "title": "Globals", "summary": "All documented globals."}, + {"kind": "mixin", "title": "Mixins", "summary": "All documented mixins."}, + {"kind": "interface", "title": "Interfaces", "summary": "All documented interfaces."}, + {"kind": "module", "title": "Modules", "summary": "All documented modules."}, + {"kind": "event", "title": "Events", "summary": "All documented events."}, + {"kind": "namespace", "title": "Namespaces", "summary": "All documented namespaces."}, + {"kind": "tutorial", "title": "Tutorials", "summary": "All available tutorials."} + ], + "scripts": [ + "https://cdnjs.cloudflare.com/ajax/libs/trianglify/1.0.1/trianglify.min.js", + "js/custom.js" + ] + } +} \ 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 c09c25fb..00000000 --- a/Gruntfile.js +++ /dev/null @@ -1,426 +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/core.js', - 'src/public.js', - 'src/data.js', - 'src/template.js', - 'src/model.js', - 'src/utils.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' - }; - }) - } - }, - - 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/css/*.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'] - }, - - // 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'], - dest: 'doc', - options: { - private: false, - template: 'node_modules/docdash', - readme: 'README.md' - } - } - }, - - // 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' - ]); -}; 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 9fb91326..f0fd8ab7 100644 --- a/README.md +++ b/README.md @@ -1,70 +1,61 @@ # jQuery QueryBuilder -[](http://querybuilder.js.org) -[](http://www.jsdelivr.com/projects/jquery.query-builder) -[](https://travis-ci.org/mistic100/jQuery-QueryBuilder) -[](https://coveralls.io/r/mistic100/jQuery-QueryBuilder) -[](https://saythanks.io/to/mistic100) +[](https://www.npmjs.com/package/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 -[querybuilder.js.org](http://querybuilder.js.org) +[querybuilder.js.org](https://querybuilder.js.org) -### Dependencies - * jQuery >= 1.10 - * Bootstrap >= 3.1 (CSS only) - * [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) - * 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) -### Browser support - * Internet Explorer >= 9 - * All other recent browsers +## Install -### Build +#### Manually -#### Prerequisites +[Download the latest release](https://github.com/mistic100/jQuery-QueryBuilder/releases) - * 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` +#### With npm -#### Run +```bash +$ npm install jQuery-QueryBuilder +``` -Install Node and Bower dependencies `npm install & bower install` then run `grunt` in the root directory to generate production files inside `dist`. +#### Via CDN -#### Options +jQuery-QueryBuilder is available on [jsDelivr](https://www.jsdelivr.com/package/npm/jQuery-QueryBuilder). +### Dependencies + * [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) + * [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 -You can choose which plugins to include with `--plugins` : -```bash -# include "sql-support" and "mongodb-support" plugins -grunt --plugins=sql-support,mongodb-support +($.extendext is directly included in the [standalone](https://github.com/mistic100/jQuery-QueryBuilder/blob/master/dist/js/query-builder.standalone.js) file) -# 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 +## Developement + +Install Node dependencies with `npm install`. + +#### Build + +Run `npm run build` in the root directory to generate production files inside `dist`. + +#### Serve + +Run `npm run serve` to open the example page with automatic build and livereload. - * `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 jsdoc` to generate the documentation. -### Inspiration - * [Knockout Query Builder](http://kindohm.github.io/knockout-query-builder/) - * [jui_filter_rules](http://www.pontikis.net/labs/jui_filter_rules/) +## License +This library is available under the MIT license. 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.js b/build/jsdoc.js new file mode 100644 index 00000000..edbad8d4 --- /dev/null +++ b/build/jsdoc.js @@ -0,0 +1,12 @@ +(function() { + var header = $('.page-header'); + var pattern = Trianglify({ + width: window.screen.width | header.outerWidth(), + height: header.outerHeight(), + cell_size: 90, + seed: 'jQuery QueryBuilder', + x_colors: ['#0074d9', '#001224'] + }); + + header.css('background-image', 'url(' + pattern.png() + ')'); +}()); diff --git a/build/jsdoc.md b/build/jsdoc.md new file mode 100644 index 00000000..e329af92 --- /dev/null +++ b/build/jsdoc.md @@ -0,0 +1,11 @@ +# [Main documentation](..) + +# Entry point: [$.fn.QueryBuilder](external-_jQuery.fn_.html) + +# [QueryBuilder](QueryBuilder.html) + +# [Rule](Rule.html) & [Group](Group.html) + +# [Events](list_event.html) + +# [Plugins](module-plugins.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 09831111..00000000 --- a/composer.json +++ /dev/null @@ -1,26 +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": { - "components/jquery": ">=1.9.0", - "moment/moment": ">=2.6.0", - "twbs/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" - } -} 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 6704b236..180652b8 100644 --- a/examples/index.html +++ b/examples/index.html @@ -1,84 +1,70 @@ - +
setOptions method
+ * Runtime modifiable options with `setOptions` method
* @type {string[]}
* @readonly
+ * @private
*/
QueryBuilder.modifiable_options = [
'display_errors',
@@ -153,7 +156,8 @@ QueryBuilder.DEFAULTS = {
group: null,
rule: null,
filterSelect: null,
- operatorSelect: null
+ operatorSelect: null,
+ ruleValueSelect: null
},
lang_code: 'en',
@@ -183,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/az.json b/src/i18n/az.json
index 3cc13816..5e12334f 100644
--- a/src/i18n/az.json
+++ b/src/i18n/az.json
@@ -52,6 +52,6 @@
"datetime_exceed_min": "{0} sonra olmalıdır",
"datetime_exceed_max": "{0} əvvəl olmalıdır",
"boolean_not_valid": "Loqik olmayan",
- "operator_not_multiple": "{0} operatoru çoxlu məna daşımır"
+ "operator_not_multiple": "\"{1}\" operatoru çoxlu məna daşımır"
}
}
\ No newline at end of file
diff --git a/src/i18n/bg.json b/src/i18n/bg.json
index 27083bb4..095d01cc 100644
--- a/src/i18n/bg.json
+++ b/src/i18n/bg.json
@@ -56,6 +56,6 @@
"datetime_exceed_min": "Трябва да е след {0}",
"datetime_exceed_max": "Трябва да е преди {0}",
"boolean_not_valid": "Не е булева",
- "operator_not_multiple": "Оператора {0} не може да приеме множество стойности"
+ "operator_not_multiple": "Оператора \"{1}\" не може да приеме множество стойности"
}
}
diff --git a/src/i18n/cs.json b/src/i18n/cs.json
index 522e6978..448cc97d 100644
--- a/src/i18n/cs.json
+++ b/src/i18n/cs.json
@@ -52,6 +52,6 @@
"datetime_exceed_min": "Musí být po {0}",
"datetime_exceed_max": "Musí být do {0}",
"boolean_not_valid": "Nelogické",
- "operator_not_multiple": "Operátor {0} nepodporuje mnoho hodnot"
+ "operator_not_multiple": "Operátor \"{1}\" nepodporuje mnoho hodnot"
}
}
\ No newline at end of file
diff --git a/src/i18n/el.json b/src/i18n/el.json
index 9e141a73..0c988473 100644
--- a/src/i18n/el.json
+++ b/src/i18n/el.json
@@ -52,6 +52,6 @@
"datetime_exceed_min": "Νεότερο από {0}",
"datetime_exceed_max": "Παλαιότερο από {0}",
"boolean_not_valid": "Δεν είναι BOOLEAN",
- "operator_not_multiple": "Η συνθήκη {0} δεν μπορεί να δεχθεί πολλαπλές τιμές"
+ "operator_not_multiple": "Η συνθήκη \"{1}\" δεν μπορεί να δεχθεί πολλαπλές τιμές"
}
}
\ No newline at end of file
diff --git a/src/i18n/en.json b/src/i18n/en.json
index b5828139..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 {0} cannot accept multiple values"
+ "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 a6da0959..55a6e456 100644
--- a/src/i18n/fa-IR.json
+++ b/src/i18n/fa-IR.json
@@ -21,7 +21,8 @@
"less_or_equal": "کمتر یا مساوی با",
"greater": "بزرگتر از",
"greater_or_equal": "بزرگتر یا مساوی با",
- "between": "مابین",
+ "between": "مابین",
+ "not_between": "مابین نباشد",
"begins_with": "شروع شود با",
"not_begins_with": "شروع نشود با",
"contains": "شامل شود",
@@ -55,6 +56,6 @@
"datetime_exceed_min": "باید بعد از {0} باشد",
"datetime_exceed_max": "باید قبل از {0} باشد",
"boolean_not_valid": "مقدار دودویی وارد کنید",
- "operator_not_multiple": "اپراتور {0} نمی تواند چند مقدار قبول کند"
+ "operator_not_multiple": "اپراتور \"{1}\" نمی تواند چند مقدار قبول کند"
}
-}
\ No newline at end of file
+}
diff --git a/src/i18n/fr.json b/src/i18n/fr.json
index 9d73e09d..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 {0} ne peut utiliser plusieurs valeurs"
+ "operator_not_multiple": "L'opérateur \"{1}\" ne peut utiliser plusieurs valeurs"
}
-}
\ No newline at end of file
+}
diff --git a/src/i18n/he.json b/src/i18n/he.json
index 99f2acf9..d1009042 100644
--- a/src/i18n/he.json
+++ b/src/i18n/he.json
@@ -56,6 +56,6 @@
"datetime_exceed_min": "התאריך חייב להיות אחרי {0}",
"datetime_exceed_max": "התאריך חייב להיות לפני {0}",
"boolean_not_valid": "זהו לא בוליאני",
- "operator_not_multiple": "האופרטור {0} לא יכול לקבל ערכים מרובים"
+ "operator_not_multiple": "האופרטור \"{1}\" לא יכול לקבל ערכים מרובים"
}
}
\ 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 09060e10..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": {
@@ -56,6 +56,6 @@
"datetime_exceed_min": "Musi być po {0}",
"datetime_exceed_max": "Musi być przed {0}",
"boolean_not_valid": "Niepoprawna wartość logiczna",
- "operator_not_multiple": "Operator {0} nie przyjmuje wielu wartości"
+ "operator_not_multiple": "Operator \"{1}\" nie przyjmuje wielu wartości"
}
}
\ No newline at end of file
diff --git a/src/i18n/pt-BR.json b/src/i18n/pt-BR.json
index db936baf..f822aa83 100644
--- a/src/i18n/pt-BR.json
+++ b/src/i18n/pt-BR.json
@@ -56,6 +56,6 @@
"datetime_exceed_max": "É necessário ser inferior a {0}",
"datetime_empty": "Nenhuma data selecionada",
"boolean_not_valid": "Não é um valor booleano",
- "operator_not_multiple": "O operador {0} não aceita valores múltiplos"
+ "operator_not_multiple": "O operador \"{1}\" não aceita valores múltiplos"
}
}
\ No newline at end of file
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 9248db0f..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,11 +50,13 @@
"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": "Оператор {0} не поддерживает много значений"
+ "operator_not_multiple": "Оператор \"{1}\" не поддерживает много значений"
}
}
\ No newline at end of file
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/sq.json b/src/i18n/sq.json
index 508a5cdd..415244bd 100644
--- a/src/i18n/sq.json
+++ b/src/i18n/sq.json
@@ -55,6 +55,6 @@
"datetime_exceed_min": "Duhet të jetë pas {0}",
"datetime_exceed_max": "Duhet të jetë para {0}",
"boolean_not_valid": "Nuk është boolean",
- "operator_not_multiple": "Operatori {0} nuk mund të pranojë vlera të shumëfishta"
+ "operator_not_multiple": "Operatori \"{1}\" nuk mund të pranojë vlera të shumëfishta"
}
}
\ No newline at end of file
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 4d5280b1..25ecbf78 100644
--- a/src/i18n/tr.json
+++ b/src/i18n/tr.json
@@ -38,24 +38,26 @@
"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 {0} birden fazla değer kabul etmiyor"
+ "operator_not_multiple": "Operatör \"{1}\" birden fazla değer kabul etmiyor"
}
}
diff --git a/src/i18n/ua.json b/src/i18n/ua.json
index 5276347b..2d1c5c2d 100644
--- a/src/i18n/ua.json
+++ b/src/i18n/ua.json
@@ -52,6 +52,6 @@
"datetime_exceed_min": "Повинне бути, після {0}",
"datetime_exceed_max": "Повинне бути, до {0}",
"boolean_not_valid": "Не логічне",
- "operator_not_multiple": "Оператор {0} не підтримує багато значень"
+ "operator_not_multiple": "Оператор \"{1}\" не підтримує багато значень"
}
}
\ No newline at end of file
diff --git a/src/i18n/zh-CN.json b/src/i18n/zh-CN.json
index 51ddfe39..666ca685 100644
--- a/src/i18n/zh-CN.json
+++ b/src/i18n/zh-CN.json
@@ -56,6 +56,6 @@
"datetime_exceed_min": "必须在{0}之后",
"datetime_exceed_max": "必须在{0}之前",
"boolean_not_valid": "不是布尔值",
- "operator_not_multiple": "选项{0}无法接受多个值"
+ "operator_not_multiple": "选项\"{1}\"无法接受多个值"
}
}
\ No newline at end of file
diff --git a/src/jquery.js b/src/jquery.js
index 7f1ca98b..941ea675 100644
--- a/src/jquery.js
+++ b/src/jquery.js
@@ -9,6 +9,11 @@
* @memberof external:"jQuery.fn"
* @param {*} option - initial configuration or method name
* @param {...*} args - method arguments
+ *
+ * @example
+ * $('#builder').queryBuilder({ /** configuration object *\/ });
+ * @example
+ * $('#builder').queryBuilder('methodName', methodParam1, methodParam2);
*/
$.fn.queryBuilder = function(option) {
if (this.length === 0) {
@@ -25,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 c7ca252d..0659d288 100644
--- a/src/main.js
+++ b/src/main.js
@@ -1,10 +1,12 @@
/**
- * @typedef {object} QueryBuilder#Filter
+ * @typedef {object} Filter
+ * @memberof QueryBuilder
* @description See {@link http://querybuilder.js.org/index.html#filters}
*/
/**
- * @typedef {object} QueryBuilder#Operator
+ * @typedef {object} Operator
+ * @memberof QueryBuilder
* @description See {@link http://querybuilder.js.org/index.html#operators}
*/
@@ -12,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;
@@ -61,7 +62,7 @@ var QueryBuilder = function($el, options) {
/**
* List of filters
- * @member {QueryBuilder#Filter[]}
+ * @member {QueryBuilder.Filter[]}
* @readonly
*/
this.filters = this.settings.filters;
@@ -75,7 +76,7 @@ var QueryBuilder = function($el, options) {
/**
* List of operators
- * @member {QueryBuilder#Operator[]}
+ * @member {QueryBuilder.Operator[]}
* @readonly
*/
this.operators = this.settings.operators;
@@ -99,7 +100,7 @@ var QueryBuilder = function($el, options) {
* @member {object}
* @readonly
*/
- this.lang = undefined;
+ this.lang = null;
// translations : english << 'lang_code' << custom
if (QueryBuilder.regional['en'] === undefined) {
@@ -120,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);
@@ -133,26 +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 QueryBuilder#afterInit
- */
- this.trigger('afterInit');
-
- if (options.rules) {
- this.setRules(options.rules);
- delete this.settings.rules;
- }
- else {
- this.setRoot(true);
- }
};
$.extend(QueryBuilder.prototype, /** @lends QueryBuilder.prototype */ {
@@ -234,58 +221,3 @@ $.extend(QueryBuilder.prototype, /** @lends QueryBuilder.prototype */ {
}).join(' ');
}
});
-
-/**
- * @typedef {object} QueryBuilder#Plugin
- * @property {object} def - default options
- * @property {function} fct - init function
- */
-
-/**
- * Definition of available plugins
- * @type {object.}
- */
-QueryBuilder.plugins = {};
-
-/**
- * Gets or extends the default configuration
- * @param {object} [options] - new configuration
- * @returns {undefined|object} nothing or configuration object (copy)
- */
-QueryBuilder.defaults = function(options) {
- if (typeof options == 'object') {
- $.extendext(true, 'replace', QueryBuilder.DEFAULTS, options);
- }
- else if (typeof options == 'string') {
- if (typeof QueryBuilder.DEFAULTS[options] == 'object') {
- return $.extend(true, {}, QueryBuilder.DEFAULTS[options]);
- }
- else {
- return QueryBuilder.DEFAULTS[options];
- }
- }
- else {
- return $.extend(true, {}, QueryBuilder.DEFAULTS);
- }
-};
-
-/**
- * Registers a new plugin
- * @param {string} name
- * @param {function} fct - init function
- * @param {object} [def] - default options
- */
-QueryBuilder.define = function(name, fct, def) {
- QueryBuilder.plugins[name] = {
- fct: fct,
- def: def || {}
- };
-};
-
-/**
- * Adds new methods to QueryBuilder prototype
- * @param {object.} methods
- */
-QueryBuilder.extend = function(methods) {
- $.extend(QueryBuilder.prototype, methods);
-};
diff --git a/src/model.js b/src/model.js
index 154b934f..a397c98f 100644
--- a/src/model.js
+++ b/src/model.js
@@ -3,17 +3,26 @@
* @constructor
*/
function Model() {
+ /**
+ * @member {Group}
+ * @readonly
+ */
this.root = null;
+
+ /**
+ * Base for event emitting
+ * @member {jQuery}
+ * @readonly
+ * @private
+ */
this.$ = $(this);
}
-$.extend(Model.prototype, {
+$.extend(Model.prototype, /** @lends Model.prototype */ {
/**
* Triggers an event on the model
* @param {string} type
* @returns {$.Event}
- * @memberof Model
- * @instance
*/
trigger: function(type) {
var event = new $.Event(type);
@@ -26,8 +35,6 @@ $.extend(Model.prototype, {
* @param {string} type
* @param {function} cb
* @returns {Model}
- * @memberof Model
- * @instance
*/
on: function() {
this.$.on.apply(this.$, Array.prototype.slice.call(arguments));
@@ -39,8 +46,6 @@ $.extend(Model.prototype, {
* @param {string} type
* @param {function} [cb]
* @returns {Model}
- * @memberof Model
- * @instance
*/
off: function() {
this.$.off.apply(this.$, Array.prototype.slice.call(arguments));
@@ -52,8 +57,6 @@ $.extend(Model.prototype, {
* @param {string} type
* @param {function} cb
* @returns {Model}
- * @memberof Model
- * @instance
*/
once: function() {
this.$.one.apply(this.$, Array.prototype.slice.call(arguments));
@@ -61,43 +64,6 @@ $.extend(Model.prototype, {
}
});
-/**
- * Defines properties on an Node prototype with getter and setter.
- * Update events are emitted in the setter through root Model (if any).
- * The object must have a `__` object, non enumerable property to store values.
- * @param {function} obj
- * @param {string[]} fields
- */
-Model.defineModelProperties = function(obj, fields) {
- fields.forEach(function(field) {
- Object.defineProperty(obj.prototype, field, {
- enumerable: true,
- get: function() {
- return this.__[field];
- },
- set: function(value) {
- var previousValue = (this.__[field] !== null && typeof this.__[field] == 'object') ?
- $.extend({}, this.__[field]) :
- this.__[field];
-
- this.__[field] = value;
-
- if (this.model !== null) {
- /**
- * After a value of the model changed
- * @event Model#model:update
- * @param {Node} node
- * @param {string} field
- * @param {*} value
- * @param {*} previousValue
- */
- this.model.trigger('update', this, field, value, previousValue);
- }
- }
- });
- });
-};
-
/**
* Root abstract object
@@ -173,7 +139,7 @@ var Node = function(parent, $el) {
this.parent = parent;
};
-Model.defineModelProperties(Node, ['level', 'error', 'data', 'flags']);
+Utils.defineModelProperties(Node, ['level', 'error', 'data', 'flags']);
Object.defineProperty(Node.prototype, 'parent', {
enumerable: true,
@@ -210,7 +176,7 @@ Node.prototype.getPos = function() {
/**
* Deletes self
- * @fires Model#model:drop
+ * @fires Model.model:drop
*/
Node.prototype.drop = function() {
var model = this.model;
@@ -224,7 +190,8 @@ Node.prototype.drop = function() {
if (model !== null) {
/**
* After a node of the model has been removed
- * @event Model#model:drop
+ * @event model:drop
+ * @memberof Model
* @param {Node} node
*/
model.trigger('drop', this);
@@ -234,7 +201,7 @@ Node.prototype.drop = function() {
/**
* Moves itself after another Node
* @param {Node} target
- * @fires Model#model:move
+ * @fires Model.model:move
*/
Node.prototype.moveAfter = function(target) {
if (!this.isRoot()) {
@@ -245,7 +212,7 @@ Node.prototype.moveAfter = function(target) {
/**
* Moves itself at the beginning of parent or another Group
* @param {Group} [target]
- * @fires Model#model:move
+ * @fires Model.model:move
*/
Node.prototype.moveAtBegin = function(target) {
if (!this.isRoot()) {
@@ -260,7 +227,7 @@ Node.prototype.moveAtBegin = function(target) {
/**
* Moves itself at the end of parent or another Group
* @param {Group} [target]
- * @fires Model#model:move
+ * @fires Model.model:move
*/
Node.prototype.moveAtEnd = function(target) {
if (!this.isRoot()) {
@@ -276,7 +243,7 @@ Node.prototype.moveAtEnd = function(target) {
* Moves itself at specific position of Group
* @param {Group} target
* @param {int} index
- * @fires Model#model:move
+ * @fires Model.model:move
*/
Node.prototype.move = function(target, index) {
if (!this.isRoot()) {
@@ -291,7 +258,8 @@ Node.prototype.move = function(target, index) {
if (this.model !== null) {
/**
* After a node of the model has been moved
- * @event Model#model:move
+ * @event model:move
+ * @memberof Model
* @param {Node} node
* @param {Node} target
* @param {int} index
@@ -334,7 +302,7 @@ var Group = function(parent, $el) {
Group.prototype = Object.create(Node.prototype);
Group.prototype.constructor = Group;
-Model.defineModelProperties(Group, ['condition']);
+Utils.defineModelProperties(Group, ['condition']);
/**
* Removes group's content
@@ -369,7 +337,7 @@ Group.prototype.length = function() {
* @param {int} [index=end]
* @param {boolean} [trigger=false] - fire 'add' event
* @returns {Node} the inserted node
- * @fires Model#model:add
+ * @fires Model.model:add
*/
Group.prototype.insertNode = function(node, index, trigger) {
if (index === undefined) {
@@ -382,7 +350,8 @@ Group.prototype.insertNode = function(node, index, trigger) {
if (trigger && this.model !== null) {
/**
* After a node of the model has been added
- * @event Model#model:add
+ * @event model:add
+ * @memberof Model
* @param {Node} parent
* @param {Node} node
* @param {int} index
@@ -398,7 +367,7 @@ Group.prototype.insertNode = function(node, index, trigger) {
* @param {jQuery} $el
* @param {int} [index=end]
* @returns {Group}
- * @fires Model#model:add
+ * @fires Model.model:add
*/
Group.prototype.addGroup = function($el, index) {
return this.insertNode(new Group(this, $el), index, true);
@@ -409,7 +378,7 @@ Group.prototype.addGroup = function($el, index) {
* @param {jQuery} $el
* @param {int} [index=end]
* @returns {Rule}
- * @fires Model#model:add
+ * @fires Model.model:add
*/
Group.prototype.addRule = function($el, index) {
return this.insertNode(new Rule(this, $el), index, true);
@@ -445,7 +414,7 @@ Group.prototype.getNodePos = function(node) {
/**
* Iterate over all Nodes
* @param {boolean} [reverse=false] - iterate in reverse order, required if you delete nodes
- * @param {Model#GroupIteratee} cbRule - callback for Rules (can be null but not omitted)
+ * @param {Model#GroupIteratee} cbRule - callback for Rules (can be `null` but not omitted)
* @param {Model#GroupIteratee} [cbGroup] - callback for Groups
* @param {object} [context] - context for callbacks
* @returns {boolean} if the iteration has been stopped by a callback
@@ -528,7 +497,7 @@ var Rule = function(parent, $el) {
/**
* @name filter
- * @member {QueryBuilder#Filter}
+ * @member {QueryBuilder.Filter}
* @memberof Rule
* @instance
*/
@@ -536,7 +505,7 @@ var Rule = function(parent, $el) {
/**
* @name operator
- * @member {QueryBuilder#Operator}
+ * @member {QueryBuilder.Operator}
* @memberof Rule
* @instance
*/
@@ -554,7 +523,7 @@ var Rule = function(parent, $el) {
Rule.prototype = Object.create(Node.prototype);
Rule.prototype.constructor = Rule;
-Model.defineModelProperties(Rule, ['filter', 'operator', 'value']);
+Utils.defineModelProperties(Rule, ['filter', 'operator', 'value']);
/**
* Checks if this Node is the root
diff --git a/src/plugins.js b/src/plugins.js
new file mode 100644
index 00000000..036f9cc4
--- /dev/null
+++ b/src/plugins.js
@@ -0,0 +1,114 @@
+/**
+ * @module plugins
+ */
+
+/**
+ * Definition of available plugins
+ * @type {object.}
+ */
+QueryBuilder.plugins = {};
+
+/**
+ * Gets or extends the default configuration
+ * @param {object} [options] - new configuration
+ * @returns {undefined|object} nothing or configuration object (copy)
+ */
+QueryBuilder.defaults = function(options) {
+ if (typeof options == 'object') {
+ $.extendext(true, 'replace', QueryBuilder.DEFAULTS, options);
+ }
+ else if (typeof options == 'string') {
+ if (typeof QueryBuilder.DEFAULTS[options] == 'object') {
+ return $.extend(true, {}, QueryBuilder.DEFAULTS[options]);
+ }
+ else {
+ return QueryBuilder.DEFAULTS[options];
+ }
+ }
+ else {
+ return $.extend(true, {}, QueryBuilder.DEFAULTS);
+ }
+};
+
+/**
+ * Registers a new plugin
+ * @param {string} name
+ * @param {function} fct - init function
+ * @param {object} [def] - default options
+ */
+QueryBuilder.define = function(name, fct, def) {
+ QueryBuilder.plugins[name] = {
+ fct: fct,
+ def: def || {}
+ };
+};
+
+/**
+ * Adds new methods to QueryBuilder prototype
+ * @param {object.} methods
+ */
+QueryBuilder.extend = function(methods) {
+ $.extend(QueryBuilder.prototype, methods);
+};
+
+/**
+ * Initializes plugins for an instance
+ * @throws ConfigError
+ * @private
+ */
+QueryBuilder.prototype.initPlugins = function() {
+ if (!this.plugins) {
+ return;
+ }
+
+ if ($.isArray(this.plugins)) {
+ var tmp = {};
+ this.plugins.forEach(function(plugin) {
+ tmp[plugin] = null;
+ });
+ this.plugins = tmp;
+ }
+
+ Object.keys(this.plugins).forEach(function(plugin) {
+ if (plugin in QueryBuilder.plugins) {
+ this.plugins[plugin] = $.extend(true, {},
+ QueryBuilder.plugins[plugin].def,
+ this.plugins[plugin] || {}
+ );
+
+ QueryBuilder.plugins[plugin].fct.call(this, this.plugins[plugin]);
+ }
+ else {
+ Utils.error('Config', 'Unable to find plugin "{0}"', plugin);
+ }
+ }, this);
+};
+
+/**
+ * Returns the config of a plugin, if the plugin is not loaded, returns the default config.
+ * @param {string} name
+ * @param {string} [property]
+ * @throws ConfigError
+ * @returns {*}
+ */
+QueryBuilder.prototype.getPluginOptions = function(name, property) {
+ var plugin;
+ if (this.plugins && this.plugins[name]) {
+ plugin = this.plugins[name];
+ }
+ else if (QueryBuilder.plugins[name]) {
+ plugin = QueryBuilder.plugins[name].def;
+ }
+
+ if (plugin) {
+ if (property) {
+ return plugin[property];
+ }
+ else {
+ return plugin;
+ }
+ }
+ else {
+ Utils.error('Config', 'Unable to find plugin "{0}"', name);
+ }
+};
diff --git a/src/plugins/bt-checkbox/plugin.js b/src/plugins/bt-checkbox/plugin.js
index fd5257af..26b0998f 100644
--- a/src/plugins/bt-checkbox/plugin.js
+++ b/src/plugins/bt-checkbox/plugin.js
@@ -1,13 +1,14 @@
/**
- * Applies Awesome Bootstrap Checkbox for checkbox and radio inputs
- * @class BtCheckboxPlugin
+ * @class BtCheckbox
+ * @memberof module:plugins
+ * @description Applies Awesome Bootstrap Checkbox for checkbox and radio inputs.
* @param {object} [options]
- * @param {string} [options.font=glypicons]
- * @param {string} [options.color=default]
+ * @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-glypicons');
+ if (options.font === 'bootstrap-icons') {
+ this.$el.addClass('bt-checkbox-bootstrap-icons');
}
this.on('getRuleInput.filter', function(h, rule, name) {
@@ -30,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
new file mode 100644
index 00000000..22e21eed
--- /dev/null
+++ b/src/plugins/bt-checkbox/plugin.scss
@@ -0,0 +1,10 @@
+.query-builder.bt-checkbox-bootstrap-icons {
+ .checkbox input[type='checkbox'] + label::before {
+ outline: 0;
+ }
+
+ .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-checkbox/plugins.scss b/src/plugins/bt-checkbox/plugins.scss
deleted file mode 100644
index f2ba2375..00000000
--- a/src/plugins/bt-checkbox/plugins.scss
+++ /dev/null
@@ -1,12 +0,0 @@
-.query-builder.bt-checkbox-glypicons {
- .checkbox input[type=checkbox]:checked + label::after {
- font-family: 'Glyphicons Halflings';
- content: '\e013';
- }
-
- .checkbox label::after {
- padding-left: 4px;
- padding-top: 2px;
- font-size: 9px;
- }
-}
diff --git a/src/plugins/bt-selectpicker/plugin.js b/src/plugins/bt-selectpicker/plugin.js
deleted file mode 100644
index 8909b425..00000000
--- a/src/plugins/bt-selectpicker/plugin.js
+++ /dev/null
@@ -1,40 +0,0 @@
-/**
- * Applies Bootstrap Select on filters and operators combo-boxes.
- * @class BtSelectpickerPlugin
- * @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');
- });
-}, {
- 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 0da378de..52f4830d 100644
--- a/src/plugins/bt-tooltip-errors/plugin.js
+++ b/src/plugins/bt-tooltip-errors/plugin.js
@@ -1,21 +1,23 @@
/**
- * Applies Bootstrap Tooltips on validation error messages.
- * @class BtTooltipErrorsPlugin
+ * @class BtTooltipErrors
+ * @memberof module:plugins
+ * @description Applies Bootstrap Tooltips on validation error messages.
* @param {object} [options]
- * @param {string} [options.placement=right]
+ * @param {string} [options.placement='right']
* @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');
});
@@ -23,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 368ac448..f44934ab 100644
--- a/src/plugins/change-filters/plugin.js
+++ b/src/plugins/change-filters/plugin.js
@@ -1,13 +1,16 @@
/**
- * Allows to change available filters after plugin initialization.
- * @class ChangeFiltersPlugin
+ * @class ChangeFilters
+ * @memberof module:plugins
+ * @description Allows to change available filters after plugin initialization.
*/
-QueryBuilder.extend(/** @lends ChangeFiltersPlugin.prototype */ {
+
+QueryBuilder.extend(/** @lends module:plugins.ChangeFilters.prototype */ {
/**
* Change the filters of the builder
* @param {boolean} [deleteOrphans=false] - delete rules using old filters
- * @param {QueryBuilder#Filter[]} filters
- * @fires ChangeFiltersPlugin#afterSetFilters
+ * @param {QueryBuilder[]} filters
+ * @fires module:plugins.ChangeFilters.changer:setFilters
+ * @fires module:plugins.ChangeFilters.afterSetFilters
* @throws ChangeFilterError
*/
setFilters: function(deleteOrphans, filters) {
@@ -21,10 +24,11 @@ QueryBuilder.extend(/** @lends ChangeFiltersPlugin.prototype */ {
filters = this.checkFilters(filters);
/**
- * Modifies the filters before {@link ChangeFiltersPlugin#setFilters} method
- * @event ChangeFiltersPlugin#filter:setFilters
- * @param {QueryBuilder#Filter[]} filters
- * @returns {QueryBuilder#Filter[]}
+ * Modifies the filters before {@link module:plugins.ChangeFilters.setFilters} method
+ * @event changer:setFilters
+ * @memberof module:plugins.ChangeFilters
+ * @param {QueryBuilder.Filter[]} filters
+ * @returns {QueryBuilder.Filter[]}
*/
filters = this.change('setFilters', filters);
@@ -55,6 +59,8 @@ QueryBuilder.extend(/** @lends ChangeFiltersPlugin.prototype */ {
function(rule) {
if (rule.filter && filtersIds.indexOf(rule.filter.id) === -1) {
rule.drop();
+
+ self.trigger('rulesChanged');
}
else {
self.createRuleFilters(rule);
@@ -88,18 +94,20 @@ QueryBuilder.extend(/** @lends ChangeFiltersPlugin.prototype */ {
}
/**
- * After {@link ChangeFiltersPlugin#setFilters} method
- * @event ChangeFiltersPlugin#afterSetFilters
- * @param {QueryBuilder#Filter[]} filters
+ * After {@link module:plugins.ChangeFilters.setFilters} method
+ * @event afterSetFilters
+ * @memberof module:plugins.ChangeFilters
+ * @param {QueryBuilder.Filter[]} filters
*/
this.trigger('afterSetFilters', filters);
},
/**
* Adds a new filter to the builder
- * @param {QueryBuilder#Filter|QueryBuilder#Filter[]} newFilters
+ * @param {QueryBuilder.Filter|Filter[]} newFilters
* @param {int|string} [position=#end] - index or '#start' or '#end'
- * @fires ChangeFiltersPlugin#afterSetFilters
+ * @fires module:plugins.ChangeFilters.changer:setFilters
+ * @fires module:plugins.ChangeFilters.afterSetFilters
* @throws ChangeFilterError
*/
addFilter: function(newFilters, position) {
@@ -144,7 +152,8 @@ QueryBuilder.extend(/** @lends ChangeFiltersPlugin.prototype */ {
* Removes a filter from the builder
* @param {string|string[]} filterIds
* @param {boolean} [deleteOrphans=false] delete rules using old filters
- * @fires ChangeFiltersPlugin#afterSetFilters
+ * @fires module:plugins.ChangeFilters.changer:setFilters
+ * @fires module:plugins.ChangeFilters.afterSetFilters
* @throws ChangeFilterError
*/
removeFilter: function(filterIds, deleteOrphans) {
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 5cbde11f..3027e761 100644
--- a/src/plugins/filter-description/plugin.js
+++ b/src/plugins/filter-description/plugin.js
@@ -1,15 +1,14 @@
/**
- * Provides three ways to display a description about a filter: inline, Bootsrap Popover or Bootbox.
- * @class FilterDescriptionPlugin
+ * @class FilterDescription
+ * @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.mode=popover] - inline, popover or bootbox
+ * @param {string} [options.icon='bi-info-circle-fill']
+ * @param {string} [options.mode='popover'] - inline, popover or bootbox
* @throws ConfigError
*/
QueryBuilder.define('filter-description', function(options) {
- /**
- * INLINE
- */
+ // INLINE
if (options.mode === 'inline') {
this.on('afterUpdateRuleFilter afterUpdateRuleOperator', function(e, rule) {
var $p = rule.$el.find('p.filter-description');
@@ -20,20 +19,18 @@ 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);
}
});
}
- /**
- * POPOVER
- */
+ // POPOVER
else if (options.mode === 'popover') {
if (!$.fn.popover || !$.fn.popover.Constructor || !$.fn.popover.Constructor.prototype.fixTitle) {
Utils.error('MissingLibrary', 'Bootstrap Popover is required to use "filter-description" plugin. Get it here: http://getbootstrap.com');
@@ -46,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');
@@ -77,9 +72,7 @@ QueryBuilder.define('filter-description', function(options) {
}
});
}
- /**
- * BOOTBOX
- */
+ // BOOTBOX
else if (options.mode === 'bootbox') {
if (!('bootbox' in window)) {
Utils.error('MissingLibrary', 'Bootbox is required to use "filter-description" plugin. Get it here: http://bootboxjs.com');
@@ -94,29 +87,33 @@ 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'
});
-QueryBuilder.extend(/** @lends FilterDescriptionPlugin.prototype */ {
+QueryBuilder.extend(/** @lends module:plugins.FilterDescription.prototype */ {
/**
* Returns the description of a filter for a particular rule (if present)
* @param {object} filter
* @param {Rule} [rule]
* @returns {string}
+ * @private
*/
getFilterDescription: function(filter, rule) {
if (!filter) {
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 188f037f..8b0ab7ef 100644
--- a/src/plugins/invert/plugin.js
+++ b/src/plugins/invert/plugin.js
@@ -1,13 +1,65 @@
/**
- * Allows to invert a rule operator, a group condition or the entire builder.
- * @class InvertPlugin
+ * @class Invert
+ * @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]
* @param {boolean} [options.silent_fail=false]
*/
+QueryBuilder.define('invert', function(options) {
+ var self = this;
+ var Selectors = QueryBuilder.selectors;
+
+ // Bind events
+ this.on('afterInit', function() {
+ self.$el.on('click.queryBuilder', '[data-invert=group]', function() {
+ var $group = $(this).closest(Selectors.group_container);
+ self.invert(self.getModel($group), options);
+ });
+
+ if (options.display_rules_button && options.invert_rules) {
+ self.$el.on('click.queryBuilder', '[data-invert=rule]', function() {
+ var $rule = $(this).closest(Selectors.rule_container);
+ self.invert(self.getModel($rule), options);
+ });
+ }
+ });
+
+ // Modify templates
+ 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: 'bi-shuffle',
+ recursive: true,
+ invert_rules: true,
+ display_rules_button: false,
+ silent_fail: false,
+ disable_template: false
+});
QueryBuilder.defaults({
operatorOpposites: {
@@ -39,57 +91,12 @@ QueryBuilder.defaults({
}
});
-QueryBuilder.define('invert', function(options) {
- var self = this;
- var Selectors = QueryBuilder.selectors;
-
- /**
- * Bind events
- */
- this.on('afterInit', function() {
- self.$el.on('click.queryBuilder', '[data-invert=group]', function() {
- var $group = $(this).closest(Selectors.group_container);
- self.invert(self.getModel($group), options);
- });
-
- if (options.display_rules_button && options.invert_rules) {
- self.$el.on('click.queryBuilder', '[data-invert=rule]', function() {
- var $rule = $(this).closest(Selectors.rule_container);
- self.invert(self.getModel($rule), options);
- });
- }
- });
-
- /**
- * Modify templates
- */
- this.on('getGroupTemplate.filter', function(h, level) {
- var $h = $(h.value);
- $h.find(Selectors.condition_container).after(' ' + self.lang.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.lang.invert + ' ');
- h.value = $h.prop('outerHTML');
- });
- }
-}, {
- icon: 'glyphicon glyphicon-random',
- recursive: true,
- invert_rules: true,
- display_rules_button: false,
- silent_fail: false
-});
-
-QueryBuilder.extend(/** @lends InvertPlugin.prototype */ {
+QueryBuilder.extend(/** @lends module:plugins.Invert.prototype */ {
/**
* Invert a Group, a Rule or the whole builder
* @param {Node} [node]
- * @param {object} [options] {@link InvertPlugin}
- * @fires InvertPlugin#afterInvert
+ * @param {object} [options] {@link module:plugins.Invert}
+ * @fires module:plugins.Invert.afterInvert
* @throws InvertConditionError, InvertOperatorError
*/
invert: function(node, options) {
@@ -144,12 +151,15 @@ QueryBuilder.extend(/** @lends InvertPlugin.prototype */ {
if (options.trigger) {
/**
- * After {@link InvertPlugin#invert} method
- * @event InvertPlugin#afterInvert
+ * After {@link module:plugins.Invert.invert} method
+ * @event afterInvert
+ * @memberof module:plugins.Invert
* @param {Node} node - the main group or rule that has been modified
* @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 619e5b0f..5df30db4 100644
--- a/src/plugins/mongodb-support/plugin.js
+++ b/src/plugins/mongodb-support/plugin.js
@@ -1,6 +1,7 @@
/**
- * Allows to export rules as a MongoDB find object as well as populating the builder from a MongoDB object.
- * @class MongoDbSupportPlugin
+ * @class MongoDbSupport
+ * @memberof module:plugins
+ * @description Allows to export rules as a MongoDB find object as well as populating the builder from a MongoDB object.
*/
QueryBuilder.defaults({
@@ -30,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) {
@@ -91,19 +92,23 @@ QueryBuilder.defaults({
}
});
-QueryBuilder.extend(/** @lends MongoDbSupportPlugin.prototype */ {
+QueryBuilder.extend(/** @lends module:plugins.MongoDbSupport.prototype */ {
/**
* Returns rules as a MongoDB query
* @param {object} [data] - current rules by default
* @returns {object}
- * @fires MongoDbSupportPlugin#filter:getMongoDBField
- * @fires MongoDbSupportPlugin#filter:ruleToMongo
- * @fires MongoDbSupportPlugin#filter:groupToMongo
+ * @fires module:plugins.MongoDbSupport.changer:getMongoDBField
+ * @fires module:plugins.MongoDbSupport.changer:ruleToMongo
+ * @fires module:plugins.MongoDbSupport.changer:groupToMongo
* @throws UndefinedMongoConditionError, UndefinedMongoOperatorError
*/
getMongo: function(data) {
data = (data === undefined) ? this.getRules() : data;
+ if (!data) {
+ return null;
+ }
+
var self = this;
return (function parse(group) {
@@ -127,7 +132,6 @@ QueryBuilder.extend(/** @lends MongoDbSupportPlugin.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);
@@ -137,15 +141,12 @@ QueryBuilder.extend(/** @lends MongoDbSupportPlugin.prototype */ {
if (!(rule.value instanceof Array)) {
rule.value = [rule.value];
}
-
- rule.value.forEach(function(v) {
- values.push(Utils.changeType(v, rule.type, false));
- });
}
/**
* Modifies the MongoDB field used by a rule
- * @event MongoDbSupportPlugin#filter:getMongoDBField
+ * @event changer:getMongoDBField
+ * @memberof module:plugins.MongoDbSupport
* @param {string} field
* @param {Rule} rule
* @returns {string}
@@ -153,18 +154,19 @@ QueryBuilder.extend(/** @lends MongoDbSupportPlugin.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
- * @event MongoDbSupportPlugin#filter:ruleToMongo
+ * @event changer:ruleToMongo
+ * @memberof module:plugins.MongoDbSupport
* @param {object} expression
* @param {Rule} rule
* @param {*} value
* @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));
}
});
@@ -173,7 +175,8 @@ QueryBuilder.extend(/** @lends MongoDbSupportPlugin.prototype */ {
/**
* Modifies the MongoDB expression generated for a group
- * @event MongoDbSupportPlugin#filter:groupToMongo
+ * @event changer:groupToMongo
+ * @memberof module:plugins.MongoDbSupport
* @param {object} expression
* @param {Group} group
* @returns {object}
@@ -186,10 +189,10 @@ QueryBuilder.extend(/** @lends MongoDbSupportPlugin.prototype */ {
* Converts a MongoDB query to rules
* @param {object} query
* @returns {object}
- * @fires MongoDbSupportPlugin#filter:parseMongoNode
- * @fires MongoDbSupportPlugin#filter:getMongoDBFieldID
- * @fires MongoDbSupportPlugin#filter:mongoToRule
- * @fires MongoDbSupportPlugin#filter:mongoToGroup
+ * @fires module:plugins.MongoDbSupport.changer:parseMongoNode
+ * @fires module:plugins.MongoDbSupport.changer:getMongoDBFieldID
+ * @fires module:plugins.MongoDbSupport.changer:mongoToRule
+ * @fires module:plugins.MongoDbSupport.changer:mongoToGroup
* @throws MongoParseError, UndefinedMongoConditionError, UndefinedMongoOperatorError
*/
getRulesFromMongo: function(query) {
@@ -201,7 +204,8 @@ QueryBuilder.extend(/** @lends MongoDbSupportPlugin.prototype */ {
/**
* Custom parsing of a MongoDB expression, you can return a sub-part of the expression, or a well formed group or rule JSON
- * @event MongoDbSupportPlugin#filter:parseMongoNode
+ * @event changer:parseMongoNode
+ * @memberof module:plugins.MongoDbSupport
* @param {object} expression
* @returns {object} expression, rule or group
*/
@@ -220,7 +224,7 @@ QueryBuilder.extend(/** @lends MongoDbSupportPlugin.prototype */ {
};
}
- var key = andOr(query);
+ var key = self.getMongoCondition(query);
if (!key) {
Utils.error('MongoParse', 'Invalid MongoDB query format');
}
@@ -245,7 +249,7 @@ QueryBuilder.extend(/** @lends MongoDbSupportPlugin.prototype */ {
return;
}
- var key = andOr(data);
+ var key = self.getMongoCondition(data);
if (key) {
parts.push(parse(data, key));
}
@@ -253,7 +257,7 @@ QueryBuilder.extend(/** @lends MongoDbSupportPlugin.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');
}
@@ -265,18 +269,12 @@ QueryBuilder.extend(/** @lends MongoDbSupportPlugin.prototype */ {
var opVal = mdbrl.call(self, value);
- /**
- * Returns a filter identifier from the MongoDB field
- * @event MongoDbSupportPlugin#filter:getMongoDBFieldID
- * @param {string} field
- * @param {*} value
- * @returns {string}
- */
- var id = self.change('getMongoDBFieldID', field, value);
+ var id = self.getMongoDBFieldID(field, value);
/**
* Modifies the rule generated from the MongoDB expression
- * @event MongoDbSupportPlugin#filter:mongoToRule
+ * @event changer:mongoToRule
+ * @memberof module:plugins.MongoDbSupport
* @param {object} rule
* @param {object} expression
* @returns {object}
@@ -294,7 +292,8 @@ QueryBuilder.extend(/** @lends MongoDbSupportPlugin.prototype */ {
/**
* Modifies the group generated from the MongoDB expression
- * @event MongoDbSupportPlugin#filter:mongoToGroup
+ * @event changer:mongoToGroup
+ * @memberof module:plugins.MongoDbSupport
* @param {object} group
* @param {object} expression
* @returns {object}
@@ -308,62 +307,87 @@ QueryBuilder.extend(/** @lends MongoDbSupportPlugin.prototype */ {
/**
* Sets rules a from MongoDB query
- * @see MongoDbSupportPlugin#getRulesFromMongo
+ * @see module:plugins.MongoDbSupport.getRulesFromMongo
*/
setRulesFromMongo: function(query) {
this.setRules(this.getRulesFromMongo(query));
- }
-});
-
-/**
- * Finds which operator is used in a MongoDB sub-object
- * @param {*} value
- * @returns {string|undefined}
- * @memberof MongoDbSupportPlugin
- * @private
- */
-function determineMongoOperator(value) {
- if (value !== null && typeof value == 'object') {
- var subkeys = Object.keys(value);
+ },
- if (subkeys.length === 1) {
- return subkeys[0];
+ /**
+ * Returns a filter identifier from the MongoDB field.
+ * Automatically use the only one filter with a matching field, fires a changer otherwise.
+ * @param {string} field
+ * @param {*} value
+ * @fires module:plugins.MongoDbSupport:changer:getMongoDBFieldID
+ * @returns {string}
+ * @private
+ */
+ getMongoDBFieldID: function(field, value) {
+ var matchingFilters = this.filters.filter(function(filter) {
+ return filter.field === field;
+ });
+
+ var id;
+ if (matchingFilters.length === 1) {
+ id = matchingFilters[0].id;
}
else {
- if (value.$gte !== undefined && value.$lte !== undefined) {
+ /**
+ * Returns a filter identifier from the MongoDB field
+ * @event changer:getMongoDBFieldID
+ * @memberof module:plugins.MongoDbSupport
+ * @param {string} field
+ * @param {*} value
+ * @returns {string}
+ */
+ id = this.change('getMongoDBFieldID', field, value);
+ }
+
+ return id;
+ },
+
+ /**
+ * 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"
- * @param {object} data
- * @returns {string}
- * @memberof MongoDbSupportPlugin
- * @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 6f2be7e1..be98bb00 100644
--- a/src/plugins/not-group/plugin.js
+++ b/src/plugins/not-group/plugin.js
@@ -1,28 +1,15 @@
/**
- * Adds a "Not" checkbox in front of group conditions.
- * @class NotGroupPlugin
+ * @class NotGroup
+ * @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']
*/
-
-/**
- * From {@link NotGroupPlugin}
- * @name not
- * @member {boolean}
- * @memberof Group
- * @instance
- */
-Model.defineModelProperties(Group, ['not']);
-
-QueryBuilder.selectors.group_not = QueryBuilder.selectors.group_header + ' [data-not=group]';
-
QueryBuilder.define('not-group', function(options) {
var self = this;
- /**
- * Bind events
- */
+ // Bind events
this.on('afterInit', function() {
self.$el.on('click.queryBuilder', '[data-not=group]', function() {
var $group = $(this).closest(QueryBuilder.selectors.group_container);
@@ -37,69 +24,72 @@ QueryBuilder.define('not-group', function(options) {
});
});
- /**
- * Init "not" property
- */
+ // Init "not" property
this.on('afterAddGroup', function(e, group) {
group.__.not = false;
});
- /**
- * Modify templates
- */
- this.on('getGroupTemplate.filter', function(h, level) {
- var $h = $(h.value);
- $h.find(QueryBuilder.selectors.condition_container).prepend(
- '' +
- ' ' + self.lang.NOT +
- ' '
- );
- h.value = $h.prop('outerHTML');
- });
+ // Modify templates
+ 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
- */
+ // Export "not" to JSON
this.on('groupToJson.filter', function(e, group) {
e.value.not = group.not;
});
- /**
- * Read "not" from JSON
- */
+ // Read "not" from JSON
this.on('jsonToGroup.filter', function(e, json) {
e.value.not = !!json.not;
});
- /**
- * Export "not" to SQL
- */
+ // Export "not" to SQL
this.on('groupToSQL.filter', function(e, group) {
if (group.not) {
e.value = 'NOT ( ' + e.value + ' )';
}
});
- /**
- * Parse "NOT" function from sqlparser
- */
+ // Parse "NOT" function from sqlparser
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;
}
});
- /**
- * Read "not" from parsed SQL
- */
+ // 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;
});
- /**
- * Export "not" to Mongo
- */
+ // Export "not" to Mongo
this.on('groupToMongo.filter', function(e, group) {
var key = '$' + group.condition.toLowerCase();
if (group.not && e.value[key]) {
@@ -107,9 +97,7 @@ QueryBuilder.define('not-group', function(options) {
}
});
- /**
- * Parse "$nor" operator from Mongo
- */
+ // Parse "$nor" operator from Mongo
this.on('parseMongoNode.filter', function(e) {
var keys = Object.keys(e.value);
@@ -119,22 +107,32 @@ QueryBuilder.define('not-group', function(options) {
}
});
- /**
- * Read "not" from parsed Mongo
- */
+ // Read "not" from parsed Mongo
this.on('mongoToGroup.filter', function(e, data) {
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
});
-QueryBuilder.extend(/** @lends NotGroupPlugin.prototype */ {
+/**
+ * From {@link module:plugins.NotGroup}
+ * @name not
+ * @member {boolean}
+ * @memberof Group
+ * @instance
+ */
+Utils.defineModelProperties(Group, ['not']);
+
+QueryBuilder.selectors.group_not = QueryBuilder.selectors.group_header + ' [data-not=group]';
+
+QueryBuilder.extend(/** @lends module:plugins.NotGroup.prototype */ {
/**
* Performs actions when a group's not changes
* @param {Group} group
- * @fires NotGroupPlugin#afterUpdateGroupNot
+ * @fires module:plugins.NotGroup.afterUpdateGroupNot
* @private
*/
updateGroupNot: function(group) {
@@ -145,9 +143,12 @@ QueryBuilder.extend(/** @lends NotGroupPlugin.prototype */ {
/**
* After the group's not flag has been modified
- * @event NotGroupPlugin#afterUpdateGroupNot
+ * @event afterUpdateGroupNot
+ * @memberof module:plugins.NotGroup
* @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 8514ffd0..84b29e1f 100644
--- a/src/plugins/sortable/plugin.js
+++ b/src/plugins/sortable/plugin.js
@@ -1,27 +1,13 @@
/**
- * Enables drag & drop sort of rules.
- * @class SortablePlugin
+ * @class Sortable
+ * @memberof module:plugins
+ * @description Enables drag & drop sort of rules.
* @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.selectors.rule_and_group_containers = QueryBuilder.selectors.rule_container + ', ' + QueryBuilder.selectors.group_container;
-QueryBuilder.selectors.drag_handle = '.drag-handle';
-
-QueryBuilder.defaults({
- default_rule_flags: {
- no_sortable: false,
- no_drop: false
- },
- default_group_flags: {
- no_sortable: false,
- no_drop: false
- }
-});
-
QueryBuilder.define('sortable', function(options) {
if (!('interact' in window)) {
Utils.error('MissingLibrary', 'interact.js is required to use "sortable" plugin. Get it here: http://interactjs.io');
@@ -41,10 +27,9 @@ QueryBuilder.define('sortable', function(options) {
var placeholder;
var ghost;
var src;
+ var moved;
- /**
- * Init drag and drop
- */
+ // Init drag and drop
this.on('afterAddRule afterAddGroup', function(e, node) {
if (node == placeholder) {
return;
@@ -52,9 +37,7 @@ QueryBuilder.define('sortable', function(options) {
var self = e.builder;
- /**
- * Inherit flags
- */
+ // Inherit flags
if (options.inherit_no_sortable && node.parent && node.parent.flags.no_sortable) {
node.flags.no_sortable = true;
}
@@ -62,14 +45,14 @@ QueryBuilder.define('sortable', function(options) {
node.flags.no_drop = true;
}
- /**
- * Configure drag
- */
+ // 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);
@@ -80,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());
@@ -93,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;
@@ -103,54 +92,55 @@ QueryBuilder.define('sortable', function(options) {
placeholder = undefined;
// show element
- src.$el.show();
+ src.$el.css('display', '');
/**
- * After a node has been moved with {@link SortablePlugin}
- * @event SortablePlugin#afterMove
+ * After a node has been moved with {@link module:plugins.Sortable}
+ * @event afterMove
+ * @memberof module:plugins.Sortable
* @param {Node} node
*/
self.trigger('afterMove', src);
+
+ self.trigger('rulesChanged');
}
});
}
if (!node.flags.no_drop) {
- /**
- * Configure drop on groups and rules
- */
+ // Configure drop on groups and rules
interact(node.$el[0])
.dropzone({
accept: QueryBuilder.selectors.rule_and_group_containers,
ondragenter: function(event) {
- moveSortableToTarget(placeholder, $(event.target));
+ moveSortableToTarget(placeholder, $(event.target), self);
},
ondrop: function(event) {
- moveSortableToTarget(src, $(event.target), self);
+ if (!moved) {
+ moveSortableToTarget(src, $(event.target), self);
+ }
}
});
- /**
- * Configure drop on group headers
- */
+ // Configure drop on group headers
if (node instanceof Group) {
interact(node.$el.find(QueryBuilder.selectors.group_header)[0])
.dropzone({
accept: QueryBuilder.selectors.rule_and_group_containers,
ondragenter: function(event) {
- moveSortableToTarget(placeholder, $(event.target));
+ moveSortableToTarget(placeholder, $(event.target), self);
},
ondrop: function(event) {
- moveSortableToTarget(src, $(event.target), self);
+ if (!moved) {
+ moveSortableToTarget(src, $(event.target), self);
+ }
}
});
}
}
});
- /**
- * Detach interactables
- */
+ // Detach interactables
this.on('beforeDeleteRule beforeDeleteGroup', function(e, node) {
if (!e.isDefaultPrevented()) {
interact(node.$el[0]).unset();
@@ -161,43 +151,56 @@ QueryBuilder.define('sortable', function(options) {
}
});
- /**
- * Remove drag handle from non-sortable items
- */
+ // Remove drag handle from non-sortable items
this.on('afterApplyRuleFlags afterApplyGroupFlags', function(e, node) {
if (node.flags.no_sortable) {
node.$el.find('.drag-handle').remove();
}
});
- /**
- * 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');
- }
- });
+ // Modify templates
+ 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;
+QueryBuilder.selectors.drag_handle = '.drag-handle';
+
+QueryBuilder.defaults({
+ default_rule_flags: {
+ no_sortable: false,
+ no_drop: false
+ },
+ default_group_flags: {
+ no_sortable: false,
+ no_drop: false
+ }
});
/**
* Moves an element (placeholder or actual object) depending on active target
+ * @memberof module:plugins.Sortable
* @param {Node} node
* @param {jQuery} target
* @param {QueryBuilder} [builder]
- * @memberof SortablePlugin
* @private
*/
function moveSortableToTarget(node, target, builder) {
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 c2956494..4bfb996a 100644
--- a/src/plugins/sql-support/plugin.js
+++ b/src/plugins/sql-support/plugin.js
@@ -1,10 +1,18 @@
/**
- * Allows to export rules as a SQL WHERE statement as well as populating the builder from an SQL query.
- * @class SqlSupportPlugin
+ * @class SqlSupport
+ * @memberof module:plugins
+ * @description Allows to export rules as a SQL WHERE statement as well as populating the builder from an SQL query.
+ * @param {object} [options]
+ * @param {boolean} [options.boolean_as_integer=true] - `true` to convert boolean values to integer in the SQL output
*/
+QueryBuilder.define('sql-support', function(options) {
+
+}, {
+ boolean_as_integer: true
+});
QueryBuilder.defaults({
- /* operators for internal -> SQL conversion */
+ // operators for internal -> SQL conversion
sqlOperators: {
equal: { op: '= ?' },
not_equal: { op: '!= ?' },
@@ -16,19 +24,19 @@ 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' },
is_not_null: { op: 'IS NOT NULL' }
},
- /* operators for SQL -> internal conversion */
+ // operators for SQL -> internal conversion
sqlRuleOperator: {
'=': function(v) {
return {
@@ -126,7 +134,7 @@ QueryBuilder.defaults({
}
},
- /* statements for internal -> SQL conversion */
+ // statements for internal -> SQL conversion
sqlStatements: {
'question_mark': function() {
var params = [];
@@ -175,7 +183,7 @@ QueryBuilder.defaults({
}
},
- /* statements for SQL -> internal conversion */
+ // statements for SQL -> internal conversion
sqlRuleStatement: {
'question_mark': function(values) {
var index = 0;
@@ -206,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;
@@ -220,28 +228,37 @@ QueryBuilder.defaults({
});
/**
- * @typedef {object} SqlSupportPlugin#SqlQuery
+ * @typedef {object} SqlQuery
+ * @memberof module:plugins.SqlSupport
* @property {string} sql
* @property {object} params
*/
-QueryBuilder.extend(/** @lends SqlSupportPlugin.prototype */ {
+QueryBuilder.extend(/** @lends module:plugins.SqlSupport.prototype */ {
/**
* Returns rules as a SQL query
* @param {boolean|string} [stmt] - use prepared statements: false, 'question_mark', 'numbered', 'numbered(@)', 'named', 'named(@)'
* @param {boolean} [nl=false] output with new lines
* @param {object} [data] - current rules by default
- * @returns {SqlSupportPlugin#SqlQuery}
- * @fires SqlSupportPlugin#filter:getSQLField
- * @fires SqlSupportPlugin#filter:ruleToSQL
- * @fires SqlSupportPlugin#filter:groupToSQL
+ * @returns {module:plugins.SqlSupport.SqlQuery}
+ * @fires module:plugins.SqlSupport.changer:getSQLField
+ * @fires module:plugins.SqlSupport.changer:ruleToSQL
+ * @fires module:plugins.SqlSupport.changer:groupToSQL
* @throws UndefinedSQLConditionError, UndefinedSQLOperatorError
*/
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]);
@@ -286,11 +303,11 @@ QueryBuilder.extend(/** @lends SqlSupportPlugin.prototype */ {
value += sql.sep;
}
- if (rule.type == 'integer' || rule.type == 'double' || rule.type == 'boolean') {
- v = Utils.changeType(v, rule.type, true);
+ 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) {
@@ -311,12 +328,15 @@ QueryBuilder.extend(/** @lends SqlSupportPlugin.prototype */ {
}
var sqlFn = function(v) {
- return sql.op.replace(/\?/, v);
+ return sql.op.replace('?', function() {
+ return v;
+ });
};
/**
* Modifies the SQL field used by a rule
- * @event SqlSupportPlugin#filter:getSQLField
+ * @event changer:getSQLField
+ * @memberof module:plugins.SqlSupport
* @param {string} field
* @param {Rule} rule
* @returns {string}
@@ -327,7 +347,8 @@ QueryBuilder.extend(/** @lends SqlSupportPlugin.prototype */ {
/**
* Modifies the SQL generated for a rule
- * @event SqlSupportPlugin#filter:ruleToSQL
+ * @event changer:ruleToSQL
+ * @memberof module:plugins.SqlSupport
* @param {string} expression
* @param {Rule} rule
* @param {*} value
@@ -342,7 +363,8 @@ QueryBuilder.extend(/** @lends SqlSupportPlugin.prototype */ {
/**
* Modifies the SQL generated for a group
- * @event SqlSupportPlugin#filter:groupToSQL
+ * @event changer:groupToSQL
+ * @memberof module:plugins.SqlSupport
* @param {string} expression
* @param {Group} group
* @returns {string}
@@ -365,13 +387,13 @@ QueryBuilder.extend(/** @lends SqlSupportPlugin.prototype */ {
/**
* Convert a SQL query to rules
- * @param {string|SqlSupportPlugin#SqlQuery} query
+ * @param {string|module:plugins.SqlSupport.SqlQuery} query
* @param {boolean|string} stmt
* @returns {object}
- * @fires SqlSupportPlugin#filter:parseSQLNode
- * @fires SqlSupportPlugin#filter:getSQLFieldID
- * @fires SqlSupportPlugin#filter:sqlToRule
- * @fires SqlSupportPlugin#filter:sqlToGroup
+ * @fires module:plugins.SqlSupport.changer:parseSQLNode
+ * @fires module:plugins.SqlSupport.changer:getSQLFieldID
+ * @fires module:plugins.SqlSupport.changer:sqlToRule
+ * @fires module:plugins.SqlSupport.changer:sqlToGroup
* @throws MissingLibraryError, SQLParseError, UndefinedSQLOperatorError
*/
getRulesFromSQL: function(query, stmt) {
@@ -407,22 +429,23 @@ QueryBuilder.extend(/** @lends SqlSupportPlugin.prototype */ {
/**
* Custom parsing of an AST node generated by SQLParser, you can return a sub-part of the tree, or a well formed group or rule JSON
- * @event SqlSupportPlugin#filter:parseSQLNode
+ * @event changer:parseSQLNode
+ * @memberof module:plugins.SqlSupport
* @param {object} AST node
* @returns {object} tree, rule or group
*/
- query = self.change('parseSQLNode', parsed.where.conditions);
+ var data = self.change('parseSQLNode', parsed.where.conditions);
// a plugin returned a group
- if ('rules' in query && 'condition' in query) {
- return query;
+ if ('rules' in data && 'condition' in data) {
+ return data;
}
// a plugin returned a rule
- if ('id' in query && 'operator' in query && 'value' in query) {
+ if ('id' in data && 'operator' in data && 'value' in data) {
return {
condition: this.settings.default_condition,
- rules: [query]
+ rules: [data]
};
}
@@ -430,12 +453,16 @@ QueryBuilder.extend(/** @lends SqlSupportPlugin.prototype */ {
var out = self.change('sqlToGroup', {
condition: this.settings.default_condition,
rules: []
- }, query);
+ }, data);
// keep track of current group
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);
@@ -459,10 +486,24 @@ QueryBuilder.extend(/** @lends SqlSupportPlugin.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 SqlSupportPlugin#filter:sqlToGroup
+ * @event changer:sqlToGroup
+ * @memberof module:plugins.SqlSupport
* @param {object} group
* @param {object} AST
* @returns {object}
@@ -525,20 +566,38 @@ QueryBuilder.extend(/** @lends SqlSupportPlugin.prototype */ {
}
var opVal = sqlrl.call(this, value, data.operation);
- var field = data.left.values.join('.');
- /**
- * Returns a filter identifier from the SQL field
- * @event SqlSupportPlugin#filter:getSQLFieldID
- * @param {string} field
- * @param {*} value
- * @returns {string}
- */
- var id = self.change('getSQLFieldID', field, value);
+ // find field name
+ var field;
+ if ('values' in data.left) {
+ field = data.left.values.join('.');
+ }
+ else if ('value' in data.left) {
+ field = data.left.value;
+ }
+ else {
+ 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);
/**
* Modifies the rule generated from the SQL expression
- * @event SqlSupportPlugin#filter:sqlToRule
+ * @event changer:sqlToRule
+ * @memberof module:plugins.SqlSupport
* @param {object} rule
* @param {object} AST
* @returns {object}
@@ -547,30 +606,63 @@ QueryBuilder.extend(/** @lends SqlSupportPlugin.prototype */ {
id: id,
field: field,
operator: opVal.op,
- value: opVal.val
+ value: finalValue
}, data);
curr.rules.push(rule);
}
- }(query, 0));
+ }(data, 0));
return out;
},
/**
- * Sets the rules from a SQL query
- * @see SqlSupportPlugin#getRulesFromSQL
+ * Sets the builder's rules from a SQL query
+ * @see module:plugins.SqlSupport.getRulesFromSQL
*/
setRulesFromSQL: function(query, stmt) {
this.setRules(this.getRulesFromSQL(query, stmt));
+ },
+
+ /**
+ * Returns a filter identifier from the SQL field.
+ * Automatically use the only one filter with a matching field, fires a changer otherwise.
+ * @param {string} field
+ * @param {*} value
+ * @fires module:plugins.SqlSupport:changer:getSQLFieldID
+ * @returns {string}
+ * @private
+ */
+ getSQLFieldID: function(field, value) {
+ var matchingFilters = this.filters.filter(function(filter) {
+ return filter.field.toLowerCase() === field.toLowerCase();
+ });
+
+ var id;
+ if (matchingFilters.length === 1) {
+ id = matchingFilters[0].id;
+ }
+ else {
+ /**
+ * Returns a filter identifier from the SQL field
+ * @event changer:getSQLFieldID
+ * @memberof module:plugins.SqlSupport
+ * @param {string} field
+ * @param {*} value
+ * @returns {string}
+ */
+ id = this.change('getSQLFieldID', field, value);
+ }
+
+ return id;
}
});
/**
* Parses the statement configuration
+ * @memberof module:plugins.SqlSupport
* @param {string} stmt
* @returns {Array} null, mode, option
- * @memberof SqlSupportPlugin
* @private
*/
function getStmtConfig(stmt) {
diff --git a/src/plugins/unique-filter/plugin.js b/src/plugins/unique-filter/plugin.js
index 7a2d6c10..5b6fb802 100644
--- a/src/plugins/unique-filter/plugin.js
+++ b/src/plugins/unique-filter/plugin.js
@@ -1,6 +1,7 @@
/**
- * Allows to define some filters as "unique": ie which can be used for only one rule, globally or in the same group.
- * @class UniqueFilterPlugin
+ * @class UniqueFilter
+ * @memberof module:plugins
+ * @description Allows to define some filters as "unique": ie which can be used for only one rule, globally or in the same group.
*/
QueryBuilder.define('unique-filter', function() {
this.status.used_filters = {};
@@ -11,10 +12,7 @@ QueryBuilder.define('unique-filter', function() {
this.on('afterReset', this.clearDisabledFilters);
this.on('afterClear', this.clearDisabledFilters);
- /**
- * Ensure that the default filter is not already used if unique
- * @throws UniqueFilterError
- */
+ // Ensure that the default filter is not already used if unique
this.on('getDefaultFilter.filter', function(e, model) {
var self = e.builder;
@@ -36,7 +34,7 @@ QueryBuilder.define('unique-filter', function() {
});
});
-QueryBuilder.extend(/** @lends UniqueFilterPlugin.prototype*/ {
+QueryBuilder.extend(/** @lends module:plugins.UniqueFilter.prototype */ {
/**
* Updates the list of used filters
* @param {$.Event} [e]
diff --git a/src/public.js b/src/public.js
index dcaf7fd1..8e8117af 100644
--- a/src/public.js
+++ b/src/public.js
@@ -1,11 +1,12 @@
/**
* Destroys the builder
- * @fires QueryBuilder#beforeDestroy
+ * @fires QueryBuilder.beforeDestroy
*/
QueryBuilder.prototype.destroy = function() {
/**
* Before the {@link QueryBuilder#destroy} method
- * @event QueryBuilder#beforeDestroy
+ * @event beforeDestroy
+ * @memberof QueryBuilder
*/
this.trigger('beforeDestroy');
@@ -26,13 +27,14 @@ QueryBuilder.prototype.destroy = function() {
/**
* Clear all rules and resets the root group
- * @fires QueryBuilder#beforeReset
- * @fires QueryBuilder#afterReset
+ * @fires QueryBuilder.beforeReset
+ * @fires QueryBuilder.afterReset
*/
QueryBuilder.prototype.reset = function() {
/**
* Before the {@link QueryBuilder#reset} method, can be prevented
- * @event QueryBuilder#beforeReset
+ * @event beforeReset
+ * @memberof QueryBuilder
*/
var e = this.trigger('beforeReset');
if (e.isDefaultPrevented()) {
@@ -44,24 +46,32 @@ 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);
/**
* After the {@link QueryBuilder#reset} method
- * @event QueryBuilder#afterReset
+ * @event afterReset
+ * @memberof QueryBuilder
*/
this.trigger('afterReset');
+
+ this.trigger('rulesChanged');
};
/**
* Clears all rules and removes the root group
- * @fires QueryBuilder#beforeClear
- * @fires QueryBuilder#afterClear
+ * @fires QueryBuilder.beforeClear
+ * @fires QueryBuilder.afterClear
*/
QueryBuilder.prototype.clear = function() {
/**
* Before the {@link QueryBuilder#clear} method, can be prevented
- * @event QueryBuilder#beforeClear
+ * @event beforeClear
+ * @memberof QueryBuilder
*/
var e = this.trigger('beforeClear');
if (e.isDefaultPrevented()) {
@@ -78,9 +88,12 @@ QueryBuilder.prototype.clear = function() {
/**
* After the {@link QueryBuilder#clear} method
- * @event QueryBuilder#afterClear
+ * @event afterClear
+ * @memberof QueryBuilder
*/
this.trigger('afterClear');
+
+ this.trigger('rulesChanged');
};
/**
@@ -118,7 +131,7 @@ QueryBuilder.prototype.getModel = function(target) {
* @param {object} [options]
* @param {boolean} [options.skip_empty=false] - skips validating rules that have no filter selected
* @returns {boolean}
- * @fires QueryBuilder#filter:validate
+ * @fires QueryBuilder.changer:validate
*/
QueryBuilder.prototype.validate = function(options) {
options = $.extend({
@@ -189,7 +202,8 @@ QueryBuilder.prototype.validate = function(options) {
/**
* Modifies the result of the {@link QueryBuilder#validate} method
- * @event QueryBuilder#filter:validate
+ * @event changer:validate
+ * @memberof QueryBuilder
* @param {boolean} valid
* @returns {boolean}
*/
@@ -203,9 +217,9 @@ QueryBuilder.prototype.validate = function(options) {
* @param {boolean} [options.allow_invalid=false] - returns rules even if they are invalid
* @param {boolean} [options.skip_empty=false] - remove rules that have no filter selected
* @returns {object}
- * @fires QueryBuilder#filter:ruleToJson
- * @fires QueryBuilder#filter:groupToJson
- * @fires QueryBuilder#filter:getRules
+ * @fires QueryBuilder.changer:ruleToJson
+ * @fires QueryBuilder.changer:groupToJson
+ * @fires QueryBuilder.changer:getRules
*/
QueryBuilder.prototype.getRules = function(options) {
options = $.extend({
@@ -258,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) {
@@ -270,7 +284,8 @@ QueryBuilder.prototype.getRules = function(options) {
/**
* Modifies the JSON generated from a Rule object
- * @event QueryBuilder#filter:ruleToJson
+ * @event changer:ruleToJson
+ * @memberof QueryBuilder
* @param {object} json
* @param {Rule} rule
* @returns {object}
@@ -286,7 +301,8 @@ QueryBuilder.prototype.getRules = function(options) {
/**
* Modifies the JSON generated from a Group object
- * @event QueryBuilder#filter:groupToJson
+ * @event changer:groupToJson
+ * @memberof QueryBuilder
* @param {object} json
* @param {Group} group
* @returns {object}
@@ -298,7 +314,9 @@ QueryBuilder.prototype.getRules = function(options) {
out.valid = valid;
/**
- * @avant QueryBuilder#filter:getRules
+ * Modifies the result of the {@link QueryBuilder#getRules} method
+ * @event changer:getRules
+ * @memberof QueryBuilder
* @param {object} json
* @returns {object}
*/
@@ -311,10 +329,10 @@ QueryBuilder.prototype.getRules = function(options) {
* @param {object} [options]
* @param {boolean} [options.allow_invalid=false] - silent-fail if the data are invalid
* @throws RulesError, UndefinedConditionError
- * @fires QueryBuilder#filter:setRules
- * @fires QueryBuilder#filter:jsonToRule
- * @fires QueryBuilder#filter:jsonToGroup
- * @fires QueryBuilder#afterSetRules
+ * @fires QueryBuilder.changer:setRules
+ * @fires QueryBuilder.changer:jsonToRule
+ * @fires QueryBuilder.changer:jsonToGroup
+ * @fires QueryBuilder.afterSetRules
*/
QueryBuilder.prototype.setRules = function(data, options) {
options = $.extend({
@@ -334,11 +352,11 @@ 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
- * @event QueryBuilder#filter:setRules
+ * @event changer:setRules
+ * @memberof QueryBuilder
* @param {object} json
* @param {object} options
* @returns {object}
@@ -376,8 +394,6 @@ QueryBuilder.prototype.setRules = function(data, options) {
return;
}
- self.applyGroupFlags(model);
-
add(item, model);
}
}
@@ -399,25 +415,29 @@ 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
- * @event QueryBuilder#filter:jsonToRule
+ * @event changer:jsonToRule
+ * @memberof QueryBuilder
* @param {Rule} rule
* @param {object} json
* @returns {Rule} the same rule
@@ -430,7 +450,8 @@ QueryBuilder.prototype.setRules = function(data, options) {
/**
* Modifies the Group object generated from the JSON
- * @event QueryBuilder#filter:jsonToGroup
+ * @event changer:jsonToGroup
+ * @memberof QueryBuilder
* @param {Group} group
* @param {object} json
* @returns {Group} the same group
@@ -443,7 +464,8 @@ QueryBuilder.prototype.setRules = function(data, options) {
/**
* After the {@link QueryBuilder#setRules} method
- * @event QueryBuilder#afterSetRules
+ * @event afterSetRules
+ * @memberof QueryBuilder
*/
this.trigger('afterSetRules');
};
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 a7bfbdc0..219aa06e 100644
--- a/src/template.js
+++ b/src/template.js
@@ -1,144 +1,174 @@
-QueryBuilder.templates.group = '\
- \
- - \
- \
-
\
- {{= it.lang.add_rule }} \
- \
- {{? it.settings.allow_groups===-1 || it.settings.allow_groups>=it.level }} \
- \
- {{= it.lang.add_group }} \
- \
- {{?}} \
- {{? it.level>1 }} \
- \
- {{= it.lang.delete_group }} \
- \
- {{?}} \
- \
- \
- {{~ it.conditions: condition }} \
- \
- {{~}} \
- \
- {{? it.settings.display_errors }} \
- \
- {{?}} \
- \
- - \
-
\
- \
-
';
+QueryBuilder.templates.group = ({ group_id, level, conditions, icons, settings, translate, builder }) => {
+ return `
+
+
+
+
+ ${translate("add_rule")}
+
+ ${settings.allow_groups === -1 || settings.allow_groups >= level ? `
+
+ ${translate("add_group")}
+
+ ` : ''}
+ ${level > 1 ? `
+
+ ${translate("delete_group")}
+
+ ` : ''}
+
+
+ ${conditions.map(condition => `
+
+ `).join('\n')}
+
+ ${settings.display_errors ? `
+
+ ` : ''}
+
+
+
+
+`;
+};
+
+QueryBuilder.templates.rule = ({ rule_id, icons, settings, translate, builder }) => {
+ return `
+
+
+
+
+ ${translate("delete_rule")}
+
+
+
+ ${settings.display_errors ? `
+
+ ` : ''}
+
+
+
+`;
+};
-QueryBuilder.templates.rule = '\
- \
- \
- \
- \
- {{= it.lang.delete_rule }} \
- \
- \
- \
- {{? it.settings.display_errors }} \
- \
- {{?}} \
- \
- \
- \
- ';
+QueryBuilder.templates.filterSelect = ({ rule, filters, icons, settings, translate, builder }) => {
+ let optgroup = null;
+ return `
+
+ ${settings.display_empty_filter ? `
+
+ ` : ''}
+ ${filters.map(filter => `
+ ${optgroup !== filter.optgroup ? `
+ ${optgroup !== null ? `` : ''}
+ ${(optgroup = filter.optgroup) !== null ? `
+ ' : ''}
+ `;
+};
-QueryBuilder.templates.filterSelect = '\
-{{ var optgroup = null; }} \
- \
- {{? it.settings.display_empty_filter }} \
- \
- {{?}} \
- {{~ it.filters: filter }} \
- {{? optgroup !== filter.optgroup }} \
- {{? optgroup !== null }}{{?}} \
- {{? (optgroup = filter.optgroup) !== null }} \
- {{?}} \
- ';
+QueryBuilder.templates.operatorSelect = ({ rule, operators, icons, settings, translate, builder }) => {
+ let optgroup = null;
+ return `
+${operators.length === 1 ? `
+
+${translate("operators", operators[0].type)}
+
+` : ''}
+
+ ${operators.map(operator => `
+ ${optgroup !== operator.optgroup ? `
+ ${optgroup !== null ? `` : ''}
+ ${(optgroup = operator.optgroup) !== null ? `
+ ' : ''}
+ `;
+};
-QueryBuilder.templates.operatorSelect = '\
-{{? it.operators.length === 1 }} \
- \
-{{= it.lang.operators[it.operators[0].type] || it.operators[0].type }} \
- \
-{{?}} \
-{{ var optgroup = null; }} \
- \
- {{~ it.operators: operator }} \
- {{? optgroup !== operator.optgroup }} \
- {{? optgroup !== null }}{{?}} \
- {{? (optgroup = operator.optgroup) !== null }} \
- {{?}} \
- ';
+QueryBuilder.templates.ruleValueSelect = ({ name, rule, icons, settings, translate, builder }) => {
+ let optgroup = null;
+ return `
+
+ ${rule.filter.placeholder ? `
+
+ ` : ''}
+ ${rule.filter.values.map(entry => `
+ ${optgroup !== entry.optgroup ? `
+ ${optgroup !== null ? `` : ''}
+ ${(optgroup = entry.optgroup) !== null ? `
+ ' : ''}
+ `;
+};
/**
* Returns group's HTML
* @param {string} group_id
* @param {int} level
* @returns {string}
- * @fires QueryBuilder#filter:getGroupTemplate
+ * @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,
- lang: this.lang,
- settings: this.settings
- });
+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 QueryBuilder#filter:getGroupTemplate
- * @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);
};
/**
* Returns rule's HTML
* @param {string} rule_id
* @returns {string}
- * @fires QueryBuilder#filter:getRuleTemplate
+ * @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,
- lang: this.lang,
- settings: this.settings
- });
+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 QueryBuilder#filter:getRuleTemplate
- * @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);
};
/**
@@ -146,29 +176,29 @@ QueryBuilder.prototype.getRuleTemplate = function(rule_id) {
* @param {Rule} rule
* @param {object[]} filters
* @returns {string}
- * @fires QueryBuilder#filter:getRuleFilterTemplate
+ * @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,
- lang: this.lang,
- settings: this.settings,
- translate: this.getTranslatedLabel
- });
+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 QueryBuilder#filter:getRuleFilterTemplate
- * @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);
};
/**
@@ -176,29 +206,59 @@ QueryBuilder.prototype.getRuleFilterSelect = function(rule, filters) {
* @param {Rule} rule
* @param {object[]} operators
* @returns {string}
- * @fires QueryBuilder#filter:getRuleOperatorTemplate
+ * @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,
- lang: this.lang,
- settings: this.settings,
- translate: this.getTranslatedLabel
- });
+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 QueryBuilder#filter:getRuleOperatorTemplate
- * @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);
};
/**
@@ -206,76 +266,71 @@ QueryBuilder.prototype.getRuleOperatorSelect = function(rule, operators) {
* @param {Rule} rule
* @param {int} value_id
* @returns {string}
- * @fires QueryBuilder#filter:getRuleInput
+ * @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 += '';
- if (filter.placeholder) {
- h += '';
- }
- Utils.iterateOptions(filter.values, function(key, val) {
- h += ' ';
- });
- 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 QueryBuilder#filter:getRuleInput - * @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 65853f18..d74f0817 100644 - a/src/utils.js + b/src/utils.js @ -1,16 +1,24 @ * @namespace * -var Utils="QueryBuilder.utils" +var Utils="{};" + + + * @member {object + * @memberof QueryBuilder + * @see Utils + * +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 @ -21,6 +29,9 @ var Utils="QueryBuilder.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 @ -29,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 { @ -96,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 } @ -163,7 +204,7 @ Utils.escapeElementId="function(str)" { } - * Sorts objects by grouping them by />key, preserving initial order when possible
+ * Sorts objects by grouping them by `key`, preserving initial order when possible
* @param {object[]} items
* @param {string} key
* @returns {object[]}
@@ -195,3 +236,41 @@ Utils.groupSort = function(items, key) {
return newItems;
};
+
+/**
+ * Defines properties on an Node prototype with getter and setter.
+ * Update events are emitted in the setter through root Model (if any).
+ * The object must have a `__` object, non enumerable property to store values.
+ * @param {function} obj
+ * @param {string[]} fields
+ */
+Utils.defineModelProperties = function(obj, fields) {
+ fields.forEach(function(field) {
+ Object.defineProperty(obj.prototype, field, {
+ enumerable: true,
+ get: function() {
+ return this.__[field];
+ },
+ set: function(value) {
+ var previousValue = (this.__[field] !== null && typeof this.__[field] == 'object') ?
+ $.extend({}, this.__[field]) :
+ this.__[field];
+
+ this.__[field] = value;
+
+ if (this.model !== null) {
+ /**
+ * After a value of the model changed
+ * @event model:update
+ * @memberof Model
+ * @param {Node} node
+ * @param {string} field
+ * @param {*} value
+ * @param {*} previousValue
+ */
+ this.model.trigger('update', this, field, value, previousValue);
+ }
+ }
+ });
+ });
+};
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 875f9320..e322dcfd 100644
--- a/tests/index.html
+++ b/tests/index.html
@@ -4,10 +4,10 @@
jQuery-QueryBuilder
-
-
-
-
+
+
+
+