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