├── .editorconfig ├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── main.yml ├── .gitignore ├── .jsdoc.json ├── LICENSE ├── README.md ├── build ├── dist.mjs ├── jsdoc.js ├── jsdoc.md └── liveserver.mjs ├── examples ├── index.html └── screenshot.png ├── package.json ├── src ├── .wrapper.js ├── core.js ├── data.js ├── defaults.js ├── i18n │ ├── .wrapper.js │ ├── ar.json │ ├── az.json │ ├── bg.json │ ├── cs.json │ ├── da.json │ ├── de.json │ ├── el.json │ ├── en.json │ ├── eo.json │ ├── es.json │ ├── fa-IR.json │ ├── fr.json │ ├── he.json │ ├── hu.json │ ├── it.json │ ├── lt.json │ ├── nl.json │ ├── no.json │ ├── pl.json │ ├── pt-BR.json │ ├── pt-PT.json │ ├── ro.json │ ├── ru.json │ ├── sk.json │ ├── sq.json │ ├── sv.json │ ├── sw.json │ ├── tr.json │ ├── ua.json │ └── zh-CN.json ├── jquery.js ├── main.js ├── model.js ├── plugins.js ├── plugins │ ├── bt-checkbox │ │ ├── plugin.js │ │ └── plugin.scss │ ├── bt-tooltip-errors │ │ ├── plugin.js │ │ └── plugin.scss │ ├── change-filters │ │ └── plugin.js │ ├── chosen-selectpicker │ │ └── plugin.js │ ├── filter-description │ │ ├── plugin.js │ │ └── plugin.scss │ ├── invert │ │ ├── i18n │ │ │ ├── ar.json │ │ │ ├── az.json │ │ │ ├── cs.json │ │ │ ├── el.json │ │ │ ├── en.json │ │ │ ├── eo.json │ │ │ ├── fr.json │ │ │ ├── he.json │ │ │ ├── hu.json │ │ │ ├── lt.json │ │ │ ├── pl.json │ │ │ ├── pt-BR.json │ │ │ ├── ru.json │ │ │ ├── sk.json │ │ │ ├── sv.json │ │ │ ├── sw.json │ │ │ ├── tr.json │ │ │ ├── ua.json │ │ │ └── zh-CN.json │ │ ├── plugin.js │ │ └── plugin.scss │ ├── mongodb-support │ │ └── plugin.js │ ├── not-group │ │ ├── i18n │ │ │ ├── en.json │ │ │ ├── eo.json │ │ │ ├── fr.json │ │ │ ├── he.json │ │ │ ├── hu.json │ │ │ ├── lt.json │ │ │ ├── ru.json │ │ │ ├── sk.json │ │ │ ├── sv.json │ │ │ └── sw.json │ │ └── plugin.js │ ├── sortable │ │ ├── plugin.js │ │ └── plugin.scss │ ├── sql-support │ │ └── plugin.js │ └── unique-filter │ │ └── plugin.js ├── public.js ├── scss │ ├── dark.scss │ └── default.scss ├── template.js └── utils.js ├── tests ├── common.js ├── core.module.js ├── data.module.js ├── index.html ├── plugins-gui.module.js ├── plugins.module.js ├── plugins.mongo-support.module.js ├── plugins.not-group.module.js ├── plugins.sql-support.module.js └── utils.module.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | indent_style = space 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | 10 | [*.json] 11 | indent_size = 2 12 | insert_final_newline = false 13 | 14 | [*.scss] 15 | indent_size = 2 16 | 17 | [*.html] 18 | indent_size = 2 19 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Guidelines for contributing 2 | 3 | ## Work on `dev` 4 | Any merge request should be created from and issued to the `dev` branch. 5 | 6 | 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`. 7 | 8 | ## Core vs Plugins 9 | 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. 10 | 11 | Check the doc about [creating plugins](http://querybuilder.js.org/dev/plugins.html) and [use events](http://querybuilder.js.org/dev/events.html). 12 | 13 | I reserve the right to refuse any plugin I think is not useful for many people. Particularly, only import/export plugins for mainstream data storages will be integrated in the main repository. Others should be in a separated repository. But it's totally possible to add a link to your repository in the documentation. 14 | 15 | ## Unit tests 16 | Any big feature must have it's own QUnit tests suite. Of course existing tests must still pass after changes. 17 | 18 | I won't merge any branch not passing the TravisCI build, including JShint/JSCS/SCSSlint compliance. 19 | 20 | ## Translations 21 | Source language files are plain JSON files which will be converted to executable JS files by the build task. The `__locale` key must be filled with the international name of the language + 2-chars code and the `__author` key can be used to give information about the translator. 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **Issues guidelines** 2 | 3 | - Please search in the [documentation](http://querybuilder.js.org) before asking. 4 | - Any issue without enough details won't get any answer and will be closed. 5 | - Help requests must be exhaustive, precise and come with some code explaining the need (use Markdown code highlight). 6 | - 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). 7 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **Merge request checklist** 2 | 3 | - [ ] I read the [guidelines for contributing](https://github.com/mistic100/jQuery-QueryBuilder/blob/master/.github/CONTRIBUTING.md) 4 | - [ ] I created my branch from `dev` and I am issuing the PR to `dev` 5 | - [ ] I didn't pushed the `dist` directory 6 | - [ ] If it's a new feature, I added the necessary unit tests 7 | - [ ] If it's a new language, I filled the `__locale` and `__author` fields 8 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v3 12 | 13 | - uses: actions/setup-node@v3 14 | with: 15 | node-version: '16' 16 | cache: 'yarn' 17 | 18 | - name: build 19 | run: | 20 | yarn install 21 | yarn build 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | doc 4 | .sass-cache 5 | .idea 6 | *.iml 7 | package-lock.json 8 | -------------------------------------------------------------------------------- /.jsdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "opts": { 3 | "private": false, 4 | "template": "node_modules/foodoc/template", 5 | "readme": "build/jsdoc.md" 6 | }, 7 | "plugins": [ 8 | "plugins/markdown" 9 | ], 10 | "templates": { 11 | "systemName": "jQuery QueryBuilder API", 12 | "systemSummary": "jQuery plugin for user friendly query/filter creator", 13 | "systemColor": "#004482", 14 | "copyright": "Licensed under MIT License, documentation under CC BY 3.0.", 15 | "includeDate": false, 16 | "inverseNav": false, 17 | "cleverLinks": true, 18 | "sort": "longname, version, since", 19 | "analytics": { 20 | "ua": "UA-28192323-3", 21 | "domain": "auto" 22 | }, 23 | "navMembers": [ 24 | {"kind": "class", "title": "Classes", "summary": "All documented classes."}, 25 | {"kind": "external", "title": "Externals", "summary": "All documented external members."}, 26 | {"kind": "global", "title": "Globals", "summary": "All documented globals."}, 27 | {"kind": "mixin", "title": "Mixins", "summary": "All documented mixins."}, 28 | {"kind": "interface", "title": "Interfaces", "summary": "All documented interfaces."}, 29 | {"kind": "module", "title": "Modules", "summary": "All documented modules."}, 30 | {"kind": "event", "title": "Events", "summary": "All documented events."}, 31 | {"kind": "namespace", "title": "Namespaces", "summary": "All documented namespaces."}, 32 | {"kind": "tutorial", "title": "Tutorials", "summary": "All available tutorials."} 33 | ], 34 | "scripts": [ 35 | "https://cdnjs.cloudflare.com/ajax/libs/trianglify/1.0.1/trianglify.min.js", 36 | "js/custom.js" 37 | ] 38 | } 39 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2018 Damien Sorel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jQuery QueryBuilder 2 | 3 | [![npm version](https://img.shields.io/npm/v/jQuery-QueryBuilder.svg?style=flat-square)](https://www.npmjs.com/package/jQuery-QueryBuilder) 4 | [![jsDelivr CDN](https://data.jsdelivr.com/v1/package/npm/jQuery-QueryBuilder/badge)](https://www.jsdelivr.com/package/npm/jQuery-QueryBuilder) 5 | [![Build Status](https://github.com/mistic100/jQuery-QueryBuilder/workflows/CI/badge.svg)](https://github.com/mistic100/jQuery-QueryBuilder/actions) 6 | [![gitlocalized](https://gitlocalize.com/repo/5259/whole_project/badge.svg)](https://gitlocalize.com/repo/5259/whole_project?utm_source=badge) 7 | 8 | jQuery plugin offering an simple interface to create complex queries. 9 | 10 | [![screenshot](https://raw.githubusercontent.com/mistic100/jQuery-QueryBuilder/master/examples/screenshot.png)](https://querybuilder.js.org) 11 | 12 | 13 | 14 | ## Documentation 15 | [querybuilder.js.org](https://querybuilder.js.org) 16 | 17 | 18 | 19 | ## Install 20 | 21 | #### Manually 22 | 23 | [Download the latest release](https://github.com/mistic100/jQuery-QueryBuilder/releases) 24 | 25 | #### With npm 26 | 27 | ```bash 28 | $ npm install jQuery-QueryBuilder 29 | ``` 30 | 31 | #### Via CDN 32 | 33 | jQuery-QueryBuilder is available on [jsDelivr](https://www.jsdelivr.com/package/npm/jQuery-QueryBuilder). 34 | ### Dependencies 35 | * [jQuery 3](https://jquery.com) 36 | * [Bootstrap 5](https://getbootstrap.com/docs/5.3/) CSS and bundle.js which includes `Popper` for tooltips and popovers 37 | * [Bootstrap Icons](https://icons.getbootstrap.com/) 38 | * [jQuery.extendext](https://github.com/mistic100/jQuery.extendext) 39 | * [MomentJS](https://momentjs.com) (optional, for Date/Time validation) 40 | * [SQL Parser](https://github.com/mistic100/sql-parser) (optional, for SQL methods) 41 | * Other Bootstrap/jQuery plugins used by plugins 42 | 43 | ($.extendext is directly included in the [standalone](https://github.com/mistic100/jQuery-QueryBuilder/blob/master/dist/js/query-builder.standalone.js) file) 44 | 45 | 46 | 47 | ## Developement 48 | 49 | Install Node dependencies with `npm install`. 50 | 51 | #### Build 52 | 53 | Run `npm run build` in the root directory to generate production files inside `dist`. 54 | 55 | #### Serve 56 | 57 | Run `npm run serve` to open the example page with automatic build and livereload. 58 | 59 | 60 | ## License 61 | This library is available under the MIT license. 62 | -------------------------------------------------------------------------------- /build/dist.mjs: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import { globSync } from 'glob'; 4 | import * as sass from 'sass'; 5 | import pkg from '../package.json' assert { type: 'json' }; 6 | 7 | const DEV = process.argv[2] === '--dev'; 8 | 9 | const DIST = 'dist/'; 10 | 11 | const CORE_JS = [ 12 | 'src/main.js', 13 | 'src/defaults.js', 14 | 'src/plugins.js', 15 | 'src/core.js', 16 | 'src/public.js', 17 | 'src/data.js', 18 | 'src/template.js', 19 | 'src/utils.js', 20 | 'src/model.js', 21 | 'src/jquery.js', 22 | ]; 23 | 24 | const CORE_SASS = [ 25 | 'src/scss/dark.scss', 26 | 'src/scss/default.scss', 27 | ]; 28 | 29 | const STANDALONE_JS = { 30 | 'jquery-extendext': 'node_modules/jquery-extendext/jquery-extendext.js', 31 | 'query-builder': `${DIST}js/query-builder.js`, 32 | }; 33 | 34 | const BANNER = () => `/*! 35 | * jQuery QueryBuilder ${pkg.version} 36 | * Copyright 2014-${new Date().getFullYear()} Damien "Mistic" Sorel (http://www.strangeplanet.fr) 37 | * Licensed under MIT (https://opensource.org/licenses/MIT) 38 | */`; 39 | 40 | const LANG_BANNER = (locale, author) => `/*! 41 | * jQuery QueryBuilder ${pkg.version} 42 | * Locale: ${locale} 43 | * Author: ${author} 44 | * Licensed under MIT (https://opensource.org/licenses/MIT) 45 | */`; 46 | 47 | const ALL_PLUGINS_JS = glob('src/plugins/*/plugin.js') 48 | .sort() 49 | .reduce((all, p) => { 50 | const n = p.split('/')[2]; 51 | all[n] = p; 52 | return all; 53 | }, {}); 54 | 55 | const ALL_PLUGINS_SASS = glob('src/plugins/*/plugin.scss') 56 | .sort() 57 | .reduce((all, p) => { 58 | const n = p.split('/')[2]; 59 | all[n] = p; 60 | return all; 61 | }, {}); 62 | 63 | const ALL_LANGS = glob('src/i18n/*.json') 64 | .map(p => p.split(/[\/\.]/)[2]) 65 | .sort(); 66 | 67 | function glob(pattern) { 68 | return globSync(pattern) 69 | .map(p => p.split(path.sep).join('/')); 70 | } 71 | 72 | /** 73 | * Build lang files 74 | */ 75 | function buildLangs() { 76 | const wrapper = fs.readFileSync('src/i18n/.wrapper.js', { encoding: 'utf8' }) 77 | .split('@@js\n'); 78 | 79 | ALL_LANGS.forEach(lang => { 80 | const outpath = `${DIST}i18n/query-builder.${lang}.js`; 81 | console.log(`LANG ${lang} (${outpath})`); 82 | fs.writeFileSync(outpath, getLang(lang, wrapper)); 83 | }); 84 | } 85 | 86 | /** 87 | * Get the content of a single lang 88 | */ 89 | function getLang(lang, wrapper = ['', '']) { 90 | const corepath = `src/i18n/${lang}.json`; 91 | const content = JSON.parse(fs.readFileSync(corepath, { encoding: 'utf8' })); 92 | 93 | Object.keys(ALL_PLUGINS_JS).forEach(plugin => { 94 | const pluginpath = `src/plugins/${plugin}/i18n/${lang}.json`; 95 | try { 96 | const plugincontent = JSON.parse(fs.readFileSync(pluginpath, { encoding: 'utf8' })); 97 | Object.assign(content, plugincontent); 98 | } catch { } 99 | }); 100 | 101 | return LANG_BANNER(content.__locale || lang, content.__author || '') 102 | + '\n\n' 103 | + wrapper[0] 104 | + `QueryBuilder.regional['${lang}'] = ` 105 | + JSON.stringify(content, null, 2) 106 | + ';\n\n' 107 | + `QueryBuilder.defaults({ lang_code: '${lang}' });` 108 | + wrapper[1]; 109 | } 110 | 111 | /** 112 | * Build main JS file 113 | */ 114 | function buildMain() { 115 | const wrapper = fs.readFileSync('src/.wrapper.js', { encoding: 'utf8' }) 116 | .split('@@js\n'); 117 | 118 | const files_to_load = [ 119 | ...CORE_JS, 120 | ...Object.values(ALL_PLUGINS_JS), 121 | ]; 122 | 123 | const output = BANNER() 124 | + '\n\n' 125 | + wrapper[0] 126 | + files_to_load.map(f => fs.readFileSync(f, { encoding: 'utf8' })).join('\n\n') 127 | + '\n\n' 128 | + getLang('en') 129 | + wrapper[1]; 130 | 131 | const outpath = `${DIST}js/query-builder.js`; 132 | console.log(`MAIN (${outpath})`); 133 | fs.writeFileSync(outpath, output); 134 | } 135 | 136 | /** 137 | * Build standalone JS file 138 | */ 139 | function buildStandalone() { 140 | const output = Object.entries(STANDALONE_JS) 141 | .map(([name, file]) => { 142 | return fs.readFileSync(file, { encoding: 'utf8' }) 143 | .replace(/define\((.*?)\);/, `define('${name}', $1);`); 144 | }) 145 | .join('\n\n'); 146 | 147 | const outpath = `${DIST}js/query-builder.standalone.js`; 148 | console.log(`STANDALONE (${outpath})`); 149 | fs.writeFileSync(outpath, output); 150 | } 151 | 152 | /** 153 | * Copy SASS files 154 | */ 155 | function copySass() { 156 | Object.entries(ALL_PLUGINS_SASS).forEach(([plugin, path]) => { 157 | const outpath = `${DIST}scss/plugins/${plugin}.scss`; 158 | console.log(`SASS ${plugin} (${path})`); 159 | fs.copyFileSync(path, outpath); 160 | }); 161 | 162 | CORE_SASS.forEach(path => { 163 | const name = path.split('/').pop(); 164 | 165 | const content = fs.readFileSync(path, { encoding: 'utf8' }); 166 | 167 | let output = BANNER() 168 | + '\n' 169 | + content; 170 | if (name === 'default.scss') { 171 | output += '\n' 172 | + Object.keys(ALL_PLUGINS_SASS).map(p => `@import "plugins/${p}";`).join('\n'); 173 | } 174 | 175 | const outpath = `${DIST}scss/${name}`; 176 | console.log(`SASS (${path})`); 177 | fs.writeFileSync(outpath, output); 178 | }); 179 | } 180 | 181 | /** 182 | * Build CSS files 183 | */ 184 | function buildCss() { 185 | CORE_SASS.forEach(p => { 186 | const path = p.replace('src/', DIST); 187 | const name = path.split('/').pop(); 188 | 189 | const output = sass.compile(path); 190 | 191 | const outpath = `${DIST}css/query-builder.${name.split('.').shift()}.css`; 192 | console.log(`CSS (${path})`); 193 | fs.writeFileSync(outpath, output.css); 194 | }); 195 | } 196 | 197 | if (!DEV) { 198 | fs.rmSync(DIST, { recursive: true, force: true }); 199 | } 200 | fs.mkdirSync(DIST + 'css', { recursive: true }); 201 | fs.mkdirSync(DIST + 'i18n', { recursive: true }); 202 | fs.mkdirSync(DIST + 'js', { recursive: true }); 203 | fs.mkdirSync(DIST + 'scss', { recursive: true }); 204 | fs.mkdirSync(DIST + 'scss/plugins', { recursive: true }); 205 | 206 | buildLangs(); 207 | buildMain(); 208 | buildStandalone(); 209 | copySass(); 210 | buildCss(); 211 | -------------------------------------------------------------------------------- /build/jsdoc.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var header = $('.page-header'); 3 | var pattern = Trianglify({ 4 | width: window.screen.width | header.outerWidth(), 5 | height: header.outerHeight(), 6 | cell_size: 90, 7 | seed: 'jQuery QueryBuilder', 8 | x_colors: ['#0074d9', '#001224'] 9 | }); 10 | 11 | header.css('background-image', 'url(' + pattern.png() + ')'); 12 | }()); 13 | -------------------------------------------------------------------------------- /build/jsdoc.md: -------------------------------------------------------------------------------- 1 | # [Main documentation](..) 2 | 3 | # Entry point: [$.fn.QueryBuilder](external-_jQuery.fn_.html) 4 | 5 | # [QueryBuilder](QueryBuilder.html) 6 | 7 | # [Rule](Rule.html) & [Group](Group.html) 8 | 9 | # [Events](list_event.html) 10 | 11 | # [Plugins](module-plugins.html) 12 | -------------------------------------------------------------------------------- /build/liveserver.mjs: -------------------------------------------------------------------------------- 1 | import liveServer from 'alive-server'; 2 | import path from 'path'; 3 | 4 | const rootDir = process.cwd(); 5 | 6 | const EXAMPLES_DIR = 'examples'; 7 | const DIST_DIR = 'dist'; 8 | 9 | liveServer.start({ 10 | open: true, 11 | root: path.join(rootDir, EXAMPLES_DIR), 12 | watch: [ 13 | path.join(rootDir, EXAMPLES_DIR), 14 | path.join(rootDir, DIST_DIR), 15 | ], 16 | mount: [ 17 | ['/node_modules', path.join(rootDir, 'node_modules')], 18 | ['/dist', path.join(rootDir, DIST_DIR)], 19 | ], 20 | }); 21 | -------------------------------------------------------------------------------- /examples/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mistic100/jQuery-QueryBuilder/90d265c2fa92cef699d32a458552246b26fc0480/examples/screenshot.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jQuery-QueryBuilder", 3 | "version": "2.5.0", 4 | "author": { 5 | "name": "Damien \"Mistic\" Sorel", 6 | "email": "contact@git.strangeplanet.fr", 7 | "url": "https://www.strangeplanet.fr" 8 | }, 9 | "description": "jQuery plugin for user friendly query/filter creator", 10 | "main": "dist/js/query-builder.js", 11 | "files": [ 12 | "dist/", 13 | "src/" 14 | ], 15 | "dependencies": { 16 | "bootstrap": "^5.3.0", 17 | "@popperjs/core": "^2.11.8", 18 | "bootstrap-icons": "^1.11.3", 19 | "jquery": "^3.5.1", 20 | "jquery-extendext": "^1.0.0", 21 | "moment": "^2.29.1", 22 | "sql-parser-mistic": "^1.2.3" 23 | }, 24 | "devDependencies": { 25 | "alive-server": "^1.3.0", 26 | "awesome-bootstrap-checkbox": "^0.3.7", 27 | "bootbox": "^6.0.0", 28 | "bootstrap-slider": "^10.0.0", 29 | "chosenjs": "^1.4.3", 30 | "concurrently": "^8.2.0", 31 | "deepmerge": "^2.1.0", 32 | "foodoc": "^0.0.9", 33 | "glob": "^10.3.1", 34 | "interactjs": "^1.3.3", 35 | "nodemon": "^2.0.22", 36 | "sass": "^1.63.6", 37 | "@selectize/selectize": "^0.15.2" 38 | }, 39 | "keywords": [ 40 | "jquery", 41 | "query", 42 | "builder", 43 | "filter" 44 | ], 45 | "license": "MIT", 46 | "homepage": "https://querybuilder.js.org", 47 | "repository": { 48 | "type": "git", 49 | "url": "git://github.com/mistic100/jQuery-QueryBuilder.git" 50 | }, 51 | "bugs": { 52 | "url": "https://github.com/mistic100/jQuery-QueryBuilder/issues" 53 | }, 54 | "scripts": { 55 | "build": "node ./build/dist.mjs", 56 | "watch:build": "nodemon --watch src -e js,scss,json ./build/dist.mjs --dev", 57 | "watch:serve": "node ./build/liveserver.mjs", 58 | "serve": "concurrently \"npm:watch:build\" \"npm:watch:serve\"" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/.wrapper.js: -------------------------------------------------------------------------------- 1 | (function(root, factory) { 2 | if (typeof define == 'function' && define.amd) { 3 | define(['jquery', 'jquery-extendext'], factory); 4 | } 5 | else if (typeof module === 'object' && module.exports) { 6 | module.exports = factory(require('jquery'), require('jquery-extendext')); 7 | } 8 | else { 9 | factory(root.jQuery); 10 | } 11 | }(this, function($) { 12 | "use strict"; 13 | 14 | @@js 15 | 16 | return QueryBuilder; 17 | 18 | })); 19 | -------------------------------------------------------------------------------- /src/defaults.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Allowed types and their internal representation 3 | * @type {object.} 4 | * @readonly 5 | * @private 6 | */ 7 | QueryBuilder.types = { 8 | 'string': 'string', 9 | 'integer': 'number', 10 | 'double': 'number', 11 | 'date': 'datetime', 12 | 'time': 'datetime', 13 | 'datetime': 'datetime', 14 | 'boolean': 'boolean' 15 | }; 16 | 17 | /** 18 | * Allowed inputs 19 | * @type {string[]} 20 | * @readonly 21 | * @private 22 | */ 23 | QueryBuilder.inputs = [ 24 | 'text', 25 | 'number', 26 | 'textarea', 27 | 'radio', 28 | 'checkbox', 29 | 'select' 30 | ]; 31 | 32 | /** 33 | * Runtime modifiable options with `setOptions` method 34 | * @type {string[]} 35 | * @readonly 36 | * @private 37 | */ 38 | QueryBuilder.modifiable_options = [ 39 | 'display_errors', 40 | 'allow_groups', 41 | 'allow_empty', 42 | 'default_condition', 43 | 'default_filter' 44 | ]; 45 | 46 | /** 47 | * CSS selectors for common components 48 | * @type {object.} 49 | * @readonly 50 | */ 51 | QueryBuilder.selectors = { 52 | group_container: '.rules-group-container', 53 | rule_container: '.rule-container', 54 | filter_container: '.rule-filter-container', 55 | operator_container: '.rule-operator-container', 56 | value_container: '.rule-value-container', 57 | error_container: '.error-container', 58 | condition_container: '.rules-group-header .group-conditions', 59 | 60 | rule_header: '.rule-header', 61 | group_header: '.rules-group-header', 62 | group_actions: '.group-actions', 63 | rule_actions: '.rule-actions', 64 | 65 | rules_list: '.rules-group-body>.rules-list', 66 | 67 | group_condition: '.rules-group-header [name$=_cond]', 68 | rule_filter: '.rule-filter-container [name$=_filter]', 69 | rule_operator: '.rule-operator-container [name$=_operator]', 70 | rule_value: '.rule-value-container [name*=_value_]', 71 | 72 | add_rule: '[data-add=rule]', 73 | delete_rule: '[data-delete=rule]', 74 | add_group: '[data-add=group]', 75 | delete_group: '[data-delete=group]' 76 | }; 77 | 78 | /** 79 | * Template strings (see template.js) 80 | * @type {object.} 81 | * @readonly 82 | */ 83 | QueryBuilder.templates = {}; 84 | 85 | /** 86 | * Localized strings (see i18n/) 87 | * @type {object.} 88 | * @readonly 89 | */ 90 | QueryBuilder.regional = {}; 91 | 92 | /** 93 | * Default operators 94 | * @type {object.} 95 | * @readonly 96 | */ 97 | QueryBuilder.OPERATORS = { 98 | equal: { type: 'equal', nb_inputs: 1, multiple: false, apply_to: ['string', 'number', 'datetime', 'boolean'] }, 99 | not_equal: { type: 'not_equal', nb_inputs: 1, multiple: false, apply_to: ['string', 'number', 'datetime', 'boolean'] }, 100 | in: { type: 'in', nb_inputs: 1, multiple: true, apply_to: ['string', 'number', 'datetime'] }, 101 | not_in: { type: 'not_in', nb_inputs: 1, multiple: true, apply_to: ['string', 'number', 'datetime'] }, 102 | less: { type: 'less', nb_inputs: 1, multiple: false, apply_to: ['number', 'datetime'] }, 103 | less_or_equal: { type: 'less_or_equal', nb_inputs: 1, multiple: false, apply_to: ['number', 'datetime'] }, 104 | greater: { type: 'greater', nb_inputs: 1, multiple: false, apply_to: ['number', 'datetime'] }, 105 | greater_or_equal: { type: 'greater_or_equal', nb_inputs: 1, multiple: false, apply_to: ['number', 'datetime'] }, 106 | between: { type: 'between', nb_inputs: 2, multiple: false, apply_to: ['number', 'datetime'] }, 107 | not_between: { type: 'not_between', nb_inputs: 2, multiple: false, apply_to: ['number', 'datetime'] }, 108 | begins_with: { type: 'begins_with', nb_inputs: 1, multiple: false, apply_to: ['string'] }, 109 | not_begins_with: { type: 'not_begins_with', nb_inputs: 1, multiple: false, apply_to: ['string'] }, 110 | contains: { type: 'contains', nb_inputs: 1, multiple: false, apply_to: ['string'] }, 111 | not_contains: { type: 'not_contains', nb_inputs: 1, multiple: false, apply_to: ['string'] }, 112 | ends_with: { type: 'ends_with', nb_inputs: 1, multiple: false, apply_to: ['string'] }, 113 | not_ends_with: { type: 'not_ends_with', nb_inputs: 1, multiple: false, apply_to: ['string'] }, 114 | is_empty: { type: 'is_empty', nb_inputs: 0, multiple: false, apply_to: ['string'] }, 115 | is_not_empty: { type: 'is_not_empty', nb_inputs: 0, multiple: false, apply_to: ['string'] }, 116 | is_null: { type: 'is_null', nb_inputs: 0, multiple: false, apply_to: ['string', 'number', 'datetime', 'boolean'] }, 117 | is_not_null: { type: 'is_not_null', nb_inputs: 0, multiple: false, apply_to: ['string', 'number', 'datetime', 'boolean'] } 118 | }; 119 | 120 | /** 121 | * Default configuration 122 | * @type {object} 123 | * @readonly 124 | */ 125 | QueryBuilder.DEFAULTS = { 126 | filters: [], 127 | plugins: [], 128 | 129 | sort_filters: false, 130 | display_errors: true, 131 | allow_groups: -1, 132 | allow_empty: false, 133 | conditions: ['AND', 'OR'], 134 | default_condition: 'AND', 135 | inputs_separator: ' , ', 136 | select_placeholder: '------', 137 | display_empty_filter: true, 138 | default_filter: null, 139 | optgroups: {}, 140 | 141 | default_rule_flags: { 142 | filter_readonly: false, 143 | operator_readonly: false, 144 | value_readonly: false, 145 | no_delete: false 146 | }, 147 | 148 | default_group_flags: { 149 | condition_readonly: false, 150 | no_add_rule: false, 151 | no_add_group: false, 152 | no_delete: false 153 | }, 154 | 155 | templates: { 156 | group: null, 157 | rule: null, 158 | filterSelect: null, 159 | operatorSelect: null, 160 | ruleValueSelect: null 161 | }, 162 | 163 | lang_code: 'en', 164 | lang: {}, 165 | 166 | operators: [ 167 | 'equal', 168 | 'not_equal', 169 | 'in', 170 | 'not_in', 171 | 'less', 172 | 'less_or_equal', 173 | 'greater', 174 | 'greater_or_equal', 175 | 'between', 176 | 'not_between', 177 | 'begins_with', 178 | 'not_begins_with', 179 | 'contains', 180 | 'not_contains', 181 | 'ends_with', 182 | 'not_ends_with', 183 | 'is_empty', 184 | 'is_not_empty', 185 | 'is_null', 186 | 'is_not_null' 187 | ], 188 | 189 | icons: { 190 | add_group: 'bi-plus-circle-fill', 191 | add_rule: 'bi-plus-lg', 192 | remove_group: 'bi-x-lg', 193 | remove_rule: 'bi-x-lg', 194 | error: 'bi-exclamation-triangle' 195 | } 196 | }; 197 | -------------------------------------------------------------------------------- /src/i18n/.wrapper.js: -------------------------------------------------------------------------------- 1 | (function(root, factory) { 2 | if (typeof define == 'function' && define.amd) { 3 | define(['jquery', 'query-builder'], factory); 4 | } 5 | else { 6 | factory(root.jQuery); 7 | } 8 | }(this, function($) { 9 | "use strict"; 10 | 11 | var QueryBuilder = $.fn.queryBuilder; 12 | 13 | @@js 14 | 15 | })); -------------------------------------------------------------------------------- /src/i18n/ar.json: -------------------------------------------------------------------------------- 1 | { 2 | "__locale": "Arabic (ar)", 3 | "__author": "Mohamed YOUNES, https://github.com/MedYOUNES", 4 | "add_rule": "إضافة حُكم", 5 | "add_group": "إضافة زُمْرَة", 6 | "delete_rule": "حذف", 7 | "delete_group": "حذف", 8 | "conditions": { 9 | "AND": "و", 10 | "OR": "أو" 11 | }, 12 | "operators": { 13 | "equal": "يساوي", 14 | "not_equal": "غير مساوٍ", 15 | "in": "في", 16 | "not_in": "ليس في", 17 | "less": "أقل من", 18 | "less_or_equal": "أصغر أو مساو", 19 | "greater": "أكبر", 20 | "greater_or_equal": "أكبر أو مساو", 21 | "between": "محصور بين", 22 | "not_between": "غير محصور بين", 23 | "begins_with": "يبدأ بـ", 24 | "not_begins_with": "لا يبدأ بـ", 25 | "contains": "يحتوي على", 26 | "not_contains": "لا يحتوي على", 27 | "ends_with": "ينتهي بـ", 28 | "not_ends_with": "لا ينتهي بـ", 29 | "is_empty": "فارغ", 30 | "is_not_empty": "غير فارغ", 31 | "is_null": "صفر", 32 | "is_not_null": "ليس صفرا" 33 | }, 34 | "errors": { 35 | "no_filter": "لم تحدد أي مرشح", 36 | "empty_group": "الزمرة فارغة", 37 | "radio_empty": "لم تحدد أي قيمة", 38 | "checkbox_empty": "لم تحدد أي قيمة", 39 | "select_empty": "لم تحدد أي قيمة", 40 | "string_empty": "النص فارغ", 41 | "string_exceed_min_length": "النص دون الأدنى المسموح به", 42 | "string_exceed_max_length": "النص فوق الأقصى المسموح به", 43 | "string_invalid_format": "تركيبة غير صحيحة", 44 | "number_nan": "ليس عددا", 45 | "number_not_integer": "ليس عددا صحيحا", 46 | "number_not_double": "ليس عددا كسريا", 47 | "number_exceed_min": "العدد أصغر من الأدنى المسموح به", 48 | "number_exceed_max": "العدد أكبر من الأقصى المسموح به", 49 | "number_wrong_step": "أخطأت في حساب مضاعفات العدد", 50 | "datetime_empty": "لم تحدد التاريخ", 51 | "datetime_invalid": "صيغة التاريخ غير صحيحة", 52 | "datetime_exceed_min": "التاريخ دون الأدنى المسموح به", 53 | "datetime_exceed_max": "التاريخ أكبر من الأقصى المسموح به", 54 | "boolean_not_valid": "ليست قيمة منطقية ثنائية", 55 | "operator_not_multiple": "العامل ليس متعدد القيَم" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/i18n/az.json: -------------------------------------------------------------------------------- 1 | { 2 | "__locale": "Azerbaijan (az)", 3 | "__author": "Megaplan, mborisv ", 4 | 5 | "add_rule": "Əlavə etmək", 6 | "add_group": "Qrup əlavə etmək", 7 | "delete_rule": "Silmək", 8 | "delete_group": "Silmək", 9 | "conditions": { 10 | "AND": "VƏ", 11 | "OR": "VƏ YA" 12 | }, 13 | "operators": { 14 | "equal": "bərabərdir", 15 | "not_equal": "bərabər deyil", 16 | "in": "qeyd edilmişlərdən", 17 | "not_in": "qeyd olunmamışlardan", 18 | "less": "daha az", 19 | "less_or_equal": "daha az və ya bərabər", 20 | "greater": "daha çox", 21 | "greater_or_equal": "daha çox və ya bərabər", 22 | "between": "arasında", 23 | "begins_with": "başlayır", 24 | "not_begins_with": "başlamır", 25 | "contains": "ibarətdir", 26 | "not_contains": "yoxdur", 27 | "ends_with": "başa çatır", 28 | "not_ends_with": "başa çatmır", 29 | "is_empty": "boş sətir", 30 | "is_not_empty": "boş olmayan sətir", 31 | "is_null": "boşdur", 32 | "is_not_null": "boş deyil" 33 | }, 34 | "errors": { 35 | "no_filter": "Filterlər seçilməyib", 36 | "empty_group": "Qrup boşdur", 37 | "radio_empty": "Məna seçilməyib", 38 | "checkbox_empty": "Məna seçilməyib", 39 | "select_empty": "Məna seçilməyib", 40 | "string_empty": "Doldurulmayıb", 41 | "string_exceed_min_length": "{0} daha çox simvol olmalıdır", 42 | "string_exceed_max_length": "{0} daha az simvol olmalıdır", 43 | "string_invalid_format": "Yanlış format ({0})", 44 | "number_nan": "Rəqəm deyil", 45 | "number_not_integer": "Rəqəm deyil", 46 | "number_not_double": "Rəqəm deyil", 47 | "number_exceed_min": "{0} daha çox olmalıdır", 48 | "number_exceed_max": "{0} daha az olmalıdır", 49 | "number_wrong_step": "{0} bölünən olmalıdır", 50 | "datetime_empty": "Doldurulmayıb", 51 | "datetime_invalid": "Yanlış tarix formatı ({0})", 52 | "datetime_exceed_min": "{0} sonra olmalıdır", 53 | "datetime_exceed_max": "{0} əvvəl olmalıdır", 54 | "boolean_not_valid": "Loqik olmayan", 55 | "operator_not_multiple": "\"{1}\" operatoru çoxlu məna daşımır" 56 | } 57 | } -------------------------------------------------------------------------------- /src/i18n/bg.json: -------------------------------------------------------------------------------- 1 | { 2 | "__locale": "Bulgarian (bg)", 3 | "__author": "Valentin Hristov", 4 | 5 | "add_rule": "Добави правило", 6 | "add_group": "Добави група", 7 | "delete_rule": "Изтрий", 8 | "delete_group": "Изтрий", 9 | 10 | "conditions": { 11 | "AND": "И", 12 | "OR": "ИЛИ" 13 | }, 14 | 15 | "operators": { 16 | "equal": "равно", 17 | "not_equal": "различно", 18 | "in": "в", 19 | "not_in": "не е в", 20 | "less": "по-малко", 21 | "less_or_equal": "по-малко или равно", 22 | "greater": "по-голям", 23 | "greater_or_equal": "по-голям или равно", 24 | "between": "между", 25 | "not_between": "не е между", 26 | "begins_with": "започва с", 27 | "not_begins_with": "не започва с", 28 | "contains": "съдържа", 29 | "not_contains": "не съдържа", 30 | "ends_with": "завършва с", 31 | "not_ends_with": "не завършва с", 32 | "is_empty": "е празно", 33 | "is_not_empty": "не е празно", 34 | "is_null": "е нищо", 35 | "is_not_null": "различно от нищо" 36 | }, 37 | 38 | "errors": { 39 | "no_filter": "Не е избран филтър", 40 | "empty_group": "Групата е празна", 41 | "radio_empty": "Не е селектирана стойност", 42 | "checkbox_empty": "Не е селектирана стойност", 43 | "select_empty": "Не е селектирана стойност", 44 | "string_empty": "Празна стойност", 45 | "string_exceed_min_length": "Необходимо е да съдържа поне {0} символа", 46 | "string_exceed_max_length": "Необходимо е да съдържа повече от {0} символа", 47 | "string_invalid_format": "Невалиден формат ({0})", 48 | "number_nan": "Не е число", 49 | "number_not_integer": "Не е цяло число", 50 | "number_not_double": "Не е реално число", 51 | "number_exceed_min": "Трябва да е по-голямо от {0}", 52 | "number_exceed_max": "Трябва да е по-малко от {0}", 53 | "number_wrong_step": "Трябва да е кратно на {0}", 54 | "datetime_empty": "Празна стойност", 55 | "datetime_invalid": "Невалиден формат на дата ({0})", 56 | "datetime_exceed_min": "Трябва да е след {0}", 57 | "datetime_exceed_max": "Трябва да е преди {0}", 58 | "boolean_not_valid": "Не е булева", 59 | "operator_not_multiple": "Оператора \"{1}\" не може да приеме множество стойности" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/i18n/cs.json: -------------------------------------------------------------------------------- 1 | { 2 | "__locale": "Čeština (cs)", 3 | "__author": "Megaplan, mborisv ", 4 | 5 | "add_rule": "Přidat", 6 | "add_group": "Přidat skupinu", 7 | "delete_rule": "Odstranit", 8 | "delete_group": "Odstranit skupinu", 9 | "conditions": { 10 | "AND": "I", 11 | "OR": "NEBO" 12 | }, 13 | "operators": { 14 | "equal": "stejně", 15 | "not_equal": "liší se", 16 | "in": "z uvedených", 17 | "not_in": "ne z uvedených", 18 | "less": "méně", 19 | "less_or_equal": "méně nebo stejně", 20 | "greater": "více", 21 | "greater_or_equal": "více nebo stejně", 22 | "between": "mezi", 23 | "begins_with": "začíná z", 24 | "not_begins_with": "nezačíná z", 25 | "contains": "obsahuje", 26 | "not_contains": "neobsahuje", 27 | "ends_with": "končí na", 28 | "not_ends_with": "nekončí na", 29 | "is_empty": "prázdný řádek", 30 | "is_not_empty": "neprázdný řádek", 31 | "is_null": "prázdno", 32 | "is_not_null": "plno" 33 | }, 34 | "errors": { 35 | "no_filter": "není vybraný filtr", 36 | "empty_group": "prázdná skupina", 37 | "radio_empty": "Není udaná hodnota", 38 | "checkbox_empty": "Není udaná hodnota", 39 | "select_empty": "Není udaná hodnota", 40 | "string_empty": "Nevyplněno", 41 | "string_exceed_min_length": "Musí obsahovat více {0} symbolů", 42 | "string_exceed_max_length": "Musí obsahovat méně {0} symbolů", 43 | "string_invalid_format": "Nesprávný formát ({0})", 44 | "number_nan": "Žádné číslo", 45 | "number_not_integer": "Žádné číslo", 46 | "number_not_double": "Žádné číslo", 47 | "number_exceed_min": "Musí být více {0}", 48 | "number_exceed_max": "Musí být méně {0}", 49 | "number_wrong_step": "Musí být násobkem {0}", 50 | "datetime_empty": "Nevyplněno", 51 | "datetime_invalid": "Nesprávný formát datumu ({0})", 52 | "datetime_exceed_min": "Musí být po {0}", 53 | "datetime_exceed_max": "Musí být do {0}", 54 | "boolean_not_valid": "Nelogické", 55 | "operator_not_multiple": "Operátor \"{1}\" nepodporuje mnoho hodnot" 56 | } 57 | } -------------------------------------------------------------------------------- /src/i18n/da.json: -------------------------------------------------------------------------------- 1 | { 2 | "__locale": "Danish (da)", 3 | "__author": "Jna Borup Coyle, github@coyle.dk", 4 | 5 | "add_rule": "Tilføj regel", 6 | "add_group": "Tilføj gruppe", 7 | "delete_rule": "Slet regel", 8 | "delete_group": "Slet gruppe", 9 | 10 | "conditions": { 11 | "AND": "OG", 12 | "OR": "ELLER" 13 | }, 14 | 15 | "condition_and": "OG", 16 | "condition_or": "ELLER", 17 | 18 | "operators": { 19 | "equal": "lig med", 20 | "not_equal": "ikke lige med", 21 | "in": "i", 22 | "not_in": "ikke i", 23 | "less": "mindre", 24 | "less_or_equal": "mindre eller lig med", 25 | "greater": "større", 26 | "greater_or_equal": "større eller lig med", 27 | "begins_with": "begynder med", 28 | "not_begins_with": "begynder ikke med", 29 | "contains": "indeholder", 30 | "not_contains": "indeholder ikke", 31 | "ends_with": "slutter med", 32 | "not_ends_with": "slutter ikke med", 33 | "is_empty": "er tom", 34 | "is_not_empty": "er ikke tom", 35 | "is_null": "er null", 36 | "is_not_null": "er ikke null" 37 | } 38 | } -------------------------------------------------------------------------------- /src/i18n/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "__locale": "German (de)", 3 | "__author": "\"raimu\"", 4 | 5 | "add_rule": "neue Regel", 6 | "add_group": "neue Gruppe", 7 | "delete_rule": "löschen", 8 | "delete_group": "löschen", 9 | 10 | "conditions": { 11 | "AND": "UND", 12 | "OR": "ODER" 13 | }, 14 | 15 | "operators": { 16 | "equal": "gleich", 17 | "not_equal": "ungleich", 18 | "in": "in", 19 | "not_in": "nicht in", 20 | "less": "kleiner", 21 | "less_or_equal": "kleiner gleich", 22 | "greater": "größer", 23 | "greater_or_equal": "größer gleich", 24 | "between": "zwischen", 25 | "not_between": "nicht zwischen", 26 | "begins_with": "beginnt mit", 27 | "not_begins_with": "beginnt nicht mit", 28 | "contains": "enthält", 29 | "not_contains": "enthält nicht", 30 | "ends_with": "endet mit", 31 | "not_ends_with": "endet nicht mit", 32 | "is_empty": "ist leer", 33 | "is_not_empty": "ist nicht leer", 34 | "is_null": "ist null", 35 | "is_not_null": "ist nicht null" 36 | }, 37 | 38 | "errors": { 39 | "no_filter": "Kein Filter ausgewählt", 40 | "empty_group": "Die Gruppe ist leer", 41 | "radio_empty": "Kein Wert ausgewählt", 42 | "checkbox_empty": "Kein Wert ausgewählt", 43 | "select_empty": "Kein Wert ausgewählt", 44 | "string_empty": "Leerer Wert", 45 | "string_exceed_min_length": "Muss mindestens {0} Zeichen enthalten", 46 | "string_exceed_max_length": "Darf nicht mehr als {0} Zeichen enthalten", 47 | "string_invalid_format": "Ungültiges Format ({0})", 48 | "number_nan": "Keine Zahl", 49 | "number_not_integer": "Keine Ganzzahl", 50 | "number_not_double": "Keine Dezimalzahl", 51 | "number_exceed_min": "Muss größer als {0} sein", 52 | "number_exceed_max": "Muss kleiner als {0} sein", 53 | "number_wrong_step": "Muss ein Vielfaches von {0} sein", 54 | "datetime_invalid": "Ungültiges Datumsformat ({0})", 55 | "datetime_exceed_min": "Muss nach dem {0} sein", 56 | "datetime_exceed_max": "Muss vor dem {0} sein" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/i18n/el.json: -------------------------------------------------------------------------------- 1 | { 2 | "__locale": "Greek (el)", 3 | "__author": "Stelios Patsatzis, https://www.linkedin.com/in/stelios-patsatzis-89841561", 4 | "add_rule": "Προσθήκη Συνθήκης", 5 | "add_group": "Προσθήκη Ομάδας", 6 | "delete_rule": "Διαγραφή", 7 | "delete_group": "Διαγραφή", 8 | "conditions": { 9 | "AND": "Λογικό ΚΑΙ", 10 | "OR": "Λογικό Η" 11 | }, 12 | "operators": { 13 | "equal": "Ισούται με", 14 | "not_equal": "Διάφορο από ", 15 | "in": "Περιέχει", 16 | "not_in": "Δεν Περιέχει", 17 | "less": "Λιγότερο από", 18 | "less_or_equal": "Λιγότερο ή Ίσο", 19 | "greater": "Μεγαλύτερο από", 20 | "greater_or_equal": "Μεγαλύτερο ή Ίσο", 21 | "between": "Μεταξύ", 22 | "not_between": "Εκτός", 23 | "begins_with": "Αρχίζει με", 24 | "not_begins_with": "Δεν αρχίζει με", 25 | "contains": "Περιέχει", 26 | "not_contains": "Δεν περιέχει", 27 | "ends_with": "Τελειώνει σε", 28 | "not_ends_with": "Δεν τελειώνει σε", 29 | "is_empty": "Είναι άδειο", 30 | "is_not_empty": "Δεν είναι άδειο", 31 | "is_null": "Είναι NULL", 32 | "is_not_null": "Δεν είναι NULL" 33 | }, 34 | "errors": { 35 | "no_filter": "Χωρίς φίλτρα", 36 | "empty_group": "Άδεια ομάδα", 37 | "radio_empty": "Χωρίς τιμή", 38 | "checkbox_empty": "Χωρίς τιμή", 39 | "select_empty": "Χωρίς τιμή", 40 | "string_empty": "Χωρίς τιμή", 41 | "string_exceed_min_length": "Ελάχιστο όριο {0} χαρακτήρων", 42 | "string_exceed_max_length": "Μέγιστο όριο {0} χαρακτήρων", 43 | "string_invalid_format": "Λανθασμένη μορφή ({0})", 44 | "number_nan": "Δεν είναι αριθμός", 45 | "number_not_integer": "Δεν είναι ακέραιος αριθμός", 46 | "number_not_double": "Δεν είναι πραγματικός αριθμός", 47 | "number_exceed_min": "Πρέπει να είναι μεγαλύτερο απο {0}", 48 | "number_exceed_max": "Πρέπει να είναι μικρότερο απο {0}", 49 | "number_wrong_step": "Πρέπει να είναι πολλαπλάσιο του {0}", 50 | "datetime_empty": "Χωρίς τιμή", 51 | "datetime_invalid": "Λανθασμένη μορφή ημερομηνίας ({0})", 52 | "datetime_exceed_min": "Νεότερο από {0}", 53 | "datetime_exceed_max": "Παλαιότερο από {0}", 54 | "boolean_not_valid": "Δεν είναι BOOLEAN", 55 | "operator_not_multiple": "Η συνθήκη \"{1}\" δεν μπορεί να δεχθεί πολλαπλές τιμές" 56 | } 57 | } -------------------------------------------------------------------------------- /src/i18n/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "__locale": "English (en)", 3 | "__author": "Damien \"Mistic\" Sorel, http://www.strangeplanet.fr", 4 | 5 | "add_rule": "Add rule", 6 | "add_group": "Add group", 7 | "delete_rule": "Delete", 8 | "delete_group": "Delete", 9 | 10 | "conditions": { 11 | "AND": "AND", 12 | "OR": "OR" 13 | }, 14 | 15 | "operators": { 16 | "equal": "equal", 17 | "not_equal": "not equal", 18 | "in": "in", 19 | "not_in": "not in", 20 | "less": "less", 21 | "less_or_equal": "less or equal", 22 | "greater": "greater", 23 | "greater_or_equal": "greater or equal", 24 | "between": "between", 25 | "not_between": "not between", 26 | "begins_with": "begins with", 27 | "not_begins_with": "doesn't begin with", 28 | "contains": "contains", 29 | "not_contains": "doesn't contain", 30 | "ends_with": "ends with", 31 | "not_ends_with": "doesn't end with", 32 | "is_empty": "is empty", 33 | "is_not_empty": "is not empty", 34 | "is_null": "is null", 35 | "is_not_null": "is not null" 36 | }, 37 | 38 | "errors": { 39 | "no_filter": "No filter selected", 40 | "empty_group": "The group is empty", 41 | "radio_empty": "No value selected", 42 | "checkbox_empty": "No value selected", 43 | "select_empty": "No value selected", 44 | "string_empty": "Empty value", 45 | "string_exceed_min_length": "Must contain at least {0} characters", 46 | "string_exceed_max_length": "Must not contain more than {0} characters", 47 | "string_invalid_format": "Invalid format ({0})", 48 | "number_nan": "Not a number", 49 | "number_not_integer": "Not an integer", 50 | "number_not_double": "Not a real number", 51 | "number_exceed_min": "Must be greater than {0}", 52 | "number_exceed_max": "Must be lower than {0}", 53 | "number_wrong_step": "Must be a multiple of {0}", 54 | "number_between_invalid": "Invalid values, {0} is greater than {1}", 55 | "datetime_empty": "Empty value", 56 | "datetime_invalid": "Invalid date format ({0})", 57 | "datetime_exceed_min": "Must be after {0}", 58 | "datetime_exceed_max": "Must be before {0}", 59 | "datetime_between_invalid": "Invalid values, {0} is greater than {1}", 60 | "boolean_not_valid": "Not a boolean", 61 | "operator_not_multiple": "Operator \"{1}\" cannot accept multiple values" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/i18n/eo.json: -------------------------------------------------------------------------------- 1 | { 2 | "__locale": "Esperanto (eo)", 3 | "__author": "Robin van der Vliet, https://robinvandervliet.com/", 4 | 5 | "add_rule": "Aldoni regulon", 6 | "add_group": "Aldoni grupon", 7 | "delete_rule": "Forigi", 8 | "delete_group": "Forigi", 9 | 10 | "conditions": { 11 | "AND": "KAJ", 12 | "OR": "AŬ" 13 | }, 14 | 15 | "operators": { 16 | "equal": "estas egala al", 17 | "not_equal": "ne estas egala al", 18 | "in": "estas en", 19 | "not_in": "ne estas en", 20 | "less": "estas malpli ol", 21 | "less_or_equal": "estas malpli ol aŭ egala al", 22 | "greater": "estas pli ol", 23 | "greater_or_equal": "estas pli ol aŭ egala al", 24 | "between": "estas inter", 25 | "not_between": "ne estas inter", 26 | "begins_with": "komenciĝas per", 27 | "not_begins_with": "ne komenciĝas per", 28 | "contains": "enhavas", 29 | "not_contains": "ne enhavas", 30 | "ends_with": "finiĝas per", 31 | "not_ends_with": "ne finiĝas per", 32 | "is_empty": "estas malplena", 33 | "is_not_empty": "ne estas malplena", 34 | "is_null": "estas senvalora", 35 | "is_not_null": "ne estas senvalora" 36 | }, 37 | 38 | "errors": { 39 | "no_filter": "Neniu filtrilo elektita", 40 | "empty_group": "La grupo estas malplena", 41 | "radio_empty": "Neniu valoro elektita", 42 | "checkbox_empty": "Neniu valoro elektita", 43 | "select_empty": "Neniu valoro elektita", 44 | "string_empty": "Malplena valoro", 45 | "string_exceed_min_length": "Devas enhavi pli ol {0} signojn", 46 | "string_exceed_max_length": "Devas ne enhavi pli ol {0} signojn", 47 | "string_invalid_format": "Nevalida strukturo ({0})", 48 | "number_nan": "Ne estas nombro", 49 | "number_not_integer": "Ne estas entjera nombro", 50 | "number_not_double": "Ne estas reela nombro", 51 | "number_exceed_min": "Devas esti pli ol {0}", 52 | "number_exceed_max": "Devas esti malpli ol {0}", 53 | "number_wrong_step": "Devas esti oblo de {0}", 54 | "number_between_invalid": "Nevalidaj valoroj, {0} estas pli ol {1}", 55 | "datetime_empty": "Malplena valoro", 56 | "datetime_invalid": "Nevalida dato ({0})", 57 | "datetime_exceed_min": "Devas esti post {0}", 58 | "datetime_exceed_max": "Devas esti antaŭ {0}", 59 | "datetime_between_invalid": "Nevalidaj valoroj, {0} estas post {1}", 60 | "boolean_not_valid": "Ne estas bulea valoro", 61 | "operator_not_multiple": "La operacio \"{1}\" ne akceptas plurajn valorojn" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/i18n/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "__locale": "Spanish (es)", 3 | "__author": "\"pyarza\", \"kddlb\"", 4 | 5 | "add_rule": "Añadir regla", 6 | "add_group": "Añadir grupo", 7 | "delete_rule": "Borrar", 8 | "delete_group": "Borrar", 9 | 10 | "conditions": { 11 | "AND": "Y", 12 | "OR": "O" 13 | }, 14 | 15 | "operators": { 16 | "equal": "igual", 17 | "not_equal": "distinto", 18 | "in": "en", 19 | "not_in": "no en", 20 | "less": "menor", 21 | "less_or_equal": "menor o igual", 22 | "greater": "mayor", 23 | "greater_or_equal": "mayor o igual", 24 | "between": "entre", 25 | "not_between": "no está entre", 26 | "begins_with": "empieza por", 27 | "not_begins_with": "no empieza por", 28 | "contains": "contiene", 29 | "not_contains": "no contiene", 30 | "ends_with": "acaba con", 31 | "not_ends_with": "no acaba con", 32 | "is_empty": "está vacío", 33 | "is_not_empty": "no está vacío", 34 | "is_null": "es nulo", 35 | "is_not_null": "no es nulo" 36 | }, 37 | 38 | "errors": { 39 | "no_filter": "No se ha seleccionado ningún filtro", 40 | "empty_group": "El grupo está vacío", 41 | "radio_empty": "Ningún valor seleccionado", 42 | "checkbox_empty": "Ningún valor seleccionado", 43 | "select_empty": "Ningún valor seleccionado", 44 | "string_empty": "Cadena vacía", 45 | "string_exceed_min_length": "Debe contener al menos {0} caracteres", 46 | "string_exceed_max_length": "No debe contener más de {0} caracteres", 47 | "string_invalid_format": "Formato inválido ({0})", 48 | "number_nan": "No es un número", 49 | "number_not_integer": "No es un número entero", 50 | "number_not_double": "No es un número real", 51 | "number_exceed_min": "Debe ser mayor que {0}", 52 | "number_exceed_max": "Debe ser menor que {0}", 53 | "number_wrong_step": "Debe ser múltiplo de {0}", 54 | "datetime_invalid": "Formato de fecha inválido ({0})", 55 | "datetime_exceed_min": "Debe ser posterior a {0}", 56 | "datetime_exceed_max": "Debe ser anterior a {0}", 57 | "number_between_invalid": "Valores Inválidos, {0} es mayor que {1}", 58 | "datetime_empty": "Campo vacio", 59 | "datetime_between_invalid": "Valores Inválidos, {0} es mayor que {1}", 60 | "boolean_not_valid": "No es booleano", 61 | "operator_not_multiple": "El operador \"{1}\" no puede aceptar valores multiples" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/i18n/fa-IR.json: -------------------------------------------------------------------------------- 1 | { 2 | "__locale": "Farsi (fa-ir)", 3 | "__author": "Behzad Sedighzade, behzad.sedighzade@gmail.com", 4 | 5 | "add_rule": "افزودن قاعده", 6 | "add_group": "افزودن گروه", 7 | "delete_rule": "حذف قاعده", 8 | "delete_group": "حذف گروه", 9 | 10 | "conditions": { 11 | "AND": "و", 12 | "OR": "یا" 13 | }, 14 | 15 | "operators": { 16 | "equal": "برابر با", 17 | "not_equal": "مخالف", 18 | "in": "شامل مجموعه شود", 19 | "not_in": "شامل مجموعه نشود", 20 | "less": "کمتر از", 21 | "less_or_equal": "کمتر یا مساوی با", 22 | "greater": "بزرگتر از", 23 | "greater_or_equal": "بزرگتر یا مساوی با", 24 | "between": "مابین", 25 | "not_between": "مابین نباشد", 26 | "begins_with": "شروع شود با", 27 | "not_begins_with": "شروع نشود با", 28 | "contains": "شامل شود", 29 | "not_contains": "شامل نشود", 30 | "ends_with": "خاتمه یابد با", 31 | "not_ends_with": "خاتمه نیابد با", 32 | "is_empty": "خالی باشد", 33 | "is_not_empty": "خالی نباشد", 34 | "is_null": "باشد ( null ) پوچ", 35 | "is_not_null": "نباشد( null ) پوچ " 36 | }, 37 | 38 | "errors": { 39 | "no_filter": "هیچ قاعده ای انتخاب نشده است", 40 | "empty_group": "گروه خالی است", 41 | "radio_empty": "مقداری انتخاب نشده است", 42 | "checkbox_empty": "مقداری انتخاب نشده است", 43 | "select_empty": "مقداری انتخاب نشده است", 44 | "string_empty": "مقدار متنی خالی است", 45 | "string_exceed_min_length": "رشته حداقل باید {0} عدد حرف داشته باشد", 46 | "string_exceed_max_length": "رشته حداکثر {0} عدد حرف می تواند قبول کند", 47 | "string_invalid_format": "قالب رشته {0} نامعتبر ست", 48 | "number_nan": "عدد وارد کنید", 49 | "number_not_integer": "مقدار صحیح وارد کنید", 50 | "number_not_double": "مقدار اعشاری وارد کنید", 51 | "number_exceed_min": "باید از {0} بزرگتر باشد", 52 | "number_exceed_max": "باید از {0} کمتر باشد", 53 | "number_wrong_step": "باید مضربی از {0} باشد", 54 | "datetime_empty": "مقدار تاریخ خالی وارد شده!", 55 | "datetime_invalid": "قالب تاریخ ( {0} ) اشتباه است", 56 | "datetime_exceed_min": "باید بعد از {0} باشد", 57 | "datetime_exceed_max": "باید قبل از {0} باشد", 58 | "boolean_not_valid": "مقدار دودویی وارد کنید", 59 | "operator_not_multiple": "اپراتور \"{1}\" نمی تواند چند مقدار قبول کند" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/i18n/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "__locale": "French (fr)", 3 | "__author": "Damien \"Mistic\" Sorel, http://www.strangeplanet.fr", 4 | 5 | "add_rule": "Ajouter une règle", 6 | "add_group": "Ajouter un groupe", 7 | "delete_rule": "Supprimer", 8 | "delete_group": "Supprimer", 9 | 10 | "conditions": { 11 | "AND": "ET", 12 | "OR": "OU" 13 | }, 14 | 15 | "operators": { 16 | "equal": "est égal à", 17 | "not_equal": "n'est pas égal à", 18 | "in": "est compris dans", 19 | "not_in": "n'est pas compris dans", 20 | "less": "est inférieur à", 21 | "less_or_equal": "est inférieur ou égal à", 22 | "greater": "est supérieur à", 23 | "greater_or_equal": "est supérieur ou égal à", 24 | "between": "est entre", 25 | "not_between": "n'est pas entre", 26 | "begins_with": "commence par", 27 | "not_begins_with": "ne commence pas par", 28 | "contains": "contient", 29 | "not_contains": "ne contient pas", 30 | "ends_with": "finit par", 31 | "not_ends_with": "ne finit pas par", 32 | "is_empty": "est vide", 33 | "is_not_empty": "n'est pas vide", 34 | "is_null": "est nul", 35 | "is_not_null": "n'est pas nul" 36 | }, 37 | 38 | "errors": { 39 | "no_filter": "Aucun filtre sélectionné", 40 | "empty_group": "Le groupe est vide", 41 | "radio_empty": "Pas de valeur selectionnée", 42 | "checkbox_empty": "Pas de valeur selectionnée", 43 | "select_empty": "Pas de valeur selectionnée", 44 | "string_empty": "Valeur vide", 45 | "string_exceed_min_length": "Doit contenir au moins {0} caractères", 46 | "string_exceed_max_length": "Ne doit pas contenir plus de {0} caractères", 47 | "string_invalid_format": "Format invalide ({0})", 48 | "number_nan": "N'est pas un nombre", 49 | "number_not_integer": "N'est pas un entier", 50 | "number_not_double": "N'est pas un nombre réel", 51 | "number_exceed_min": "Doit être plus grand que {0}", 52 | "number_exceed_max": "Doit être plus petit que {0}", 53 | "number_wrong_step": "Doit être un multiple de {0}", 54 | "number_between_invalid": "Valeurs invalides, {0} est plus grand que {1}", 55 | "datetime_empty": "Valeur vide", 56 | "datetime_invalid": "Fomat de date invalide ({0})", 57 | "datetime_exceed_min": "Doit être après {0}", 58 | "datetime_exceed_max": "Doit être avant {0}", 59 | "datetime_between_invalid": "Valeurs invalides, {0} est plus grand que {1}", 60 | "boolean_not_valid": "N'est pas un booléen", 61 | "operator_not_multiple": "L'opérateur \"{1}\" ne peut utiliser plusieurs valeurs" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/i18n/he.json: -------------------------------------------------------------------------------- 1 | { 2 | "__locale": "Hebrew (he)", 3 | "__author": "Kfir Stri https://github.com/kfirstri", 4 | 5 | "add_rule": "הוסף כלל", 6 | "add_group": "הוסף קבוצה", 7 | "delete_rule": "מחק", 8 | "delete_group": "מחק", 9 | 10 | "conditions": { 11 | "AND": "וגם", 12 | "OR": "או" 13 | }, 14 | 15 | "operators": { 16 | "equal": "שווה ל", 17 | "not_equal": "שונה מ", 18 | "in": "חלק מ", 19 | "not_in": "לא חלק מ", 20 | "less": "פחות מ", 21 | "less_or_equal": "פחות או שווה ל", 22 | "greater": "גדול מ", 23 | "greater_or_equal": "גדול או שווה ל", 24 | "between": "בין", 25 | "not_between": "לא בין", 26 | "begins_with": "מתחיל ב", 27 | "not_begins_with": "לא מתחיל ב", 28 | "contains": "מכיל", 29 | "not_contains": "לא מכיל", 30 | "ends_with": "מסתיים ב", 31 | "not_ends_with": "לא מסתיים ב", 32 | "is_empty": "ריק", 33 | "is_not_empty": "לא ריק", 34 | "is_null": "חסר ערך", 35 | "is_not_null": "לא חסר ערך" 36 | }, 37 | 38 | "errors": { 39 | "no_filter": "לא נבחרו מסננים", 40 | "empty_group": "הקבוצה רירקה", 41 | "radio_empty": "לא נבחר אף ערך", 42 | "checkbox_empty": "לא נבחר אף ערך", 43 | "select_empty": "לא נבחר אף ערך", 44 | "string_empty": "חסר ערך", 45 | "string_exceed_min_length": "המחרוזת חייבת להכיל לפחות {0} תווים", 46 | "string_exceed_max_length": "המחרוזת לא יכולה להכיל יותר מ{0} תווים", 47 | "string_invalid_format": "המחרוזת בפורמט שגוי ({0})", 48 | "number_nan": "זהו לא מספר", 49 | "number_not_integer": "המספר אינו מספר שלם", 50 | "number_not_double": "המספר אינו מספר עשרוני", 51 | "number_exceed_min": "המספר צריך להיות גדול מ {0}", 52 | "number_exceed_max": "המספר צריך להיות קטן מ{0}", 53 | "number_wrong_step": "המספר צריך להיות כפולה של {0}", 54 | "datetime_empty": "תאריך ריק", 55 | "datetime_invalid": "פורמט תאריך שגוי ({0})", 56 | "datetime_exceed_min": "התאריך חייב להיות אחרי {0}", 57 | "datetime_exceed_max": "התאריך חייב להיות לפני {0}", 58 | "boolean_not_valid": "זהו לא בוליאני", 59 | "operator_not_multiple": "האופרטור \"{1}\" לא יכול לקבל ערכים מרובים" 60 | } 61 | } -------------------------------------------------------------------------------- /src/i18n/hu.json: -------------------------------------------------------------------------------- 1 | { 2 | "__locale": "Hungarian - Magyar (hu)", 3 | "__author": "Szabó Attila \"Tailor993\", https://www.tailor993.hu", 4 | 5 | "add_rule": "Feltétel hozzáadása", 6 | "add_group": "Csoport hozzáadása", 7 | "delete_rule": "Feltétel törlése", 8 | "delete_group": "Csoport törlése", 9 | 10 | "conditions": { 11 | "AND": "ÉS", 12 | "OR": "VAGY" 13 | }, 14 | 15 | "operators": { 16 | "equal": "egyenlő", 17 | "not_equal": "nem egyenlő", 18 | "in": "bennevan", 19 | "not_in": "nincs benne", 20 | "less": "kisebb", 21 | "less_or_equal": "kisebb vagy egyenlő", 22 | "greater": "nagyobb", 23 | "greater_or_equal": "nagyobb vagy egyenlő", 24 | "between": "közötte", 25 | "not_between": "nincs közötte", 26 | "begins_with": "ezzel kezdődik", 27 | "not_begins_with": "ezzel nem kezdődik", 28 | "contains": "tartalmazza", 29 | "not_contains": "nem tartalmazza", 30 | "ends_with": "erre végződik", 31 | "not_ends_with": "errre nem végződik", 32 | "is_empty": "üres", 33 | "is_not_empty": "nem üres", 34 | "is_null": "null", 35 | "is_not_null": "nem null" 36 | }, 37 | 38 | "errors": { 39 | "no_filter": "Nincs kiválasztott feltétel", 40 | "empty_group": "A csoport üres", 41 | "radio_empty": "Nincs kiválasztott érték", 42 | "checkbox_empty": "Nincs kiválasztott érték", 43 | "select_empty": "Nincs kiválasztott érték", 44 | "string_empty": "Üres érték", 45 | "string_exceed_min_length": "A megadott szöveg rövidebb a várt {0} karakternél", 46 | "string_exceed_max_length": "A megadott szöveg nem tartalmazhat többet, mint {0} karaktert", 47 | "string_invalid_format": "Nem megfelelő formátum ({0})", 48 | "number_nan": "Nem szám", 49 | "number_not_integer": "Nem egész szám (integer)", 50 | "number_not_double": "Nem valós szám", 51 | "number_exceed_min": "Nagyobbnak kell lennie, mint {0}", 52 | "number_exceed_max": "Kisebbnek kell lennie, mint {0}", 53 | "number_wrong_step": "{0} többszörösének kell lennie.", 54 | "number_between_invalid": "INem megfelelő érték, {0} nagyobb, mint {1}", 55 | "datetime_empty": "Üres érték", 56 | "datetime_invalid": "nem megfelelő dátum formátum ({0})", 57 | "datetime_exceed_min": "A dátumnak későbbinek kell lennie, mint{0}", 58 | "datetime_exceed_max": "A dátumnak korábbinak kell lennie, mint {0}", 59 | "datetime_between_invalid": "Nem megfelelő értékek, {0} nagyobb, mint {1}", 60 | "boolean_not_valid": "Nem igaz/hamis (boolean)", 61 | "operator_not_multiple": "Ez a művelet: \"{1}\" nem fogadhat el több értéket" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/i18n/it.json: -------------------------------------------------------------------------------- 1 | { 2 | "__locale": "Italian (it)", 3 | "__author": "davegraziosi, Giuseppe Lodi Rizzini", 4 | 5 | "add_rule": "Aggiungi regola", 6 | "add_group": "Aggiungi gruppo", 7 | "delete_rule": "Elimina", 8 | "delete_group": "Elimina", 9 | 10 | "conditions": { 11 | "AND": "E", 12 | "OR": "O" 13 | }, 14 | 15 | "operators": { 16 | "equal": "uguale", 17 | "not_equal": "non uguale", 18 | "in": "in", 19 | "not_in": "non in", 20 | "less": "minore", 21 | "less_or_equal": "minore o uguale", 22 | "greater": "maggiore", 23 | "greater_or_equal": "maggiore o uguale", 24 | "between" : "compreso tra", 25 | "not_between" : "non compreso tra", 26 | "begins_with": "inizia con", 27 | "not_begins_with": "non inizia con", 28 | "contains": "contiene", 29 | "not_contains": "non contiene", 30 | "ends_with": "finisce con", 31 | "not_ends_with": "non finisce con", 32 | "is_empty": "è vuoto", 33 | "is_not_empty": "non è vuoto", 34 | "is_null": "è nullo", 35 | "is_not_null": "non è nullo" 36 | }, 37 | 38 | "errors": { 39 | "no_filter": "Nessun filtro selezionato", 40 | "empty_group": "Il gruppo è vuoto", 41 | "radio_empty": "No value selected", 42 | "checkbox_empty": "Nessun valore selezionato", 43 | "select_empty": "Nessun valore selezionato", 44 | "string_empty": "Valore vuoto", 45 | "string_exceed_min_length": "Deve contenere almeno {0} caratteri", 46 | "string_exceed_max_length": "Non deve contenere più di {0} caratteri", 47 | "string_invalid_format": "Formato non valido ({0})", 48 | "number_nan": "Non è un numero", 49 | "number_not_integer": "Non è un intero", 50 | "number_not_double": "Non è un numero con la virgola", 51 | "number_exceed_min": "Deve essere maggiore di {0}", 52 | "number_exceed_max": "Deve essere minore di {0}", 53 | "number_wrong_step": "Deve essere multiplo di {0}", 54 | "number_between_invalid": "Valori non validi, {0} è maggiore di {1}", 55 | "datetime_empty": "Valore vuoto", 56 | "datetime_invalid": "Formato data non valido ({0})", 57 | "datetime_exceed_min": "Deve essere successivo a {0}", 58 | "datetime_exceed_max": "Deve essere precedente a {0}", 59 | "datetime_between_invalid": "Valori non validi, {0} è maggiore di {1}", 60 | "boolean_not_valid": "Non è un booleano", 61 | "operator_not_multiple": "L'Operatore {0} non può accettare valori multipli" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/i18n/lt.json: -------------------------------------------------------------------------------- 1 | { 2 | "__locale": "Lithuanian (lt)", 3 | "__author": "Dalius Guzauskas (aka Tichij), https://lt.linkedin.com/in/daliusg", 4 | 5 | "add_rule": "Pridėti taisyklę", 6 | "add_group": "Pridėti grupę", 7 | "delete_rule": "Ištrinti", 8 | "delete_group": "Ištrinti", 9 | 10 | "conditions": { 11 | "AND": "IR", 12 | "OR": "ARBA" 13 | }, 14 | 15 | "operators": { 16 | "equal": "lygu", 17 | "not_equal": "nėra lygu", 18 | "in": "iš nurodytų", 19 | "not_in": "ne iš nurodytų", 20 | "less": "mažiau", 21 | "less_or_equal": "mažiau arba lygu", 22 | "greater": "daugiau", 23 | "greater_or_equal": "daugiau arba lygu", 24 | "between": "tarp", 25 | "not_between": "nėra tarp", 26 | "begins_with": "prasideda", 27 | "not_begins_with": "neprasideda", 28 | "contains": "turi", 29 | "not_contains": "neturi", 30 | "ends_with": "baigiasi", 31 | "not_ends_with": "nesibaigia", 32 | "is_empty": "tuščia", 33 | "is_not_empty": "ne tuščia", 34 | "is_null": "neapibrėžta", 35 | "is_not_null": "nėra neapibrėžta" 36 | }, 37 | 38 | "errors": { 39 | "no_filter": "Nepasirinktas filtras", 40 | "empty_group": "Grupė tuščia", 41 | "radio_empty": "Nepasirinkta reikšmė", 42 | "checkbox_empty": "Nepasirinkta reikšmė", 43 | "select_empty": "Nepasirinkta reikšmė", 44 | "string_empty": "Tuščia reikšmė", 45 | "string_exceed_min_length": "Turi būti bent {0} simbolių", 46 | "string_exceed_max_length": "Turi būti ne daugiau kaip {0} simbolių", 47 | "string_invalid_format": "Klaidingas formatas ({0})", 48 | "number_nan": "Nėra skaičius", 49 | "number_not_integer": "Ne sveikasis skaičius", 50 | "number_not_double": "Ne realusis skaičius", 51 | "number_exceed_min": "Turi būti daugiau už {0}", 52 | "number_exceed_max": "Turi būti mažiau už {0}", 53 | "number_wrong_step": "Turi būti {0} kartotinis", 54 | "number_between_invalid": "Klaidingos reikšmės, {0} yra daugiau už {1}", 55 | "datetime_empty": "Tuščia reikšmė", 56 | "datetime_invalid": "Klaidingas datos formatas ({0})", 57 | "datetime_exceed_min": "Turi būti po {0}", 58 | "datetime_exceed_max": "Turi būti prieš {0}", 59 | "datetime_between_invalid": "Klaidingos reikšmės, {0} yra daugiau už {1}", 60 | "boolean_not_valid": "Nėra loginis tipas", 61 | "operator_not_multiple": "Operatorius \"{1}\" negali priimti kelių reikšmių" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/i18n/nl.json: -------------------------------------------------------------------------------- 1 | { 2 | "__locale": "Dutch (nl)", 3 | "__author": "\"Roywcm\"", 4 | 5 | "add_rule": "Nieuwe regel", 6 | "add_group": "Nieuwe groep", 7 | "delete_rule": "Verwijder", 8 | "delete_group": "Verwijder", 9 | 10 | "conditions": { 11 | "AND": "EN", 12 | "OR": "OF" 13 | }, 14 | 15 | "operators": { 16 | "equal": "gelijk", 17 | "not_equal": "niet gelijk", 18 | "in": "in", 19 | "not_in": "niet in", 20 | "less": "minder", 21 | "less_or_equal": "minder of gelijk", 22 | "greater": "groter", 23 | "greater_or_equal": "groter of gelijk", 24 | "between": "tussen", 25 | "not_between": "niet tussen", 26 | "begins_with": "begint met", 27 | "not_begins_with": "begint niet met", 28 | "contains": "bevat", 29 | "not_contains": "bevat niet", 30 | "ends_with": "eindigt met", 31 | "not_ends_with": "eindigt niet met", 32 | "is_empty": "is leeg", 33 | "is_not_empty": "is niet leeg", 34 | "is_null": "is null", 35 | "is_not_null": "is niet null" 36 | }, 37 | 38 | "errors": { 39 | "no_filter": "Geen filter geselecteerd", 40 | "empty_group": "De groep is leeg", 41 | "radio_empty": "Geen waarde geselecteerd", 42 | "checkbox_empty": "Geen waarde geselecteerd", 43 | "select_empty": "Geen waarde geselecteerd", 44 | "string_empty": "Lege waarde", 45 | "string_exceed_min_length": "Dient minstens {0} karakters te bevatten", 46 | "string_exceed_max_length": "Dient niet meer dan {0} karakters te bevatten", 47 | "string_invalid_format": "Ongeldig format ({0})", 48 | "number_nan": "Niet een nummer", 49 | "number_not_integer": "Geen geheel getal", 50 | "number_not_double": "Geen echt nummer", 51 | "number_exceed_min": "Dient groter te zijn dan {0}", 52 | "number_exceed_max": "Dient lager te zijn dan {0}", 53 | "number_wrong_step": "Dient een veelvoud te zijn van {0}", 54 | "datetime_invalid": "Ongeldige datumformat ({0})", 55 | "datetime_exceed_min": "Dient na {0}", 56 | "datetime_exceed_max": "Dient voor {0}" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/i18n/no.json: -------------------------------------------------------------------------------- 1 | { 2 | "__locale": "Norwegian (no)", 3 | "__author": "Jna Borup Coyle, github@coyle.dk", 4 | 5 | "add_rule": "Legg til regel", 6 | "add_group": "Legg til gruppe", 7 | "delete_rule": "Slett regel", 8 | "delete_group": "Slett gruppe", 9 | 10 | "conditions": { 11 | "AND": "OG", 12 | "OR": "ELLER" 13 | }, 14 | 15 | "operators": { 16 | "equal": "er lik", 17 | "not_equal": "er ikke lik", 18 | "in": "finnes i", 19 | "not_in": "finnes ikke i", 20 | "less": "er mindre enn", 21 | "less_or_equal": "er mindre eller lik", 22 | "greater": "er større enn", 23 | "greater_or_equal": "er større eller lik", 24 | "begins_with": "begynner med", 25 | "not_begins_with": "begynner ikke med", 26 | "contains": "inneholder", 27 | "not_contains": "inneholder ikke", 28 | "ends_with": "slutter med", 29 | "not_ends_with": "slutter ikke med", 30 | "is_empty": "er tom", 31 | "is_not_empty": "er ikke tom", 32 | "is_null": "er null", 33 | "is_not_null": "er ikke null" 34 | } 35 | } -------------------------------------------------------------------------------- /src/i18n/pl.json: -------------------------------------------------------------------------------- 1 | { 2 | "__locale": "Polish (pl)", 3 | "__author": "Artur Smolarek", 4 | 5 | "add_rule": "Dodaj regułę", 6 | "add_group": "Dodaj grupę", 7 | "delete_rule": "Usuń", 8 | "delete_group": "Usuń", 9 | 10 | "conditions": { 11 | "AND": "ORAZ", 12 | "OR": "LUB" 13 | }, 14 | 15 | "operators": { 16 | "equal": "równa się", 17 | "not_equal": "jest różne od", 18 | "in": "zawiera", 19 | "not_in": "nie zawiera", 20 | "less": "mniejsze", 21 | "less_or_equal": "mniejsze lub równe", 22 | "greater": "większe", 23 | "greater_or_equal": "większe lub równe", 24 | "between": "pomiędzy", 25 | "not_between": "nie jest pomiędzy", 26 | "begins_with": "rozpoczyna się od", 27 | "not_begins_with": "nie rozpoczyna się od", 28 | "contains": "zawiera", 29 | "not_contains": "nie zawiera", 30 | "ends_with": "kończy się na", 31 | "not_ends_with": "nie kończy się na", 32 | "is_empty": "jest puste", 33 | "is_not_empty": "nie jest puste", 34 | "is_null": "jest niezdefiniowane", 35 | "is_not_null": "nie jest niezdefiniowane" 36 | }, 37 | 38 | "errors": { 39 | "no_filter": "Nie wybrano żadnego filtra", 40 | "empty_group": "Grupa jest pusta", 41 | "radio_empty": "Nie wybrano wartości", 42 | "checkbox_empty": "Nie wybrano wartości", 43 | "select_empty": "Nie wybrano wartości", 44 | "string_empty": "Nie wpisano wartości", 45 | "string_exceed_min_length": "Minimalna długość to {0} znaków", 46 | "string_exceed_max_length": "Maksymalna długość to {0} znaków", 47 | "string_invalid_format": "Nieprawidłowy format ({0})", 48 | "number_nan": "To nie jest liczba", 49 | "number_not_integer": "To nie jest liczba całkowita", 50 | "number_not_double": "To nie jest liczba rzeczywista", 51 | "number_exceed_min": "Musi być większe niż {0}", 52 | "number_exceed_max": "Musi być mniejsze niż {0}", 53 | "number_wrong_step": "Musi być wielokrotnością {0}", 54 | "datetime_empty": "Nie wybrano wartości", 55 | "datetime_invalid": "Nieprawidłowy format daty ({0})", 56 | "datetime_exceed_min": "Musi być po {0}", 57 | "datetime_exceed_max": "Musi być przed {0}", 58 | "boolean_not_valid": "Niepoprawna wartość logiczna", 59 | "operator_not_multiple": "Operator \"{1}\" nie przyjmuje wielu wartości" 60 | } 61 | } -------------------------------------------------------------------------------- /src/i18n/pt-BR.json: -------------------------------------------------------------------------------- 1 | { 2 | "__locale": "Brazilian Portuguese (pr-BR)", 3 | "__author": "Leandro Gehlen, leandrogehlen@gmail.com; Marcos Ferretti, marcosvferretti@gmail.com", 4 | 5 | "add_rule": "Nova Regra", 6 | "add_group": "Novo Grupo", 7 | "delete_rule": "Excluir", 8 | "delete_group": "Excluir", 9 | 10 | "conditions": { 11 | "AND": "E", 12 | "OR": "OU" 13 | }, 14 | 15 | "operators": { 16 | "equal": "Igual", 17 | "not_equal": "Diferente", 18 | "in": "Contido", 19 | "not_in": "Não contido", 20 | "less": "Menor", 21 | "less_or_equal": "Menor ou igual", 22 | "greater": "Maior", 23 | "greater_or_equal": "Maior ou igual", 24 | "between": "Entre", 25 | "not_between": "Não entre", 26 | "begins_with": "Iniciando com", 27 | "not_begins_with": "Não iniciando com", 28 | "contains": "Contém", 29 | "not_contains": "Não contém", 30 | "ends_with": "Terminando com", 31 | "not_ends_with": "Terminando sem", 32 | "is_empty": "É vazio", 33 | "is_not_empty": "Não é vazio", 34 | "is_null": "É nulo", 35 | "is_not_null": "Não é nulo" 36 | }, 37 | 38 | "errors": { 39 | "no_filter": "Nenhum filtro selecionado", 40 | "empty_group": "O grupo está vazio", 41 | "radio_empty": "Nenhum valor selecionado", 42 | "checkbox_empty": "Nenhum valor selecionado", 43 | "select_empty": "Nenhum valor selecionado", 44 | "string_empty": "Valor vazio", 45 | "string_exceed_min_length": "É necessário conter pelo menos {0} caracteres", 46 | "string_exceed_max_length": "É necessário conter mais de {0} caracteres", 47 | "string_invalid_format": "Formato inválido ({0})", 48 | "number_nan": "Não é um número", 49 | "number_not_integer": "Não é um número inteiro", 50 | "number_not_double": "Não é um número real", 51 | "number_exceed_min": "É necessário ser maior que {0}", 52 | "number_exceed_max": "É necessário ser menor que {0}", 53 | "number_wrong_step": "É necessário ser múltiplo de {0}", 54 | "datetime_invalid": "Formato de data inválido ({0})", 55 | "datetime_exceed_min": "É necessário ser superior a {0}", 56 | "datetime_exceed_max": "É necessário ser inferior a {0}", 57 | "datetime_empty": "Nenhuma data selecionada", 58 | "boolean_not_valid": "Não é um valor booleano", 59 | "operator_not_multiple": "O operador \"{1}\" não aceita valores múltiplos" 60 | } 61 | } -------------------------------------------------------------------------------- /src/i18n/pt-PT.json: -------------------------------------------------------------------------------- 1 | { 2 | "__locale": "Portuguese (pt-PT)", 3 | "__author": "Miguel Guerreiro, migas.csi@gmail.com", 4 | 5 | "add_rule": "Nova Regra", 6 | "add_group": "Novo Grupo", 7 | "delete_rule": "Excluir", 8 | "delete_group": "Excluir", 9 | 10 | "conditions": { 11 | "AND": "E", 12 | "OR": "OU" 13 | }, 14 | 15 | "operators": { 16 | "equal": "Igual a", 17 | "not_equal": "Diferente de", 18 | "in": "Contido", 19 | "not_in": "Não contido", 20 | "less": "Menor que", 21 | "less_or_equal": "Menor ou igual a", 22 | "greater": "Maior que", 23 | "greater_or_equal": "Maior ou igual que", 24 | "between": "Entre", 25 | "begins_with": "Começar por", 26 | "not_begins_with": "Não a começar por", 27 | "contains": "Contém", 28 | "not_contains": "Não contém", 29 | "ends_with": "Terminando com", 30 | "not_ends_with": "Terminando sem", 31 | "is_empty": "É vazio", 32 | "is_not_empty": "Não é vazio", 33 | "is_null": "É nulo", 34 | "is_not_null": "Não é nulo" 35 | }, 36 | 37 | "errors": { 38 | "no_filter": "Nenhum filtro selecionado", 39 | "empty_group": "O grupo está vazio", 40 | "radio_empty": "Nenhum valor selecionado", 41 | "checkbox_empty": "Nenhum valor selecionado", 42 | "select_empty": "Nenhum valor selecionado", 43 | "string_empty": "Valor vazio", 44 | "string_exceed_min_length": "É necessário conter pelo menos {0} caracteres", 45 | "string_exceed_max_length": "É necessário conter mais de {0} caracteres", 46 | "string_invalid_format": "Formato inválido ({0})", 47 | "number_nan": "Não é um número", 48 | "number_not_integer": "Não é um número inteiro", 49 | "number_not_double": "Não é um número real", 50 | "number_exceed_min": "É necessário ser maior que {0}", 51 | "number_exceed_max": "É necessário ser menor que {0}", 52 | "number_wrong_step": "É necessário ser múltiplo de {0}", 53 | "datetime_invalid": "Formato de data inválido ({0})", 54 | "datetime_exceed_min": "É necessário ser superior a {0}", 55 | "datetime_exceed_max": "É necessário ser inferior a {0}" 56 | } 57 | } -------------------------------------------------------------------------------- /src/i18n/ro.json: -------------------------------------------------------------------------------- 1 | { 2 | "__locale": "Romanian (ro)", 3 | "__author": "ArianServ, totpero", 4 | 5 | "add_rule": "Adaugă regulă", 6 | "add_group": "Adaugă grup", 7 | "delete_rule": "Şterge", 8 | "delete_group": "Şterge", 9 | 10 | "conditions": { 11 | "AND": "ŞI", 12 | "OR": "SAU" 13 | }, 14 | 15 | "operators": { 16 | "equal": "egal", 17 | "not_equal": "diferit", 18 | "in": "în", 19 | "not_in": "nu în", 20 | "less": "mai mic", 21 | "less_or_equal": "mai mic sau egal", 22 | "greater": "mai mare", 23 | "greater_or_equal": "mai mare sau egal", 24 | "between": "între", 25 | "not_between": "nu între", 26 | "begins_with": "începe cu", 27 | "not_begins_with": "nu începe cu", 28 | "contains": "conţine", 29 | "not_contains": "nu conţine", 30 | "ends_with": "se termină cu", 31 | "not_ends_with": "nu se termină cu", 32 | "is_empty": "este gol", 33 | "is_not_empty": "nu este gol", 34 | "is_null": "e nul", 35 | "is_not_null": "nu e nul" 36 | }, 37 | 38 | "errors": { 39 | "no_filter": "Nici un filtru selectat", 40 | "empty_group": "Grupul este gol", 41 | "radio_empty": "Nici o valoare nu este selectată", 42 | "checkbox_empty": "Nici o valoare nu este selectată", 43 | "select_empty": "Nici o valoare nu este selectată", 44 | "string_empty": "Valoare goală", 45 | "string_exceed_min_length": "Trebuie să conţină mai puţin de {0} caractere", 46 | "string_exceed_max_length": "Trebuie să conţină mai mult de {0} caractere", 47 | "string_invalid_format": "Format invalid ({0})", 48 | "number_nan": "Nu este număr", 49 | "number_not_integer": "Nu este număr întreg", 50 | "number_not_double": "Nu este număr real", 51 | "number_exceed_min": "Trebuie să fie mai mare decât {0}", 52 | "number_exceed_max": "Trebuie să fie mai mic decât {0}", 53 | "number_wrong_step": "Trebuie să fie multiplu de {0}", 54 | "number_between_invalid": "Valori invalide, {0} este mai mare decât {1}", 55 | "datetime_empty": "Valoare goală", 56 | "datetime_invalid": "Format dată invalid ({0})", 57 | "datetime_exceed_min": "Trebuie să fie după {0}", 58 | "datetime_exceed_max": "Trebuie să fie înainte {0}", 59 | "datetime_between_invalid": "Valori invalide, {0} este mai mare decât {1}", 60 | "boolean_not_valid": "Nu este boolean", 61 | "operator_not_multiple": "Operatorul \"{1}\" nu poate accepta mai multe valori" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/i18n/ru.json: -------------------------------------------------------------------------------- 1 | { 2 | "__locale": "Russian (ru)", 3 | 4 | "add_rule": "Добавить", 5 | "add_group": "Добавить группу", 6 | "delete_rule": "Удалить", 7 | "delete_group": "Удалить", 8 | 9 | "conditions": { 10 | "AND": "И", 11 | "OR": "ИЛИ" 12 | }, 13 | 14 | "operators": { 15 | "equal": "равно", 16 | "not_equal": "не равно", 17 | "in": "из указанных", 18 | "not_in": "не из указанных", 19 | "less": "меньше", 20 | "less_or_equal": "меньше или равно", 21 | "greater": "больше", 22 | "greater_or_equal": "больше или равно", 23 | "between": "между", 24 | "not_between": "не между", 25 | "begins_with": "начинается с", 26 | "not_begins_with": "не начинается с", 27 | "contains": "содержит", 28 | "not_contains": "не содержит", 29 | "ends_with": "оканчивается на", 30 | "not_ends_with": "не оканчивается на", 31 | "is_empty": "пустая строка", 32 | "is_not_empty": "не пустая строка", 33 | "is_null": "пусто", 34 | "is_not_null": "не пусто" 35 | }, 36 | 37 | "errors": { 38 | "no_filter": "Фильтр не выбран", 39 | "empty_group": "Группа пуста", 40 | "radio_empty": "Не выбрано значение", 41 | "checkbox_empty": "Не выбрано значение", 42 | "select_empty": "Не выбрано значение", 43 | "string_empty": "Не заполнено", 44 | "string_exceed_min_length": "Должен содержать больше {0} символов", 45 | "string_exceed_max_length": "Должен содержать меньше {0} символов", 46 | "string_invalid_format": "Неверный формат ({0})", 47 | "number_nan": "Не число", 48 | "number_not_integer": "Не число", 49 | "number_not_double": "Не число", 50 | "number_exceed_min": "Должно быть больше {0}", 51 | "number_exceed_max": "Должно быть меньше, чем {0}", 52 | "number_wrong_step": "Должно быть кратно {0}", 53 | "number_between_invalid": "Недопустимые значения, {0} больше {1}", 54 | "datetime_empty": "Не заполнено", 55 | "datetime_invalid": "Неверный формат даты ({0})", 56 | "datetime_exceed_min": "Должно быть, после {0}", 57 | "datetime_exceed_max": "Должно быть, до {0}", 58 | "datetime_between_invalid": "Недопустимые значения, {0} больше {1}", 59 | "boolean_not_valid": "Не логическое", 60 | "operator_not_multiple": "Оператор \"{1}\" не поддерживает много значений" 61 | } 62 | } -------------------------------------------------------------------------------- /src/i18n/sk.json: -------------------------------------------------------------------------------- 1 | { 2 | "__locale": "Slovensky (sk)", 3 | "__author": "k2s", 4 | 5 | "add_rule": "Pridať podmienku", 6 | "add_group": "Pridať skupinu", 7 | "delete_rule": "Zmazať", 8 | "delete_group": "Zmazať", 9 | 10 | "conditions": { 11 | "AND": "A", 12 | "OR": "ALEBO" 13 | }, 14 | 15 | "operators": { 16 | "equal": "rovné", 17 | "not_equal": "nerovné", 18 | "in": "v", 19 | "not_in": "nie v", 20 | "less": "menej", 21 | "less_or_equal": "menej alebo rovné", 22 | "greater": "väčšie", 23 | "greater_or_equal": "väčšie alebo rovné", 24 | "between": "medzi", 25 | "not_between": "nie medzi", 26 | "begins_with": "začína na", 27 | "not_begins_with": "nezačína na", 28 | "contains": "obsahuje", 29 | "not_contains": "neobsahuje", 30 | "ends_with": "končí na", 31 | "not_ends_with": "nekončí na", 32 | "is_empty": "je prázdne", 33 | "is_not_empty": "nie je prázdne", 34 | "is_null": "je null", 35 | "is_not_null": "nie je null" 36 | }, 37 | 38 | "errors": { 39 | "no_filter": "Nie je zvolený filter", 40 | "empty_group": "Skupina je prázdna", 41 | "radio_empty": "Nie je označená hodnota", 42 | "checkbox_empty": "Nie je označená hodnota", 43 | "select_empty": "Nie je označená hodnota", 44 | "string_empty": "Prázdna hodnota", 45 | "string_exceed_min_length": "Musí obsahovať aspon {0} znakov", 46 | "string_exceed_max_length": "Nesmie obsahovať viac ako {0} znakov", 47 | "string_invalid_format": "Chybný formát ({0})", 48 | "number_nan": "Nie je číslo", 49 | "number_not_integer": "Nie je celé číslo", 50 | "number_not_double": "Nie je desatinné číslo", 51 | "number_exceed_min": "Musí byť väčšie ako {0}", 52 | "number_exceed_max": "Musí byť menšie ako {0}", 53 | "number_wrong_step": "Musí byť násobkom čísla {0}", 54 | "number_between_invalid": "Chybné hodnoty, {0} je väčšie ako {1}", 55 | "datetime_empty": "Prázdna hodnota", 56 | "datetime_invalid": "Chybný formát dátumu ({0})", 57 | "datetime_exceed_min": "Musí byť neskôr ako {0}", 58 | "datetime_exceed_max": "Musí byť skôr ako {0}", 59 | "datetime_between_invalid": "Chybné hodnoty, {0} je neskôr ako {1}", 60 | "boolean_not_valid": "Neplatné áno/nie", 61 | "operator_not_multiple": "Operátor '{1}' nepodporuje viacero hodnôt" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/i18n/sq.json: -------------------------------------------------------------------------------- 1 | { 2 | "__locale": "Albanian (sq)", 3 | "__author": "Tomor Pupovci", 4 | 5 | "add_rule": "Shto rregull", 6 | "add_group": "Shto grup", 7 | "delete_rule": "Fshij", 8 | "delete_group": "Fshij", 9 | 10 | "conditions": { 11 | "AND": "DHE", 12 | "OR": "OSE" 13 | }, 14 | 15 | "operators": { 16 | "equal": "barabartë", 17 | "not_equal": "e ndryshme prej", 18 | "in": "në", 19 | "not_in": "jo në", 20 | "less": "më e vogël", 21 | "less_or_equal": "më e vogël ose e barabartë me", 22 | "greater": "më e madhe", 23 | "greater_or_equal": "më e madhe ose e barabartë", 24 | "between": "në mes", 25 | "begins_with": "fillon me", 26 | "not_begins_with": "nuk fillon me", 27 | "contains": "përmban", 28 | "not_contains": "nuk përmban", 29 | "ends_with": "mbaron me", 30 | "not_ends_with": "nuk mbaron me", 31 | "is_empty": "është e zbrazët", 32 | "is_not_empty": "nuk është e zbrazët", 33 | "is_null": "është null", 34 | "is_not_null": "nuk është null" 35 | }, 36 | 37 | "errors": { 38 | "no_filter": "Nuk ka filter të zgjedhur", 39 | "empty_group": "Grupi është i zbrazët", 40 | "radio_empty": "Nuk ka vlerë të zgjedhur", 41 | "checkbox_empty": "Nuk ka vlerë të zgjedhur", 42 | "select_empty": "Nuk ka vlerë të zgjedhur", 43 | "string_empty": "Vlerë e zbrazët", 44 | "string_exceed_min_length": "Duhet të përmbajë së paku {0} karaktere", 45 | "string_exceed_max_length": "Nuk duhet të përmbajë më shumë se {0} karaktere", 46 | "string_invalid_format": "Format i pasaktë ({0})", 47 | "number_nan": "Nuk është numër", 48 | "number_not_integer": "Nuk është numër i plotë", 49 | "number_not_double": "Nuk është numër me presje", 50 | "number_exceed_min": "Duhet të jetë më i madh se {0}", 51 | "number_exceed_max": "Duhet të jetë më i vogël se {0}", 52 | "number_wrong_step": "Duhet të jetë shumëfish i {0}", 53 | "datetime_empty": "Vlerë e zbrazët", 54 | "datetime_invalid": "Format i pasaktë i datës ({0})", 55 | "datetime_exceed_min": "Duhet të jetë pas {0}", 56 | "datetime_exceed_max": "Duhet të jetë para {0}", 57 | "boolean_not_valid": "Nuk është boolean", 58 | "operator_not_multiple": "Operatori \"{1}\" nuk mund të pranojë vlera të shumëfishta" 59 | } 60 | } -------------------------------------------------------------------------------- /src/i18n/sv.json: -------------------------------------------------------------------------------- 1 | { 2 | "__locale": "Svenska (sv)", 3 | "__author": "hekin1", 4 | 5 | "add_rule": "Lägg till regel", 6 | "add_group": "Lägg till grupp", 7 | "delete_rule": "Ta bort", 8 | "delete_group": "Ta bort", 9 | 10 | "conditions": { 11 | "AND": "OCH", 12 | "OR": "ELLER" 13 | }, 14 | 15 | "operators": { 16 | "equal": "lika med", 17 | "not_equal": "ej lika med", 18 | "in": "en av", 19 | "not_in": "ej en av", 20 | "less": "mindre", 21 | "less_or_equal": "mindre eller lika med", 22 | "greater": "större", 23 | "greater_or_equal": "större eller lika med", 24 | "between": "mellan", 25 | "not_between": "ej mellan", 26 | "begins_with": "börjar med", 27 | "not_begins_with": "börjar inte med", 28 | "contains": "innehåller", 29 | "not_contains": "innehåller inte", 30 | "ends_with": "slutar med", 31 | "not_ends_with": "slutar inte med", 32 | "is_empty": "är tom", 33 | "is_not_empty": "är inte tom", 34 | "is_null": "är null", 35 | "is_not_null": "är inte null" 36 | }, 37 | 38 | "errors": { 39 | "no_filter": "Inget filter valt", 40 | "empty_group": "Gruppen är tom", 41 | "radio_empty": "Inget värde valt", 42 | "checkbox_empty": "Inget värde valt", 43 | "select_empty": "Inget värde valt", 44 | "string_empty": "Tomt värde", 45 | "string_exceed_min_length": "Måste innehålla minst {0} tecken", 46 | "string_exceed_max_length": "Får ej innehålla fler än {0} tecken", 47 | "string_invalid_format": "Felaktigt format ({0})", 48 | "number_nan": "Inte numeriskt", 49 | "number_not_integer": "Inte en siffra", 50 | "number_not_double": "Inte ett decimaltal", 51 | "number_exceed_min": "Måste vara större än {0}", 52 | "number_exceed_max": "Måste vara lägre än {0}", 53 | "number_wrong_step": "Måste vara en mutipel av {0}", 54 | "number_between_invalid": "Felaktiga värden, {0} är större än {1}", 55 | "datetime_empty": "Tomt värde", 56 | "datetime_invalid": "Felaktigt datumformat ({0})", 57 | "datetime_exceed_min": "Måste vara efter {0}", 58 | "datetime_exceed_max": "Måste vara före {0}", 59 | "datetime_between_invalid": "Felaktiga värden, {0} är större än {1}", 60 | "boolean_not_valid": "Inte en boolean", 61 | "operator_not_multiple": "Operatorn \"{1}\" accepterar inte flera värden" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/i18n/sw.json: -------------------------------------------------------------------------------- 1 | { 2 | "__locale": "Swahili (sw)", 3 | "__author": "Timothy Anyona", 4 | 5 | "add_rule": "Ongeza kanuni", 6 | "add_group": "Ongeza kikundi", 7 | "delete_rule": "Futa", 8 | "delete_group": "Futa", 9 | 10 | "conditions": { 11 | "AND": "NA", 12 | "OR": "AU" 13 | }, 14 | 15 | "operators": { 16 | "equal": "ni", 17 | "not_equal": "sio", 18 | "in": "mojawapo ya", 19 | "not_in": "sio mojawapo ya", 20 | "less": "isiyozidi", 21 | "less_or_equal": "isiyozidi au ni sawa na", 22 | "greater": "inayozidi", 23 | "greater_or_equal": "inayozidi au ni sawa na", 24 | "between": "kati ya", 25 | "not_between": "isiyo kati ya", 26 | "begins_with": "inaanza na", 27 | "not_begins_with": "isiyoanza na", 28 | "contains": "ina", 29 | "not_contains": "haina", 30 | "ends_with": "inaisha na", 31 | "not_ends_with": "isiyoisha na", 32 | "is_empty": "ni tupu", 33 | "is_not_empty": "sio tupu", 34 | "is_null": "ni batili", 35 | "is_not_null": "sio batili" 36 | }, 37 | 38 | "errors": { 39 | "no_filter": "Chujio halijachaguliwa", 40 | "empty_group": "Kikundi ki tupu", 41 | "radio_empty": "Thamani haijachaguliwa", 42 | "checkbox_empty": "Thamani haijachaguliwa", 43 | "select_empty": "Thamani haijachaguliwa", 44 | "string_empty": "Thamani tupu", 45 | "string_exceed_min_length": "Lazima iwe na vibambo visiopungua {0}", 46 | "string_exceed_max_length": "Haifai kuwa na vibambo zaidi ya {0}", 47 | "string_invalid_format": "Fomati batili ({0})", 48 | "number_nan": "Sio nambari", 49 | "number_not_integer": "Sio namba kamili", 50 | "number_not_double": "Sio namba desimali", 51 | "number_exceed_min": "Lazima iwe zaidi ya {0}", 52 | "number_exceed_max": "Lazima iwe chini ya {0}", 53 | "number_wrong_step": "Lazima iwe kigawe cha {0}", 54 | "number_between_invalid": "Thamani batili, {0} ni kubwa kuliko {1}", 55 | "datetime_empty": "Thamani tupu", 56 | "datetime_invalid": "Fomati tarehe batili ({0})", 57 | "datetime_exceed_min": "Lazima iwe baada ya {0}", 58 | "datetime_exceed_max": "Lazima iwe kabla ya {0}", 59 | "datetime_between_invalid": "Thamani batili, {0} ni baada ya {1}", 60 | "boolean_not_valid": "Sio buleani", 61 | "operator_not_multiple": "Opereta \"{1}\" haikubali thamani nyingi" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/i18n/tr.json: -------------------------------------------------------------------------------- 1 | { 2 | "__locale": "Turkish (tr)", 3 | "__author": "Aykut Alpgiray Ateş", 4 | 5 | "add_rule": "Kural Ekle", 6 | "add_group": "Grup Ekle", 7 | "delete_rule": "Sil", 8 | "delete_group": "Sil", 9 | 10 | "conditions": { 11 | "AND": "Ve", 12 | "OR": "Veya" 13 | }, 14 | 15 | "operators": { 16 | "equal": "eşit", 17 | "not_equal": "eşit değil", 18 | "in": "içinde", 19 | "not_in": "içinde değil", 20 | "less": "küçük", 21 | "less_or_equal": "küçük veya eşit", 22 | "greater": "büyük", 23 | "greater_or_equal": "büyük veya eşit", 24 | "between": "arasında", 25 | "not_between": "arasında değil", 26 | "begins_with": "ile başlayan", 27 | "not_begins_with": "ile başlamayan", 28 | "contains": "içeren", 29 | "not_contains": "içermeyen", 30 | "ends_with": "ile biten", 31 | "not_ends_with": "ile bitmeyen", 32 | "is_empty": "boş ise", 33 | "is_not_empty": "boş değil ise", 34 | "is_null": "var ise", 35 | "is_not_null": "yok ise" 36 | }, 37 | 38 | "errors": { 39 | "no_filter": "Bir filtre seçili değil", 40 | "empty_group": "Grup bir eleman içermiyor", 41 | "radio_empty": "Seçim yapılmalı", 42 | "checkbox_empty": "Seçim yapılmalı", 43 | "select_empty": "Seçim yapılmalı", 44 | "string_empty": "Bir metin girilmeli", 45 | "string_exceed_min_length": "En az {0} karakter girilmeli", 46 | "string_exceed_max_length": "En fazla {0} karakter girilebilir", 47 | "string_invalid_format": "Uyumsuz format ({0})", 48 | "number_nan": "Sayı değil", 49 | "number_not_integer": "Tam sayı değil", 50 | "number_not_double": "Ondalıklı sayı değil", 51 | "number_exceed_min": "Sayı {0}'den/dan daha büyük olmalı", 52 | "number_exceed_max": "Sayı {0}'den/dan daha küçük olmalı", 53 | "number_wrong_step": "{0} veya katı olmalı", 54 | "number_between_invalid": "Geçersiz değerler, {0} değeri {1} değerinden büyük", 55 | "datetime_empty": "Tarih Seçilmemiş", 56 | "datetime_invalid": "Uygun olmayan tarih formatı ({0})", 57 | "datetime_exceed_min": "{0} Tarihinden daha sonrası olmalı.", 58 | "datetime_exceed_max": "{0} Tarihinden daha öncesi olmalı.", 59 | "datetime_between_invalid": "Geçersiz değerler, {0} değeri {1} değerinden büyük", 60 | "boolean_not_valid": "Değer Doğru/Yanlış(bool) olmalı", 61 | "operator_not_multiple": "Operatör \"{1}\" birden fazla değer kabul etmiyor" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/i18n/ua.json: -------------------------------------------------------------------------------- 1 | { 2 | "__locale": "Ukrainian (ua)", 3 | "__author": "Megaplan, mborisv ", 4 | 5 | "add_rule": "Додати", 6 | "add_group": "Додати групу", 7 | "delete_rule": "Видалити", 8 | "delete_group": "Видалити", 9 | "conditions": { 10 | "AND": "І", 11 | "OR": "АБО" 12 | }, 13 | "operators": { 14 | "equal": "дорівнює", 15 | "not_equal": "не дорівнює", 16 | "in": "з вказаних", 17 | "not_in": "не з вказаних", 18 | "less": "менше", 19 | "less_or_equal": "менше або дорівнюж", 20 | "greater": "більше", 21 | "greater_or_equal": "більше або дорівнює", 22 | "between": "між", 23 | "begins_with": "починається з", 24 | "not_begins_with": "не починається з", 25 | "contains": "містить", 26 | "not_contains": "не містить", 27 | "ends_with": "закінчується на", 28 | "not_ends_with": "не не закінчується на", 29 | "is_empty": "порожній рядок", 30 | "is_not_empty": "не порожній рядок", 31 | "is_null": "порожньо", 32 | "is_not_null": "не порожньо" 33 | }, 34 | "errors": { 35 | "no_filter": "Фільтр не вибраний", 36 | "empty_group": "Група порожня", 37 | "radio_empty": "Значення не вибрано", 38 | "checkbox_empty": "Значення не вибрано", 39 | "select_empty": "Значення не вибрано", 40 | "string_empty": "Не заповнено", 41 | "string_exceed_min_length": "Повинен містити більше {0} символів", 42 | "string_exceed_max_length": "Повинен містити менше {0} символів", 43 | "string_invalid_format": "Невірний формат ({0})", 44 | "number_nan": "Не число", 45 | "number_not_integer": "Не число", 46 | "number_not_double": "Не число", 47 | "number_exceed_min": "Повинне бути більше {0}", 48 | "number_exceed_max": "Повинне бути менше, ніж {0}", 49 | "number_wrong_step": "Повинне бути кратне {0}", 50 | "datetime_empty": "Не заповнено", 51 | "datetime_invalid": "Невірний формат дати ({0})", 52 | "datetime_exceed_min": "Повинне бути, після {0}", 53 | "datetime_exceed_max": "Повинне бути, до {0}", 54 | "boolean_not_valid": "Не логічне", 55 | "operator_not_multiple": "Оператор \"{1}\" не підтримує багато значень" 56 | } 57 | } -------------------------------------------------------------------------------- /src/i18n/zh-CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "__locale": "Simplified Chinese (zh_CN)", 3 | "__author": "shadowwind, shatteredwindgo@gmail.com", 4 | 5 | "add_rule": "添加规则", 6 | "add_group": "添加组", 7 | "delete_rule": "删除", 8 | "delete_group": "删除组", 9 | 10 | "conditions": { 11 | "AND": "和", 12 | "OR": "或" 13 | }, 14 | 15 | "operators": { 16 | "equal": "等于", 17 | "not_equal": "不等于", 18 | "in": "在...之內", 19 | "not_in": "不在...之內", 20 | "less": "小于", 21 | "less_or_equal": "小于或等于", 22 | "greater": "大于", 23 | "greater_or_equal": "大于或等于", 24 | "between": "在...之间", 25 | "not_between": "不在...之间", 26 | "begins_with": "以...开始", 27 | "not_begins_with": "不以...开始", 28 | "contains": "包含以下内容", 29 | "not_contains": "不包含以下内容", 30 | "ends_with": "以...结束", 31 | "not_ends_with": "不以...结束", 32 | "is_empty": "为空", 33 | "is_not_empty": "不为空", 34 | "is_null": "为 null", 35 | "is_not_null": "不为 null" 36 | }, 37 | 38 | "errors": { 39 | "no_filter": "没有选择过滤器", 40 | "empty_group": "该组为空", 41 | "radio_empty": "没有选中项", 42 | "checkbox_empty": "没有选中项", 43 | "select_empty": "没有选中项", 44 | "string_empty": "没有输入值", 45 | "string_exceed_min_length": "必须至少包含{0}个字符", 46 | "string_exceed_max_length": "必须不超过{0}个字符", 47 | "string_invalid_format": "无效格式({0})", 48 | "number_nan": "值不是数字", 49 | "number_not_integer": "不是整数", 50 | "number_not_double": "不是浮点数", 51 | "number_exceed_min": "必须大于{0}", 52 | "number_exceed_max": "必须小于{0}", 53 | "number_wrong_step": "必须是{0}的倍数", 54 | "datetime_empty": "值为空", 55 | "datetime_invalid": "不是有效日期({0})", 56 | "datetime_exceed_min": "必须在{0}之后", 57 | "datetime_exceed_max": "必须在{0}之前", 58 | "boolean_not_valid": "不是布尔值", 59 | "operator_not_multiple": "选项\"{1}\"无法接受多个值" 60 | } 61 | } -------------------------------------------------------------------------------- /src/jquery.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The {@link http://learn.jquery.com/plugins/|jQuery Plugins} namespace 3 | * @external "jQuery.fn" 4 | */ 5 | 6 | /** 7 | * Instanciates or accesses the {@link QueryBuilder} on an element 8 | * @function 9 | * @memberof external:"jQuery.fn" 10 | * @param {*} option - initial configuration or method name 11 | * @param {...*} args - method arguments 12 | * 13 | * @example 14 | * $('#builder').queryBuilder({ /** configuration object *\/ }); 15 | * @example 16 | * $('#builder').queryBuilder('methodName', methodParam1, methodParam2); 17 | */ 18 | $.fn.queryBuilder = function(option) { 19 | if (this.length === 0) { 20 | Utils.error('Config', 'No target defined'); 21 | } 22 | if (this.length > 1) { 23 | Utils.error('Config', 'Unable to initialize on multiple target'); 24 | } 25 | 26 | var data = this.data('queryBuilder'); 27 | var options = (typeof option == 'object' && option) || {}; 28 | 29 | if (!data && option == 'destroy') { 30 | return this; 31 | } 32 | if (!data) { 33 | var builder = new QueryBuilder(this, options); 34 | this.data('queryBuilder', builder); 35 | builder.init(options.rules); 36 | } 37 | if (typeof option == 'string') { 38 | return data[option].apply(data, Array.prototype.slice.call(arguments, 1)); 39 | } 40 | 41 | return this; 42 | }; 43 | 44 | /** 45 | * @function 46 | * @memberof external:"jQuery.fn" 47 | * @see QueryBuilder 48 | */ 49 | $.fn.queryBuilder.constructor = QueryBuilder; 50 | 51 | /** 52 | * @function 53 | * @memberof external:"jQuery.fn" 54 | * @see QueryBuilder.defaults 55 | */ 56 | $.fn.queryBuilder.defaults = QueryBuilder.defaults; 57 | 58 | /** 59 | * @function 60 | * @memberof external:"jQuery.fn" 61 | * @see QueryBuilder.defaults 62 | */ 63 | $.fn.queryBuilder.extend = QueryBuilder.extend; 64 | 65 | /** 66 | * @function 67 | * @memberof external:"jQuery.fn" 68 | * @see QueryBuilder.define 69 | */ 70 | $.fn.queryBuilder.define = QueryBuilder.define; 71 | 72 | /** 73 | * @function 74 | * @memberof external:"jQuery.fn" 75 | * @see QueryBuilder.regional 76 | */ 77 | $.fn.queryBuilder.regional = QueryBuilder.regional; 78 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {object} Filter 3 | * @memberof QueryBuilder 4 | * @description See {@link http://querybuilder.js.org/index.html#filters} 5 | */ 6 | 7 | /** 8 | * @typedef {object} Operator 9 | * @memberof QueryBuilder 10 | * @description See {@link http://querybuilder.js.org/index.html#operators} 11 | */ 12 | 13 | /** 14 | * @param {jQuery} $el 15 | * @param {object} options - see {@link http://querybuilder.js.org/#options} 16 | * @constructor 17 | */ 18 | var QueryBuilder = function($el, options) { 19 | $el[0].queryBuilder = this; 20 | 21 | /** 22 | * Element container 23 | * @member {jQuery} 24 | * @readonly 25 | */ 26 | this.$el = $el; 27 | 28 | /** 29 | * Configuration object 30 | * @member {object} 31 | * @readonly 32 | */ 33 | this.settings = $.extendext(true, 'replace', {}, QueryBuilder.DEFAULTS, options); 34 | 35 | /** 36 | * Internal model 37 | * @member {Model} 38 | * @readonly 39 | */ 40 | this.model = new Model(); 41 | 42 | /** 43 | * Internal status 44 | * @member {object} 45 | * @property {string} id - id of the container 46 | * @property {boolean} generated_id - if the container id has been generated 47 | * @property {int} group_id - current group id 48 | * @property {int} rule_id - current rule id 49 | * @property {boolean} has_optgroup - if filters have optgroups 50 | * @property {boolean} has_operator_optgroup - if operators have optgroups 51 | * @readonly 52 | * @private 53 | */ 54 | this.status = { 55 | id: null, 56 | generated_id: false, 57 | group_id: 0, 58 | rule_id: 0, 59 | has_optgroup: false, 60 | has_operator_optgroup: false 61 | }; 62 | 63 | /** 64 | * List of filters 65 | * @member {QueryBuilder.Filter[]} 66 | * @readonly 67 | */ 68 | this.filters = this.settings.filters; 69 | 70 | /** 71 | * List of icons 72 | * @member {object.} 73 | * @readonly 74 | */ 75 | this.icons = this.settings.icons; 76 | 77 | /** 78 | * List of operators 79 | * @member {QueryBuilder.Operator[]} 80 | * @readonly 81 | */ 82 | this.operators = this.settings.operators; 83 | 84 | /** 85 | * List of templates 86 | * @member {object.} 87 | * @readonly 88 | */ 89 | this.templates = this.settings.templates; 90 | 91 | /** 92 | * Plugins configuration 93 | * @member {object.} 94 | * @readonly 95 | */ 96 | this.plugins = this.settings.plugins; 97 | 98 | /** 99 | * Translations object 100 | * @member {object} 101 | * @readonly 102 | */ 103 | this.lang = null; 104 | 105 | // translations : english << 'lang_code' << custom 106 | if (QueryBuilder.regional['en'] === undefined) { 107 | Utils.error('Config', '"i18n/en.js" not loaded.'); 108 | } 109 | this.lang = $.extendext(true, 'replace', {}, QueryBuilder.regional['en'], QueryBuilder.regional[this.settings.lang_code], this.settings.lang); 110 | 111 | // "allow_groups" can be boolean or int 112 | if (this.settings.allow_groups === false) { 113 | this.settings.allow_groups = 0; 114 | } 115 | else if (this.settings.allow_groups === true) { 116 | this.settings.allow_groups = -1; 117 | } 118 | 119 | // init templates 120 | Object.keys(this.templates).forEach(function(tpl) { 121 | if (!this.templates[tpl]) { 122 | this.templates[tpl] = QueryBuilder.templates[tpl]; 123 | } 124 | if (typeof this.templates[tpl] !== 'function') { 125 | throw new Error(`Template ${tpl} must be a function`); 126 | } 127 | }, this); 128 | 129 | // ensure we have a container id 130 | if (!this.$el.attr('id')) { 131 | this.$el.attr('id', 'qb_' + Math.floor(Math.random() * 99999)); 132 | this.status.generated_id = true; 133 | } 134 | this.status.id = this.$el.attr('id'); 135 | 136 | // INIT 137 | this.$el.addClass('query-builder'); 138 | 139 | this.filters = this.checkFilters(this.filters); 140 | this.operators = this.checkOperators(this.operators); 141 | this.bindEvents(); 142 | this.initPlugins(); 143 | }; 144 | 145 | $.extend(QueryBuilder.prototype, /** @lends QueryBuilder.prototype */ { 146 | /** 147 | * Triggers an event on the builder container 148 | * @param {string} type 149 | * @returns {$.Event} 150 | */ 151 | trigger: function(type) { 152 | var event = new $.Event(this._tojQueryEvent(type), { 153 | builder: this 154 | }); 155 | 156 | this.$el.triggerHandler(event, Array.prototype.slice.call(arguments, 1)); 157 | 158 | return event; 159 | }, 160 | 161 | /** 162 | * Triggers an event on the builder container and returns the modified value 163 | * @param {string} type 164 | * @param {*} value 165 | * @returns {*} 166 | */ 167 | change: function(type, value) { 168 | var event = new $.Event(this._tojQueryEvent(type, true), { 169 | builder: this, 170 | value: value 171 | }); 172 | 173 | this.$el.triggerHandler(event, Array.prototype.slice.call(arguments, 2)); 174 | 175 | return event.value; 176 | }, 177 | 178 | /** 179 | * Attaches an event listener on the builder container 180 | * @param {string} type 181 | * @param {function} cb 182 | * @returns {QueryBuilder} 183 | */ 184 | on: function(type, cb) { 185 | this.$el.on(this._tojQueryEvent(type), cb); 186 | return this; 187 | }, 188 | 189 | /** 190 | * Removes an event listener from the builder container 191 | * @param {string} type 192 | * @param {function} [cb] 193 | * @returns {QueryBuilder} 194 | */ 195 | off: function(type, cb) { 196 | this.$el.off(this._tojQueryEvent(type), cb); 197 | return this; 198 | }, 199 | 200 | /** 201 | * Attaches an event listener called once on the builder container 202 | * @param {string} type 203 | * @param {function} cb 204 | * @returns {QueryBuilder} 205 | */ 206 | once: function(type, cb) { 207 | this.$el.one(this._tojQueryEvent(type), cb); 208 | return this; 209 | }, 210 | 211 | /** 212 | * Appends `.queryBuilder` and optionally `.filter` to the events names 213 | * @param {string} name 214 | * @param {boolean} [filter=false] 215 | * @returns {string} 216 | * @private 217 | */ 218 | _tojQueryEvent: function(name, filter) { 219 | return name.split(' ').map(function(type) { 220 | return type + '.queryBuilder' + (filter ? '.filter' : ''); 221 | }).join(' '); 222 | } 223 | }); 224 | -------------------------------------------------------------------------------- /src/plugins.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module plugins 3 | */ 4 | 5 | /** 6 | * Definition of available plugins 7 | * @type {object.} 8 | */ 9 | QueryBuilder.plugins = {}; 10 | 11 | /** 12 | * Gets or extends the default configuration 13 | * @param {object} [options] - new configuration 14 | * @returns {undefined|object} nothing or configuration object (copy) 15 | */ 16 | QueryBuilder.defaults = function(options) { 17 | if (typeof options == 'object') { 18 | $.extendext(true, 'replace', QueryBuilder.DEFAULTS, options); 19 | } 20 | else if (typeof options == 'string') { 21 | if (typeof QueryBuilder.DEFAULTS[options] == 'object') { 22 | return $.extend(true, {}, QueryBuilder.DEFAULTS[options]); 23 | } 24 | else { 25 | return QueryBuilder.DEFAULTS[options]; 26 | } 27 | } 28 | else { 29 | return $.extend(true, {}, QueryBuilder.DEFAULTS); 30 | } 31 | }; 32 | 33 | /** 34 | * Registers a new plugin 35 | * @param {string} name 36 | * @param {function} fct - init function 37 | * @param {object} [def] - default options 38 | */ 39 | QueryBuilder.define = function(name, fct, def) { 40 | QueryBuilder.plugins[name] = { 41 | fct: fct, 42 | def: def || {} 43 | }; 44 | }; 45 | 46 | /** 47 | * Adds new methods to QueryBuilder prototype 48 | * @param {object.} methods 49 | */ 50 | QueryBuilder.extend = function(methods) { 51 | $.extend(QueryBuilder.prototype, methods); 52 | }; 53 | 54 | /** 55 | * Initializes plugins for an instance 56 | * @throws ConfigError 57 | * @private 58 | */ 59 | QueryBuilder.prototype.initPlugins = function() { 60 | if (!this.plugins) { 61 | return; 62 | } 63 | 64 | if ($.isArray(this.plugins)) { 65 | var tmp = {}; 66 | this.plugins.forEach(function(plugin) { 67 | tmp[plugin] = null; 68 | }); 69 | this.plugins = tmp; 70 | } 71 | 72 | Object.keys(this.plugins).forEach(function(plugin) { 73 | if (plugin in QueryBuilder.plugins) { 74 | this.plugins[plugin] = $.extend(true, {}, 75 | QueryBuilder.plugins[plugin].def, 76 | this.plugins[plugin] || {} 77 | ); 78 | 79 | QueryBuilder.plugins[plugin].fct.call(this, this.plugins[plugin]); 80 | } 81 | else { 82 | Utils.error('Config', 'Unable to find plugin "{0}"', plugin); 83 | } 84 | }, this); 85 | }; 86 | 87 | /** 88 | * Returns the config of a plugin, if the plugin is not loaded, returns the default config. 89 | * @param {string} name 90 | * @param {string} [property] 91 | * @throws ConfigError 92 | * @returns {*} 93 | */ 94 | QueryBuilder.prototype.getPluginOptions = function(name, property) { 95 | var plugin; 96 | if (this.plugins && this.plugins[name]) { 97 | plugin = this.plugins[name]; 98 | } 99 | else if (QueryBuilder.plugins[name]) { 100 | plugin = QueryBuilder.plugins[name].def; 101 | } 102 | 103 | if (plugin) { 104 | if (property) { 105 | return plugin[property]; 106 | } 107 | else { 108 | return plugin; 109 | } 110 | } 111 | else { 112 | Utils.error('Config', 'Unable to find plugin "{0}"', name); 113 | } 114 | }; 115 | -------------------------------------------------------------------------------- /src/plugins/bt-checkbox/plugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class BtCheckbox 3 | * @memberof module:plugins 4 | * @description Applies Awesome Bootstrap Checkbox for checkbox and radio inputs. 5 | * @param {object} [options] 6 | * @param {string} [options.font='bootstrap-icons'] 7 | * @param {string} [options.color='default'] 8 | */ 9 | QueryBuilder.define('bt-checkbox', function(options) { 10 | if (options.font === 'bootstrap-icons') { 11 | this.$el.addClass('bt-checkbox-bootstrap-icons'); 12 | } 13 | 14 | this.on('getRuleInput.filter', function(h, rule, name) { 15 | var filter = rule.filter; 16 | 17 | if ((filter.input === 'radio' || filter.input === 'checkbox') && !filter.plugin) { 18 | h.value = ''; 19 | 20 | if (!filter.colors) { 21 | filter.colors = {}; 22 | } 23 | if (filter.color) { 24 | filter.colors._def_ = filter.color; 25 | } 26 | 27 | var style = filter.vertical ? ' style="display:block"' : ''; 28 | var i = 0; 29 | 30 | Utils.iterateOptions(filter.values, function(key, val) { 31 | var color = filter.colors[key] || filter.colors._def_ || options.color; 32 | var id = name + '_' + (i++); 33 | 34 | h.value += `
`; 35 | }); 36 | } 37 | }); 38 | }, { 39 | font: 'bootstrap-icons', 40 | color: 'default' 41 | }); 42 | -------------------------------------------------------------------------------- /src/plugins/bt-checkbox/plugin.scss: -------------------------------------------------------------------------------- 1 | .query-builder.bt-checkbox-bootstrap-icons { 2 | .checkbox input[type='checkbox'] + label::before { 3 | outline: 0; 4 | } 5 | 6 | .checkbox input[type='checkbox']:checked + label::after { 7 | font-family: 'bootstrap-icons'; 8 | content: '\F633'; // https://icons.getbootstrap.com/icons/check-lg/ 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/plugins/bt-tooltip-errors/plugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class BtTooltipErrors 3 | * @memberof module:plugins 4 | * @description Applies Bootstrap Tooltips on validation error messages. 5 | * @param {object} [options] 6 | * @param {string} [options.placement='right'] 7 | * @throws MissingLibraryError 8 | */ 9 | QueryBuilder.define('bt-tooltip-errors', function(options) { 10 | if (! typeof bootstrap.Tooltip === "function") { 11 | alert(typeof bootstrap.Tooltip ); 12 | Utils.error('MissingLibrary', 'Bootstrap Popper is required to use "bt-tooltip-errors" plugin. Get it here: http://getbootstrap.com'); 13 | } 14 | 15 | var self = this; 16 | 17 | // add BT Tooltip data 18 | this.on('getRuleTemplate.filter getGroupTemplate.filter', function(h) { 19 | var $h = $($.parseHTML(h.value)); 20 | $h.find(QueryBuilder.selectors.error_container).attr('data-bs-toggle', 'tooltip'); 21 | h.value = $h.prop('outerHTML'); 22 | }); 23 | 24 | // init/refresh tooltip when title changes 25 | this.model.on('update', function(e, node, field) { 26 | if (field == 'error' && self.settings.display_errors) { 27 | node.$el.find(QueryBuilder.selectors.error_container).eq(0) 28 | .attr('data-bs-original-title',options).attr('data-bs-title',options).tooltip(); 29 | } 30 | }); 31 | }, { 32 | placement: 'right' 33 | }); 34 | -------------------------------------------------------------------------------- /src/plugins/bt-tooltip-errors/plugin.scss: -------------------------------------------------------------------------------- 1 | $error-tooltip-color: #F99; 2 | 3 | @if $theme-name == 'dark' { 4 | $error-tooltip-color: #F22; 5 | } 6 | 7 | .query-builder .error-container + .tooltip .tooltip-inner { 8 | color: $error-tooltip-color !important; 9 | } 10 | -------------------------------------------------------------------------------- /src/plugins/change-filters/plugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class ChangeFilters 3 | * @memberof module:plugins 4 | * @description Allows to change available filters after plugin initialization. 5 | */ 6 | 7 | QueryBuilder.extend(/** @lends module:plugins.ChangeFilters.prototype */ { 8 | /** 9 | * Change the filters of the builder 10 | * @param {boolean} [deleteOrphans=false] - delete rules using old filters 11 | * @param {QueryBuilder[]} filters 12 | * @fires module:plugins.ChangeFilters.changer:setFilters 13 | * @fires module:plugins.ChangeFilters.afterSetFilters 14 | * @throws ChangeFilterError 15 | */ 16 | setFilters: function(deleteOrphans, filters) { 17 | var self = this; 18 | 19 | if (filters === undefined) { 20 | filters = deleteOrphans; 21 | deleteOrphans = false; 22 | } 23 | 24 | filters = this.checkFilters(filters); 25 | 26 | /** 27 | * Modifies the filters before {@link module:plugins.ChangeFilters.setFilters} method 28 | * @event changer:setFilters 29 | * @memberof module:plugins.ChangeFilters 30 | * @param {QueryBuilder.Filter[]} filters 31 | * @returns {QueryBuilder.Filter[]} 32 | */ 33 | filters = this.change('setFilters', filters); 34 | 35 | var filtersIds = filters.map(function(filter) { 36 | return filter.id; 37 | }); 38 | 39 | // check for orphans 40 | if (!deleteOrphans) { 41 | (function checkOrphans(node) { 42 | node.each( 43 | function(rule) { 44 | if (rule.filter && filtersIds.indexOf(rule.filter.id) === -1) { 45 | Utils.error('ChangeFilter', 'A rule is using filter "{0}"', rule.filter.id); 46 | } 47 | }, 48 | checkOrphans 49 | ); 50 | }(this.model.root)); 51 | } 52 | 53 | // replace filters 54 | this.filters = filters; 55 | 56 | // apply on existing DOM 57 | (function updateBuilder(node) { 58 | node.each(true, 59 | function(rule) { 60 | if (rule.filter && filtersIds.indexOf(rule.filter.id) === -1) { 61 | rule.drop(); 62 | 63 | self.trigger('rulesChanged'); 64 | } 65 | else { 66 | self.createRuleFilters(rule); 67 | 68 | rule.$el.find(QueryBuilder.selectors.rule_filter).val(rule.filter ? rule.filter.id : '-1'); 69 | self.trigger('afterUpdateRuleFilter', rule); 70 | } 71 | }, 72 | updateBuilder 73 | ); 74 | }(this.model.root)); 75 | 76 | // update plugins 77 | if (this.settings.plugins) { 78 | if (this.settings.plugins['unique-filter']) { 79 | this.updateDisabledFilters(); 80 | } 81 | if (this.settings.plugins['bt-selectpicker']) { 82 | this.$el.find(QueryBuilder.selectors.rule_filter).selectpicker('render'); 83 | } 84 | } 85 | 86 | // reset the default_filter if does not exist anymore 87 | if (this.settings.default_filter) { 88 | try { 89 | this.getFilterById(this.settings.default_filter); 90 | } 91 | catch (e) { 92 | this.settings.default_filter = null; 93 | } 94 | } 95 | 96 | /** 97 | * After {@link module:plugins.ChangeFilters.setFilters} method 98 | * @event afterSetFilters 99 | * @memberof module:plugins.ChangeFilters 100 | * @param {QueryBuilder.Filter[]} filters 101 | */ 102 | this.trigger('afterSetFilters', filters); 103 | }, 104 | 105 | /** 106 | * Adds a new filter to the builder 107 | * @param {QueryBuilder.Filter|Filter[]} newFilters 108 | * @param {int|string} [position=#end] - index or '#start' or '#end' 109 | * @fires module:plugins.ChangeFilters.changer:setFilters 110 | * @fires module:plugins.ChangeFilters.afterSetFilters 111 | * @throws ChangeFilterError 112 | */ 113 | addFilter: function(newFilters, position) { 114 | if (position === undefined || position == '#end') { 115 | position = this.filters.length; 116 | } 117 | else if (position == '#start') { 118 | position = 0; 119 | } 120 | 121 | if (!$.isArray(newFilters)) { 122 | newFilters = [newFilters]; 123 | } 124 | 125 | var filters = $.extend(true, [], this.filters); 126 | 127 | // numeric position 128 | if (parseInt(position) == position) { 129 | Array.prototype.splice.apply(filters, [position, 0].concat(newFilters)); 130 | } 131 | else { 132 | // after filter by its id 133 | if (this.filters.some(function(filter, index) { 134 | if (filter.id == position) { 135 | position = index + 1; 136 | return true; 137 | } 138 | }) 139 | ) { 140 | Array.prototype.splice.apply(filters, [position, 0].concat(newFilters)); 141 | } 142 | // defaults to end of list 143 | else { 144 | Array.prototype.push.apply(filters, newFilters); 145 | } 146 | } 147 | 148 | this.setFilters(filters); 149 | }, 150 | 151 | /** 152 | * Removes a filter from the builder 153 | * @param {string|string[]} filterIds 154 | * @param {boolean} [deleteOrphans=false] delete rules using old filters 155 | * @fires module:plugins.ChangeFilters.changer:setFilters 156 | * @fires module:plugins.ChangeFilters.afterSetFilters 157 | * @throws ChangeFilterError 158 | */ 159 | removeFilter: function(filterIds, deleteOrphans) { 160 | var filters = $.extend(true, [], this.filters); 161 | if (typeof filterIds === 'string') { 162 | filterIds = [filterIds]; 163 | } 164 | 165 | filters = filters.filter(function(filter) { 166 | return filterIds.indexOf(filter.id) === -1; 167 | }); 168 | 169 | this.setFilters(deleteOrphans, filters); 170 | } 171 | }); 172 | -------------------------------------------------------------------------------- /src/plugins/chosen-selectpicker/plugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class ChosenSelectpicker 3 | * @memberof module:plugins 4 | * @descriptioon Applies chosen-js Select on filters and operators combo-boxes. 5 | * @param {object} [options] Supports all the options for chosen 6 | * @throws MissingLibraryError 7 | */ 8 | QueryBuilder.define('chosen-selectpicker', function(options) { 9 | 10 | if (!$.fn.chosen) { 11 | Utils.error('MissingLibrary', 'chosen is required to use "chosen-selectpicker" plugin. Get it here: https://github.com/harvesthq/chosen'); 12 | } 13 | 14 | if (this.settings.plugins['bt-selectpicker']) { 15 | Utils.error('Conflict', 'bt-selectpicker is already selected as the dropdown plugin. Please remove chosen-selectpicker from the plugin list'); 16 | } 17 | 18 | var Selectors = QueryBuilder.selectors; 19 | 20 | // init selectpicker 21 | this.on('afterCreateRuleFilters', function(e, rule) { 22 | rule.$el.find(Selectors.rule_filter).removeClass('form-control').chosen(options); 23 | }); 24 | 25 | this.on('afterCreateRuleOperators', function(e, rule) { 26 | if (e.builder.getOperators(rule.filter).length > 1) { 27 | rule.$el.find(Selectors.rule_operator).removeClass('form-control').chosen(options); 28 | } 29 | }); 30 | 31 | // update selectpicker on change 32 | this.on('afterUpdateRuleFilter', function(e, rule) { 33 | rule.$el.find(Selectors.rule_filter).trigger('chosen:updated'); 34 | }); 35 | 36 | this.on('afterUpdateRuleOperator', function(e, rule) { 37 | rule.$el.find(Selectors.rule_operator).trigger('chosen:updated'); 38 | }); 39 | 40 | this.on('beforeDeleteRule', function(e, rule) { 41 | rule.$el.find(Selectors.rule_filter).chosen('destroy'); 42 | rule.$el.find(Selectors.rule_operator).chosen('destroy'); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /src/plugins/filter-description/plugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class FilterDescription 3 | * @memberof module:plugins 4 | * @description Provides three ways to display a description about a filter: inline, Bootsrap Popover or Bootbox. 5 | * @param {object} [options] 6 | * @param {string} [options.icon='bi-info-circle-fill'] 7 | * @param {string} [options.mode='popover'] - inline, popover or bootbox 8 | * @throws ConfigError 9 | */ 10 | QueryBuilder.define('filter-description', function(options) { 11 | // INLINE 12 | if (options.mode === 'inline') { 13 | this.on('afterUpdateRuleFilter afterUpdateRuleOperator', function(e, rule) { 14 | var $p = rule.$el.find('p.filter-description'); 15 | var description = e.builder.getFilterDescription(rule.filter, rule); 16 | 17 | if (!description) { 18 | $p.hide(); 19 | } 20 | else { 21 | if ($p.length === 0) { 22 | $p = $($.parseHTML('

')); 23 | $p.appendTo(rule.$el); 24 | } 25 | else { 26 | $p.css('display', ''); 27 | } 28 | 29 | $p.html(' ' + description); 30 | } 31 | }); 32 | } 33 | // POPOVER 34 | else if (options.mode === 'popover') { 35 | if (!$.fn.popover || !$.fn.popover.Constructor || !$.fn.popover.Constructor.prototype.fixTitle) { 36 | Utils.error('MissingLibrary', 'Bootstrap Popover is required to use "filter-description" plugin. Get it here: http://getbootstrap.com'); 37 | } 38 | 39 | this.on('afterUpdateRuleFilter afterUpdateRuleOperator', function(e, rule) { 40 | var $b = rule.$el.find('button.filter-description'); 41 | var description = e.builder.getFilterDescription(rule.filter, rule); 42 | 43 | if (!description) { 44 | $b.hide(); 45 | 46 | if ($b.data('bs-popover')) { 47 | $b.popover('hide'); 48 | } 49 | } 50 | else { 51 | if ($b.length === 0) { 52 | $b = $($.parseHTML('')); 53 | $b.prependTo(rule.$el.find(QueryBuilder.selectors.rule_actions)); 54 | const popover = new bootstrap.Popover($b.get(0), { 55 | placement: 'left', 56 | container: 'body', 57 | html: true 58 | }) 59 | $b.on('mouseout', function() { 60 | popover('hide'); 61 | }); 62 | } 63 | else { 64 | $b.css('display', ''); 65 | } 66 | 67 | $b.data('bs-popover').options.content = description; 68 | 69 | if ($b.attr('aria-describedby')) { 70 | $b.popover('show'); 71 | } 72 | } 73 | }); 74 | } 75 | // BOOTBOX 76 | else if (options.mode === 'bootbox') { 77 | if (!('bootbox' in window)) { 78 | Utils.error('MissingLibrary', 'Bootbox is required to use "filter-description" plugin. Get it here: http://bootboxjs.com'); 79 | } 80 | 81 | this.on('afterUpdateRuleFilter afterUpdateRuleOperator', function(e, rule) { 82 | var $b = rule.$el.find('button.filter-description'); 83 | var description = e.builder.getFilterDescription(rule.filter, rule); 84 | 85 | if (!description) { 86 | $b.hide(); 87 | } 88 | else { 89 | if ($b.length === 0) { 90 | $b = $($.parseHTML('')); 91 | $b.prependTo(rule.$el.find(QueryBuilder.selectors.rule_actions)); 92 | 93 | $b.on('click', function() { 94 | bootbox.alert($b.data('description')); 95 | }); 96 | } 97 | else { 98 | $b.css('display', ''); 99 | } 100 | 101 | $b.data('description', description); 102 | } 103 | }); 104 | } 105 | }, { 106 | icon: 'bi-info-circle-fill', 107 | mode: 'popover' 108 | }); 109 | 110 | QueryBuilder.extend(/** @lends module:plugins.FilterDescription.prototype */ { 111 | /** 112 | * Returns the description of a filter for a particular rule (if present) 113 | * @param {object} filter 114 | * @param {Rule} [rule] 115 | * @returns {string} 116 | * @private 117 | */ 118 | getFilterDescription: function(filter, rule) { 119 | if (!filter) { 120 | return undefined; 121 | } 122 | else if (typeof filter.description == 'function') { 123 | return filter.description.call(this, rule); 124 | } 125 | else { 126 | return filter.description; 127 | } 128 | } 129 | }); 130 | -------------------------------------------------------------------------------- /src/plugins/filter-description/plugin.scss: -------------------------------------------------------------------------------- 1 | $description-background-color: #D9EDF7; 2 | $description-border-color: #BCE8F1; 3 | $description-text-color: #31708F; 4 | 5 | @if $theme-name == 'dark' { 6 | $description-background-color: rgba(0, 170, 255, .2); 7 | $description-text-color: #AAD1E4; 8 | $description-border-color: #346F7B; 9 | } 10 | 11 | $description-border: 1px solid $description-border-color; 12 | 13 | .query-builder p.filter-description { 14 | margin: $rule-padding 0 0 0; 15 | background: $description-background-color; 16 | border: $description-border; 17 | color: $description-text-color; 18 | border-radius: $item-border-radius; 19 | padding: #{$rule-padding * .5} $rule-padding; 20 | font-size: .8em; 21 | } 22 | -------------------------------------------------------------------------------- /src/plugins/invert/i18n/ar.json: -------------------------------------------------------------------------------- 1 | { 2 | "invert": "قَلْبُ" 3 | } 4 | -------------------------------------------------------------------------------- /src/plugins/invert/i18n/az.json: -------------------------------------------------------------------------------- 1 | { 2 | "invert": "invert" 3 | } 4 | -------------------------------------------------------------------------------- /src/plugins/invert/i18n/cs.json: -------------------------------------------------------------------------------- 1 | { 2 | "invert": "invertní" 3 | } 4 | -------------------------------------------------------------------------------- /src/plugins/invert/i18n/el.json: -------------------------------------------------------------------------------- 1 | { 2 | "invert": "Εναλλαγή" 3 | } -------------------------------------------------------------------------------- /src/plugins/invert/i18n/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "invert": "Invert" 3 | } 4 | -------------------------------------------------------------------------------- /src/plugins/invert/i18n/eo.json: -------------------------------------------------------------------------------- 1 | { 2 | "invert": "Inversigi" 3 | } 4 | -------------------------------------------------------------------------------- /src/plugins/invert/i18n/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "invert": "Inverser" 3 | } 4 | -------------------------------------------------------------------------------- /src/plugins/invert/i18n/he.json: -------------------------------------------------------------------------------- 1 | { 2 | "invert": "הפוך שאילתא" 3 | } 4 | -------------------------------------------------------------------------------- /src/plugins/invert/i18n/hu.json: -------------------------------------------------------------------------------- 1 | { 2 | "invert": "Megfordítás (Invertálás)" 3 | } 4 | -------------------------------------------------------------------------------- /src/plugins/invert/i18n/lt.json: -------------------------------------------------------------------------------- 1 | { 2 | "invert": "Invertuoti" 3 | } 4 | -------------------------------------------------------------------------------- /src/plugins/invert/i18n/pl.json: -------------------------------------------------------------------------------- 1 | { 2 | "invert": "Odwróć" 3 | } 4 | -------------------------------------------------------------------------------- /src/plugins/invert/i18n/pt-BR.json: -------------------------------------------------------------------------------- 1 | { 2 | "invert": "Inverter" 3 | } -------------------------------------------------------------------------------- /src/plugins/invert/i18n/ru.json: -------------------------------------------------------------------------------- 1 | { 2 | "invert": "Инвертировать" 3 | } 4 | -------------------------------------------------------------------------------- /src/plugins/invert/i18n/sk.json: -------------------------------------------------------------------------------- 1 | { 2 | "invert": "Invertný" 3 | } 4 | -------------------------------------------------------------------------------- /src/plugins/invert/i18n/sv.json: -------------------------------------------------------------------------------- 1 | { 2 | "invert": "Invertera" 3 | } 4 | -------------------------------------------------------------------------------- /src/plugins/invert/i18n/sw.json: -------------------------------------------------------------------------------- 1 | { 2 | "invert": "Pindua" 3 | } 4 | -------------------------------------------------------------------------------- /src/plugins/invert/i18n/tr.json: -------------------------------------------------------------------------------- 1 | { 2 | "invert": "Ters Çevir" 3 | } 4 | -------------------------------------------------------------------------------- /src/plugins/invert/i18n/ua.json: -------------------------------------------------------------------------------- 1 | { 2 | "invert": "інвертувати" 3 | } 4 | -------------------------------------------------------------------------------- /src/plugins/invert/i18n/zh-CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "invert": "倒置" 3 | } 4 | -------------------------------------------------------------------------------- /src/plugins/invert/plugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class Invert 3 | * @memberof module:plugins 4 | * @description Allows to invert a rule operator, a group condition or the entire builder. 5 | * @param {object} [options] 6 | * @param {string} [options.icon='bi-shuffle'] 7 | * @param {boolean} [options.recursive=true] 8 | * @param {boolean} [options.invert_rules=true] 9 | * @param {boolean} [options.display_rules_button=false] 10 | * @param {boolean} [options.silent_fail=false] 11 | */ 12 | QueryBuilder.define('invert', function(options) { 13 | var self = this; 14 | var Selectors = QueryBuilder.selectors; 15 | 16 | // Bind events 17 | this.on('afterInit', function() { 18 | self.$el.on('click.queryBuilder', '[data-invert=group]', function() { 19 | var $group = $(this).closest(Selectors.group_container); 20 | self.invert(self.getModel($group), options); 21 | }); 22 | 23 | if (options.display_rules_button && options.invert_rules) { 24 | self.$el.on('click.queryBuilder', '[data-invert=rule]', function() { 25 | var $rule = $(this).closest(Selectors.rule_container); 26 | self.invert(self.getModel($rule), options); 27 | }); 28 | } 29 | }); 30 | 31 | // Modify templates 32 | if (!options.disable_template) { 33 | this.on('getGroupTemplate.filter', function(h) { 34 | var $h = $($.parseHTML(h.value)); 35 | $h.find(Selectors.condition_container).after( 36 | '' 39 | ); 40 | h.value = $h.prop('outerHTML'); 41 | }); 42 | 43 | if (options.display_rules_button && options.invert_rules) { 44 | this.on('getRuleTemplate.filter', function(h) { 45 | var $h = $($.parseHTML(h.value)); 46 | $h.find(Selectors.rule_actions).prepend( 47 | '' 50 | ); 51 | h.value = $h.prop('outerHTML'); 52 | }); 53 | } 54 | } 55 | }, { 56 | icon: 'bi-shuffle', 57 | recursive: true, 58 | invert_rules: true, 59 | display_rules_button: false, 60 | silent_fail: false, 61 | disable_template: false 62 | }); 63 | 64 | QueryBuilder.defaults({ 65 | operatorOpposites: { 66 | 'equal': 'not_equal', 67 | 'not_equal': 'equal', 68 | 'in': 'not_in', 69 | 'not_in': 'in', 70 | 'less': 'greater_or_equal', 71 | 'less_or_equal': 'greater', 72 | 'greater': 'less_or_equal', 73 | 'greater_or_equal': 'less', 74 | 'between': 'not_between', 75 | 'not_between': 'between', 76 | 'begins_with': 'not_begins_with', 77 | 'not_begins_with': 'begins_with', 78 | 'contains': 'not_contains', 79 | 'not_contains': 'contains', 80 | 'ends_with': 'not_ends_with', 81 | 'not_ends_with': 'ends_with', 82 | 'is_empty': 'is_not_empty', 83 | 'is_not_empty': 'is_empty', 84 | 'is_null': 'is_not_null', 85 | 'is_not_null': 'is_null' 86 | }, 87 | 88 | conditionOpposites: { 89 | 'AND': 'OR', 90 | 'OR': 'AND' 91 | } 92 | }); 93 | 94 | QueryBuilder.extend(/** @lends module:plugins.Invert.prototype */ { 95 | /** 96 | * Invert a Group, a Rule or the whole builder 97 | * @param {Node} [node] 98 | * @param {object} [options] {@link module:plugins.Invert} 99 | * @fires module:plugins.Invert.afterInvert 100 | * @throws InvertConditionError, InvertOperatorError 101 | */ 102 | invert: function(node, options) { 103 | if (!(node instanceof Node)) { 104 | if (!this.model.root) return; 105 | options = node; 106 | node = this.model.root; 107 | } 108 | 109 | if (typeof options != 'object') options = {}; 110 | if (options.recursive === undefined) options.recursive = true; 111 | if (options.invert_rules === undefined) options.invert_rules = true; 112 | if (options.silent_fail === undefined) options.silent_fail = false; 113 | if (options.trigger === undefined) options.trigger = true; 114 | 115 | if (node instanceof Group) { 116 | // invert group condition 117 | if (this.settings.conditionOpposites[node.condition]) { 118 | node.condition = this.settings.conditionOpposites[node.condition]; 119 | } 120 | else if (!options.silent_fail) { 121 | Utils.error('InvertCondition', 'Unknown inverse of condition "{0}"', node.condition); 122 | } 123 | 124 | // recursive call 125 | if (options.recursive) { 126 | var tempOpts = $.extend({}, options, { trigger: false }); 127 | node.each(function(rule) { 128 | if (options.invert_rules) { 129 | this.invert(rule, tempOpts); 130 | } 131 | }, function(group) { 132 | this.invert(group, tempOpts); 133 | }, this); 134 | } 135 | } 136 | else if (node instanceof Rule) { 137 | if (node.operator && !node.filter.no_invert) { 138 | // invert rule operator 139 | if (this.settings.operatorOpposites[node.operator.type]) { 140 | var invert = this.settings.operatorOpposites[node.operator.type]; 141 | // check if the invert is "authorized" 142 | if (!node.filter.operators || node.filter.operators.indexOf(invert) != -1) { 143 | node.operator = this.getOperatorByType(invert); 144 | } 145 | } 146 | else if (!options.silent_fail) { 147 | Utils.error('InvertOperator', 'Unknown inverse of operator "{0}"', node.operator.type); 148 | } 149 | } 150 | } 151 | 152 | if (options.trigger) { 153 | /** 154 | * After {@link module:plugins.Invert.invert} method 155 | * @event afterInvert 156 | * @memberof module:plugins.Invert 157 | * @param {Node} node - the main group or rule that has been modified 158 | * @param {object} options 159 | */ 160 | this.trigger('afterInvert', node, options); 161 | 162 | this.trigger('rulesChanged'); 163 | } 164 | } 165 | }); 166 | -------------------------------------------------------------------------------- /src/plugins/invert/plugin.scss: -------------------------------------------------------------------------------- 1 | .query-builder { 2 | .rules-group-header [data-invert] { 3 | margin-left: 5px; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/plugins/not-group/i18n/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "NOT": "NOT" 3 | } 4 | -------------------------------------------------------------------------------- /src/plugins/not-group/i18n/eo.json: -------------------------------------------------------------------------------- 1 | { 2 | "NOT": "NE" 3 | } 4 | -------------------------------------------------------------------------------- /src/plugins/not-group/i18n/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "NOT": "NON" 3 | } 4 | -------------------------------------------------------------------------------- /src/plugins/not-group/i18n/he.json: -------------------------------------------------------------------------------- 1 | { 2 | "NOT": "לא" 3 | } 4 | -------------------------------------------------------------------------------- /src/plugins/not-group/i18n/hu.json: -------------------------------------------------------------------------------- 1 | { 2 | "NOT": "NEM" 3 | } 4 | -------------------------------------------------------------------------------- /src/plugins/not-group/i18n/lt.json: -------------------------------------------------------------------------------- 1 | { 2 | "NOT": "NE" 3 | } 4 | -------------------------------------------------------------------------------- /src/plugins/not-group/i18n/ru.json: -------------------------------------------------------------------------------- 1 | { 2 | "NOT": "НЕ" 3 | } 4 | -------------------------------------------------------------------------------- /src/plugins/not-group/i18n/sk.json: -------------------------------------------------------------------------------- 1 | { 2 | "NOT": "NIE" 3 | } 4 | -------------------------------------------------------------------------------- /src/plugins/not-group/i18n/sv.json: -------------------------------------------------------------------------------- 1 | { 2 | "NOT": "INTE" 3 | } 4 | -------------------------------------------------------------------------------- /src/plugins/not-group/i18n/sw.json: -------------------------------------------------------------------------------- 1 | { 2 | "NOT": "SIO" 3 | } 4 | -------------------------------------------------------------------------------- /src/plugins/not-group/plugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class NotGroup 3 | * @memberof module:plugins 4 | * @description Adds a "Not" checkbox in front of group conditions. 5 | * @param {object} [options] 6 | * @param {string} [options.icon_checked='bi-check2-square'] 7 | * @param {string} [options.icon_unchecked='bi-square'] 8 | */ 9 | QueryBuilder.define('not-group', function(options) { 10 | var self = this; 11 | 12 | // Bind events 13 | this.on('afterInit', function() { 14 | self.$el.on('click.queryBuilder', '[data-not=group]', function() { 15 | var $group = $(this).closest(QueryBuilder.selectors.group_container); 16 | var group = self.getModel($group); 17 | group.not = !group.not; 18 | }); 19 | 20 | self.model.on('update', function(e, node, field) { 21 | if (node instanceof Group && field === 'not') { 22 | self.updateGroupNot(node); 23 | } 24 | }); 25 | }); 26 | 27 | // Init "not" property 28 | this.on('afterAddGroup', function(e, group) { 29 | group.__.not = false; 30 | }); 31 | 32 | // Modify templates 33 | if (!options.disable_template) { 34 | this.on('getGroupTemplate.filter', function(h) { 35 | var $h = $($.parseHTML(h.value)); 36 | $h.find(QueryBuilder.selectors.condition_container).prepend( 37 | '' 40 | ); 41 | h.value = $h.prop('outerHTML'); 42 | }); 43 | } 44 | 45 | // Export "not" to JSON 46 | this.on('groupToJson.filter', function(e, group) { 47 | e.value.not = group.not; 48 | }); 49 | 50 | // Read "not" from JSON 51 | this.on('jsonToGroup.filter', function(e, json) { 52 | e.value.not = !!json.not; 53 | }); 54 | 55 | // Export "not" to SQL 56 | this.on('groupToSQL.filter', function(e, group) { 57 | if (group.not) { 58 | e.value = 'NOT ( ' + e.value + ' )'; 59 | } 60 | }); 61 | 62 | // Parse "NOT" function from sqlparser 63 | this.on('parseSQLNode.filter', function(e) { 64 | if (e.value.name && e.value.name.toUpperCase() == 'NOT') { 65 | e.value = e.value.arguments.value[0]; 66 | 67 | // if the there is no sub-group, create one 68 | if (['AND', 'OR'].indexOf(e.value.operation.toUpperCase()) === -1) { 69 | e.value = new SQLParser.nodes.Op( 70 | self.settings.default_condition, 71 | e.value, 72 | null 73 | ); 74 | } 75 | 76 | e.value.not = true; 77 | } 78 | }); 79 | 80 | // Request to create sub-group if the "not" flag is set 81 | this.on('sqlGroupsDistinct.filter', function(e, group, data, i) { 82 | if (data.not && i > 0) { 83 | e.value = true; 84 | } 85 | }); 86 | 87 | // Read "not" from parsed SQL 88 | this.on('sqlToGroup.filter', function(e, data) { 89 | e.value.not = !!data.not; 90 | }); 91 | 92 | // Export "not" to Mongo 93 | this.on('groupToMongo.filter', function(e, group) { 94 | var key = '$' + group.condition.toLowerCase(); 95 | if (group.not && e.value[key]) { 96 | e.value = { '$nor': [e.value] }; 97 | } 98 | }); 99 | 100 | // Parse "$nor" operator from Mongo 101 | this.on('parseMongoNode.filter', function(e) { 102 | var keys = Object.keys(e.value); 103 | 104 | if (keys[0] == '$nor') { 105 | e.value = e.value[keys[0]][0]; 106 | e.value.not = true; 107 | } 108 | }); 109 | 110 | // Read "not" from parsed Mongo 111 | this.on('mongoToGroup.filter', function(e, data) { 112 | e.value.not = !!data.not; 113 | }); 114 | }, { 115 | icon_unchecked: 'bi-square', 116 | icon_checked: 'bi-check2-square', 117 | disable_template: false 118 | }); 119 | 120 | /** 121 | * From {@link module:plugins.NotGroup} 122 | * @name not 123 | * @member {boolean} 124 | * @memberof Group 125 | * @instance 126 | */ 127 | Utils.defineModelProperties(Group, ['not']); 128 | 129 | QueryBuilder.selectors.group_not = QueryBuilder.selectors.group_header + ' [data-not=group]'; 130 | 131 | QueryBuilder.extend(/** @lends module:plugins.NotGroup.prototype */ { 132 | /** 133 | * Performs actions when a group's not changes 134 | * @param {Group} group 135 | * @fires module:plugins.NotGroup.afterUpdateGroupNot 136 | * @private 137 | */ 138 | updateGroupNot: function(group) { 139 | var options = this.plugins['not-group']; 140 | group.$el.find('>' + QueryBuilder.selectors.group_not) 141 | .toggleClass('active', group.not) 142 | .find('i').attr('class', group.not ? options.icon_checked : options.icon_unchecked); 143 | 144 | /** 145 | * After the group's not flag has been modified 146 | * @event afterUpdateGroupNot 147 | * @memberof module:plugins.NotGroup 148 | * @param {Group} group 149 | */ 150 | this.trigger('afterUpdateGroupNot', group); 151 | 152 | this.trigger('rulesChanged'); 153 | } 154 | }); 155 | -------------------------------------------------------------------------------- /src/plugins/sortable/plugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class Sortable 3 | * @memberof module:plugins 4 | * @description Enables drag & drop sort of rules. 5 | * @param {object} [options] 6 | * @param {boolean} [options.inherit_no_drop=true] 7 | * @param {boolean} [options.inherit_no_sortable=true] 8 | * @param {string} [options.icon='bi-sort-down'] 9 | * @throws MissingLibraryError, ConfigError 10 | */ 11 | QueryBuilder.define('sortable', function(options) { 12 | if (!('interact' in window)) { 13 | Utils.error('MissingLibrary', 'interact.js is required to use "sortable" plugin. Get it here: http://interactjs.io'); 14 | } 15 | 16 | if (options.default_no_sortable !== undefined) { 17 | Utils.error(false, 'Config', 'Sortable plugin : "default_no_sortable" options is deprecated, use standard "default_rule_flags" and "default_group_flags" instead'); 18 | this.settings.default_rule_flags.no_sortable = this.settings.default_group_flags.no_sortable = options.default_no_sortable; 19 | } 20 | 21 | // recompute drop-zones during drag (when a rule is hidden) 22 | interact.dynamicDrop(true); 23 | 24 | // set move threshold to 10px 25 | interact.pointerMoveTolerance(10); 26 | 27 | var placeholder; 28 | var ghost; 29 | var src; 30 | var moved; 31 | 32 | // Init drag and drop 33 | this.on('afterAddRule afterAddGroup', function(e, node) { 34 | if (node == placeholder) { 35 | return; 36 | } 37 | 38 | var self = e.builder; 39 | 40 | // Inherit flags 41 | if (options.inherit_no_sortable && node.parent && node.parent.flags.no_sortable) { 42 | node.flags.no_sortable = true; 43 | } 44 | if (options.inherit_no_drop && node.parent && node.parent.flags.no_drop) { 45 | node.flags.no_drop = true; 46 | } 47 | 48 | // Configure drag 49 | if (!node.flags.no_sortable) { 50 | interact(node.$el[0]) 51 | .draggable({ 52 | allowFrom: QueryBuilder.selectors.drag_handle, 53 | onstart: function(event) { 54 | moved = false; 55 | 56 | // get model of dragged element 57 | src = self.getModel(event.target); 58 | 59 | // create ghost 60 | ghost = src.$el.clone() 61 | .appendTo(src.$el.parent()) 62 | .width(src.$el.outerWidth()) 63 | .addClass('dragging'); 64 | 65 | // create drop placeholder 66 | var ph = $($.parseHTML('
 
')) 67 | .height(src.$el.outerHeight()); 68 | 69 | placeholder = src.parent.addRule(ph, src.getPos()); 70 | 71 | // hide dragged element 72 | src.$el.hide(); 73 | }, 74 | onmove: function(event) { 75 | // make the ghost follow the cursor 76 | ghost[0].style.top = event.clientY - 15 + 'px'; 77 | ghost[0].style.left = event.clientX - 15 + 'px'; 78 | }, 79 | onend: function(event) { 80 | // starting from Interact 1.3.3, onend is called before ondrop 81 | if (event.dropzone) { 82 | moveSortableToTarget(src, $(event.relatedTarget), self); 83 | moved = true; 84 | } 85 | 86 | // remove ghost 87 | ghost.remove(); 88 | ghost = undefined; 89 | 90 | // remove placeholder 91 | placeholder.drop(); 92 | placeholder = undefined; 93 | 94 | // show element 95 | src.$el.css('display', ''); 96 | 97 | /** 98 | * After a node has been moved with {@link module:plugins.Sortable} 99 | * @event afterMove 100 | * @memberof module:plugins.Sortable 101 | * @param {Node} node 102 | */ 103 | self.trigger('afterMove', src); 104 | 105 | self.trigger('rulesChanged'); 106 | } 107 | }); 108 | } 109 | 110 | if (!node.flags.no_drop) { 111 | // Configure drop on groups and rules 112 | interact(node.$el[0]) 113 | .dropzone({ 114 | accept: QueryBuilder.selectors.rule_and_group_containers, 115 | ondragenter: function(event) { 116 | moveSortableToTarget(placeholder, $(event.target), self); 117 | }, 118 | ondrop: function(event) { 119 | if (!moved) { 120 | moveSortableToTarget(src, $(event.target), self); 121 | } 122 | } 123 | }); 124 | 125 | // Configure drop on group headers 126 | if (node instanceof Group) { 127 | interact(node.$el.find(QueryBuilder.selectors.group_header)[0]) 128 | .dropzone({ 129 | accept: QueryBuilder.selectors.rule_and_group_containers, 130 | ondragenter: function(event) { 131 | moveSortableToTarget(placeholder, $(event.target), self); 132 | }, 133 | ondrop: function(event) { 134 | if (!moved) { 135 | moveSortableToTarget(src, $(event.target), self); 136 | } 137 | } 138 | }); 139 | } 140 | } 141 | }); 142 | 143 | // Detach interactables 144 | this.on('beforeDeleteRule beforeDeleteGroup', function(e, node) { 145 | if (!e.isDefaultPrevented()) { 146 | interact(node.$el[0]).unset(); 147 | 148 | if (node instanceof Group) { 149 | interact(node.$el.find(QueryBuilder.selectors.group_header)[0]).unset(); 150 | } 151 | } 152 | }); 153 | 154 | // Remove drag handle from non-sortable items 155 | this.on('afterApplyRuleFlags afterApplyGroupFlags', function(e, node) { 156 | if (node.flags.no_sortable) { 157 | node.$el.find('.drag-handle').remove(); 158 | } 159 | }); 160 | 161 | // Modify templates 162 | if (!options.disable_template) { 163 | this.on('getGroupTemplate.filter', function(h, level) { 164 | if (level > 1) { 165 | var $h = $($.parseHTML(h.value)); 166 | $h.find(QueryBuilder.selectors.condition_container).after('
'); 167 | h.value = $h.prop('outerHTML'); 168 | } 169 | }); 170 | 171 | this.on('getRuleTemplate.filter', function(h) { 172 | var $h = $($.parseHTML(h.value)); 173 | $h.find(QueryBuilder.selectors.rule_header).after('
'); 174 | h.value = $h.prop('outerHTML'); 175 | }); 176 | } 177 | }, { 178 | inherit_no_sortable: true, 179 | inherit_no_drop: true, 180 | icon: 'bi-sort-down', 181 | disable_template: false 182 | }); 183 | 184 | QueryBuilder.selectors.rule_and_group_containers = QueryBuilder.selectors.rule_container + ', ' + QueryBuilder.selectors.group_container; 185 | QueryBuilder.selectors.drag_handle = '.drag-handle'; 186 | 187 | QueryBuilder.defaults({ 188 | default_rule_flags: { 189 | no_sortable: false, 190 | no_drop: false 191 | }, 192 | default_group_flags: { 193 | no_sortable: false, 194 | no_drop: false 195 | } 196 | }); 197 | 198 | /** 199 | * Moves an element (placeholder or actual object) depending on active target 200 | * @memberof module:plugins.Sortable 201 | * @param {Node} node 202 | * @param {jQuery} target 203 | * @param {QueryBuilder} [builder] 204 | * @private 205 | */ 206 | function moveSortableToTarget(node, target, builder) { 207 | var parent, method; 208 | var Selectors = QueryBuilder.selectors; 209 | 210 | // on rule 211 | parent = target.closest(Selectors.rule_container); 212 | if (parent.length) { 213 | method = 'moveAfter'; 214 | } 215 | 216 | // on group header 217 | if (!method) { 218 | parent = target.closest(Selectors.group_header); 219 | if (parent.length) { 220 | parent = target.closest(Selectors.group_container); 221 | method = 'moveAtBegin'; 222 | } 223 | } 224 | 225 | // on group 226 | if (!method) { 227 | parent = target.closest(Selectors.group_container); 228 | if (parent.length) { 229 | method = 'moveAtEnd'; 230 | } 231 | } 232 | 233 | if (method) { 234 | node[method](builder.getModel(parent)); 235 | 236 | // refresh radio value 237 | if (builder && node instanceof Rule) { 238 | builder.setRuleInputValue(node, node.value); 239 | } 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /src/plugins/sortable/plugin.scss: -------------------------------------------------------------------------------- 1 | $placeholder-border-color: #BBB; 2 | $placeholder-border: 1px dashed $placeholder-border-color; 3 | 4 | .query-builder { 5 | .drag-handle { 6 | @extend %rule-component; 7 | cursor: move; 8 | vertical-align: middle; 9 | margin-left: 5px; 10 | } 11 | 12 | .dragging { 13 | position: fixed; 14 | opacity: .5; 15 | z-index: 100; 16 | 17 | &::before, 18 | &::after { 19 | display: none; 20 | } 21 | } 22 | 23 | .rule-placeholder { 24 | @extend %base-container; 25 | border: $placeholder-border; 26 | opacity: .7; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/plugins/unique-filter/plugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class UniqueFilter 3 | * @memberof module:plugins 4 | * @description Allows to define some filters as "unique": ie which can be used for only one rule, globally or in the same group. 5 | */ 6 | QueryBuilder.define('unique-filter', function() { 7 | this.status.used_filters = {}; 8 | 9 | this.on('afterUpdateRuleFilter', this.updateDisabledFilters); 10 | this.on('afterDeleteRule', this.updateDisabledFilters); 11 | this.on('afterCreateRuleFilters', this.applyDisabledFilters); 12 | this.on('afterReset', this.clearDisabledFilters); 13 | this.on('afterClear', this.clearDisabledFilters); 14 | 15 | // Ensure that the default filter is not already used if unique 16 | this.on('getDefaultFilter.filter', function(e, model) { 17 | var self = e.builder; 18 | 19 | self.updateDisabledFilters(); 20 | 21 | if (e.value.id in self.status.used_filters) { 22 | var found = self.filters.some(function(filter) { 23 | if (!(filter.id in self.status.used_filters) || self.status.used_filters[filter.id].length > 0 && self.status.used_filters[filter.id].indexOf(model.parent) === -1) { 24 | e.value = filter; 25 | return true; 26 | } 27 | }); 28 | 29 | if (!found) { 30 | Utils.error(false, 'UniqueFilter', 'No more non-unique filters available'); 31 | e.value = undefined; 32 | } 33 | } 34 | }); 35 | }); 36 | 37 | QueryBuilder.extend(/** @lends module:plugins.UniqueFilter.prototype */ { 38 | /** 39 | * Updates the list of used filters 40 | * @param {$.Event} [e] 41 | * @private 42 | */ 43 | updateDisabledFilters: function(e) { 44 | var self = e ? e.builder : this; 45 | 46 | self.status.used_filters = {}; 47 | 48 | if (!self.model) { 49 | return; 50 | } 51 | 52 | // get used filters 53 | (function walk(group) { 54 | group.each(function(rule) { 55 | if (rule.filter && rule.filter.unique) { 56 | if (!self.status.used_filters[rule.filter.id]) { 57 | self.status.used_filters[rule.filter.id] = []; 58 | } 59 | if (rule.filter.unique == 'group') { 60 | self.status.used_filters[rule.filter.id].push(rule.parent); 61 | } 62 | } 63 | }, function(group) { 64 | walk(group); 65 | }); 66 | }(self.model.root)); 67 | 68 | self.applyDisabledFilters(e); 69 | }, 70 | 71 | /** 72 | * Clear the list of used filters 73 | * @param {$.Event} [e] 74 | * @private 75 | */ 76 | clearDisabledFilters: function(e) { 77 | var self = e ? e.builder : this; 78 | 79 | self.status.used_filters = {}; 80 | 81 | self.applyDisabledFilters(e); 82 | }, 83 | 84 | /** 85 | * Disabled filters depending on the list of used ones 86 | * @param {$.Event} [e] 87 | * @private 88 | */ 89 | applyDisabledFilters: function(e) { 90 | var self = e ? e.builder : this; 91 | 92 | // re-enable everything 93 | self.$el.find(QueryBuilder.selectors.filter_container + ' option').prop('disabled', false); 94 | 95 | // disable some 96 | $.each(self.status.used_filters, function(filterId, groups) { 97 | if (groups.length === 0) { 98 | self.$el.find(QueryBuilder.selectors.filter_container + ' option[value="' + filterId + '"]:not(:selected)').prop('disabled', true); 99 | } 100 | else { 101 | groups.forEach(function(group) { 102 | group.each(function(rule) { 103 | rule.$el.find(QueryBuilder.selectors.filter_container + ' option[value="' + filterId + '"]:not(:selected)').prop('disabled', true); 104 | }); 105 | }); 106 | } 107 | }); 108 | 109 | // update Selectpicker 110 | if (self.settings.plugins && self.settings.plugins['bt-selectpicker']) { 111 | self.$el.find(QueryBuilder.selectors.rule_filter).selectpicker('render'); 112 | } 113 | } 114 | }); 115 | -------------------------------------------------------------------------------- /src/scss/dark.scss: -------------------------------------------------------------------------------- 1 | $theme-name: dark; 2 | 3 | $group-background-color: rgba(50, 70, 80, .5); 4 | $group-border-color: #00164A; 5 | 6 | $rule-background-color: rgba(40, 40, 40, .9); 7 | $rule-border-color: #111; 8 | 9 | $error-border-color: #800; 10 | $error-background-color: #322; 11 | 12 | $ticks-color: #222; 13 | 14 | @import 'default'; 15 | -------------------------------------------------------------------------------- /src/scss/default.scss: -------------------------------------------------------------------------------- 1 | $theme-name: default !default; 2 | 3 | // common 4 | $item-vertical-spacing: 4px !default; 5 | $item-border-radius: 5px !default; 6 | 7 | // groups 8 | $group-background-color: rgba(250, 240, 210, .5) !default; 9 | $group-border-color: #DCC896 !default; 10 | $group-border: 1px solid $group-border-color !default; 11 | $group-padding: 10px !default; 12 | 13 | // rules 14 | $rule-background-color: rgba(255, 255, 255, .9) !default; 15 | $rule-border-color: #EEE !default; 16 | $rule-border: 1px solid $rule-border-color !default; 17 | $rule-padding: 5px !default; 18 | // scss-lint:disable ColorVariable 19 | $rule-value-separator: 1px solid #DDD !default; 20 | 21 | // errors 22 | $error-icon-color: #F00 !default; 23 | $error-border-color: #F99 !default; 24 | $error-background-color: #FDD !default; 25 | 26 | // ticks 27 | $ticks-width: 2px !default; 28 | $ticks-color: #CCC !default; 29 | $ticks-position: 5px, 10px !default; 30 | 31 | 32 | // ABSTRACTS 33 | %base-container { 34 | position: relative; 35 | margin: $item-vertical-spacing 0; 36 | border-radius: $item-border-radius; 37 | padding: $rule-padding; 38 | border: $rule-border; 39 | background: $rule-background-color; 40 | } 41 | 42 | %rule-component { 43 | display: inline-block; 44 | margin: 0 5px 0 0; 45 | vertical-align: middle; 46 | } 47 | 48 | .query-builder { 49 | 50 | // GROUPS 51 | .rules-group-container { 52 | @extend %base-container; 53 | 54 | padding: $group-padding; 55 | padding-bottom: #{$group-padding - $item-vertical-spacing}; 56 | border: $group-border; 57 | background: $group-background-color; 58 | } 59 | 60 | .rules-group-header { 61 | margin-bottom: $group-padding; 62 | 63 | .group-conditions { 64 | .btn.readonly:not(.active), 65 | input[name$='_cond'] { 66 | border: 0; 67 | clip: rect(0 0 0 0); 68 | height: 1px; 69 | margin: -1px; 70 | overflow: hidden; 71 | padding: 0; 72 | position: absolute; 73 | width: 1px; 74 | white-space: nowrap; 75 | } 76 | 77 | .btn.readonly { 78 | border-radius: 3px; 79 | } 80 | } 81 | } 82 | 83 | .rules-list { 84 | list-style: none; 85 | padding: 0 0 0 #{nth($ticks-position, 1) + nth($ticks-position, 2)}; 86 | margin: 0; 87 | } 88 | 89 | // RULES 90 | .rule-container { 91 | @extend %base-container; 92 | 93 | .rule-filter-container, 94 | .rule-operator-container, 95 | .rule-value-container { 96 | @extend %rule-component; 97 | } 98 | } 99 | 100 | .rule-value-container { 101 | border-left: $rule-value-separator; 102 | padding-left: 5px; 103 | 104 | label { 105 | margin-bottom: 0; 106 | font-weight: normal; 107 | 108 | &.block { 109 | display: block; 110 | } 111 | } 112 | } 113 | 114 | // ERRORS 115 | .error-container { 116 | @extend %rule-component; 117 | display: none; 118 | cursor: help; 119 | color: $error-icon-color; 120 | } 121 | 122 | .has-error { 123 | background-color: $error-background-color; 124 | border-color: $error-border-color; 125 | 126 | .error-container { 127 | display: inline-block !important; 128 | } 129 | } 130 | 131 | // TICKS 132 | .rules-list>* { 133 | &::before, 134 | &::after { 135 | content: ''; 136 | position: absolute; 137 | left: #{-1 * nth($ticks-position, 2)}; 138 | width: nth($ticks-position, 2); 139 | height: calc(50% + #{$item-vertical-spacing}); 140 | border-color: $ticks-color; 141 | border-style: solid; 142 | } 143 | 144 | &::before { 145 | top: #{-2 * $ticks-width}; 146 | border-width: 0 0 $ticks-width $ticks-width; 147 | } 148 | 149 | &::after { 150 | top: 50%; 151 | border-width: 0 0 0 $ticks-width; 152 | } 153 | 154 | &:first-child::before { 155 | top: #{-$group-padding - $ticks-width}; 156 | height: calc(50% + #{$group-padding + $item-vertical-spacing}); 157 | } 158 | 159 | &:last-child::before { 160 | border-radius: 0 0 0 #{2 * $ticks-width}; 161 | } 162 | 163 | &:last-child::after { 164 | display: none; 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @namespace 3 | */ 4 | var Utils = {}; 5 | 6 | /** 7 | * @member {object} 8 | * @memberof QueryBuilder 9 | * @see Utils 10 | */ 11 | QueryBuilder.utils = Utils; 12 | 13 | /** 14 | * @callback Utils#OptionsIteratee 15 | * @param {string} key 16 | * @param {string} value 17 | * @param {string} [optgroup] 18 | */ 19 | 20 | /** 21 | * Iterates over radio/checkbox/selection options, it accept four formats 22 | * 23 | * @example 24 | * // array of values 25 | * options = ['one', 'two', 'three'] 26 | * @example 27 | * // simple key-value map 28 | * options = {1: 'one', 2: 'two', 3: 'three'} 29 | * @example 30 | * // array of 1-element maps 31 | * options = [{1: 'one'}, {2: 'two'}, {3: 'three'}] 32 | * @example 33 | * // array of elements 34 | * options = [{value: 1, label: 'one', optgroup: 'group'}, {value: 2, label: 'two'}] 35 | * 36 | * @param {object|array} options 37 | * @param {Utils#OptionsIteratee} tpl 38 | */ 39 | Utils.iterateOptions = function(options, tpl) { 40 | if (options) { 41 | if ($.isArray(options)) { 42 | options.forEach(function(entry) { 43 | if ($.isPlainObject(entry)) { 44 | // array of elements 45 | if ('value' in entry) { 46 | tpl(entry.value, entry.label || entry.value, entry.optgroup); 47 | } 48 | // array of one-element maps 49 | else { 50 | $.each(entry, function(key, val) { 51 | tpl(key, val); 52 | return false; // break after first entry 53 | }); 54 | } 55 | } 56 | // array of values 57 | else { 58 | tpl(entry, entry); 59 | } 60 | }); 61 | } 62 | // unordered map 63 | else { 64 | $.each(options, function(key, val) { 65 | tpl(key, val); 66 | }); 67 | } 68 | } 69 | }; 70 | 71 | /** 72 | * Replaces {0}, {1}, ... in a string 73 | * @param {string} str 74 | * @param {...*} args 75 | * @returns {string} 76 | */ 77 | Utils.fmt = function(str, args) { 78 | if (!Array.isArray(args)) { 79 | args = Array.prototype.slice.call(arguments, 1); 80 | } 81 | 82 | return str.replace(/{([0-9]+)}/g, function(m, i) { 83 | return args[parseInt(i)]; 84 | }); 85 | }; 86 | 87 | /** 88 | * Throws an Error object with custom name or logs an error 89 | * @param {boolean} [doThrow=true] 90 | * @param {string} type 91 | * @param {string} message 92 | * @param {...*} args 93 | */ 94 | Utils.error = function() { 95 | var i = 0; 96 | var doThrow = typeof arguments[i] === 'boolean' ? arguments[i++] : true; 97 | var type = arguments[i++]; 98 | var message = arguments[i++]; 99 | var args = Array.isArray(arguments[i]) ? arguments[i] : Array.prototype.slice.call(arguments, i); 100 | 101 | if (doThrow) { 102 | var err = new Error(Utils.fmt(message, args)); 103 | err.name = type + 'Error'; 104 | err.args = args; 105 | throw err; 106 | } 107 | else { 108 | console.error(type + 'Error: ' + Utils.fmt(message, args)); 109 | } 110 | }; 111 | 112 | /** 113 | * Changes the type of a value to int, float or bool 114 | * @param {*} value 115 | * @param {string} type - 'integer', 'double', 'boolean' or anything else (passthrough) 116 | * @returns {*} 117 | */ 118 | Utils.changeType = function(value, type) { 119 | if (value === '' || value === undefined) { 120 | return undefined; 121 | } 122 | 123 | switch (type) { 124 | // @formatter:off 125 | case 'integer': 126 | if (typeof value === 'string' && !/^-?\d+$/.test(value)) { 127 | return value; 128 | } 129 | return parseInt(value); 130 | case 'double': 131 | if (typeof value === 'string' && !/^-?\d+\.?\d*$/.test(value)) { 132 | return value; 133 | } 134 | return parseFloat(value); 135 | case 'boolean': 136 | if (typeof value === 'string' && !/^(0|1|true|false){1}$/i.test(value)) { 137 | return value; 138 | } 139 | return value === true || value === 1 || value.toLowerCase() === 'true' || value === '1'; 140 | default: return value; 141 | // @formatter:on 142 | } 143 | }; 144 | 145 | /** 146 | * Escapes a string like PHP's mysql_real_escape_string does 147 | * @param {string} value 148 | * @param {string} [additionalEscape] additionnal chars to escape 149 | * @returns {string} 150 | */ 151 | Utils.escapeString = function(value, additionalEscape) { 152 | if (typeof value != 'string') { 153 | return value; 154 | } 155 | 156 | var escaped = value 157 | .replace(/[\0\n\r\b\\\'\"]/g, function(s) { 158 | switch (s) { 159 | // @formatter:off 160 | case '\0': return '\\0'; 161 | case '\n': return '\\n'; 162 | case '\r': return '\\r'; 163 | case '\b': return '\\b'; 164 | case '\'': return '\'\''; 165 | default: return '\\' + s; 166 | // @formatter:off 167 | } 168 | }) 169 | // uglify compliant 170 | .replace(/\t/g, '\\t') 171 | .replace(/\x1a/g, '\\Z'); 172 | 173 | if (additionalEscape) { 174 | escaped = escaped 175 | .replace(new RegExp('[' + additionalEscape + ']', 'g'), function(s) { 176 | return '\\' + s; 177 | }); 178 | } 179 | 180 | return escaped; 181 | }; 182 | 183 | /** 184 | * Escapes a string for use in regex 185 | * @param {string} str 186 | * @returns {string} 187 | */ 188 | Utils.escapeRegExp = function(str) { 189 | return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); 190 | }; 191 | 192 | /** 193 | * Escapes a string for use in HTML element id 194 | * @param {string} str 195 | * @returns {string} 196 | */ 197 | Utils.escapeElementId = function(str) { 198 | // Regex based on that suggested by: 199 | // https://learn.jquery.com/using-jquery-core/faq/how-do-i-select-an-element-by-an-id-that-has-characters-used-in-css-notation/ 200 | // - escapes : . [ ] , 201 | // - avoids escaping already escaped values 202 | return (str) ? str.replace(/(\\)?([:.\[\],])/g, 203 | function( $0, $1, $2 ) { return $1 ? $0 : '\\' + $2; }) : str; 204 | }; 205 | 206 | /** 207 | * Sorts objects by grouping them by `key`, preserving initial order when possible 208 | * @param {object[]} items 209 | * @param {string} key 210 | * @returns {object[]} 211 | */ 212 | Utils.groupSort = function(items, key) { 213 | var optgroups = []; 214 | var newItems = []; 215 | 216 | items.forEach(function(item) { 217 | var idx; 218 | 219 | if (item[key]) { 220 | idx = optgroups.lastIndexOf(item[key]); 221 | 222 | if (idx == -1) { 223 | idx = optgroups.length; 224 | } 225 | else { 226 | idx++; 227 | } 228 | } 229 | else { 230 | idx = optgroups.length; 231 | } 232 | 233 | optgroups.splice(idx, 0, item[key]); 234 | newItems.splice(idx, 0, item); 235 | }); 236 | 237 | return newItems; 238 | }; 239 | 240 | /** 241 | * Defines properties on an Node prototype with getter and setter.
242 | * Update events are emitted in the setter through root Model (if any).
243 | * The object must have a `__` object, non enumerable property to store values. 244 | * @param {function} obj 245 | * @param {string[]} fields 246 | */ 247 | Utils.defineModelProperties = function(obj, fields) { 248 | fields.forEach(function(field) { 249 | Object.defineProperty(obj.prototype, field, { 250 | enumerable: true, 251 | get: function() { 252 | return this.__[field]; 253 | }, 254 | set: function(value) { 255 | var previousValue = (this.__[field] !== null && typeof this.__[field] == 'object') ? 256 | $.extend({}, this.__[field]) : 257 | this.__[field]; 258 | 259 | this.__[field] = value; 260 | 261 | if (this.model !== null) { 262 | /** 263 | * After a value of the model changed 264 | * @event model:update 265 | * @memberof Model 266 | * @param {Node} node 267 | * @param {string} field 268 | * @param {*} value 269 | * @param {*} previousValue 270 | */ 271 | this.model.trigger('update', this, field, value, previousValue); 272 | } 273 | } 274 | }); 275 | }); 276 | }; 277 | -------------------------------------------------------------------------------- /tests/common.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Sync load of language file once QUnit and Blanket are ready 3 | * Otherwise the language file is loaded before instrumented files 4 | */ 5 | QUnit.begin(function() { 6 | $.ajax({ 7 | async: false, 8 | url: '../dist/i18n/query-builder.en.js', 9 | dataType: 'script' 10 | }); 11 | }); 12 | 13 | /** 14 | * Add GitHub link in header 15 | */ 16 | QUnit.begin(function(){ 17 | $('#qunit-header').append( 18 | '
' + 19 | '' + 20 | '' + 21 | '' + 22 | '
' 23 | ); 24 | }); 25 | 26 | /** 27 | * Modify Blanket results display 28 | */ 29 | QUnit.done(function(){ 30 | $('#blanket-main') 31 | .css('marginTop', '10px') 32 | .addClass('col-lg-8 col-lg-push-2') 33 | .find('.bl-file a').each(function(){ 34 | this.innerHTML = this.innerHTML.replace(/(.*)\/src\/(.*)$/, '$2'); 35 | }); 36 | }); 37 | 38 | 39 | /** 40 | * Custom assert to compare rules objects 41 | */ 42 | QUnit.assert.rulesMatch = function(actual, expected, message) { 43 | var ok = (function match(a, b){ 44 | var ok = true; 45 | 46 | if (a.hasOwnProperty('valid') && b.hasOwnProperty('valid')) { 47 | ok = QUnit.equiv(a.valid, b.valid); 48 | } 49 | 50 | if (b.hasOwnProperty('data')) { 51 | if (!a.hasOwnProperty('data')) { 52 | ok = false; 53 | } 54 | else { 55 | ok = QUnit.equiv(a.data, b.data); 56 | } 57 | } 58 | 59 | if (b.hasOwnProperty('flags')) { 60 | if (!a.hasOwnProperty('flags')) { 61 | ok = false; 62 | } 63 | else { 64 | ok = QUnit.equiv(a.flags, b.flags); 65 | } 66 | } 67 | 68 | if (b.hasOwnProperty('rules')) { 69 | if (!a.hasOwnProperty('rules')) { 70 | ok = false; 71 | } 72 | else { 73 | for (var i=0, l=a.rules.length; i 2 | 3 | 4 | jQuery-QueryBuilder 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 |
72 |
73 |
74 | 75 |
76 |
77 | 78 | 79 | -------------------------------------------------------------------------------- /tests/plugins-gui.module.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | var $b = $('#builder'); 3 | 4 | QUnit.module('plugins-gui', { 5 | afterEach: function() { 6 | $b.queryBuilder('destroy'); 7 | } 8 | }); 9 | 10 | /** 11 | * Test bt-checkbox 12 | */ 13 | QUnit.test('bt-checkbox', function(assert) { 14 | $b.queryBuilder({ 15 | plugins: ['bt-checkbox'], 16 | filters: [{ 17 | id: 'no-color', 18 | type: 'integer', 19 | input: 'checkbox', 20 | values: { 21 | 10: 'foo', 22 | 20: 'bar' 23 | } 24 | }, { 25 | id: 'one-color', 26 | type: 'integer', 27 | input: 'checkbox', 28 | values: { 29 | 1: 'one', 30 | 2: 'two', 31 | 3: 'three' 32 | }, 33 | color: 'primary' 34 | }, { 35 | id: 'multi-color', 36 | type: 'integer', 37 | input: 'radio', 38 | values: { 39 | 0: 'no', 40 | 1: 'yes', 41 | 2: 'perhaps' 42 | }, 43 | colors: { 44 | 0: 'danger', 45 | 1: 'success' 46 | } 47 | }], 48 | rules: { 49 | condition: 'AND', 50 | rules: [{ 51 | id: 'no-color', 52 | value: 10 53 | }, { 54 | id: 'one-color', 55 | value: [1,2,3] 56 | }, { 57 | id: 'multi-color', 58 | value: 2 59 | }] 60 | } 61 | }); 62 | 63 | assert.ok( 64 | $('#builder_rule_0 .checkbox.checkbox-default').length == 2, 65 | 'Should have 2 checkboxes with default color' 66 | ); 67 | 68 | assert.ok( 69 | $('#builder_rule_1 .checkbox.checkbox-primary').length == 3, 70 | 'Should have 3 checkboxes with primary color' 71 | ); 72 | 73 | assert.ok( 74 | $('#builder_rule_2 .radio.radio-danger').length == 1 && 75 | $('#builder_rule_2 .radio.radio-success').length == 1 && 76 | $('#builder_rule_2 .radio.radio-default').length == 1, 77 | 'Should have 3 radios with danger, success and default colors' 78 | ); 79 | }); 80 | 81 | /** 82 | * Test chosen-selectpicker 83 | */ 84 | QUnit.test('chosen-selectpicker', function(assert) { 85 | $b.queryBuilder({ 86 | plugins: ['chosen-selectpicker'], 87 | filters: basic_filters, 88 | rules: basic_rules 89 | }); 90 | 91 | assert.ok( 92 | $b.find('.chosen-single').length == 8, 93 | 'Should have initialized chosen on all filters and operators selectors' 94 | ); 95 | }); 96 | 97 | 98 | /** 99 | * Test bt-tooltip-errors 100 | */ 101 | QUnit.test('bt-tooltip-errors', function(assert) { 102 | $b.queryBuilder({ 103 | plugins: ['bt-tooltip-errors'], 104 | filters: basic_filters, 105 | rules: { 106 | condition: 'AND', 107 | rules: [{ 108 | id: 'id', 109 | operator: 'equal', 110 | value: '' 111 | }] 112 | } 113 | }); 114 | 115 | $b.queryBuilder('validate'); 116 | 117 | assert.equal( 118 | $('#builder_group_0 .error-container').eq(0).data('bs-toggle'), 119 | 'tooltip', 120 | 'Should have added data-bs-toggle="tooltip" in the template' 121 | ); 122 | 123 | assert.equal( 124 | $('#builder_rule_0 .error-container').data('originalTitle'), 125 | 'Empty value', 126 | 'Error title should be "Empty value"' 127 | ); 128 | }); 129 | 130 | /** 131 | * Test filter-description 132 | */ 133 | QUnit.test('filter-description', function(assert) { 134 | var filters = [{ 135 | id: 'name', 136 | type: 'string', 137 | description: 'Lorem Ipsum sit amet.' 138 | }, { 139 | id: 'age', 140 | type: 'integer', 141 | description: function(rule) { 142 | return 'Description of operator ' + rule.operator.type; 143 | } 144 | }]; 145 | 146 | var rules = { 147 | condition: 'AND', 148 | rules: [{ 149 | id: 'name', 150 | value: 'Mistic' 151 | }, { 152 | id: 'age', 153 | value: 25 154 | }] 155 | }; 156 | 157 | $b.queryBuilder({ 158 | plugins: { 159 | 'filter-description': { mode: 'inline' } 160 | }, 161 | filters: filters, 162 | rules: rules 163 | }); 164 | 165 | assert.match( 166 | $('#builder_rule_0 p.filter-description').html(), 167 | new RegExp('Lorem Ipsum sit amet.'), 168 | 'Paragraph should contain filter description' 169 | ); 170 | 171 | assert.match( 172 | $('#builder_rule_1 p.filter-description').html(), 173 | new RegExp('Description of operator equal'), 174 | 'Paragraph should contain filter description after function execution' 175 | ); 176 | 177 | $b.queryBuilder('destroy'); 178 | 179 | $b.queryBuilder({ 180 | plugins: { 181 | 'filter-description': { mode: 'popover' } 182 | }, 183 | filters: filters, 184 | rules: rules 185 | }); 186 | 187 | assert.ok( 188 | $('#builder_rule_0 button.filter-description').data('bs-toggle') == 'popover', 189 | 'Rule should contain a new button enabled with Popover' 190 | ); 191 | 192 | $b.queryBuilder('destroy'); 193 | 194 | $b.queryBuilder({ 195 | plugins: { 196 | 'filter-description': { mode: 'bootbox' } 197 | }, 198 | filters: filters, 199 | rules: rules 200 | }); 201 | 202 | assert.ok( 203 | $('#builder_rule_0 button.filter-description').data('bs-toggle') == 'bootbox', 204 | 'Rule should contain a new button enabled with Bootbox' 205 | ); 206 | }); 207 | 208 | /** 209 | * Test sortable 210 | */ 211 | QUnit.test('sortable', function(assert) { 212 | $b.queryBuilder({ 213 | filters: basic_filters, 214 | rules: basic_rules, 215 | plugins: ['sortable'] 216 | }); 217 | 218 | assert.ok( 219 | $b.find('.drag-handle').length > 0, 220 | 'Should add the drag handles' 221 | ); 222 | 223 | $b.queryBuilder('destroy'); 224 | 225 | $b.queryBuilder({ 226 | plugins: { 227 | 'sortable': {disable_template: true} 228 | }, 229 | filters: basic_filters, 230 | rules: basic_rules 231 | }); 232 | 233 | assert.ok( 234 | $b.find('.drag-handle').length === 0, 235 | 'Should not have added the handles with disable_template=true' 236 | ); 237 | }); 238 | }); 239 | -------------------------------------------------------------------------------- /tests/plugins.module.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | var $b = $('#builder'); 3 | 4 | QUnit.module('plugins', { 5 | afterEach: function() { 6 | $b.queryBuilder('destroy'); 7 | } 8 | }); 9 | 10 | /** 11 | * Test plugins loading 12 | */ 13 | QUnit.test('Plugins loading', function(assert) { 14 | assert.ok(QueryBuilder.prototype.getSQL !== undefined, 'Should load SQL plugin automatically'); 15 | 16 | $b.queryBuilder({ 17 | filters: basic_filters, 18 | plugins: ['bt-tooltip-errors', 'filter-description'] 19 | }); 20 | 21 | assert.deepEqual( 22 | $b[0].queryBuilder.plugins['bt-tooltip-errors'], 23 | QueryBuilder.plugins['bt-tooltip-errors'].def, 24 | 'Should load "bt-tooltip-errors" with default config' 25 | ); 26 | 27 | assert.deepEqual( 28 | $b[0].queryBuilder.plugins['filter-description'], 29 | QueryBuilder.plugins['filter-description'].def, 30 | 'Should load "filter-description" with default config' 31 | ); 32 | 33 | $b.queryBuilder('destroy'); 34 | 35 | $b.queryBuilder({ 36 | filters: basic_filters, 37 | plugins: { 38 | 'bt-tooltip-errors': null, 39 | 'filter-description': { icon: 'fa fa-info' } 40 | } 41 | }); 42 | 43 | assert.deepEqual( 44 | $b[0].queryBuilder.plugins['bt-tooltip-errors'], 45 | QueryBuilder.plugins['bt-tooltip-errors'].def, 46 | 'Should load "bt-tooltip-errors" with default config' 47 | ); 48 | 49 | assert.deepEqual( 50 | $b[0].queryBuilder.plugins['filter-description'], 51 | { icon: 'fa fa-info', mode: 'popover' }, 52 | 'Should load "filter-description" with custom config' 53 | ); 54 | 55 | $b.queryBuilder('destroy'); 56 | 57 | assert.throws( 58 | function(){ 59 | $b.queryBuilder({ 60 | filters: basic_filters, 61 | plugins: ['__unknown__'] 62 | }); 63 | }, 64 | /Unable to find plugin "__unknown__"/, 65 | 'Should throw error on unknown plugin' 66 | ); 67 | }); 68 | 69 | /** 70 | * Test unique-filter 71 | */ 72 | QUnit.test('unique-filter', function(assert) { 73 | var unique_filters = $.extend(true, [], basic_filters); 74 | unique_filters[3].unique = 'group'; 75 | unique_filters[4].unique = true; 76 | 77 | $b.queryBuilder({ 78 | plugins: ['unique-filter'], 79 | filters: unique_filters, 80 | rules: basic_rules 81 | }); 82 | 83 | assert.ok( 84 | $('select[name=builder_rule_0_filter] option[value=id]').is(':disabled') && 85 | $('select[name=builder_rule_1_filter] option[value=id]').is(':disabled') && 86 | $('select[name=builder_rule_2_filter] option[value=id]').is(':disabled'), 87 | '"Identifier" filter should be disabled everywhere' 88 | ); 89 | 90 | assert.ok( 91 | $('select[name=builder_rule_1_filter] option[value=price]').is(':disabled') && 92 | !$('select[name=builder_rule_2_filter] option[value=price]').is(':disabled') && 93 | !$('select[name=builder_rule_3_filter] option[value=price]').is(':disabled'), 94 | '"Price" filter should be disabled in his group only' 95 | ); 96 | }); 97 | 98 | /** 99 | * Test inversion 100 | */ 101 | QUnit.test('invert', function(assert) { 102 | $b.queryBuilder({ 103 | plugins: ['invert'], 104 | filters: basic_filters, 105 | rules: basic_rules 106 | }); 107 | 108 | $b.queryBuilder('invert'); 109 | 110 | assert.rulesMatch( 111 | $b.queryBuilder('getRules'), 112 | { 113 | condition: 'OR', 114 | rules: [{ 115 | id: 'price', 116 | field: 'price', 117 | operator: 'greater_or_equal', 118 | value: 10.25 119 | }, { 120 | id: 'name', 121 | field: 'name', 122 | operator: 'is_not_null', 123 | value: null 124 | }, { 125 | condition: 'AND', 126 | rules: [{ 127 | id: 'category', 128 | field: 'category', 129 | operator: 'not_in', 130 | value: ['mo', 'mu'] 131 | }, { 132 | id: 'id', 133 | field: 'id', 134 | operator: 'equal', 135 | value: '1234-azer-5678' 136 | }] 137 | }] 138 | }, 139 | 'Should have inverted all conditions and operators' 140 | ); 141 | 142 | $b.queryBuilder('destroy'); 143 | 144 | $b.queryBuilder({ 145 | plugins: { 146 | invert: {disable_template: true} 147 | }, 148 | filters: basic_filters, 149 | rules: basic_rules 150 | }); 151 | 152 | assert.ok( 153 | $b.find('[data-invert="group"]').length === 0, 154 | 'Should not have added the button with disable_template=true' 155 | ); 156 | }); 157 | 158 | /** 159 | * Test change filters 160 | */ 161 | QUnit.test('change-filters', function(assert) { 162 | var filter_a = { 163 | id: 'a', 164 | type: 'string' 165 | }; 166 | 167 | var filter_b = { 168 | id: 'b', 169 | type: 'string' 170 | }; 171 | 172 | var filter_c = { 173 | id: 'c', 174 | type: 'string' 175 | }; 176 | 177 | var rule_a = { 178 | id: 'a', 179 | field: 'a', 180 | operator: 'equal', 181 | value: 'foo' 182 | }; 183 | 184 | var rule_b = { 185 | id: 'b', 186 | field: 'b', 187 | operator: 'equal', 188 | value: 'bar' 189 | }; 190 | 191 | $b.queryBuilder({ 192 | filters: [filter_a, filter_b], 193 | rules: [rule_a, rule_b] 194 | }); 195 | 196 | assert.throws( 197 | function(){ 198 | $b.queryBuilder('removeFilter', 'a'); 199 | }, 200 | /A rule is using filter "a"/, 201 | 'Should throw error when deleting filter "a" w/o force' 202 | ); 203 | 204 | $b.queryBuilder('removeFilter', 'a', true); 205 | 206 | assert.rulesMatch( 207 | $b.queryBuilder('getRules'), 208 | {condition:'AND', rules: [rule_b]}, 209 | 'Should have deleted rule using filter "a"' 210 | ); 211 | 212 | $b.queryBuilder('addFilter', filter_c, 0); 213 | 214 | assert.optionsMatch( 215 | $('#builder_rule_1 [name$=_filter] option'), 216 | ['-1', filter_c.id, filter_b.id], 217 | 'Should have added filter "c" at begining' 218 | ); 219 | 220 | $b.queryBuilder('addFilter', filter_a, 'c'); 221 | 222 | assert.optionsMatch( 223 | $('#builder_rule_1 [name$=_filter] option'), 224 | ['-1', filter_c.id, filter_a.id, filter_b.id], 225 | 'Should have added filter "a" after "c"' 226 | ); 227 | }); 228 | }); 229 | -------------------------------------------------------------------------------- /tests/plugins.mongo-support.module.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | var $b = $('#builder'); 3 | 4 | QUnit.module('plugins.mongo-support', { 5 | afterEach: function() { 6 | $b.queryBuilder('destroy'); 7 | } 8 | }); 9 | 10 | QUnit.test('Basics', function(assert) { 11 | var basic_rules_mongodb = { 12 | '$and': [ 13 | {'price': {'$lt': 10.25}}, 14 | {'name': null}, 15 | { 16 | '$or': [ 17 | {'category': {'$in': ['mo', 'mu']}}, 18 | {'id': {'$ne': '1234-azer-5678'}} 19 | ] 20 | } 21 | ] 22 | }; 23 | 24 | $b.queryBuilder({ 25 | filters: basic_filters, 26 | rules: basic_rules 27 | }); 28 | 29 | assert.deepEqual( 30 | $b.queryBuilder('getMongo'), 31 | basic_rules_mongodb, 32 | 'Should create MongoDB query' 33 | ); 34 | 35 | assert.deepEqual( 36 | $b.queryBuilder('getRulesFromMongo', basic_rules_mongodb), 37 | basic_rules, 38 | 'Should return rules object from MongoDB query' 39 | ); 40 | }); 41 | 42 | QUnit.test('All operators', function(assert) { 43 | $b.queryBuilder({ 44 | filters: basic_filters, 45 | rules: all_operators_rules 46 | }); 47 | 48 | assert.deepEqual( 49 | $b.queryBuilder('getMongo'), 50 | all_operators_rules_mongodb, 51 | 'Should successfully convert all kind of operators to MongoDB' 52 | ); 53 | 54 | $b.queryBuilder('reset'); 55 | 56 | $b.queryBuilder('setRulesFromMongo', all_operators_rules_mongodb); 57 | 58 | assert.rulesMatch( 59 | $b.queryBuilder('getRules'), 60 | all_operators_rules, 61 | 'Should successfully parse all kind of operators from MongoDB' 62 | ); 63 | }); 64 | 65 | QUnit.test('Automatically use filter from field', function(assert) { 66 | var rules = { 67 | condition: 'AND', 68 | rules: [ 69 | { 70 | id: 'name', 71 | operator: 'equal', 72 | value: 'Mistic' 73 | } 74 | ] 75 | }; 76 | 77 | var mongo = { 78 | $and: [{ 79 | username: 'Mistic' 80 | }] 81 | }; 82 | 83 | $b.queryBuilder({ 84 | filters: [ 85 | { 86 | id: 'name', 87 | field: 'username', 88 | type: 'string' 89 | }, 90 | { 91 | id: 'last_days', 92 | field: 'display_date', 93 | type: 'integer' 94 | } 95 | ] 96 | }); 97 | 98 | $b.queryBuilder('setRulesFromMongo', mongo); 99 | 100 | assert.rulesMatch( 101 | $b.queryBuilder('getRules'), 102 | rules, 103 | 'Should use "name" filter from "username" field' 104 | ); 105 | }); 106 | 107 | 108 | var all_operators_rules = { 109 | condition: 'AND', 110 | rules: [{ 111 | id: 'name', 112 | operator: 'equal', 113 | value: 'foo' 114 | }, { 115 | id: 'name', 116 | operator: 'not_equal', 117 | value: 'foo' 118 | }, { 119 | id: 'category', 120 | operator: 'in', 121 | value: ['bk','mo'] 122 | }, { 123 | id: 'category', 124 | operator: 'not_in', 125 | value: ['bk','mo'] 126 | }, { 127 | id: 'price', 128 | operator: 'less', 129 | value: 5 130 | }, { 131 | id: 'price', 132 | operator: 'less_or_equal', 133 | value: 5 134 | }, { 135 | id: 'price', 136 | operator: 'greater', 137 | value: 4 138 | }, { 139 | id: 'price', 140 | operator: 'greater_or_equal', 141 | value: 4 142 | }, { 143 | id: 'price', 144 | operator: 'between', 145 | value: [4,5] 146 | }, { 147 | id: 'price', 148 | operator: 'not_between', 149 | value: [4,5] 150 | }, { 151 | id: 'name', 152 | operator: 'begins_with', 153 | value: 'foo' 154 | }, { 155 | id: 'name', 156 | operator: 'not_begins_with', 157 | value: 'foo' 158 | }, { 159 | id: 'name', 160 | operator: 'contains', 161 | value: 'foo' 162 | }, { 163 | id: 'name', 164 | operator: 'not_contains', 165 | value: 'foo' 166 | }, { 167 | id: 'name', 168 | operator: 'ends_with', 169 | value: 'foo' 170 | }, { 171 | id: 'name', 172 | operator: 'not_ends_with', 173 | value: 'foo' 174 | }, { 175 | id: 'name', 176 | operator: 'is_empty', 177 | value: null 178 | }, { 179 | id: 'name', 180 | operator: 'is_not_empty', 181 | value: null 182 | }, { 183 | id: 'name', 184 | operator: 'is_null', 185 | value: null 186 | }, { 187 | id: 'name', 188 | operator: 'is_not_null', 189 | value: null 190 | }] 191 | }; 192 | 193 | var all_operators_rules_mongodb = { 194 | $and: [ 195 | { name: 'foo' }, 196 | { name: {$ne: 'foo'} }, 197 | { category: { $in: ['bk','mo'] }}, 198 | { category: { $nin: ['bk','mo'] }}, 199 | { price: {$lt: 5} }, 200 | { price: {$lte: 5} }, 201 | { price: {$gt: 4} }, 202 | { price: {$gte: 4} }, 203 | { price: {$gte: 4, $lte: 5} }, 204 | { price: {$lt: 4, $gt: 5} }, 205 | { name: {$regex: '^foo'} }, 206 | { name: {$regex: '^(?!foo)'} }, 207 | { name: {$regex: 'foo'} }, 208 | { name: {$regex: '^((?!foo).)*$', $options: 's'} }, 209 | { name: {$regex: 'foo$'} }, 210 | { name: {$regex: '(? 0, 19 | 'Should add "not" buttons"' 20 | ); 21 | 22 | $('#builder_group_0>.rules-group-header [data-not=group]').trigger('click'); 23 | 24 | assert.ok( 25 | $b.queryBuilder('getModel').not, 26 | 'The root group should have "not" flag set to true' 27 | ); 28 | 29 | assert.ok( 30 | $b.queryBuilder('getRules').not, 31 | 'The root json should have "not" flag set to true' 32 | ); 33 | 34 | $b.queryBuilder('destroy'); 35 | 36 | $b.queryBuilder({ 37 | plugins: { 38 | 'not-group': {disable_template: true} 39 | }, 40 | filters: basic_filters, 41 | rules: basic_rules 42 | }); 43 | 44 | assert.ok( 45 | $b.find('[data-not="group"]').length === 0, 46 | 'Should not have added the button with disable_template=true' 47 | ); 48 | }); 49 | 50 | QUnit.test('SQL export', function (assert) { 51 | $b.queryBuilder({ 52 | filters: basic_filters, 53 | rules: rules, 54 | plugins: ['not-group'] 55 | }); 56 | 57 | assert.equal( 58 | $b.queryBuilder('getSQL').sql, 59 | sql, 60 | 'Should export SQL with NOT function' 61 | ); 62 | }); 63 | 64 | QUnit.test('SQL import', function (assert) { 65 | $b.queryBuilder({ 66 | filters: basic_filters, 67 | plugins: ['not-group'] 68 | }); 69 | 70 | $b.queryBuilder('setRulesFromSQL', sql); 71 | 72 | assert.rulesMatch( 73 | $b.queryBuilder('getRules'), 74 | rules, 75 | 'Should parse NOT SQL function' 76 | ); 77 | 78 | $b.queryBuilder('setRulesFromSQL', sql2); 79 | 80 | assert.rulesMatch( 81 | $b.queryBuilder('getRules'), 82 | rules2, 83 | 'Should parse NOT SQL function with only one rule' 84 | ); 85 | 86 | $b.queryBuilder('setRulesFromSQL', sql3); 87 | 88 | assert.rulesMatch( 89 | $b.queryBuilder('getRules'), 90 | rules3, 91 | 'Should parse NOT SQL function with same operation' 92 | ); 93 | 94 | $b.queryBuilder('setRulesFromSQL', sql4); 95 | 96 | assert.rulesMatch( 97 | $b.queryBuilder('getRules'), 98 | rules4, 99 | 'Should parse NOT SQL function with negated root group' 100 | ); 101 | 102 | $b.queryBuilder('setRulesFromSQL', sql5); 103 | 104 | assert.rulesMatch( 105 | $b.queryBuilder('getRules'), 106 | rules5, 107 | 'Should parse NOT SQL function with double negated root group' 108 | ); 109 | }); 110 | 111 | QUnit.test('Mongo export', function (assert) { 112 | $b.queryBuilder({ 113 | filters: basic_filters, 114 | rules: rules, 115 | plugins: ['not-group'] 116 | }); 117 | 118 | assert.deepEqual( 119 | $b.queryBuilder('getMongo'), 120 | mongo, 121 | 'Should export MongoDB with $nor function' 122 | ); 123 | 124 | $b.queryBuilder('reset'); 125 | 126 | $b.queryBuilder('setRulesFromMongo', mongo); 127 | 128 | assert.rulesMatch( 129 | $b.queryBuilder('getRules'), 130 | rules, 131 | 'Should parse $nor MongoDB function' 132 | ); 133 | }); 134 | 135 | var rules = { 136 | condition: 'OR', 137 | not: false, 138 | rules: [{ 139 | id: 'name', 140 | operator: 'equal', 141 | value: 'Mistic' 142 | }, { 143 | condition: 'AND', 144 | not: true, 145 | rules: [{ 146 | id: 'price', 147 | operator: 'less', 148 | value: 10.25 149 | }, { 150 | id: 'category', 151 | field: 'category', 152 | operator: 'in', 153 | value: ['mo', 'mu'] 154 | }] 155 | }] 156 | }; 157 | 158 | var sql = 'name = \'Mistic\' OR ( NOT ( price < 10.25 AND category IN(\'mo\', \'mu\') ) ) '; 159 | 160 | var rules2 = { 161 | condition: 'OR', 162 | not: false, 163 | rules: [{ 164 | id: 'name', 165 | operator: 'equal', 166 | value: 'Mistic' 167 | }, { 168 | condition: 'AND', 169 | not: true, 170 | rules: [{ 171 | id: 'price', 172 | operator: 'less', 173 | value: 10.25 174 | }] 175 | }] 176 | }; 177 | 178 | var sql2 = 'name = \'Mistic\' OR ( NOT ( price < 10.25 ) ) '; 179 | 180 | var rules3 = { 181 | condition: 'OR', 182 | not: false, 183 | rules: [{ 184 | id: 'name', 185 | operator: 'equal', 186 | value: 'Mistic' 187 | }, { 188 | condition: 'OR', 189 | not: true, 190 | rules: [{ 191 | id: 'price', 192 | operator: 'less', 193 | value: 10.25 194 | }, { 195 | id: 'category', 196 | field: 'category', 197 | operator: 'in', 198 | value: ['mo', 'mu'] 199 | }] 200 | }] 201 | }; 202 | 203 | var sql3 = 'name = \'Mistic\' OR ( NOT ( price < 10.25 OR category IN(\'mo\', \'mu\') ) ) '; 204 | 205 | var rules4 = { 206 | condition: 'AND', 207 | not: true, 208 | rules: [{ 209 | id: 'price', 210 | operator: 'less', 211 | value: 10.25 212 | }] 213 | }; 214 | 215 | var sql4 = 'NOT ( price < 10.25 )'; 216 | 217 | var rules5 = { 218 | condition: 'AND', 219 | not: false, 220 | rules: [{ 221 | condition: 'AND', 222 | not: true, 223 | rules: [{ 224 | id: 'price', 225 | operator: 'less', 226 | value: 10.25 227 | }] 228 | }, { 229 | condition: 'AND', 230 | not: true, 231 | rules: [{ 232 | id: 'price', 233 | operator: 'greater', 234 | value: 20.5 235 | }] 236 | }] 237 | }; 238 | 239 | var sql5 = 'NOT ( price < 10.25 ) AND NOT ( price > 20.5 )'; 240 | 241 | var mongo = { 242 | "$or": [{ 243 | "name": "Mistic" 244 | }, 245 | { 246 | "$nor": [{ 247 | "$and": [{ 248 | "price": {"$lt": 10.25} 249 | }, { 250 | "category": {"$in": ["mo", "mu"]} 251 | }] 252 | }] 253 | }] 254 | }; 255 | 256 | }); 257 | -------------------------------------------------------------------------------- /tests/utils.module.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | 3 | QUnit.module('utils'); 4 | 5 | /** 6 | * Test iterateOptions 7 | */ 8 | QUnit.test('iterateOptions', function (assert) { 9 | var output; 10 | function callback(a, b, c) { 11 | output.push(a, b, c); 12 | } 13 | 14 | output = []; 15 | Utils.iterateOptions(['one', 'foo', 'bar'], callback); 16 | assert.deepEqual( 17 | output, 18 | ['one', 'one', undefined, 'foo', 'foo', undefined, 'bar', 'bar', undefined], 19 | 'Should iterate simple array' 20 | ); 21 | 22 | output = []; 23 | Utils.iterateOptions({1: 'one', 2: 'foo', 3: 'bar'}, callback); 24 | assert.deepEqual( 25 | output, 26 | ['1', 'one', undefined, '2', 'foo', undefined, '3', 'bar', undefined], 27 | 'Should iterate simple hash-map' 28 | ); 29 | 30 | output = []; 31 | Utils.iterateOptions([{1: 'one'}, {2: 'foo'}, {3: 'bar'}], callback); 32 | assert.deepEqual( 33 | output, 34 | ['1', 'one', undefined, '2', 'foo', undefined, '3', 'bar', undefined], 35 | 'Should iterate array of one element hash-maps' 36 | ); 37 | 38 | output = []; 39 | Utils.iterateOptions([{value: 1, label: 'one', optgroup: 'group'}, {value: 2, label: 'foo'}, {value: 3, label: 'bar', optgroup: 'group'}], callback); 40 | assert.deepEqual( 41 | output, 42 | [1, 'one', 'group', 2, 'foo', undefined, 3, 'bar', 'group'], 43 | 'Should iterate array of hash-maps' 44 | ); 45 | }); 46 | 47 | /** 48 | * Test groupSort 49 | */ 50 | QUnit.test('groupSort', function (assert) { 51 | var input = [ 52 | {id: '1'}, 53 | {id: '1.1', group: '1'}, 54 | {id: '2'}, 55 | {id: '2.1', group: '2'}, 56 | {id: '1.2', group: '1'}, 57 | {id: '2.2', group: '2'}, 58 | {id: '3'}, 59 | {id: '1.3', group: '1'} 60 | ]; 61 | 62 | var output = [ 63 | {id: '1'}, 64 | {id: '1.1', group: '1'}, 65 | {id: '1.2', group: '1'}, 66 | {id: '1.3', group: '1'}, 67 | {id: '2'}, 68 | {id: '2.1', group: '2'}, 69 | {id: '2.2', group: '2'}, 70 | {id: '3'} 71 | ]; 72 | 73 | assert.deepEqual( 74 | Utils.groupSort(input, 'group'), 75 | output, 76 | 'Should sort items by group' 77 | ); 78 | }); 79 | 80 | /** 81 | * Test fmt 82 | */ 83 | QUnit.test('fmt', function (assert) { 84 | assert.equal( 85 | Utils.fmt('{0} is equal to {1}', 1, 'one'), 86 | '1 is equal to one', 87 | 'Should replace arguments' 88 | ); 89 | 90 | assert.equal( 91 | Utils.fmt('{0} is equal to {0}', 1), 92 | '1 is equal to 1', 93 | 'Should replace one argument multiple times' 94 | ); 95 | }); 96 | 97 | /** 98 | * Test changeType 99 | */ 100 | QUnit.test('changeType', function (assert) { 101 | assert.ok( 102 | Utils.changeType('10', 'integer') === 10, 103 | '"10" should be parsed as integer' 104 | ); 105 | 106 | assert.ok( 107 | Utils.changeType('10.5', 'double') === 10.5, 108 | '"10.5" should be parsed as double' 109 | ); 110 | 111 | assert.ok( 112 | Utils.changeType('true', 'boolean') === true, 113 | '"true" should be parsed as boolean' 114 | ); 115 | }); 116 | 117 | /** 118 | * Test escapeElementId 119 | */ 120 | QUnit.test('escapeElementId', function (assert) { 121 | assert.equal( 122 | Utils.escapeElementId('abc123'), 123 | 'abc123', 124 | 'Should not alter id' 125 | ); 126 | 127 | var chars = ':.[],'; 128 | for (var i = 0; i < chars.length; ++i) { 129 | assert.equal( 130 | Utils.escapeElementId('abc' + chars[i] + '123'), 131 | 'abc\\' + chars[i] + '123', 132 | 'Should escape \'' + chars[i] + '\' in id' 133 | ); 134 | 135 | assert.equal( 136 | Utils.escapeElementId('abc\\' + chars[i] + '123'), 137 | 'abc\\' + chars[i] + '123', 138 | 'Should not escape \'\\' + chars[i] + '\' in id' 139 | ); 140 | 141 | assert.equal( 142 | Utils.escapeElementId(chars[i] + 'abc123'), 143 | '\\' + chars[i] + 'abc123', 144 | 'Should escape \'' + chars[i] + '\' prefixing id' 145 | ); 146 | 147 | assert.equal( 148 | Utils.escapeElementId('\\' + chars[i] + 'abc123'), 149 | '\\' + chars[i] + 'abc123', 150 | 'Should not escape \'\\' + chars[i] + '\' prefixing id' 151 | ); 152 | 153 | assert.equal( 154 | Utils.escapeElementId('abc123' + chars[i]), 155 | 'abc123\\' + chars[i], 156 | 'Should escape \'' + chars[i] + '\' trailing in id' 157 | ); 158 | 159 | assert.equal( 160 | Utils.escapeElementId('abc123\\' + chars[i]), 161 | 'abc123\\' + chars[i], 162 | 'Should not escape \'\\' + chars[i] + '\' trailing in id' 163 | ); 164 | } 165 | }); 166 | }); 167 | --------------------------------------------------------------------------------