diff --git a/.gitignore b/.gitignore index d675f018..2b7141a4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ bower_components node_modules +dist +doc .sass-cache .coverage-results .idea diff --git a/.jsdoc.json b/.jsdoc.json new file mode 100644 index 00000000..d8a3fd67 --- /dev/null +++ b/.jsdoc.json @@ -0,0 +1,39 @@ +{ + "opts": { + "private": false, + "template": "node_modules/foodoc/template", + "readme": "build/jsdoc.md" + }, + "plugins": [ + "plugins/markdown" + ], + "templates": { + "systemName": "jQuery QueryBuilder API", + "systemSummary": "jQuery plugin for user friendly query/filter creator", + "systemColor": "#004482", + "copyright": "Licensed under MIT License, documentation under CC BY 3.0.", + "includeDate": false, + "inverseNav": false, + "cleverLinks": true, + "sort": "longname, version, since", + "analytics": { + "ua": "UA-28192323-3", + "domain": "auto" + }, + "navMembers": [ + {"kind": "class", "title": "Classes", "summary": "All documented classes."}, + {"kind": "external", "title": "Externals", "summary": "All documented external members."}, + {"kind": "global", "title": "Globals", "summary": "All documented globals."}, + {"kind": "mixin", "title": "Mixins", "summary": "All documented mixins."}, + {"kind": "interface", "title": "Interfaces", "summary": "All documented interfaces."}, + {"kind": "module", "title": "Modules", "summary": "All documented modules."}, + {"kind": "event", "title": "Events", "summary": "All documented events."}, + {"kind": "namespace", "title": "Namespaces", "summary": "All documented namespaces."}, + {"kind": "tutorial", "title": "Tutorials", "summary": "All available tutorials."} + ], + "scripts": [ + "https://cdnjs.cloudflare.com/ajax/libs/trianglify/1.0.1/trianglify.min.js", + "js/custom.js" + ] + } +} \ No newline at end of file diff --git a/.npmignore b/.npmignore new file mode 100644 index 00000000..4dec3eec --- /dev/null +++ b/.npmignore @@ -0,0 +1,5 @@ +.* +build +composer.json +Gruntfile.js +bower_components diff --git a/Gruntfile.js b/Gruntfile.js index 36ab0d62..bdb40cc2 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,54 +1,23 @@ -var deepmerge = require('deepmerge'); +var initConfig = require('./build/initConfig'); +var processLang = require('./build/processLang'); +var removeJshint = require('./build/removeJshint'); +var cleanLn = require('./build/cleanLn'); module.exports = function(grunt) { require('time-grunt')(grunt); require('jit-grunt')(grunt, { - scsslint: 'grunt-scss-lint' + scsslint: 'grunt-scss-lint', + sass_injection: 'grunt-sass-injection', + usebanner: 'grunt-banner' }); grunt.util.linefeed = '\n'; - function removeJshint(src) { - return src - .replace(/\/\*jshint [a-z:]+ \*\/\r?\n\r?\n?/g, '') - .replace(/\/\*jshint -[EWI]{1}[0-9]{3} \*\/\r?\n\r?\n?/g, ''); - } - - function process_lang(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]; - } - - - var all_plugins = {}, - all_langs = {}, - loaded_plugins = [], - loaded_langs = [], - js_core_files = [ + 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', @@ -57,88 +26,23 @@ module.exports = function(grunt) { 'src/utils.js', 'src/jquery.js' ], - js_files_to_load = js_core_files.slice(), - all_js_files = js_core_files.slice(), - js_files_for_standalone = [ + js_files_for_standalone: [ 'bower_components/jquery-extendext/jQuery.extendext.js', 'bower_components/doT/doT.js', 'dist/js/query-builder.js' - ]; - - - (function() { - // list available plugins and languages - grunt.file.expand('src/plugins/**/plugin.js') - .forEach(function(f) { - var n = f.split('/')[2]; - all_plugins[n] = f; - }); - - grunt.file.expand('src/i18n/*.json') - .forEach(function(f) { - var n = f.split(/[\/\.]/)[2]; - all_langs[n] = f; - }); - - // fill all js files - for (var p in all_plugins) { - all_js_files.push(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 (all_plugins[p]) { - js_files_to_load.push(all_plugins[p]); - loaded_plugins.push(p); - } - else { - grunt.fail.warn('Plugin ' + p + ' unknown'); - } - }); - } - else if (arg_plugins === undefined) { - for (var p in all_plugins) { - js_files_to_load.push(all_plugins[p]); - loaded_plugins.push(p); - } - } - - // default language - js_files_to_load.push('.temp/i18n/en.js'); - 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 (all_langs[l]) { - if (l !== 'en') { - js_files_to_load.push(all_langs[l].replace(/^src/, '.temp').replace(/json$/, 'js')); - loaded_langs.push(l); - } - } - else { - grunt.fail.warn('Language ' + l + ' unknown'); - } - }); - } - }()); - + ] + }); grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), - banner: - '/*!\n' + + banner: '/*!\n' + ' * jQuery QueryBuilder <%= pkg.version %>\n' + ' * Copyright 2014-<%= grunt.template.today("yyyy") %> Damien "Mistic" Sorel (http://www.strangeplanet.fr)\n' + ' * Licensed under MIT (http://opensource.org/licenses/MIT)\n' + ' */', - langBanner: - '/*!\n' + + langBanner: '/*!\n' + ' * jQuery QueryBuilder <%= pkg.version %>\n' + ' * Locale: <%= lang_locale %>\n' + '<% if (lang_author) { %> * Author: <%= lang_author %>\n<% } %>' + @@ -163,7 +67,7 @@ module.exports = function(grunt) { }, js: { files: ['src/*.js', 'src/plugins/**/plugin.js'], - tasks: ['build_js'] + tasks: ['injector:example'] }, css: { files: ['src/scss/*.scss', 'src/plugins/**/plugin.scss'], @@ -197,31 +101,35 @@ module.exports = function(grunt) { }] }, sass_plugins: { - files: loaded_plugins.map(function(name) { + files: config.loaded_plugins.map(function(name) { return { src: 'src/plugins/' + name + '/plugin.scss', - dest: 'dist/scss/plugins/' + name + '.scss' + dest: 'dist/scss/plugins/_' + name + '.scss' }; }) + }, + doc_script: { + src: 'build/jsdoc.js', + dest: 'doc/js/custom.js' } }, concat: { // concat all JS js: { - src: js_files_to_load, + src: config.js_files_to_load, dest: 'dist/js/query-builder.js', options: { stripBanners: false, separator: '\n\n', process: function(src) { - return removeJshint(src).replace(/\r\n/g, '\n'); + return cleanLn(removeJshint(src)); } } }, // create standalone version js_standalone: { - src: js_files_for_standalone, + src: config.js_files_for_standalone, dest: 'dist/js/query-builder.standalone.js', options: { stripBanners: false, @@ -229,15 +137,14 @@ module.exports = function(grunt) { process: function(src, file) { var name = file.match(/([^\/]+?).js$/)[1]; - return removeJshint(src) - .replace(/\r\n/g, '\n') + return cleanLn(removeJshint(src)) .replace(/define\((.*?)\);/, 'define(\'' + name + '\', $1);'); } } }, // compile language files with AMD wrapper lang: { - files: Object.keys(all_langs).map(function(name) { + files: Object.keys(config.all_langs).map(function(name) { return { src: 'src/i18n/' + name + '.json', dest: 'dist/i18n/query-builder.' + name + '.js' @@ -245,14 +152,14 @@ module.exports = function(grunt) { }), options: { process: function(src, file) { - var wrapper = grunt.file.read('src/i18n/.wrapper.js').replace(/\r\n/g, '\n').split(/@@js\n/); - return process_lang(file, src, wrapper); + 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 wrapper + // compile language files without AMD wrapper lang_temp: { - files: Object.keys(all_langs).map(function(name) { + files: Object.keys(config.all_langs).map(function(name) { return { src: 'src/i18n/' + name + '.json', dest: '.temp/i18n/' + name + '.js' @@ -260,60 +167,50 @@ module.exports = function(grunt) { }), options: { process: function(src, file) { - return process_lang(file, src); + return processLang(grunt, config.loaded_plugins)(file, src); } } - }, - // add banner to CSS files - css: { - options: { - banner: '<%= banner %>\n\n', - }, - files: [{ - expand: true, - src: ['dist/css/*.css', 'dist/scss/*.scss'], - dest: '' - }] } }, + // add AMD wrapper wrap: { - // add AMD wrapper and banner js: { src: ['dist/js/query-builder.js'], dest: '', options: { separator: '', wrapper: function() { - var wrapper = grunt.file.read('src/.wrapper.js').replace(/\r\n/g, '\n').split(/@@js\n/); - - if (loaded_plugins.length) { - wrapper[0] = '// Plugins: ' + loaded_plugins.join(', ') + '\n' + wrapper[0]; - } - if (loaded_langs.length) { - wrapper[0] = '// Languages: ' + loaded_langs.join(', ') + '\n' + wrapper[0]; - } - wrapper[0] = grunt.template.process('<%= banner %>\n\n') + wrapper[0]; - - return wrapper; + return cleanLn(grunt.file.read('src/.wrapper.js')).split(/@@js\n/); } } + } + }, + + // add banners + usebanner: { + options: { + banner: '<%= banner %>' }, - // add plugins SASS imports - sass: { - src: ['dist/scss/default.scss'], - dest: '', + js: { + src: ['dist/js/*.js'] + }, + css: { + src: ['dist/css/*.css', 'dist/scss/*.scss'] + } + }, + + // add plugins SASS imports + sass_injection: { + dist: { options: { - separator: '', - wrapper: function() { - return ['', loaded_plugins.reduce(function(wrapper, name) { - if (grunt.file.exists('dist/scss/plugins/' + name + '.scss')) { - wrapper += '\n@import \'plugins/' + name + '\';'; - } - return wrapper; - }, '\n')]; + replacePath: { + pattern: 'dist/scss/', + replace: '' } - } + }, + src: ['dist/scss/plugins/*.scss'], + target: 'dist/scss/default.scss' } }, @@ -340,7 +237,7 @@ module.exports = function(grunt) { // compress js uglify: { options: { - banner: '<%= banner %>\n\n', + banner: '<%= banner %>\n', mangle: { except: ['$'] } }, dist: { @@ -371,7 +268,8 @@ module.exports = function(grunt) { // clean build dir clean: { - temp: ['.temp'] + temp: ['.temp'], + doc: ['doc'] }, // jshint tests @@ -380,7 +278,7 @@ module.exports = function(grunt) { options: { jshintrc: '.jshintrc' }, - src: js_files_to_load + src: ['src/**/*.js', '!src/**/.wrapper.js'] } }, @@ -390,7 +288,7 @@ module.exports = function(grunt) { options: { config: '.jscsrc' }, - src: js_files_to_load + src: ['src/**/*.js', '!src/**/.wrapper.js'] } }, @@ -405,45 +303,46 @@ module.exports = function(grunt) { } }, - // inject all source files and test modules in the test file - 'string-replace': { - test: { - src: 'tests/index.html', - dest: 'tests/index.html', + // jsDoc generation + jsdoc: { + lib: { + src: ['src/**/*.js', '!src/**/.wrapper.js'], options: { - replacements: [{ - pattern: /()(?:[\s\S]*)()/m, - replacement: function(match, m1, m2) { - var scripts = '\n'; - - js_core_files.forEach(function(file) { - scripts += '\n'; - }); - - scripts += '\n'; - - for (var p in all_plugins) { - scripts += '\n'; - } - - return m1 + scripts + m2; - } - }, { - pattern: /()(?:[\s\S]*)()/m, - replacement: function(match, m1, m2) { - var scripts = '\n'; - - grunt.file.expand('tests/*.module.js').forEach(function(file) { - scripts += '\n'; - }); - - return m1 + scripts + m2; - } - }] + 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: { @@ -473,106 +372,7 @@ module.exports = function(grunt) { force: true }, all: { - src: '.coverage-results/all.lcov', - } - } - }); - - - // list the triggers and changes - grunt.registerTask('describe_triggers', 'List QueryBuilder triggers.', function() { - var triggers = {}; - var total = 0; - - for (var f in all_js_files) { - grunt.file.read(all_js_files[f]).split(/\r?\n/).forEach(function(line, i) { - var matches = /(e = )?(?:this|that)\.(trigger|change)\('(\w+)'([^)]*)\);/.exec(line); - if (matches !== null) { - triggers[matches[3]] = { - name: matches[3], - type: matches[2], - file: all_js_files[f], - line: i, - args: matches[4].slice(2), - prevent: !!matches[1] - }; - - total++; - } - }); - } - - grunt.log.write('\n'); - - for (var t in triggers) { - grunt.log.write(t['cyan'] + ' ' + triggers[t].type['magenta']); - if (triggers[t].prevent) grunt.log.write(' (*)'['yellow']); - grunt.log.write('\n'); - grunt.log.writeln(' ' + (triggers[t].file + ':' + triggers[t].line)['red'] + ' ' + triggers[t].args); - grunt.log.write('\n'); - } - - grunt.log.writeln((total + ' Triggers in QueryBuilder.')['cyan']['bold']); - }); - - // list all possible thrown errors - grunt.registerTask('describe_errors', 'List QueryBuilder errors.', function() { - var errors = {}; - var total = 0; - - for (var f in all_js_files) { - grunt.file.read(all_js_files[f]).split(/\r?\n/).forEach(function(line, i) { - var matches = /Utils\.error\('(\w+)', '([^)]+)'([^)]*)\);/.exec(line); - if (matches !== null) { - (errors[matches[1]] = errors[matches[1]] || []).push({ - type: matches[1], - message: matches[2], - file: all_js_files[f], - line: i, - args: matches[3].slice(2).split(', ') - }); - - total++; - } - }); - } - - grunt.log.write('\n'); - - for (var e in errors) { - grunt.log.writeln((e + 'Error')['cyan']); - errors[e].forEach(function(error) { - var message = error.message.replace(/{([0-9]+)}/g, function(m, i) { - return error.args[parseInt(i)]['yellow']; - }); - grunt.log.writeln(' ' + (error.file + ':' + error.line)['red']); - grunt.log.writeln(' ' + message); - }); - grunt.log.write('\n'); - } - - grunt.log.writeln((total + ' Errors in QueryBuilder.')['cyan']['bold']); - }); - - // display available modules - grunt.registerTask('list_modules', 'List QueryBuilder plugins and languages.', function() { - grunt.log.writeln('\nAvailable QueryBuilder plugins:\n'); - - for (var p in all_plugins) { - grunt.log.write(p['cyan']); - - if (grunt.file.exists(all_plugins[p].replace(/js$/, 'scss'))) { - grunt.log.write(' + CSS'); - } - - grunt.log.write('\n'); - } - - grunt.log.writeln('\nAvailable QueryBuilder languages:\n'); - - for (var l in all_langs) { - if (l !== 'en') { - grunt.log.writeln(l['cyan']); + src: '.coverage-results/all.lcov' } } }); @@ -582,6 +382,7 @@ module.exports = function(grunt) { 'concat:lang_temp', 'concat:js', 'wrap:js', + 'usebanner:js', 'concat:js_standalone', 'uglify', 'clean:temp' @@ -590,10 +391,10 @@ module.exports = function(grunt) { grunt.registerTask('build_css', [ 'copy:sass_core', 'copy:sass_plugins', - 'wrap:sass', + 'sass_injection', 'sass', 'cssmin', - 'concat:css' + 'usebanner:css' ]); grunt.registerTask('build_lang', [ @@ -610,16 +411,26 @@ module.exports = function(grunt) { 'jshint', 'jscs', 'scsslint', - 'default', - 'string-replace:test', + 'build_lang', + 'build_css', + 'injector:testSrc', + 'injector:testModules', 'qunit_blanket_lcov', 'qunit' ]); grunt.registerTask('serve', [ - 'default', + 'build_lang', + 'build_css', + 'injector:example', 'open', 'connect', 'watch' ]); + + grunt.registerTask('doc', [ + 'clean:doc', + 'jsdoc', + 'copy:doc_script' + ]); }; diff --git a/README.md b/README.md index 4aa83f81..e48cf2e2 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,39 @@ # jQuery QueryBuilder [![Bower version](https://img.shields.io/bower/v/jQuery-QueryBuilder.svg?style=flat-square)](http://querybuilder.js.org) +[![npm version](https://img.shields.io/npm/v/jQuery-QueryBuilder.svg?style=flat-square)](https://www.npmjs.com/package/jQuery-QueryBuilder) [![CDN](https://img.shields.io/badge/cdn-jsdelivr-%23EB4C36.svg?style=flat-square)](http://www.jsdelivr.com/projects/jquery.query-builder) [![Build Status](https://img.shields.io/travis/mistic100/jQuery-QueryBuilder.svg?style=flat-square)](https://travis-ci.org/mistic100/jQuery-QueryBuilder) [![Coverage Status](https://img.shields.io/coveralls/mistic100/jQuery-QueryBuilder/master.svg?style=flat-square)](https://coveralls.io/r/mistic100/jQuery-QueryBuilder) -[![Say thanks](https://img.shields.io/badge/SayThanks.io-%E2%98%80-1EAEDB.svg?style=flat-square)](https://saythanks.io/to/mistic100) jQuery plugin offering an simple interface to create complex queries. [![screenshot](https://raw.githubusercontent.com/mistic100/jQuery-QueryBuilder/master/examples/screenshot.png)](http://querybuilder.js.org) + + ## Documentation -http://querybuilder.js.org +[http://querybuilder.js.org](http://querybuilder.js.org) + + + +## Install + +#### Manually + +[Download the latest release](https://github.com/mistic100/jQuery-QueryBuilder/releases) + +#### With Bower + +```bash +$ bower install jQuery-QueryBuilder +``` + +#### With npm + +```bash +$ npm install jQuery-QueryBuilder +``` ### Dependencies * jQuery >= 1.10 @@ -27,7 +49,9 @@ http://querybuilder.js.org * Internet Explorer >= 9 * All other recent browsers -### Build + + +## Build #### Prerequisites @@ -62,11 +86,14 @@ grunt --languages=fr,it #### Other commands * `grunt test` to run jshint/jscs/scsslint and the QUnit test suite. - * `grunt list_modules` to get the list of available plugins and languages. - * `grunt describe_triggers` to get the list of all triggers. - * `grunt describe_errors` to get the list of all fatal errors. - * `grunt watch` to automatically build the library when modifying the source files. + * `grunt serve` to open the example page with automatic build and livereload. + * `grunt doc` to generate the documentation. + + + +## License +This library is available under the MIT license. -### Inspiration +#### Inspirations * [Knockout Query Builder](http://kindohm.github.io/knockout-query-builder/) * [jui_filter_rules](http://www.pontikis.net/labs/jui_filter_rules/) diff --git a/bower.json b/bower.json index bf6b6519..9f597a38 100644 --- a/bower.json +++ b/bower.json @@ -45,6 +45,7 @@ "**/.*", "node_modules", "bower_components", + "build", "src", "tests", "composer.json", diff --git a/build/cleanLn.js b/build/cleanLn.js new file mode 100644 index 00000000..f93432e8 --- /dev/null +++ b/build/cleanLn.js @@ -0,0 +1,3 @@ +module.exports = function(src) { + return src.replace(/\r\n/g, '\n'); +}; diff --git a/build/initConfig.js b/build/initConfig.js new file mode 100644 index 00000000..5bb333ee --- /dev/null +++ b/build/initConfig.js @@ -0,0 +1,68 @@ +module.exports = function(grunt, config) { + config.all_plugins = {}; + config.all_langs = {}; + config.loaded_plugins = []; + config.loaded_langs = []; + config.js_files_to_load = config.js_core_files.slice(); + config.all_js_files = config.js_core_files.slice(); + + // list available plugins and languages + grunt.file.expand('src/plugins/**/plugin.js') + .forEach(function(f) { + var n = f.split('/')[2]; + config.all_plugins[n] = f; + }); + + grunt.file.expand('src/i18n/*.json') + .forEach(function(f) { + var n = f.split(/[\/\.]/)[2]; + config.all_langs[n] = f; + }); + + // fill all js files + for (var p in config.all_plugins) { + config.all_js_files.push(config.all_plugins[p]); + } + + // parse 'plugins' parameter + var arg_plugins = grunt.option('plugins'); + if (typeof arg_plugins === 'string') { + arg_plugins.replace(/ /g, '').split(',').forEach(function(p) { + if (config.all_plugins[p]) { + config.js_files_to_load.push(config.all_plugins[p]); + config.loaded_plugins.push(p); + } + else { + grunt.fail.warn('Plugin ' + p + ' unknown'); + } + }); + } + else if (arg_plugins === undefined) { + for (var p in config.all_plugins) { + config.js_files_to_load.push(config.all_plugins[p]); + config.loaded_plugins.push(p); + } + } + + // default language + config.js_files_to_load.push('.temp/i18n/en.js'); + config.loaded_langs.push('en'); + + // parse 'lang' parameter + var arg_langs = grunt.option('languages'); + if (typeof arg_langs === 'string') { + arg_langs.replace(/ /g, '').split(',').forEach(function(l) { + if (config.all_langs[l]) { + if (l !== 'en') { + config.js_files_to_load.push(config.all_langs[l].replace(/^src/, '.temp').replace(/json$/, 'js')); + config.loaded_langs.push(l); + } + } + else { + grunt.fail.warn('Language ' + l + ' unknown'); + } + }); + } + + return config; +}; diff --git a/build/jsdoc.js b/build/jsdoc.js new file mode 100644 index 00000000..edbad8d4 --- /dev/null +++ b/build/jsdoc.js @@ -0,0 +1,12 @@ +(function() { + var header = $('.page-header'); + var pattern = Trianglify({ + width: window.screen.width | header.outerWidth(), + height: header.outerHeight(), + cell_size: 90, + seed: 'jQuery QueryBuilder', + x_colors: ['#0074d9', '#001224'] + }); + + header.css('background-image', 'url(' + pattern.png() + ')'); +}()); diff --git a/build/jsdoc.md b/build/jsdoc.md new file mode 100644 index 00000000..029fecb1 --- /dev/null +++ b/build/jsdoc.md @@ -0,0 +1,11 @@ +# [Main documentation](..) + +# Entry point: [$.fn.QueryBuilder](external-_jQuery.fn_.html) + +# [QueryBuilder](QueryBuilder.html) + +# [Rule](Rule.html) & [Group](Group.html) + +# [Events](list_event.html) + +# [Plugins](module-plugins.html) diff --git a/build/processLang.js b/build/processLang.js new file mode 100644 index 00000000..33c6b2c7 --- /dev/null +++ b/build/processLang.js @@ -0,0 +1,30 @@ +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 new file mode 100644 index 00000000..4ff583ac --- /dev/null +++ b/build/removeJshint.js @@ -0,0 +1,5 @@ +module.exports = function(src) { + return src + .replace(/\/\*jshint [a-z:]+ \*\/\r?\n\r?\n?/g, '') + .replace(/\/\*jshint -[EWI]{1}[0-9]{3} \*\/\r?\n\r?\n?/g, ''); +}; diff --git a/composer.json b/composer.json index 09831111..8bb72252 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "mistic100/jquery-querybuilder", - "version": "2.4.0", + "version": "2.4.2", "authors": [{ "name": "Damien \"Mistic\" Sorel", "email": "contact@git.strangeplanet.fr", @@ -8,9 +8,10 @@ }], "description": "jQuery plugin for user friendly query/filter creator", "require": { + "robloach/component-installer": "*", "components/jquery": ">=1.9.0", "moment/moment": ">=2.6.0", - "twbs/bootstrap": ">=3.1.0" + "components/bootstrap": ">=3.1.0" }, "keywords": [ "jquery", @@ -22,5 +23,24 @@ "homepage": "https://github.com/mistic100/jQuery-QueryBuilder", "support": { "issues": "https://github.com/mistic100/jQuery-QueryBuilder/issues" + }, + "extra": { + "component": { + "styles": [ + "dist/css/query-builder.default.css" + ], + "scripts": [ + "dist/js/query-builder.standalone.js" + ], + "files": [ + "dist/css/query-builder.default.min.css", + "dist/css/query-builder.dark.css", + "dist/css/query-builder.dark.min.css", + "dist/js/query-builder.js", + "dist/js/query-builder.min.js", + "dist/js/query-builder.standalone.min.js", + "dist/i18n/*.js" + ] + } } } diff --git a/dist/css/query-builder.dark.css b/dist/css/query-builder.dark.css index 347cfb4c..881baa60 100644 --- a/dist/css/query-builder.dark.css +++ b/dist/css/query-builder.dark.css @@ -1,9 +1,8 @@ /*! - * jQuery QueryBuilder 2.4.1 + * jQuery QueryBuilder 2.4.2 * Copyright 2014-2017 Damien "Mistic" Sorel (http://www.strangeplanet.fr) * Licensed under MIT (http://opensource.org/licenses/MIT) */ - .query-builder .rules-group-container, .query-builder .rule-container, .query-builder .rule-placeholder { position: relative; margin: 4px 0; @@ -104,6 +103,16 @@ display: none; } +.query-builder.bt-checkbox-glyphicons .checkbox input[type=checkbox]:checked + label::after { + font-family: 'Glyphicons Halflings'; + content: '\e013'; +} +.query-builder.bt-checkbox-glyphicons .checkbox label::after { + padding-left: 4px; + padding-top: 2px; + font-size: 9px; +} + .query-builder .error-container + .tooltip .tooltip-inner { color: #F22 !important; } diff --git a/dist/css/query-builder.dark.min.css b/dist/css/query-builder.dark.min.css index b2e12c41..1c04d8ac 100644 --- a/dist/css/query-builder.dark.min.css +++ b/dist/css/query-builder.dark.min.css @@ -1,7 +1,6 @@ /*! - * jQuery QueryBuilder 2.4.1 + * jQuery QueryBuilder 2.4.2 * Copyright 2014-2017 Damien "Mistic" Sorel (http://www.strangeplanet.fr) * Licensed under MIT (http://opensource.org/licenses/MIT) */ - -.query-builder .rule-container,.query-builder .rule-placeholder,.query-builder .rules-group-container{position:relative;margin:4px 0;border-radius:5px;padding:5px;border:1px solid #111;background:rgba(40,40,40,.9)}.query-builder .drag-handle,.query-builder .error-container,.query-builder .rule-container .rule-filter-container,.query-builder .rule-container .rule-operator-container,.query-builder .rule-container .rule-value-container{display:inline-block;margin:0 5px 0 0;vertical-align:middle}.query-builder .rules-group-container{padding:10px 10px 6px;border:1px solid #00164A;background:rgba(50,70,80,.5)}.query-builder .rules-group-header{margin-bottom:10px}.query-builder .rules-group-header .group-conditions .btn.readonly:not(.active),.query-builder .rules-group-header .group-conditions input[name$=_cond]{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;white-space:nowrap}.query-builder .rules-group-header .group-conditions .btn.readonly{border-radius:3px}.query-builder .rules-list{list-style:none;padding:0 0 0 15px;margin:0}.query-builder .rule-value-container{border-left:1px solid #DDD;padding-left:5px}.query-builder .rule-value-container label{margin-bottom:0;font-weight:400}.query-builder .rule-value-container label.block{display:block}.query-builder .rule-value-container input[type=number],.query-builder .rule-value-container input[type=text],.query-builder .rule-value-container select{padding:1px}.query-builder .error-container{display:none;cursor:help;color:red}.query-builder .has-error{background-color:#322;border-color:#800}.query-builder .has-error .error-container{display:inline-block!important}.query-builder .dragging::after,.query-builder .dragging::before,.query-builder .rules-list>:last-child::after{display:none}.query-builder .rules-list>::after,.query-builder .rules-list>::before{content:'';position:absolute;left:-10px;width:10px;height:calc(50% + 4px);border-color:#222;border-style:solid}.query-builder .rules-list>::before{top:-4px;border-width:0 0 2px 2px}.query-builder .rules-list>::after{top:50%;border-width:0 0 0 2px}.query-builder .rules-list>:first-child::before{top:-12px;height:calc(50% + 14px)}.query-builder .rules-list>:last-child::before{border-radius:0 0 0 4px}.query-builder .error-container+.tooltip .tooltip-inner{color:#F22!important}.query-builder p.filter-description{margin:5px 0 0;background:rgba(0,170,255,.2);border:1px solid #346F7B;color:#AAD1E4;border-radius:5px;padding:2.5px 5px;font-size:.8em}.query-builder .rules-group-header [data-invert]{margin-left:5px}.query-builder .drag-handle{cursor:move;vertical-align:middle;margin-left:5px}.query-builder .dragging{position:fixed;opacity:.5;z-index:100}.query-builder .rule-placeholder{border:1px dashed #BBB;opacity:.7} \ No newline at end of file +.query-builder .rule-container,.query-builder .rule-placeholder,.query-builder .rules-group-container{position:relative;margin:4px 0;border-radius:5px;padding:5px;border:1px solid #111;background:rgba(40,40,40,.9)}.query-builder .drag-handle,.query-builder .error-container,.query-builder .rule-container .rule-filter-container,.query-builder .rule-container .rule-operator-container,.query-builder .rule-container .rule-value-container{display:inline-block;margin:0 5px 0 0;vertical-align:middle}.query-builder .rules-group-container{padding:10px;padding-bottom:6px;border:1px solid #00164a;background:rgba(50,70,80,.5)}.query-builder .rules-group-header{margin-bottom:10px}.query-builder .rules-group-header .group-conditions .btn.readonly:not(.active),.query-builder .rules-group-header .group-conditions input[name$=_cond]{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;white-space:nowrap}.query-builder .rules-group-header .group-conditions .btn.readonly{border-radius:3px}.query-builder .rules-list{list-style:none;padding:0 0 0 15px;margin:0}.query-builder .rule-value-container{border-left:1px solid #ddd;padding-left:5px}.query-builder .rule-value-container label{margin-bottom:0;font-weight:400}.query-builder .rule-value-container label.block{display:block}.query-builder .rule-value-container input[type=number],.query-builder .rule-value-container input[type=text],.query-builder .rule-value-container select{padding:1px}.query-builder .error-container{display:none;cursor:help;color:red}.query-builder .has-error{background-color:#322;border-color:#800}.query-builder .has-error .error-container{display:inline-block!important}.query-builder .rules-list>::after,.query-builder .rules-list>::before{content:'';position:absolute;left:-10px;width:10px;height:calc(50% + 4px);border-color:#222;border-style:solid}.query-builder .rules-list>::before{top:-4px;border-width:0 0 2px 2px}.query-builder .rules-list>::after{top:50%;border-width:0 0 0 2px}.query-builder .rules-list>:first-child::before{top:-12px;height:calc(50% + 14px)}.query-builder .rules-list>:last-child::before{border-radius:0 0 0 4px}.query-builder .rules-list>:last-child::after{display:none}.query-builder.bt-checkbox-glyphicons .checkbox input[type=checkbox]:checked+label::after{font-family:'Glyphicons Halflings';content:'\e013'}.query-builder.bt-checkbox-glyphicons .checkbox label::after{padding-left:4px;padding-top:2px;font-size:9px}.query-builder .error-container+.tooltip .tooltip-inner{color:#f22!important}.query-builder p.filter-description{margin:5px 0 0 0;background:rgba(0,170,255,.2);border:1px solid #346f7b;color:#aad1e4;border-radius:5px;padding:2.5px 5px;font-size:.8em}.query-builder .rules-group-header [data-invert]{margin-left:5px}.query-builder .drag-handle{cursor:move;vertical-align:middle;margin-left:5px}.query-builder .dragging{position:fixed;opacity:.5;z-index:100}.query-builder .dragging::after,.query-builder .dragging::before{display:none}.query-builder .rule-placeholder{border:1px dashed #bbb;opacity:.7} \ No newline at end of file diff --git a/dist/css/query-builder.default.css b/dist/css/query-builder.default.css index faeb394b..f2ee533f 100644 --- a/dist/css/query-builder.default.css +++ b/dist/css/query-builder.default.css @@ -1,9 +1,8 @@ /*! - * jQuery QueryBuilder 2.4.1 + * jQuery QueryBuilder 2.4.2 * Copyright 2014-2017 Damien "Mistic" Sorel (http://www.strangeplanet.fr) * Licensed under MIT (http://opensource.org/licenses/MIT) */ - .query-builder .rules-group-container, .query-builder .rule-container, .query-builder .rule-placeholder { position: relative; margin: 4px 0; @@ -104,6 +103,16 @@ display: none; } +.query-builder.bt-checkbox-glyphicons .checkbox input[type=checkbox]:checked + label::after { + font-family: 'Glyphicons Halflings'; + content: '\e013'; +} +.query-builder.bt-checkbox-glyphicons .checkbox label::after { + padding-left: 4px; + padding-top: 2px; + font-size: 9px; +} + .query-builder .error-container + .tooltip .tooltip-inner { color: #F99 !important; } diff --git a/dist/css/query-builder.default.min.css b/dist/css/query-builder.default.min.css index 5a278a46..7e043e66 100644 --- a/dist/css/query-builder.default.min.css +++ b/dist/css/query-builder.default.min.css @@ -1,7 +1,6 @@ /*! - * jQuery QueryBuilder 2.4.1 + * jQuery QueryBuilder 2.4.2 * Copyright 2014-2017 Damien "Mistic" Sorel (http://www.strangeplanet.fr) * Licensed under MIT (http://opensource.org/licenses/MIT) */ - -.query-builder .rule-container,.query-builder .rule-placeholder,.query-builder .rules-group-container{position:relative;margin:4px 0;border-radius:5px;padding:5px;border:1px solid #EEE;background:rgba(255,255,255,.9)}.query-builder .drag-handle,.query-builder .error-container,.query-builder .rule-container .rule-filter-container,.query-builder .rule-container .rule-operator-container,.query-builder .rule-container .rule-value-container{display:inline-block;margin:0 5px 0 0;vertical-align:middle}.query-builder .rules-group-container{padding:10px 10px 6px;border:1px solid #DCC896;background:rgba(250,240,210,.5)}.query-builder .rules-group-header{margin-bottom:10px}.query-builder .rules-group-header .group-conditions .btn.readonly:not(.active),.query-builder .rules-group-header .group-conditions input[name$=_cond]{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;white-space:nowrap}.query-builder .rules-group-header .group-conditions .btn.readonly{border-radius:3px}.query-builder .rules-list{list-style:none;padding:0 0 0 15px;margin:0}.query-builder .rule-value-container{border-left:1px solid #DDD;padding-left:5px}.query-builder .rule-value-container label{margin-bottom:0;font-weight:400}.query-builder .rule-value-container label.block{display:block}.query-builder .rule-value-container input[type=number],.query-builder .rule-value-container input[type=text],.query-builder .rule-value-container select{padding:1px}.query-builder .error-container{display:none;cursor:help;color:red}.query-builder .has-error{background-color:#FDD;border-color:#F99}.query-builder .has-error .error-container{display:inline-block!important}.query-builder .dragging::after,.query-builder .dragging::before,.query-builder .rules-list>:last-child::after{display:none}.query-builder .rules-list>::after,.query-builder .rules-list>::before{content:'';position:absolute;left:-10px;width:10px;height:calc(50% + 4px);border-color:#CCC;border-style:solid}.query-builder .rules-list>::before{top:-4px;border-width:0 0 2px 2px}.query-builder .rules-list>::after{top:50%;border-width:0 0 0 2px}.query-builder .rules-list>:first-child::before{top:-12px;height:calc(50% + 14px)}.query-builder .rules-list>:last-child::before{border-radius:0 0 0 4px}.query-builder .error-container+.tooltip .tooltip-inner{color:#F99!important}.query-builder p.filter-description{margin:5px 0 0;background:#D9EDF7;border:1px solid #BCE8F1;color:#31708F;border-radius:5px;padding:2.5px 5px;font-size:.8em}.query-builder .rules-group-header [data-invert]{margin-left:5px}.query-builder .drag-handle{cursor:move;vertical-align:middle;margin-left:5px}.query-builder .dragging{position:fixed;opacity:.5;z-index:100}.query-builder .rule-placeholder{border:1px dashed #BBB;opacity:.7} \ No newline at end of file +.query-builder .rule-container,.query-builder .rule-placeholder,.query-builder .rules-group-container{position:relative;margin:4px 0;border-radius:5px;padding:5px;border:1px solid #eee;background:rgba(255,255,255,.9)}.query-builder .drag-handle,.query-builder .error-container,.query-builder .rule-container .rule-filter-container,.query-builder .rule-container .rule-operator-container,.query-builder .rule-container .rule-value-container{display:inline-block;margin:0 5px 0 0;vertical-align:middle}.query-builder .rules-group-container{padding:10px;padding-bottom:6px;border:1px solid #dcc896;background:rgba(250,240,210,.5)}.query-builder .rules-group-header{margin-bottom:10px}.query-builder .rules-group-header .group-conditions .btn.readonly:not(.active),.query-builder .rules-group-header .group-conditions input[name$=_cond]{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;white-space:nowrap}.query-builder .rules-group-header .group-conditions .btn.readonly{border-radius:3px}.query-builder .rules-list{list-style:none;padding:0 0 0 15px;margin:0}.query-builder .rule-value-container{border-left:1px solid #ddd;padding-left:5px}.query-builder .rule-value-container label{margin-bottom:0;font-weight:400}.query-builder .rule-value-container label.block{display:block}.query-builder .rule-value-container input[type=number],.query-builder .rule-value-container input[type=text],.query-builder .rule-value-container select{padding:1px}.query-builder .error-container{display:none;cursor:help;color:red}.query-builder .has-error{background-color:#fdd;border-color:#f99}.query-builder .has-error .error-container{display:inline-block!important}.query-builder .rules-list>::after,.query-builder .rules-list>::before{content:'';position:absolute;left:-10px;width:10px;height:calc(50% + 4px);border-color:#ccc;border-style:solid}.query-builder .rules-list>::before{top:-4px;border-width:0 0 2px 2px}.query-builder .rules-list>::after{top:50%;border-width:0 0 0 2px}.query-builder .rules-list>:first-child::before{top:-12px;height:calc(50% + 14px)}.query-builder .rules-list>:last-child::before{border-radius:0 0 0 4px}.query-builder .rules-list>:last-child::after{display:none}.query-builder.bt-checkbox-glyphicons .checkbox input[type=checkbox]:checked+label::after{font-family:'Glyphicons Halflings';content:'\e013'}.query-builder.bt-checkbox-glyphicons .checkbox label::after{padding-left:4px;padding-top:2px;font-size:9px}.query-builder .error-container+.tooltip .tooltip-inner{color:#f99!important}.query-builder p.filter-description{margin:5px 0 0 0;background:#d9edf7;border:1px solid #bce8f1;color:#31708f;border-radius:5px;padding:2.5px 5px;font-size:.8em}.query-builder .rules-group-header [data-invert]{margin-left:5px}.query-builder .drag-handle{cursor:move;vertical-align:middle;margin-left:5px}.query-builder .dragging{position:fixed;opacity:.5;z-index:100}.query-builder .dragging::after,.query-builder .dragging::before{display:none}.query-builder .rule-placeholder{border:1px dashed #bbb;opacity:.7} \ No newline at end of file diff --git a/dist/i18n/query-builder.ar.js b/dist/i18n/query-builder.ar.js index 4aa80af6..9033e976 100644 --- a/dist/i18n/query-builder.ar.js +++ b/dist/i18n/query-builder.ar.js @@ -1,5 +1,5 @@ /*! - * jQuery QueryBuilder 2.4.1 + * jQuery QueryBuilder 2.4.2 * Locale: Arabic (ar) * Author: Mohamed YOUNES, https://github.com/MedYOUNES * Licensed under MIT (http://opensource.org/licenses/MIT) diff --git a/dist/i18n/query-builder.az.js b/dist/i18n/query-builder.az.js index d627b717..dda973e0 100644 --- a/dist/i18n/query-builder.az.js +++ b/dist/i18n/query-builder.az.js @@ -1,5 +1,5 @@ /*! - * jQuery QueryBuilder 2.4.1 + * jQuery QueryBuilder 2.4.2 * Locale: Azerbaijan (az) * Author: Megaplan, mborisv * Licensed under MIT (http://opensource.org/licenses/MIT) @@ -70,7 +70,7 @@ QueryBuilder.regional['az'] = { "datetime_exceed_min": "{0} sonra olmalıdır", "datetime_exceed_max": "{0} əvvəl olmalıdır", "boolean_not_valid": "Loqik olmayan", - "operator_not_multiple": "{0} operatoru çoxlu məna daşımır" + "operator_not_multiple": "\"{1}\" operatoru çoxlu məna daşımır" }, "invert": "invert" }; diff --git a/dist/i18n/query-builder.bg.js b/dist/i18n/query-builder.bg.js index 0847c1fb..e80d9bf3 100644 --- a/dist/i18n/query-builder.bg.js +++ b/dist/i18n/query-builder.bg.js @@ -1,5 +1,5 @@ /*! - * jQuery QueryBuilder 2.4.1 + * jQuery QueryBuilder 2.4.2 * Locale: Bulgarian (bg) * Author: Valentin Hristov * Licensed under MIT (http://opensource.org/licenses/MIT) @@ -71,7 +71,7 @@ QueryBuilder.regional['bg'] = { "datetime_exceed_min": "Трябва да е след {0}", "datetime_exceed_max": "Трябва да е преди {0}", "boolean_not_valid": "Не е булева", - "operator_not_multiple": "Оператора {0} не може да приеме множество стойности" + "operator_not_multiple": "Оператора \"{1}\" не може да приеме множество стойности" } }; diff --git a/dist/i18n/query-builder.cs.js b/dist/i18n/query-builder.cs.js index a001a3a2..bbe390e9 100644 --- a/dist/i18n/query-builder.cs.js +++ b/dist/i18n/query-builder.cs.js @@ -1,5 +1,5 @@ /*! - * jQuery QueryBuilder 2.4.1 + * jQuery QueryBuilder 2.4.2 * Locale: Čeština (cs) * Author: Megaplan, mborisv * Licensed under MIT (http://opensource.org/licenses/MIT) @@ -70,7 +70,7 @@ QueryBuilder.regional['cs'] = { "datetime_exceed_min": "Musí být po {0}", "datetime_exceed_max": "Musí být do {0}", "boolean_not_valid": "Nelogické", - "operator_not_multiple": "Operátor {0} nepodporuje mnoho hodnot" + "operator_not_multiple": "Operátor \"{1}\" nepodporuje mnoho hodnot" }, "invert": "invertní" }; diff --git a/dist/i18n/query-builder.da.js b/dist/i18n/query-builder.da.js index 3136eadf..ab4f325a 100644 --- a/dist/i18n/query-builder.da.js +++ b/dist/i18n/query-builder.da.js @@ -1,5 +1,5 @@ /*! - * jQuery QueryBuilder 2.4.1 + * jQuery QueryBuilder 2.4.2 * Locale: Danish (da) * Author: Jna Borup Coyle, github@coyle.dk * Licensed under MIT (http://opensource.org/licenses/MIT) diff --git a/dist/i18n/query-builder.de.js b/dist/i18n/query-builder.de.js index a5b62089..13ad9b1d 100644 --- a/dist/i18n/query-builder.de.js +++ b/dist/i18n/query-builder.de.js @@ -1,5 +1,5 @@ /*! - * jQuery QueryBuilder 2.4.1 + * jQuery QueryBuilder 2.4.2 * Locale: German (de) * Author: "raimu" * Licensed under MIT (http://opensource.org/licenses/MIT) diff --git a/dist/i18n/query-builder.el.js b/dist/i18n/query-builder.el.js index cefe118f..5dbb8bc8 100644 --- a/dist/i18n/query-builder.el.js +++ b/dist/i18n/query-builder.el.js @@ -1,5 +1,5 @@ /*! - * jQuery QueryBuilder 2.4.1 + * jQuery QueryBuilder 2.4.2 * Locale: Greek (el) * Author: Stelios Patsatzis, https://www.linkedin.com/in/stelios-patsatzis-89841561 * Licensed under MIT (http://opensource.org/licenses/MIT) @@ -71,7 +71,7 @@ QueryBuilder.regional['el'] = { "datetime_exceed_min": "Νεότερο από {0}", "datetime_exceed_max": "Παλαιότερο από {0}", "boolean_not_valid": "Δεν είναι BOOLEAN", - "operator_not_multiple": "Η συνθήκη {0} δεν μπορεί να δεχθεί πολλαπλές τιμές" + "operator_not_multiple": "Η συνθήκη \"{1}\" δεν μπορεί να δεχθεί πολλαπλές τιμές" }, "invert": "Εναλλαγή" }; diff --git a/dist/i18n/query-builder.en.js b/dist/i18n/query-builder.en.js index 6e234ea9..5a9c5399 100644 --- a/dist/i18n/query-builder.en.js +++ b/dist/i18n/query-builder.en.js @@ -1,5 +1,5 @@ /*! - * jQuery QueryBuilder 2.4.1 + * jQuery QueryBuilder 2.4.2 * Locale: English (en) * Author: Damien "Mistic" Sorel, http://www.strangeplanet.fr * Licensed under MIT (http://opensource.org/licenses/MIT) @@ -71,7 +71,7 @@ QueryBuilder.regional['en'] = { "datetime_exceed_min": "Must be after {0}", "datetime_exceed_max": "Must be before {0}", "boolean_not_valid": "Not a boolean", - "operator_not_multiple": "Operator {0} cannot accept multiple values" + "operator_not_multiple": "Operator \"{1}\" cannot accept multiple values" }, "invert": "Invert", "NOT": "NOT" diff --git a/dist/i18n/query-builder.es.js b/dist/i18n/query-builder.es.js index 6fca27f2..f3cbc474 100644 --- a/dist/i18n/query-builder.es.js +++ b/dist/i18n/query-builder.es.js @@ -1,5 +1,5 @@ /*! - * jQuery QueryBuilder 2.4.1 + * jQuery QueryBuilder 2.4.2 * Locale: Spanish (es) * Author: "pyarza", "kddlb" * Licensed under MIT (http://opensource.org/licenses/MIT) diff --git a/dist/i18n/query-builder.fa-IR.js b/dist/i18n/query-builder.fa-IR.js index 60bc34a5..db41911f 100644 --- a/dist/i18n/query-builder.fa-IR.js +++ b/dist/i18n/query-builder.fa-IR.js @@ -1,5 +1,5 @@ /*! - * jQuery QueryBuilder 2.4.1 + * jQuery QueryBuilder 2.4.2 * Locale: Farsi (fa-ir) * Author: Behzad Sedighzade, behzad.sedighzade@gmail.com * Licensed under MIT (http://opensource.org/licenses/MIT) @@ -70,7 +70,7 @@ QueryBuilder.regional['fa-IR'] = { "datetime_exceed_min": "باید بعد از {0} باشد", "datetime_exceed_max": "باید قبل از {0} باشد", "boolean_not_valid": "مقدار دودویی وارد کنید", - "operator_not_multiple": "اپراتور {0} نمی تواند چند مقدار قبول کند" + "operator_not_multiple": "اپراتور \"{1}\" نمی تواند چند مقدار قبول کند" } }; diff --git a/dist/i18n/query-builder.fr.js b/dist/i18n/query-builder.fr.js index aa4a5bf9..58935e51 100644 --- a/dist/i18n/query-builder.fr.js +++ b/dist/i18n/query-builder.fr.js @@ -1,5 +1,5 @@ /*! - * jQuery QueryBuilder 2.4.1 + * jQuery QueryBuilder 2.4.2 * Locale: French (fr) * Author: Damien "Mistic" Sorel, http://www.strangeplanet.fr * Licensed under MIT (http://opensource.org/licenses/MIT) @@ -71,7 +71,7 @@ QueryBuilder.regional['fr'] = { "datetime_exceed_min": "Doit être après {0}", "datetime_exceed_max": "Doit être avant {0}", "boolean_not_valid": "N'est pas un booléen", - "operator_not_multiple": "L'opérateur {0} ne peut utiliser plusieurs valeurs" + "operator_not_multiple": "L'opérateur \"{1}\" ne peut utiliser plusieurs valeurs" }, "invert": "Inverser", "NOT": "NON" diff --git a/dist/i18n/query-builder.he.js b/dist/i18n/query-builder.he.js index f5766f9a..9aa37cb7 100644 --- a/dist/i18n/query-builder.he.js +++ b/dist/i18n/query-builder.he.js @@ -1,5 +1,5 @@ /*! - * jQuery QueryBuilder 2.4.1 + * jQuery QueryBuilder 2.4.2 * Locale: Hebrew (he) * Author: Kfir Stri https://github.com/kfirstri * Licensed under MIT (http://opensource.org/licenses/MIT) @@ -71,7 +71,7 @@ QueryBuilder.regional['he'] = { "datetime_exceed_min": "התאריך חייב להיות אחרי {0}", "datetime_exceed_max": "התאריך חייב להיות לפני {0}", "boolean_not_valid": "זהו לא בוליאני", - "operator_not_multiple": "האופרטור {0} לא יכול לקבל ערכים מרובים" + "operator_not_multiple": "האופרטור \"{1}\" לא יכול לקבל ערכים מרובים" }, "invert": "הפוך שאילתא", "NOT": "לא" diff --git a/dist/i18n/query-builder.it.js b/dist/i18n/query-builder.it.js index a4248662..73865bc3 100644 --- a/dist/i18n/query-builder.it.js +++ b/dist/i18n/query-builder.it.js @@ -1,5 +1,5 @@ /*! - * jQuery QueryBuilder 2.4.1 + * jQuery QueryBuilder 2.4.2 * Locale: Italian (it) * Licensed under MIT (http://opensource.org/licenses/MIT) */ diff --git a/dist/i18n/query-builder.nl.js b/dist/i18n/query-builder.nl.js index c3998564..d878c0c4 100644 --- a/dist/i18n/query-builder.nl.js +++ b/dist/i18n/query-builder.nl.js @@ -1,5 +1,5 @@ /*! - * jQuery QueryBuilder 2.4.1 + * jQuery QueryBuilder 2.4.2 * Locale: Dutch (nl) * Author: "Roywcm" * Licensed under MIT (http://opensource.org/licenses/MIT) diff --git a/dist/i18n/query-builder.no.js b/dist/i18n/query-builder.no.js index dd98d4b0..28bb2450 100644 --- a/dist/i18n/query-builder.no.js +++ b/dist/i18n/query-builder.no.js @@ -1,5 +1,5 @@ /*! - * jQuery QueryBuilder 2.4.1 + * jQuery QueryBuilder 2.4.2 * Locale: Norwegian (no) * Author: Jna Borup Coyle, github@coyle.dk * Licensed under MIT (http://opensource.org/licenses/MIT) diff --git a/dist/i18n/query-builder.pl.js b/dist/i18n/query-builder.pl.js index 21032f3d..4a53a478 100644 --- a/dist/i18n/query-builder.pl.js +++ b/dist/i18n/query-builder.pl.js @@ -1,5 +1,5 @@ /*! - * jQuery QueryBuilder 2.4.1 + * jQuery QueryBuilder 2.4.2 * Locale: Polish (pl) * Author: Artur Smolarek * Licensed under MIT (http://opensource.org/licenses/MIT) @@ -71,7 +71,7 @@ QueryBuilder.regional['pl'] = { "datetime_exceed_min": "Musi być po {0}", "datetime_exceed_max": "Musi być przed {0}", "boolean_not_valid": "Niepoprawna wartość logiczna", - "operator_not_multiple": "Operator {0} nie przyjmuje wielu wartości" + "operator_not_multiple": "Operator \"{1}\" nie przyjmuje wielu wartości" }, "invert": "Odwróć" }; diff --git a/dist/i18n/query-builder.pt-BR.js b/dist/i18n/query-builder.pt-BR.js index adcbd8c0..6f6bce06 100644 --- a/dist/i18n/query-builder.pt-BR.js +++ b/dist/i18n/query-builder.pt-BR.js @@ -1,5 +1,5 @@ /*! - * jQuery QueryBuilder 2.4.1 + * jQuery QueryBuilder 2.4.2 * Locale: Brazilian Portuguese (pr-BR) * Author: Leandro Gehlen, leandrogehlen@gmail.com; Marcos Ferretti, marcosvferretti@gmail.com * Licensed under MIT (http://opensource.org/licenses/MIT) @@ -71,7 +71,7 @@ QueryBuilder.regional['pt-BR'] = { "datetime_exceed_max": "É necessário ser inferior a {0}", "datetime_empty": "Nenhuma data selecionada", "boolean_not_valid": "Não é um valor booleano", - "operator_not_multiple": "O operador {0} não aceita valores múltiplos" + "operator_not_multiple": "O operador \"{1}\" não aceita valores múltiplos" }, "invert": "Inverter" }; diff --git a/dist/i18n/query-builder.pt-PT.js b/dist/i18n/query-builder.pt-PT.js index fdf34ba8..8ab75867 100644 --- a/dist/i18n/query-builder.pt-PT.js +++ b/dist/i18n/query-builder.pt-PT.js @@ -1,5 +1,5 @@ /*! - * jQuery QueryBuilder 2.4.1 + * jQuery QueryBuilder 2.4.2 * Locale: Portuguese (pt-PT) * Author: Miguel Guerreiro, migas.csi@gmail.com * Licensed under MIT (http://opensource.org/licenses/MIT) diff --git a/dist/i18n/query-builder.ro.js b/dist/i18n/query-builder.ro.js index 6b6423e6..10e74873 100644 --- a/dist/i18n/query-builder.ro.js +++ b/dist/i18n/query-builder.ro.js @@ -1,5 +1,5 @@ /*! - * jQuery QueryBuilder 2.4.1 + * jQuery QueryBuilder 2.4.2 * Locale: Romanian (ro) * Author: ArianServ * Licensed under MIT (http://opensource.org/licenses/MIT) diff --git a/dist/i18n/query-builder.ru.js b/dist/i18n/query-builder.ru.js index 85579491..1b275404 100644 --- a/dist/i18n/query-builder.ru.js +++ b/dist/i18n/query-builder.ru.js @@ -1,5 +1,5 @@ /*! - * jQuery QueryBuilder 2.4.1 + * jQuery QueryBuilder 2.4.2 * Locale: Russian (ru) * Licensed under MIT (http://opensource.org/licenses/MIT) */ @@ -68,7 +68,7 @@ QueryBuilder.regional['ru'] = { "datetime_exceed_min": "Должно быть, после {0}", "datetime_exceed_max": "Должно быть, до {0}", "boolean_not_valid": "Не логическое", - "operator_not_multiple": "Оператор {0} не поддерживает много значений" + "operator_not_multiple": "Оператор \"{1}\" не поддерживает много значений" }, "invert": "Инвертировать" }; diff --git a/dist/i18n/query-builder.sq.js b/dist/i18n/query-builder.sq.js index ee1ce5ef..48d22feb 100644 --- a/dist/i18n/query-builder.sq.js +++ b/dist/i18n/query-builder.sq.js @@ -1,5 +1,5 @@ /*! - * jQuery QueryBuilder 2.4.1 + * jQuery QueryBuilder 2.4.2 * Locale: Albanian (sq) * Author: Tomor Pupovci * Licensed under MIT (http://opensource.org/licenses/MIT) @@ -70,7 +70,7 @@ QueryBuilder.regional['sq'] = { "datetime_exceed_min": "Duhet të jetë pas {0}", "datetime_exceed_max": "Duhet të jetë para {0}", "boolean_not_valid": "Nuk është boolean", - "operator_not_multiple": "Operatori {0} nuk mund të pranojë vlera të shumëfishta" + "operator_not_multiple": "Operatori \"{1}\" nuk mund të pranojë vlera të shumëfishta" } }; diff --git a/dist/i18n/query-builder.tr.js b/dist/i18n/query-builder.tr.js index 088ff375..b23e445d 100644 --- a/dist/i18n/query-builder.tr.js +++ b/dist/i18n/query-builder.tr.js @@ -1,5 +1,5 @@ /*! - * jQuery QueryBuilder 2.4.1 + * jQuery QueryBuilder 2.4.2 * Locale: Turkish (tr) * Author: Aykut Alpgiray Ateş * Licensed under MIT (http://opensource.org/licenses/MIT) @@ -71,7 +71,7 @@ QueryBuilder.regional['tr'] = { "datetime_exceed_min": "{0} Tarihinden daha sonrası olmalı.", "datetime_exceed_max": "{0} Tarihinden daha öncesi olmalı.", "boolean_not_valid": "Değer Doğru/Yanlış(bool) olmalı", - "operator_not_multiple": "Operatör {0} birden fazla değer kabul etmiyor" + "operator_not_multiple": "Operatör \"{1}\" birden fazla değer kabul etmiyor" }, "invert": "Ters Çevir" }; diff --git a/dist/i18n/query-builder.ua.js b/dist/i18n/query-builder.ua.js index 80079e1a..dc496176 100644 --- a/dist/i18n/query-builder.ua.js +++ b/dist/i18n/query-builder.ua.js @@ -1,5 +1,5 @@ /*! - * jQuery QueryBuilder 2.4.1 + * jQuery QueryBuilder 2.4.2 * Locale: Ukrainian (ua) * Author: Megaplan, mborisv * Licensed under MIT (http://opensource.org/licenses/MIT) @@ -70,7 +70,7 @@ QueryBuilder.regional['ua'] = { "datetime_exceed_min": "Повинне бути, після {0}", "datetime_exceed_max": "Повинне бути, до {0}", "boolean_not_valid": "Не логічне", - "operator_not_multiple": "Оператор {0} не підтримує багато значень" + "operator_not_multiple": "Оператор \"{1}\" не підтримує багато значень" }, "invert": "інвертувати" }; diff --git a/dist/i18n/query-builder.zh-CN.js b/dist/i18n/query-builder.zh-CN.js index 21144340..77c479c4 100644 --- a/dist/i18n/query-builder.zh-CN.js +++ b/dist/i18n/query-builder.zh-CN.js @@ -1,5 +1,5 @@ /*! - * jQuery QueryBuilder 2.4.1 + * jQuery QueryBuilder 2.4.2 * Locale: Simplified Chinese (zh_CN) * Author: shadowwind, shatteredwindgo@gmail.com * Licensed under MIT (http://opensource.org/licenses/MIT) @@ -71,7 +71,7 @@ QueryBuilder.regional['zh-CN'] = { "datetime_exceed_min": "必须在{0}之后", "datetime_exceed_max": "必须在{0}之前", "boolean_not_valid": "不是布尔值", - "operator_not_multiple": "选项{0}无法接受多个值" + "operator_not_multiple": "选项\"{1}\"无法接受多个值" }, "invert": "倒置" }; diff --git a/dist/js/query-builder.js b/dist/js/query-builder.js index 61d44e19..4728f114 100644 --- a/dist/js/query-builder.js +++ b/dist/js/query-builder.js @@ -1,11 +1,8 @@ /*! - * jQuery QueryBuilder 2.4.1 + * jQuery QueryBuilder 2.4.2 * Copyright 2014-2017 Damien "Mistic" Sorel (http://www.strangeplanet.fr) * Licensed under MIT (http://opensource.org/licenses/MIT) */ - -// Languages: en -// Plugins: bt-checkbox, bt-selectpicker, bt-tooltip-errors, change-filters, filter-description, invert, mongodb-support, not-group, sortable, sql-support, unique-filter (function(root, factory) { if (typeof define == 'function' && define.amd) { define(['jquery', 'doT', 'jQuery.extendext'], factory); @@ -16,29 +13,174 @@ }(this, function($, doT) { "use strict"; -// CLASS DEFINITION -// =============================== +/** + * @typedef {object} Filter + * @memberof QueryBuilder + * @description See {@link http://querybuilder.js.org/index.html#filters} + */ + +/** + * @typedef {object} Operator + * @memberof QueryBuilder + * @description See {@link http://querybuilder.js.org/index.html#operators} + */ + +/** + * @param {jQuery} $el + * @param {object} options - see {@link http://querybuilder.js.org/#options} + * @constructor + * @fires QueryBuilder.afterInit + */ var QueryBuilder = function($el, options) { - this.init($el, options); -}; + $el[0].queryBuilder = this; + /** + * Element container + * @member {jQuery} + * @readonly + */ + this.$el = $el; -// EVENTS SYSTEM -// =============================== -$.extend(QueryBuilder.prototype, { - change: function(type, value) { - var event = new $.Event(this.tojQueryEvent(type, true), { - builder: this, - value: value - }); + /** + * Configuration object + * @member {object} + * @readonly + */ + this.settings = $.extendext(true, 'replace', {}, QueryBuilder.DEFAULTS, options); - this.$el.triggerHandler(event, Array.prototype.slice.call(arguments, 2)); + /** + * Internal model + * @member {Model} + * @readonly + */ + this.model = new Model(); - return event.value; - }, + /** + * Internal status + * @member {object} + * @property {string} id - id of the container + * @property {boolean} generated_id - if the container id has been generated + * @property {int} group_id - current group id + * @property {int} rule_id - current rule id + * @property {boolean} has_optgroup - if filters have optgroups + * @property {boolean} has_operator_optgroup - if operators have optgroups + * @readonly + * @private + */ + this.status = { + id: null, + generated_id: false, + group_id: 0, + rule_id: 0, + has_optgroup: false, + has_operator_optgroup: false + }; + + /** + * List of filters + * @member {QueryBuilder.Filter[]} + * @readonly + */ + this.filters = this.settings.filters; + + /** + * List of icons + * @member {object.} + * @readonly + */ + this.icons = this.settings.icons; + + /** + * List of operators + * @member {QueryBuilder.Operator[]} + * @readonly + */ + this.operators = this.settings.operators; + + /** + * List of templates + * @member {object.} + * @readonly + */ + this.templates = this.settings.templates; + + /** + * Plugins configuration + * @member {object.} + * @readonly + */ + this.plugins = this.settings.plugins; + + /** + * Translations object + * @member {object} + * @readonly + */ + this.lang = null; + + // translations : english << 'lang_code' << custom + if (QueryBuilder.regional['en'] === undefined) { + Utils.error('Config', '"i18n/en.js" not loaded.'); + } + this.lang = $.extendext(true, 'replace', {}, QueryBuilder.regional['en'], QueryBuilder.regional[this.settings.lang_code], this.settings.lang); + + // "allow_groups" can be boolean or int + if (this.settings.allow_groups === false) { + this.settings.allow_groups = 0; + } + else if (this.settings.allow_groups === true) { + this.settings.allow_groups = -1; + } + + // init templates + Object.keys(this.templates).forEach(function(tpl) { + if (!this.templates[tpl]) { + this.templates[tpl] = QueryBuilder.templates[tpl]; + } + if (typeof this.templates[tpl] == 'string') { + this.templates[tpl] = doT.template(this.templates[tpl]); + } + }, this); + + // ensure we have a container id + if (!this.$el.attr('id')) { + this.$el.attr('id', 'qb_' + Math.floor(Math.random() * 99999)); + this.status.generated_id = true; + } + this.status.id = this.$el.attr('id'); + + // INIT + this.$el.addClass('query-builder form-inline'); + + this.filters = this.checkFilters(this.filters); + this.operators = this.checkOperators(this.operators); + this.bindEvents(); + this.initPlugins(); + + /** + * When the initilization is done, just before creating the root group + * @event afterInit + * @memberof QueryBuilder + */ + this.trigger('afterInit'); + + if (options.rules) { + this.setRules(options.rules); + delete this.settings.rules; + } + else { + this.setRoot(true); + } +}; +$.extend(QueryBuilder.prototype, /** @lends QueryBuilder.prototype */ { + /** + * Triggers an event on the builder container + * @param {string} type + * @returns {$.Event} + */ trigger: function(type) { - var event = new $.Event(this.tojQueryEvent(type), { + var event = new $.Event(this._tojQueryEvent(type), { builder: this }); @@ -47,22 +189,64 @@ $.extend(QueryBuilder.prototype, { return event; }, + /** + * Triggers an event on the builder container and returns the modified value + * @param {string} type + * @param {*} value + * @returns {*} + */ + change: function(type, value) { + var event = new $.Event(this._tojQueryEvent(type, true), { + builder: this, + value: value + }); + + this.$el.triggerHandler(event, Array.prototype.slice.call(arguments, 2)); + + return event.value; + }, + + /** + * Attaches an event listener on the builder container + * @param {string} type + * @param {function} cb + * @returns {QueryBuilder} + */ on: function(type, cb) { - this.$el.on(this.tojQueryEvent(type), cb); + this.$el.on(this._tojQueryEvent(type), cb); return this; }, + /** + * Removes an event listener from the builder container + * @param {string} type + * @param {function} [cb] + * @returns {QueryBuilder} + */ off: function(type, cb) { - this.$el.off(this.tojQueryEvent(type), cb); + this.$el.off(this._tojQueryEvent(type), cb); return this; }, + /** + * Attaches an event listener called once on the builder container + * @param {string} type + * @param {function} cb + * @returns {QueryBuilder} + */ once: function(type, cb) { - this.$el.one(this.tojQueryEvent(type), cb); + this.$el.one(this._tojQueryEvent(type), cb); return this; }, - tojQueryEvent: function(name, filter) { + /** + * Appends `.queryBuilder` and optionally `.filter` to the events names + * @param {string} name + * @param {boolean} [filter=false] + * @returns {string} + * @private + */ + _tojQueryEvent: function(name, filter) { return name.split(' ').map(function(type) { return type + '.queryBuilder' + (filter ? '.filter' : ''); }).join(' '); @@ -70,88 +254,11 @@ $.extend(QueryBuilder.prototype, { }); -// PLUGINS SYSTEM -// =============================== -QueryBuilder.plugins = {}; - -/** - * Get or extend the default configuration - * @param options {object,optional} new configuration, leave undefined to get the default config - * @return {undefined|object} nothing or configuration object (copy) - */ -QueryBuilder.defaults = function(options) { - if (typeof options == 'object') { - $.extendext(true, 'replace', QueryBuilder.DEFAULTS, options); - } - else if (typeof options == 'string') { - if (typeof QueryBuilder.DEFAULTS[options] == 'object') { - return $.extend(true, {}, QueryBuilder.DEFAULTS[options]); - } - else { - return QueryBuilder.DEFAULTS[options]; - } - } - else { - return $.extend(true, {}, QueryBuilder.DEFAULTS); - } -}; - -/** - * Define a new plugin - * @param {string} - * @param {function} - * @param {object,optional} default configuration - */ -QueryBuilder.define = function(name, fct, def) { - QueryBuilder.plugins[name] = { - fct: fct, - def: def || {} - }; -}; - -/** - * Add new methods - * @param {object} - */ -QueryBuilder.extend = function(methods) { - $.extend(QueryBuilder.prototype, methods); -}; - -/** - * Init plugins for an instance - * @throws ConfigError - */ -QueryBuilder.prototype.initPlugins = function() { - if (!this.plugins) { - return; - } - - if ($.isArray(this.plugins)) { - var tmp = {}; - this.plugins.forEach(function(plugin) { - tmp[plugin] = null; - }); - this.plugins = tmp; - } - - Object.keys(this.plugins).forEach(function(plugin) { - if (plugin in QueryBuilder.plugins) { - this.plugins[plugin] = $.extend(true, {}, - QueryBuilder.plugins[plugin].def, - this.plugins[plugin] || {} - ); - - QueryBuilder.plugins[plugin].fct.call(this, this.plugins[plugin]); - } - else { - Utils.error('Config', 'Unable to find plugin "{0}"', plugin); - } - }, this); -}; - - /** * Allowed types and their internal representation + * @type {object.} + * @readonly + * @private */ QueryBuilder.types = { 'string': 'string', @@ -165,9 +272,13 @@ QueryBuilder.types = { /** * Allowed inputs + * @type {string[]} + * @readonly + * @private */ QueryBuilder.inputs = [ 'text', + 'number', 'textarea', 'radio', 'checkbox', @@ -176,6 +287,9 @@ QueryBuilder.inputs = [ /** * Runtime modifiable options with `setOptions` method + * @type {string[]} + * @readonly + * @private */ QueryBuilder.modifiable_options = [ 'display_errors', @@ -187,8 +301,10 @@ QueryBuilder.modifiable_options = [ /** * CSS selectors for common components + * @type {object.} + * @readonly */ -var Selectors = QueryBuilder.selectors = { +QueryBuilder.selectors = { group_container: '.rules-group-container', rule_container: '.rule-container', filter_container: '.rule-filter-container', @@ -216,17 +332,23 @@ var Selectors = QueryBuilder.selectors = { }; /** - * Template strings (see `template.js`) + * Template strings (see template.js) + * @type {object.} + * @readonly */ QueryBuilder.templates = {}; /** - * Localized strings (see `i18n/`) + * Localized strings (see i18n/) + * @type {object.} + * @readonly */ QueryBuilder.regional = {}; /** * Default operators + * @type {object.} + * @readonly */ QueryBuilder.OPERATORS = { equal: { type: 'equal', nb_inputs: 1, multiple: false, apply_to: ['string', 'number', 'datetime', 'boolean'] }, @@ -253,6 +375,8 @@ QueryBuilder.OPERATORS = { /** * Default configuration + * @type {object} + * @readonly */ QueryBuilder.DEFAULTS = { filters: [], @@ -328,83 +452,96 @@ QueryBuilder.DEFAULTS = { /** - * Init the builder + * @module plugins */ -QueryBuilder.prototype.init = function($el, options) { - $el[0].queryBuilder = this; - this.$el = $el; - - // PROPERTIES - this.settings = $.extendext(true, 'replace', {}, QueryBuilder.DEFAULTS, options); - this.model = new Model(); - this.status = { - group_id: 0, - rule_id: 0, - generated_id: false, - has_optgroup: false, - has_operator_oprgroup: false, - id: null - }; - - // "allow_groups" can be boolean or int - if (this.settings.allow_groups === false) { - this.settings.allow_groups = 0; - } - else if (this.settings.allow_groups === true) { - this.settings.allow_groups = -1; - } - // SETTINGS SHORTCUTS - this.filters = this.settings.filters; - this.icons = this.settings.icons; - this.operators = this.settings.operators; - this.templates = this.settings.templates; - this.plugins = this.settings.plugins; +/** + * Definition of available plugins + * @type {object.} + */ +QueryBuilder.plugins = {}; - // translations : english << 'lang_code' << custom - if (QueryBuilder.regional['en'] === undefined) { - Utils.error('Config', '"i18n/en.js" not loaded.'); +/** + * Gets or extends the default configuration + * @param {object} [options] - new configuration + * @returns {undefined|object} nothing or configuration object (copy) + */ +QueryBuilder.defaults = function(options) { + if (typeof options == 'object') { + $.extendext(true, 'replace', QueryBuilder.DEFAULTS, options); } - this.lang = $.extendext(true, 'replace', {}, QueryBuilder.regional['en'], QueryBuilder.regional[this.settings.lang_code], this.settings.lang); - - // init templates - Object.keys(this.templates).forEach(function(tpl) { - if (!this.templates[tpl]) { - this.templates[tpl] = QueryBuilder.templates[tpl]; + else if (typeof options == 'string') { + if (typeof QueryBuilder.DEFAULTS[options] == 'object') { + return $.extend(true, {}, QueryBuilder.DEFAULTS[options]); } - if (typeof this.templates[tpl] == 'string') { - this.templates[tpl] = doT.template(this.templates[tpl]); + else { + return QueryBuilder.DEFAULTS[options]; } - }, this); - - // ensure we have a container id - if (!this.$el.attr('id')) { - this.$el.attr('id', 'qb_' + Math.floor(Math.random() * 99999)); - this.status.generated_id = true; } - this.status.id = this.$el.attr('id'); + else { + return $.extend(true, {}, QueryBuilder.DEFAULTS); + } +}; - // INIT - this.$el.addClass('query-builder form-inline'); +/** + * Registers a new plugin + * @param {string} name + * @param {function} fct - init function + * @param {object} [def] - default options + */ +QueryBuilder.define = function(name, fct, def) { + QueryBuilder.plugins[name] = { + fct: fct, + def: def || {} + }; +}; - this.filters = this.checkFilters(this.filters); - this.operators = this.checkOperators(this.operators); - this.bindEvents(); - this.initPlugins(); +/** + * Adds new methods to QueryBuilder prototype + * @param {object.} methods + */ +QueryBuilder.extend = function(methods) { + $.extend(QueryBuilder.prototype, methods); +}; - this.trigger('afterInit'); - if (options.rules) { - this.setRules(options.rules); - delete this.settings.rules; +/** + * Initializes plugins for an instance + * @throws ConfigError + * @private + */ +QueryBuilder.prototype.initPlugins = function() { + if (!this.plugins) { + return; } - else { - this.setRoot(true); + + if ($.isArray(this.plugins)) { + var tmp = {}; + this.plugins.forEach(function(plugin) { + tmp[plugin] = null; + }); + this.plugins = tmp; } + + Object.keys(this.plugins).forEach(function(plugin) { + if (plugin in QueryBuilder.plugins) { + this.plugins[plugin] = $.extend(true, {}, + QueryBuilder.plugins[plugin].def, + this.plugins[plugin] || {} + ); + + QueryBuilder.plugins[plugin].fct.call(this, this.plugins[plugin]); + } + else { + Utils.error('Config', 'Unable to find plugin "{0}"', plugin); + } + }, this); }; /** * Checks the configuration of each filter + * @param {QueryBuilder.Filter[]} filters + * @returns {QueryBuilder.Filter[]} * @throws ConfigError */ QueryBuilder.prototype.checkFilters = function(filters) { @@ -431,7 +568,7 @@ QueryBuilder.prototype.checkFilters = function(filters) { } if (!filter.input) { - filter.input = 'text'; + filter.input = QueryBuilder.types[filter.type] === 'number' ? 'number' : 'text'; } else if (typeof filter.input != 'function' && QueryBuilder.inputs.indexOf(filter.input) == -1) { Utils.error('Config', 'Invalid input "{0}"', filter.input); @@ -465,7 +602,8 @@ QueryBuilder.prototype.checkFilters = function(filters) { } switch (filter.input) { - case 'radio': case 'checkbox': + case 'radio': + case 'checkbox': if (!filter.values || filter.values.length < 1) { Utils.error('Config', 'Missing filter "{0}" values', filter.id); } @@ -493,7 +631,7 @@ QueryBuilder.prototype.checkFilters = function(filters) { else { var self = this; filters.sort(function(a, b) { - return self.translateLabel(a.label).localeCompare(self.translateLabel(b.label)); + return self.translate(a.label).localeCompare(self.translate(b.label)); }); } } @@ -507,6 +645,8 @@ QueryBuilder.prototype.checkFilters = function(filters) { /** * Checks the configuration of each operator + * @param {QueryBuilder.Operator[]} operators + * @returns {QueryBuilder.Operator[]} * @throws ConfigError */ QueryBuilder.prototype.checkOperators = function(operators) { @@ -560,54 +700,56 @@ QueryBuilder.prototype.checkOperators = function(operators) { }; /** - * Add all events listeners + * Adds all events listeners to the builder + * @private */ QueryBuilder.prototype.bindEvents = function() { var self = this; + var Selectors = QueryBuilder.selectors; // group condition change this.$el.on('change.queryBuilder', Selectors.group_condition, function() { if ($(this).is(':checked')) { var $group = $(this).closest(Selectors.group_container); - Model($group).condition = $(this).val(); + self.getModel($group).condition = $(this).val(); } }); // rule filter change this.$el.on('change.queryBuilder', Selectors.rule_filter, function() { var $rule = $(this).closest(Selectors.rule_container); - Model($rule).filter = self.getFilterById($(this).val()); + self.getModel($rule).filter = self.getFilterById($(this).val()); }); // rule operator change this.$el.on('change.queryBuilder', Selectors.rule_operator, function() { var $rule = $(this).closest(Selectors.rule_container); - Model($rule).operator = self.getOperatorByType($(this).val()); + self.getModel($rule).operator = self.getOperatorByType($(this).val()); }); // add rule button this.$el.on('click.queryBuilder', Selectors.add_rule, function() { var $group = $(this).closest(Selectors.group_container); - self.addRule(Model($group)); + self.addRule(self.getModel($group)); }); // delete rule button this.$el.on('click.queryBuilder', Selectors.delete_rule, function() { var $rule = $(this).closest(Selectors.rule_container); - self.deleteRule(Model($rule)); + self.deleteRule(self.getModel($rule)); }); if (this.settings.allow_groups !== 0) { // add group button this.$el.on('click.queryBuilder', Selectors.add_group, function() { var $group = $(this).closest(Selectors.group_container); - self.addGroup(Model($group)); + self.addGroup(self.getModel($group)); }); // delete group button this.$el.on('click.queryBuilder', Selectors.delete_group, function() { var $group = $(this).closest(Selectors.group_container); - self.deleteGroup(Model($group)); + self.deleteGroup(self.getModel($group)); }); } @@ -617,12 +759,12 @@ QueryBuilder.prototype.bindEvents = function() { node.$el.remove(); self.refreshGroupsConditions(); }, - 'add': function(e, node, index) { + 'add': function(e, parent, node, index) { if (index === 0) { - node.$el.prependTo(node.parent.$el.find('>' + Selectors.rules_list)); + node.$el.prependTo(parent.$el.find('>' + QueryBuilder.selectors.rules_list)); } else { - node.$el.insertAfter(node.parent.rules[index - 1].$el); + node.$el.insertAfter(parent.rules[index - 1].$el); } self.refreshGroupsConditions(); }, @@ -630,7 +772,7 @@ QueryBuilder.prototype.bindEvents = function() { node.$el.detach(); if (index === 0) { - node.$el.prependTo(group.$el.find('>' + Selectors.rules_list)); + node.$el.prependTo(group.$el.find('>' + QueryBuilder.selectors.rules_list)); } else { node.$el.insertAfter(group.rules[index - 1].$el); @@ -641,7 +783,7 @@ QueryBuilder.prototype.bindEvents = function() { if (node instanceof Rule) { switch (field) { case 'error': - self.displayError(node); + self.updateError(node); break; case 'flags': @@ -664,7 +806,7 @@ QueryBuilder.prototype.bindEvents = function() { else { switch (field) { case 'error': - self.displayError(node); + self.updateError(node); break; case 'flags': @@ -681,11 +823,12 @@ QueryBuilder.prototype.bindEvents = function() { }; /** - * Create the root group - * @param addRule {bool,optional} add a default empty rule - * @param data {mixed,optional} group custom data - * @param flags {object,optional} flags to apply to the group - * @return group {Root} + * Creates the root group + * @param {boolean} [addRule=true] - adds a default empty rule + * @param {object} [data] - group custom data + * @param {object} [flags] - flags to apply to the group + * @returns {Group} root group + * @fires QueryBuilder.afterAddGroup */ QueryBuilder.prototype.setRoot = function(addRule, data, flags) { addRule = (addRule === undefined || addRule === true); @@ -712,18 +855,28 @@ QueryBuilder.prototype.setRoot = function(addRule, data, flags) { }; /** - * Add a new group - * @param parent {Group} - * @param addRule {bool,optional} add a default empty rule - * @param data {mixed,optional} group custom data - * @param flags {object,optional} flags to apply to the group - * @return group {Group} + * Adds a new group + * @param {Group} parent + * @param {boolean} [addRule=true] - adds a default empty rule + * @param {object} [data] - group custom data + * @param {object} [flags] - flags to apply to the group + * @returns {Group} + * @fires QueryBuilder.beforeAddGroup + * @fires QueryBuilder.afterAddGroup */ QueryBuilder.prototype.addGroup = function(parent, addRule, data, flags) { addRule = (addRule === undefined || addRule === true); var level = parent.level + 1; + /** + * Just before adding a group, can be prevented. + * @event beforeAddGroup + * @memberof QueryBuilder + * @param {Group} parent + * @param {boolean} addRule - if an empty rule will be added in the group + * @param {int} level - nesting level of the group, 1 is the root group + */ var e = this.trigger('beforeAddGroup', parent, addRule, level); if (e.isDefaultPrevented()) { return null; @@ -736,6 +889,12 @@ QueryBuilder.prototype.addGroup = function(parent, addRule, data, flags) { model.data = data; model.__.flags = $.extend({}, this.settings.default_group_flags, flags); + /** + * Just after adding a group + * @event afterAddGroup + * @memberof QueryBuilder + * @param {Group} group + */ this.trigger('afterAddGroup', model); model.condition = this.settings.default_condition; @@ -748,15 +907,23 @@ QueryBuilder.prototype.addGroup = function(parent, addRule, data, flags) { }; /** - * Tries to delete a group. The group is not deleted if at least one rule is no_delete. - * @param group {Group} - * @return {boolean} true if the group has been deleted + * Tries to delete a group. The group is not deleted if at least one rule is flagged `no_delete`. + * @param {Group} group + * @returns {boolean} if the group has been deleted + * @fires QueryBuilder.beforeDeleteGroup + * @fires QueryBuilder.afterDeleteGroup */ QueryBuilder.prototype.deleteGroup = function(group) { if (group.isRoot()) { return false; } + /** + * Just before deleting a group, can be prevented + * @event beforeDeleteGroup + * @memberof QueryBuilder + * @param {Group} parent + */ var e = this.trigger('beforeDeleteGroup', group); if (e.isDefaultPrevented()) { return false; @@ -765,13 +932,19 @@ QueryBuilder.prototype.deleteGroup = function(group) { var del = true; group.each('reverse', function(rule) { - del&= this.deleteRule(rule); + del &= this.deleteRule(rule); }, function(group) { - del&= this.deleteGroup(group); + del &= this.deleteGroup(group); }, this); if (del) { group.drop(); + + /** + * Just after deleting a group + * @event afterDeleteGroup + * @memberof QueryBuilder + */ this.trigger('afterDeleteGroup'); } @@ -779,43 +952,61 @@ QueryBuilder.prototype.deleteGroup = function(group) { }; /** - * Changes the condition of a group - * @param group {Group} + * Performs actions when a group's condition changes + * @param {Group} group + * @fires QueryBuilder.afterUpdateGroupCondition + * @private */ QueryBuilder.prototype.updateGroupCondition = function(group) { - group.$el.find('>' + Selectors.group_condition).each(function() { + group.$el.find('>' + QueryBuilder.selectors.group_condition).each(function() { var $this = $(this); $this.prop('checked', $this.val() === group.condition); $this.parent().toggleClass('active', $this.val() === group.condition); }); + /** + * After the group condition has been modified + * @event afterUpdateGroupCondition + * @memberof QueryBuilder + * @param {Group} group + */ this.trigger('afterUpdateGroupCondition', group); }; /** - * Update visibility of conditions based on number of rules inside each group + * Updates the visibility of conditions based on number of rules inside each group + * @private */ QueryBuilder.prototype.refreshGroupsConditions = function() { (function walk(group) { if (!group.flags || (group.flags && !group.flags.condition_readonly)) { - group.$el.find('>' + Selectors.group_condition).prop('disabled', group.rules.length <= 1) + group.$el.find('>' + QueryBuilder.selectors.group_condition).prop('disabled', group.rules.length <= 1) .parent().toggleClass('disabled', group.rules.length <= 1); } - group.each(function(rule) {}, function(group) { + group.each(null, function(group) { walk(group); }, this); }(this.model.root)); }; /** - * Add a new rule - * @param parent {Group} - * @param data {mixed,optional} rule custom data - * @param flags {object,optional} flags to apply to the rule - * @return rule {Rule} + * Adds a new rule + * @param {Group} parent + * @param {object} [data] - rule custom data + * @param {object} [flags] - flags to apply to the rule + * @returns {Rule} + * @fires QueryBuilder.beforeAddRule + * @fires QueryBuilder.afterAddRule + * @fires QueryBuilder.changer:getDefaultFilter */ QueryBuilder.prototype.addRule = function(parent, data, flags) { + /** + * Just before adding a rule, can be prevented + * @event beforeAddRule + * @memberof QueryBuilder + * @param {Group} parent + */ var e = this.trigger('beforeAddRule', parent); if (e.isDefaultPrevented()) { return null; @@ -831,11 +1022,25 @@ QueryBuilder.prototype.addRule = function(parent, data, flags) { model.__.flags = $.extend({}, this.settings.default_rule_flags, flags); + /** + * Just after adding a rule + * @event afterAddRule + * @memberof QueryBuilder + * @param {Rule} rule + */ this.trigger('afterAddRule', model); this.createRuleFilters(model); if (this.settings.default_filter || !this.settings.display_empty_filter) { + /** + * Modifies the default filter for a rule + * @event changer:getDefaultFilter + * @memberof QueryBuilder + * @param {QueryBuilder.Filter} filter + * @param {Rule} rule + * @returns {QueryBuilder.Filter} + */ model.filter = this.change('getDefaultFilter', this.getFilterById(this.settings.default_filter || this.filters[0].id), model @@ -846,15 +1051,23 @@ QueryBuilder.prototype.addRule = function(parent, data, flags) { }; /** - * Delete a rule. - * @param rule {Rule} - * @return {boolean} true if the rule has been deleted + * Tries to delete a rule + * @param {Rule} rule + * @returns {boolean} if the rule has been deleted + * @fires QueryBuilder.beforeDeleteRule + * @fires QueryBuilder.afterDeleteRule */ QueryBuilder.prototype.deleteRule = function(rule) { if (rule.flags.no_delete) { return false; } + /** + * Just before deleting a rule, can be prevented + * @event beforeDeleteRule + * @memberof QueryBuilder + * @param {Rule} rule + */ var e = this.trigger('beforeDeleteRule', rule); if (e.isDefaultPrevented()) { return false; @@ -862,30 +1075,54 @@ QueryBuilder.prototype.deleteRule = function(rule) { rule.drop(); + /** + * Just after deleting a rule + * @event afterDeleteRule + * @memberof QueryBuilder + */ this.trigger('afterDeleteRule'); return true; }; /** - * Create the filters for a rule and init the rule operator - * @param rule {Rule} + * Creates the operators for a rule and init the rule operator + * @param {Rule} rule + * @fires QueryBuilder.afterCreateRuleOperators + * @private */ QueryBuilder.prototype.createRuleOperators = function(rule) { - var $operatorContainer = rule.$el.find(Selectors.operator_container).empty(); + var $operatorContainer = rule.$el.find(QueryBuilder.selectors.operator_container).empty(); if (!rule.filter) { return; @@ -899,15 +1136,24 @@ QueryBuilder.prototype.createRuleOperators = function(rule) { // set the operator without triggering update event rule.__.operator = operators[0]; + /** + * After creating the dropdown for operators + * @event afterCreateRuleOperators + * @memberof QueryBuilder + * @param {Rule} rule + * @param {QueryBuilder.Operator[]} operators - allowed operators for this rule + */ this.trigger('afterCreateRuleOperators', rule, operators); }; /** - * Create the main input for a rule - * @param rule {Rule} + * Creates the main input for a rule + * @param {Rule} rule + * @fires QueryBuilder.afterCreateRuleInput + * @private */ QueryBuilder.prototype.createRuleInput = function(rule) { - var $valueContainer = rule.$el.find(Selectors.value_container).empty(); + var $valueContainer = rule.$el.find(QueryBuilder.selectors.value_container).empty(); rule.__.value = undefined; @@ -940,6 +1186,12 @@ QueryBuilder.prototype.createRuleInput = function(rule) { $inputs[filter.plugin](filter.plugin_config || {}); } + /** + * After creating the input for a rule and initializing optional plugin + * @event afterCreateRuleInput + * @memberof QueryBuilder + * @param {Rule} rule + */ this.trigger('afterCreateRuleInput', rule); if (filter.default_value !== undefined) { @@ -953,31 +1205,41 @@ QueryBuilder.prototype.createRuleInput = function(rule) { }; /** - * Perform action when rule's filter is changed - * @param rule {Rule} - * @param previousFilter {object} + * Performs action when a rule's filter changes + * @param {Rule} rule + * @param {object} previousFilter + * @fires QueryBuilder.afterUpdateRuleFilter + * @private */ QueryBuilder.prototype.updateRuleFilter = function(rule, previousFilter) { this.createRuleOperators(rule); this.createRuleInput(rule); - rule.$el.find(Selectors.rule_filter).val(rule.filter ? rule.filter.id : '-1'); + rule.$el.find(QueryBuilder.selectors.rule_filter).val(rule.filter ? rule.filter.id : '-1'); // clear rule data if the filter changed if (previousFilter && rule.filter && previousFilter.id !== rule.filter.id) { rule.data = undefined; } + /** + * After the filter has been updated and the operators and input re-created + * @event afterUpdateRuleFilter + * @memberof QueryBuilder + * @param {Rule} rule + */ this.trigger('afterUpdateRuleFilter', rule); }; /** - * Update main visibility when rule operator changes - * @param rule {Rule} - * @param previousOperator {object} + * Performs actions when a rule's operator changes + * @param {Rule} rule + * @param {object} previousOperator + * @fires QueryBuilder.afterUpdateRuleOperator + * @private */ QueryBuilder.prototype.updateRuleOperator = function(rule, previousOperator) { - var $valueContainer = rule.$el.find(Selectors.value_container); + var $valueContainer = rule.$el.find(QueryBuilder.selectors.value_container); if (!rule.operator || rule.operator.nb_inputs === 0) { $valueContainer.hide(); @@ -987,38 +1249,58 @@ QueryBuilder.prototype.updateRuleOperator = function(rule, previousOperator) { else { $valueContainer.show(); - if ($valueContainer.is(':empty') || !previousOperator || rule.operator.nb_inputs !== previousOperator.nb_inputs) { + if ($valueContainer.is(':empty') || !previousOperator || + rule.operator.nb_inputs !== previousOperator.nb_inputs || + rule.operator.optgroup !== previousOperator.optgroup + ) { this.createRuleInput(rule); } } if (rule.operator) { - rule.$el.find(Selectors.rule_operator).val(rule.operator.type); + rule.$el.find(QueryBuilder.selectors.rule_operator).val(rule.operator.type); } + /** + * After the operator has been updated and the input optionally re-created + * @event afterUpdateRuleOperator + * @memberof QueryBuilder + * @param {Rule} rule + */ this.trigger('afterUpdateRuleOperator', rule); this.updateRuleValue(rule); }; /** - * Perform action when rule's value is changed - * @param rule {Rule} + * Performs actions when rule's value changes + * @param {Rule} rule + * @fires QueryBuilder.afterUpdateRuleValue + * @private */ QueryBuilder.prototype.updateRuleValue = function(rule) { if (!rule._updating_value) { this.setRuleInputValue(rule, rule.value); } + /** + * After the rule value has been modified + * @event afterUpdateRuleValue + * @memberof QueryBuilder + * @param {Rule} rule + */ this.trigger('afterUpdateRuleValue', rule); }; /** - * Change rules properties depending on flags. - * @param rule {Rule} + * Changes a rule's properties depending on its flags + * @param {Rule} rule + * @fires QueryBuilder.afterApplyRuleFlags + * @private */ QueryBuilder.prototype.applyRuleFlags = function(rule) { var flags = rule.flags; + var Selectors = QueryBuilder.selectors; if (flags.filter_readonly) { rule.$el.find(Selectors.rule_filter).prop('disabled', true); @@ -1033,15 +1315,24 @@ QueryBuilder.prototype.applyRuleFlags = function(rule) { rule.$el.find(Selectors.delete_rule).remove(); } + /** + * After rule's flags has been applied + * @event afterApplyRuleFlags + * @memberof QueryBuilder + * @param {Rule} rule + */ this.trigger('afterApplyRuleFlags', rule); }; /** - * Change group properties depending on flags. - * @param group {Group} + * Changes group's properties depending on its flags + * @param {Group} group + * @fires QueryBuilder.afterApplyGroupFlags + * @private */ QueryBuilder.prototype.applyGroupFlags = function(group) { var flags = group.flags; + var Selectors = QueryBuilder.selectors; if (flags.condition_readonly) { group.$el.find('>' + Selectors.group_condition).prop('disabled', true) @@ -1057,12 +1348,18 @@ QueryBuilder.prototype.applyGroupFlags = function(group) { group.$el.find(Selectors.delete_group).remove(); } + /** + * After group's flags has been applied + * @event afterApplyGroupFlags + * @memberof QueryBuilder + * @param {Group} group + */ this.trigger('afterApplyGroupFlags', group); }; /** - * Clear all errors markers - * @param node {Node,optional} default is root Group + * Clears all errors markers + * @param {Node} [node] default is root Group */ QueryBuilder.prototype.clearErrors = function(node) { node = node || this.model.root; @@ -1083,37 +1380,59 @@ QueryBuilder.prototype.clearErrors = function(node) { }; /** - * Add/Remove class .has-error and update error title - * @param node {Node} + * Adds/Removes error on a Rule or Group + * @param {Node} node + * @fires QueryBuilder.changer:displayError + * @private */ -QueryBuilder.prototype.displayError = function(node) { +QueryBuilder.prototype.updateError = function(node) { if (this.settings.display_errors) { if (node.error === null) { node.$el.removeClass('has-error'); } else { - var errorMessage = this.lang.errors[node.error[0]] || node.error[0]; + var errorMessage = this.translate('errors', node.error[0]); errorMessage = Utils.fmt(errorMessage, node.error.slice(1)); + + /** + * Modifies an error message before display + * @event changer:displayError + * @memberof QueryBuilder + * @param {string} errorMessage - the error message (translated and formatted) + * @param {array} error - the raw error array (error code and optional arguments) + * @param {Node} node + * @returns {string} + */ errorMessage = this.change('displayError', errorMessage, node.error, node); node.$el.addClass('has-error') - .find(Selectors.error_container).eq(0) + .find(QueryBuilder.selectors.error_container).eq(0) .attr('title', errorMessage); } } }; /** - * Trigger a validation error event - * @param node {Node} - * @param error {array} - * @param value {mixed} + * Triggers a validation error event + * @param {Node} node + * @param {string|array} error + * @param {*} value + * @fires QueryBuilder.validationError + * @private */ QueryBuilder.prototype.triggerValidationError = function(node, error, value) { if (!$.isArray(error)) { error = [error]; } + /** + * Fired when a validation error occurred, can be prevented + * @event validationError + * @memberof QueryBuilder + * @param {Node} node + * @param {string} error + * @param {*} value + */ var e = this.trigger('validationError', node, error, value); if (!e.isDefaultPrevented()) { node.error = error; @@ -1122,9 +1441,15 @@ QueryBuilder.prototype.triggerValidationError = function(node, error, value) { /** - * Destroy the plugin + * Destroys the builder + * @fires QueryBuilder.beforeDestroy */ QueryBuilder.prototype.destroy = function() { + /** + * Before the {@link QueryBuilder#destroy} method + * @event beforeDestroy + * @memberof QueryBuilder + */ this.trigger('beforeDestroy'); if (this.status.generated_id) { @@ -1143,9 +1468,21 @@ QueryBuilder.prototype.destroy = function() { }; /** - * Reset the plugin + * Clear all rules and resets the root group + * @fires QueryBuilder.beforeReset + * @fires QueryBuilder.afterReset */ QueryBuilder.prototype.reset = function() { + /** + * Before the {@link QueryBuilder#reset} method, can be prevented + * @event beforeReset + * @memberof QueryBuilder + */ + var e = this.trigger('beforeReset'); + if (e.isDefaultPrevented()) { + return; + } + this.status.group_id = 1; this.status.rule_id = 0; @@ -1153,13 +1490,30 @@ QueryBuilder.prototype.reset = function() { this.addRule(this.model.root); + /** + * After the {@link QueryBuilder#reset} method + * @event afterReset + * @memberof QueryBuilder + */ this.trigger('afterReset'); }; /** - * Clear the plugin + * Clears all rules and removes the root group + * @fires QueryBuilder.beforeClear + * @fires QueryBuilder.afterClear */ QueryBuilder.prototype.clear = function() { + /** + * Before the {@link QueryBuilder#clear} method, can be prevented + * @event beforeClear + * @memberof QueryBuilder + */ + var e = this.trigger('beforeClear'); + if (e.isDefaultPrevented()) { + return; + } + this.status.group_id = 0; this.status.rule_id = 0; @@ -1168,13 +1522,18 @@ QueryBuilder.prototype.clear = function() { this.model.root = null; } + /** + * After the {@link QueryBuilder#clear} method + * @event afterClear + * @memberof QueryBuilder + */ this.trigger('afterClear'); }; /** - * Modify the builder configuration + * Modifies the builder configuration.
* Only options defined in QueryBuilder.modifiable_options are modifiable - * @param {object} + * @param {object} options */ QueryBuilder.prototype.setOptions = function(options) { $.each(options, function(opt, value) { @@ -1185,19 +1544,34 @@ QueryBuilder.prototype.setOptions = function(options) { }; /** - * Return the model associated to a DOM object, or root model - * @param {jQuery,optional} - * @return {Node} + * Returns the model associated to a DOM object, or the root model + * @param {jQuery} [target] + * @returns {Node} */ QueryBuilder.prototype.getModel = function(target) { - return !target ? this.model.root : Model(target); + if (!target) { + return this.model.root; + } + else if (target instanceof Node) { + return target; + } + else { + return $(target).data('queryBuilderModel'); + } }; /** - * Validate the whole builder - * @return {boolean} + * Validates the whole builder + * @param {object} [options] + * @param {boolean} [options.skip_empty=false] - skips validating rules that have no filter selected + * @returns {boolean} + * @fires QueryBuilder.changer:validate */ -QueryBuilder.prototype.validate = function() { +QueryBuilder.prototype.validate = function(options) { + options = $.extend({ + skip_empty: false + }, options); + this.clearErrors(); var self = this; @@ -1207,6 +1581,10 @@ QueryBuilder.prototype.validate = function() { var errors = 0; group.each(function(rule) { + if (!rule.filter && options.skip_empty) { + return; + } + if (!rule.filter) { self.triggerValidationError(rule, 'no_filter', null); errors++; @@ -1232,10 +1610,11 @@ QueryBuilder.prototype.validate = function() { done++; }, function(group) { - if (parse(group)) { + var res = parse(group); + if (res === true) { done++; } - else { + else if (res === false) { errors++; } }); @@ -1243,6 +1622,9 @@ QueryBuilder.prototype.validate = function() { if (errors > 0) { return false; } + else if (done === 0 && !group.isRoot() && options.skip_empty) { + return null; + } else if (done === 0 && (!self.settings.allow_empty || !group.isRoot())) { self.triggerValidationError(group, 'empty_group', null); return false; @@ -1252,23 +1634,35 @@ QueryBuilder.prototype.validate = function() { }(this.model.root)); + /** + * Modifies the result of the {@link QueryBuilder#validate} method + * @event changer:validate + * @memberof QueryBuilder + * @param {boolean} valid + * @returns {boolean} + */ return this.change('validate', valid); }; /** - * Get an object representing current rules - * @param {object} options - * - get_flags: false[default] | true(only changes from default flags) | 'all' - * - allow_invalid: false[default] | true(returns rules even if they are invalid) - * @return {object} + * Gets an object representing current rules + * @param {object} [options] + * @param {boolean|string} [options.get_flags=false] - export flags, true: only changes from default flags or 'all' + * @param {boolean} [options.allow_invalid=false] - returns rules even if they are invalid + * @param {boolean} [options.skip_empty=false] - remove rules that have no filter selected + * @returns {object} + * @fires QueryBuilder.changer:ruleToJson + * @fires QueryBuilder.changer:groupToJson + * @fires QueryBuilder.changer:getRules */ QueryBuilder.prototype.getRules = function(options) { options = $.extend({ get_flags: false, - allow_invalid: false + allow_invalid: false, + skip_empty: false }, options); - var valid = this.validate(); + var valid = this.validate(options); if (!valid && !options.allow_invalid) { return null; } @@ -1293,6 +1687,10 @@ QueryBuilder.prototype.getRules = function(options) { } group.each(function(rule) { + if (!rule.filter && options.skip_empty) { + return; + } + var value = null; if (!rule.operator || rule.operator.nb_inputs !== 0) { value = rule.value; @@ -1318,27 +1716,57 @@ QueryBuilder.prototype.getRules = function(options) { } } + /** + * Modifies the JSON generated from a Rule object + * @event changer:ruleToJson + * @memberof QueryBuilder + * @param {object} json + * @param {Rule} rule + * @returns {object} + */ groupData.rules.push(self.change('ruleToJson', ruleData, rule)); }, function(model) { - groupData.rules.push(parse(model)); + var data = parse(model); + if (data.rules.length !== 0 || !options.skip_empty) { + groupData.rules.push(data); + } }, this); + /** + * Modifies the JSON generated from a Group object + * @event changer:groupToJson + * @memberof QueryBuilder + * @param {object} json + * @param {Group} group + * @returns {object} + */ return self.change('groupToJson', groupData, group); }(this.model.root)); out.valid = valid; + /** + * Modifies the result of the {@link QueryBuilder#getRules} method + * @event changer:getRules + * @memberof QueryBuilder + * @param {object} json + * @returns {object} + */ return this.change('getRules', out); }; /** - * Set rules from object + * Sets rules from object + * @param {object} data + * @param {object} [options] + * @param {boolean} [options.allow_invalid=false] - silent-fail if the data are invalid * @throws RulesError, UndefinedConditionError - * @param data {object} - * @param {object} options - * - allow_invalid: false[default] | true(silent-fail if the data are invalid) + * @fires QueryBuilder.changer:setRules + * @fires QueryBuilder.changer:jsonToRule + * @fires QueryBuilder.changer:jsonToGroup + * @fires QueryBuilder.afterSetRules */ QueryBuilder.prototype.setRules = function(data, options) { options = $.extend({ @@ -1360,7 +1788,15 @@ QueryBuilder.prototype.setRules = function(data, options) { this.setRoot(false, data.data, this.parseGroupFlags(data)); this.applyGroupFlags(this.model.root); - data = this.change('setRules', data); + /** + * Modifies data before the {@link QueryBuilder#setRules} method + * @event changer:setRules + * @memberof QueryBuilder + * @param {object} json + * @param {object} options + * @returns {object} + */ + data = this.change('setRules', data, options); var self = this; @@ -1432,25 +1868,49 @@ QueryBuilder.prototype.setRules = function(data, options) { self.applyRuleFlags(model); + /** + * Modifies the Rule object generated from the JSON + * @event changer:jsonToRule + * @memberof QueryBuilder + * @param {Rule} rule + * @param {object} json + * @returns {Rule} the same rule + */ if (self.change('jsonToRule', model, item) != model) { Utils.error('RulesParse', 'Plugin tried to change rule reference'); } } }); + /** + * Modifies the Group object generated from the JSON + * @event changer:jsonToGroup + * @memberof QueryBuilder + * @param {Group} group + * @param {object} json + * @returns {Group} the same group + */ if (self.change('jsonToGroup', group, data) != group) { Utils.error('RulesParse', 'Plugin tried to change group reference'); } }(data, this.model.root)); + + /** + * After the {@link QueryBuilder#setRules} method + * @event afterSetRules + * @memberof QueryBuilder + */ + this.trigger('afterSetRules'); }; /** - * Check if a value is correct for a filter - * @param rule {Rule} - * @param value {string|string[]|undefined} - * @return {array|true} + * Performs value validation + * @param {Rule} rule + * @param {string|string[]} value + * @returns {array|boolean} true or error array + * @fires QueryBuilder.changer:validateValue */ QueryBuilder.prototype.validateValue = function(rule, value) { var validation = rule.filter.validation || {}; @@ -1460,31 +1920,46 @@ QueryBuilder.prototype.validateValue = function(rule, value) { result = validation.callback.call(this, value, rule); } else { - result = this.validateValueInternal(rule, value); + result = this._validateValue(rule, value); } + /** + * Modifies the result of the rule validation method + * @event changer:validateValue + * @memberof QueryBuilder + * @param {array|boolean} result - true or an error array + * @param {*} value + * @param {Rule} rule + * @returns {array|boolean} + */ return this.change('validateValue', result, value, rule); }; /** * Default validation function + * @param {Rule} rule + * @param {string|string[]} value + * @returns {array|boolean} true or error array * @throws ConfigError - * @param rule {Rule} - * @param value {string|string[]|undefined} - * @return {Array|boolean} error array or true + * @private */ -QueryBuilder.prototype.validateValueInternal = function(rule, value) { +QueryBuilder.prototype._validateValue = function(rule, value) { var filter = rule.filter; var operator = rule.operator; var validation = filter.validation || {}; var result = true; - var tmp; + var tmp, tempValue; if (rule.operator.nb_inputs === 1) { value = [value]; } for (var i = 0; i < operator.nb_inputs; i++) { + if (!operator.multiple && $.isArray(value[i]) && value[i].length > 1) { + result = ['operator_not_multiple', operator.type, this.translate('operators', operator.type)]; + break; + } + switch (filter.input) { case 'radio': if (value[i] === undefined || value[i].length === 0) { @@ -1502,10 +1977,6 @@ QueryBuilder.prototype.validateValueInternal = function(rule, value) { } break; } - else if (!operator.multiple && value[i].length > 1) { - result = ['operator_not_multiple', operator.type]; - break; - } break; case 'select': @@ -1515,136 +1986,140 @@ QueryBuilder.prototype.validateValueInternal = function(rule, value) { } break; } - if (filter.multiple && !operator.multiple && value[i].length > 1) { - result = ['operator_not_multiple', operator.type]; - break; - } break; - - default: - switch (QueryBuilder.types[filter.type]) { - case 'string': - if (value[i] === undefined || value[i].length === 0) { - if (!validation.allow_empty_value) { - result = ['string_empty']; - } - break; - } - if (validation.min !== undefined) { - if (value[i].length < parseInt(validation.min)) { - result = [this.getValidationMessage(validation, 'min', 'string_exceed_min_length'), validation.min]; - break; - } - } - if (validation.max !== undefined) { - if (value[i].length > parseInt(validation.max)) { - result = [this.getValidationMessage(validation, 'max', 'string_exceed_max_length'), validation.max]; + + default: + tempValue = $.isArray(value[i]) ? value[i] : [value[i]]; + + for (var j = 0; j < tempValue.length; j++) { + switch (QueryBuilder.types[filter.type]) { + case 'string': + if (tempValue[j] === undefined || tempValue[j].length === 0) { + if (!validation.allow_empty_value) { + result = ['string_empty']; + } break; } - } - if (validation.format) { - if (typeof validation.format == 'string') { - validation.format = new RegExp(validation.format); + if (validation.min !== undefined) { + if (tempValue[j].length < parseInt(validation.min)) { + result = [this.getValidationMessage(validation, 'min', 'string_exceed_min_length'), validation.min]; + break; + } } - if (!validation.format.test(value[i])) { - result = [this.getValidationMessage(validation, 'format', 'string_invalid_format'), validation.format]; - break; + if (validation.max !== undefined) { + if (tempValue[j].length > parseInt(validation.max)) { + result = [this.getValidationMessage(validation, 'max', 'string_exceed_max_length'), validation.max]; + break; + } } - } - break; - - case 'number': - if (value[i] === undefined || value[i].length === 0) { - if (!validation.allow_empty_value) { - result = ['number_nan']; + if (validation.format) { + if (typeof validation.format == 'string') { + validation.format = new RegExp(validation.format); + } + if (!validation.format.test(tempValue[j])) { + result = [this.getValidationMessage(validation, 'format', 'string_invalid_format'), validation.format]; + break; + } } break; - } - if (isNaN(value[i])) { - result = ['number_nan']; - break; - } - if (filter.type == 'integer') { - if (parseInt(value[i]) != value[i]) { - result = ['number_not_integer']; + + case 'number': + if (tempValue[j] === undefined || tempValue[j].length === 0) { + if (!validation.allow_empty_value) { + result = ['number_nan']; + } break; } - } - else { - if (parseFloat(value[i]) != value[i]) { - result = ['number_not_double']; + if (isNaN(tempValue[j])) { + result = ['number_nan']; break; } - } - if (validation.min !== undefined) { - if (value[i] < parseFloat(validation.min)) { - result = [this.getValidationMessage(validation, 'min', 'number_exceed_min'), validation.min]; - break; + if (filter.type == 'integer') { + if (parseInt(tempValue[j]) != tempValue[j]) { + result = ['number_not_integer']; + break; + } } - } - if (validation.max !== undefined) { - if (value[i] > parseFloat(validation.max)) { - result = [this.getValidationMessage(validation, 'max', 'number_exceed_max'), validation.max]; - break; + else { + if (parseFloat(tempValue[j]) != tempValue[j]) { + result = ['number_not_double']; + break; + } } - } - if (validation.step !== undefined && validation.step !== 'any') { - var v = (value[i] / validation.step).toPrecision(14); - if (parseInt(v) != v) { - result = [this.getValidationMessage(validation, 'step', 'number_wrong_step'), validation.step]; - break; + if (validation.min !== undefined) { + if (tempValue[j] < parseFloat(validation.min)) { + result = [this.getValidationMessage(validation, 'min', 'number_exceed_min'), validation.min]; + break; + } } - } - break; - - case 'datetime': - if (value[i] === undefined || value[i].length === 0) { - if (!validation.allow_empty_value) { - result = ['datetime_empty']; + if (validation.max !== undefined) { + if (tempValue[j] > parseFloat(validation.max)) { + result = [this.getValidationMessage(validation, 'max', 'number_exceed_max'), validation.max]; + break; + } } - break; - } - - // we need MomentJS - if (validation.format) { - if (!('moment' in window)) { - Utils.error('MissingLibrary', 'MomentJS is required for Date/Time validation. Get it here http://momentjs.com'); + if (validation.step !== undefined && validation.step !== 'any') { + var v = (tempValue[j] / validation.step).toPrecision(14); + if (parseInt(v) != v) { + result = [this.getValidationMessage(validation, 'step', 'number_wrong_step'), validation.step]; + break; + } } + break; - var datetime = moment(value[i], validation.format); - if (!datetime.isValid()) { - result = [this.getValidationMessage(validation, 'format', 'datetime_invalid'), validation.format]; + case 'datetime': + if (tempValue[j] === undefined || tempValue[j].length === 0) { + if (!validation.allow_empty_value) { + result = ['datetime_empty']; + } break; } - else { - if (validation.min) { - if (datetime < moment(validation.min, validation.format)) { - result = [this.getValidationMessage(validation, 'min', 'datetime_exceed_min'), validation.min]; - break; - } + + // we need MomentJS + if (validation.format) { + if (!('moment' in window)) { + Utils.error('MissingLibrary', 'MomentJS is required for Date/Time validation. Get it here http://momentjs.com'); + } + + var datetime = moment(tempValue[j], validation.format); + if (!datetime.isValid()) { + result = [this.getValidationMessage(validation, 'format', 'datetime_invalid'), validation.format]; + break; } - if (validation.max) { - if (datetime > moment(validation.max, validation.format)) { - result = [this.getValidationMessage(validation, 'max', 'datetime_exceed_max'), validation.max]; - break; + else { + if (validation.min) { + if (datetime < moment(validation.min, validation.format)) { + result = [this.getValidationMessage(validation, 'min', 'datetime_exceed_min'), validation.min]; + break; + } + } + if (validation.max) { + if (datetime > moment(validation.max, validation.format)) { + result = [this.getValidationMessage(validation, 'max', 'datetime_exceed_max'), validation.max]; + break; + } } } } - } - break; + break; - case 'boolean': - if (value[i] === undefined || value[i].length === 0) { - if (!validation.allow_empty_value) { + case 'boolean': + if (tempValue[j] === undefined || tempValue[j].length === 0) { + if (!validation.allow_empty_value) { + result = ['boolean_not_valid']; + } + break; + } + tmp = ('' + tempValue[j]).trim().toLowerCase(); + if (tmp !== 'true' && tmp !== 'false' && tmp !== '1' && tmp !== '0' && tempValue[j] !== 1 && tempValue[j] !== 0) { result = ['boolean_not_valid']; + break; } - break; - } - tmp = ('' + value[i]).trim().toLowerCase(); - if (tmp !== 'true' && tmp !== 'false' && tmp !== '1' && tmp !== '0' && value[i] !== 1 && value[i] !== 0) { - result = ['boolean_not_valid']; - break; - } + } + + if (result !== true) { + break; + } } } @@ -1658,7 +2133,8 @@ QueryBuilder.prototype.validateValueInternal = function(rule, value) { /** * Returns an incremented group ID - * @return {string} + * @returns {string} + * @private */ QueryBuilder.prototype.nextGroupId = function() { return this.status.id + '_group_' + (this.status.group_id++); @@ -1666,7 +2142,8 @@ QueryBuilder.prototype.nextGroupId = function() { /** * Returns an incremented rule ID - * @return {string} + * @returns {string} + * @private */ QueryBuilder.prototype.nextRuleId = function() { return this.status.id + '_rule_' + (this.status.rule_id++); @@ -1674,8 +2151,10 @@ QueryBuilder.prototype.nextRuleId = function() { /** * Returns the operators for a filter - * @param filter {string|object} (filter id name or filter object) - * @return {object[]} + * @param {string|object} filter - filter id or filter object + * @returns {object[]} + * @fires QueryBuilder.changer:getOperators + * @private */ QueryBuilder.prototype.getOperators = function(filter) { if (typeof filter == 'string') { @@ -1706,15 +2185,24 @@ QueryBuilder.prototype.getOperators = function(filter) { }); } + /** + * Modifies the operators available for a filter + * @event changer:getOperators + * @memberof QueryBuilder + * @param {QueryBuilder.Operator[]} operators + * @param {QueryBuilder.Filter} filter + * @returns {QueryBuilder.Operator[]} + */ return this.change('getOperators', result, filter); }; /** * Returns a particular filter by its id + * @param {string} id + * @param {boolean} [doThrow=true] + * @returns {object|null} * @throws UndefinedFilterError - * @param id {string} - * @param [doThrow=true] {boolean} - * @return {object|null} + * @private */ QueryBuilder.prototype.getFilterById = function(id, doThrow) { if (id == '-1') { @@ -1733,11 +2221,12 @@ QueryBuilder.prototype.getFilterById = function(id, doThrow) { }; /** - * Return a particular operator by its type + * Returns a particular operator by its type + * @param {string} type + * @param {boolean} [doThrow=true] + * @returns {object|null} * @throws UndefinedOperatorError - * @param type {string} - * @param [doThrow=true] {boolean} - * @return {object|null} + * @private */ QueryBuilder.prototype.getOperatorByType = function(type, doThrow) { if (type == '-1') { @@ -1756,9 +2245,11 @@ QueryBuilder.prototype.getOperatorByType = function(type, doThrow) { }; /** - * Returns rule's input value - * @param rule {Rule} - * @return {mixed} + * Returns rule's current input value + * @param {Rule} rule + * @returns {*} + * @fires QueryBuilder.changer:getRuleValue + * @private */ QueryBuilder.prototype.getRuleInputValue = function(rule) { var filter = rule.filter; @@ -1769,7 +2260,7 @@ QueryBuilder.prototype.getRuleInputValue = function(rule) { value = filter.valueGetter.call(this, rule); } else { - var $value = rule.$el.find(Selectors.value_container); + var $value = rule.$el.find(QueryBuilder.selectors.value_container); for (var i = 0; i < operator.nb_inputs; i++) { var name = Utils.escapeElementId(rule.id + '_value_' + i); @@ -1782,18 +2273,22 @@ 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 { @@ -1822,13 +2317,22 @@ QueryBuilder.prototype.getRuleInputValue = function(rule) { } } + /** + * Modifies the rule's value grabbed from the DOM + * @event changer:getRuleValue + * @memberof QueryBuilder + * @param {*} value + * @param {Rule} rule + * @returns {*} + */ return this.change('getRuleValue', value, rule); }; /** - * Sets the value of a rule's input. - * @param rule {Rule} - * @param value {mixed} + * Sets the value of a rule's input + * @param {Rule} rule + * @param {*} value + * @private */ QueryBuilder.prototype.setRuleInputValue = function(rule, value) { var filter = rule.filter; @@ -1844,7 +2348,7 @@ QueryBuilder.prototype.setRuleInputValue = function(rule, value) { filter.valueSetter.call(this, rule, value); } else { - var $value = rule.$el.find(Selectors.value_container); + var $value = rule.$el.find(QueryBuilder.selectors.value_container); if (operator.nb_inputs == 1) { value = [value]; @@ -1862,9 +2366,11 @@ 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: @@ -1881,9 +2387,11 @@ QueryBuilder.prototype.setRuleInputValue = function(rule, value) { }; /** - * Clean rule flags. - * @param rule {object} - * @return {object} + * Parses rule flags + * @param {object} rule + * @returns {object} + * @fires QueryBuilder.changer:parseRuleFlags + * @private */ QueryBuilder.prototype.parseRuleFlags = function(rule) { var flags = $.extend({}, this.settings.default_rule_flags); @@ -1901,14 +2409,23 @@ QueryBuilder.prototype.parseRuleFlags = function(rule) { $.extend(flags, rule.flags); } + /** + * Modifies the consolidated rule's flags + * @event changer:parseRuleFlags + * @memberof QueryBuilder + * @param {object} flags + * @param {object} rule - not a Rule object + * @returns {object} + */ return this.change('parseRuleFlags', flags, rule); }; /** - * Get a copy of flags of a rule. + * Gets a copy of flags of a rule * @param {object} flags - * @param {boolean} all - true to return all flags, false to return only changes from default + * @param {boolean} [all=false] - return all flags or only changes from default flags * @returns {object} + * @private */ QueryBuilder.prototype.getRuleFlags = function(flags, all) { if (all) { @@ -1926,9 +2443,11 @@ QueryBuilder.prototype.getRuleFlags = function(flags, all) { }; /** - * Clean group flags. - * @param group {object} - * @return {object} + * Parses group flags + * @param {object} group + * @returns {object} + * @fires QueryBuilder.changer:parseGroupFlags + * @private */ QueryBuilder.prototype.parseGroupFlags = function(group) { var flags = $.extend({}, this.settings.default_group_flags); @@ -1946,14 +2465,23 @@ QueryBuilder.prototype.parseGroupFlags = function(group) { $.extend(flags, group.flags); } + /** + * Modifies the consolidated group's flags + * @event changer:parseGroupFlags + * @memberof QueryBuilder + * @param {object} flags + * @param {object} group - not a Group object + * @returns {object} + */ return this.change('parseGroupFlags', flags, group); }; /** - * Get a copy of flags of a group. + * Gets a copy of flags of a group * @param {object} flags - * @param {boolean} all - true to return all flags, false to return only changes from default + * @param {boolean} [all=false] - return all flags or only changes from default flags * @returns {object} + * @private */ QueryBuilder.prototype.getGroupFlags = function(flags, all) { if (all) { @@ -1971,20 +2499,45 @@ QueryBuilder.prototype.getGroupFlags = function(flags, all) { }; /** - * Translate a label - * @param label {string|object} - * @return string + * Translate a label either by looking in the `lang` object or in itself if it's an object where keys are language codes + * @param {string} [category] + * @param {string|object} key + * @returns {string} + * @fires QueryBuilder.changer:translate */ -QueryBuilder.prototype.translateLabel = function(label) { - return typeof label == 'object' ? (label[this.settings.lang_code] || label['en']) : label; +QueryBuilder.prototype.translate = function(category, key) { + if (!key) { + key = category; + category = undefined; + } + + var translation; + if (typeof key === 'object') { + translation = key[this.settings.lang_code] || key['en']; + } + else { + translation = (category ? this.lang[category] : this.lang)[key] || key; + } + + /** + * Modifies the translated label + * @event changer:translate + * @memberof QueryBuilder + * @param {string} translation + * @param {string|object} key + * @param {string} [category] + * @returns {string} + */ + return this.change('translate', translation, key, category); }; /** - * Return a validation message + * Returns a validation message * @param {object} validation * @param {string} type * @param {string} def * @returns {string} + * @private */ QueryBuilder.prototype.getValidationMessage = function(validation, type, def) { return validation.messages && validation.messages[type] || def; @@ -1996,23 +2549,23 @@ QueryBuilder.templates.group = '\
\
\ \ {{? it.settings.allow_groups===-1 || it.settings.allow_groups>=it.level }} \ \ {{?}} \ {{? it.level>1 }} \ \ {{?}} \
\
\ {{~ it.conditions: condition }} \ \ {{~}} \
\ @@ -2030,7 +2583,7 @@ QueryBuilder.templates.rule = '\
\
\ \
\
\ @@ -2063,7 +2616,7 @@ QueryBuilder.templates.filterSelect = '\ QueryBuilder.templates.operatorSelect = '\ {{? it.operators.length === 1 }} \ \ -{{= it.lang.operators[it.operators[0].type] || it.operators[0].type }} \ +{{= it.translate("operators", it.operators[0].type) }} \ \ {{?}} \ {{ var optgroup = null; }} \ @@ -2075,16 +2628,18 @@ QueryBuilder.templates.operatorSelect = '\ \ {{?}} \ {{?}} \ - \ + \ {{~}} \ {{? optgroup !== null }}{{?}} \ '; /** - * Returns group HTML - * @param group_id {string} - * @param level {int} - * @return {string} + * Returns group's HTML + * @param {string} group_id + * @param {int} level + * @returns {string} + * @fires QueryBuilder.changer:getGroupTemplate + * @private */ QueryBuilder.prototype.getGroupTemplate = function(group_id, level) { var h = this.templates.group({ @@ -2093,35 +2648,54 @@ QueryBuilder.prototype.getGroupTemplate = function(group_id, level) { level: level, conditions: this.settings.conditions, icons: this.icons, - lang: this.lang, - settings: this.settings + settings: this.settings, + translate: this.translate.bind(this) }); + /** + * Modifies the raw HTML of a group + * @event changer:getGroupTemplate + * @memberof QueryBuilder + * @param {string} html + * @param {int} level + * @returns {string} + */ return this.change('getGroupTemplate', h, level); }; /** - * Returns rule HTML - * @param rule_id {string} - * @return {string} + * Returns rule's HTML + * @param {string} rule_id + * @returns {string} + * @fires QueryBuilder.changer:getRuleTemplate + * @private */ QueryBuilder.prototype.getRuleTemplate = function(rule_id) { var h = this.templates.rule({ builder: this, rule_id: rule_id, icons: this.icons, - lang: this.lang, - settings: this.settings + settings: this.settings, + translate: this.translate.bind(this) }); + /** + * Modifies the raw HTML of a rule + * @event changer:getRuleTemplate + * @memberof QueryBuilder + * @param {string} html + * @returns {string} + */ return this.change('getRuleTemplate', h); }; /** - * Returns rule filter HTML - * @param rule {Rule} - * @param operators {object} - * @return {string} + * Returns rule's operator HTML + * @param {Rule} rule + * @param {object[]} operators + * @returns {string} + * @fires QueryBuilder.changer:getRuleOperatorTemplate + * @private */ QueryBuilder.prototype.getRuleOperatorSelect = function(rule, operators) { var h = this.templates.operatorSelect({ @@ -2149,20 +2733,29 @@ QueryBuilder.prototype.getRuleOperatorSelect = function(rule, operators) { rule: rule, operators: operators, icons: this.icons, - lang: this.lang, settings: this.settings, - translate: this.translateLabel + translate: this.translate.bind(this) }); - return this.change('getRuleOperatorSelect', h, rule); + /** + * Modifies the raw HTML of the rule's operator dropdown + * @event changer:getRuleOperatorTemplate + * @memberof QueryBuilder + * @param {string} html + * @param {Rule} rule + * @param {QueryBuilder.Operator[]} operators + * @returns {string} + */ + return this.change('getRuleOperatorSelect', h, rule, operators); }; /** - * Return the rule value HTML - * @param rule {Rule} - * @param filter {object} - * @param value_id {int} - * @return {string} + * Returns the rule's value HTML + * @param {Rule} rule + * @param {int} value_id + * @returns {string} + * @fires QueryBuilder.changer:getRuleInput + * @private */ QueryBuilder.prototype.getRuleInput = function(rule, value_id) { var filter = rule.filter; @@ -2176,94 +2769,127 @@ QueryBuilder.prototype.getRuleInput = function(rule, value_id) { } else { switch (filter.input) { - case 'radio': case 'checkbox': + case 'radio': + case 'checkbox': Utils.iterateOptions(filter.values, function(key, val) { - h+= ' ' + val + ' '; + h += ' ' + val + ' '; }); break; case 'select': - h+= ''; if (filter.placeholder) { - h+= ''; + h += ''; } Utils.iterateOptions(filter.values, function(key, val) { - h+= ' '; + h += ' '; }); - h+= ''; + h += ''; break; case 'textarea': - h+= '";break;default:switch(g.types[c.type]){case"number":h+='=f:e<=f},i=!1;h()&&(this.rules[e]instanceof j?void 0!==c&&(i=c.call(d,this.rules[e])===!1):i=b.call(d,this.rules[e])===!1,!i);e+=g);return!i},j.prototype.contains=function(a,b){return this.getNodePos(a)!==-1||!!b&&!this.each(function(a){return!0},function(b){return!b.contains(a,!0)})};var k=function(a,b){return this instanceof k?(i.call(this,a,b),this._updating_value=!1,this._updating_input=!1,this.__.filter=null,this.__.operator=null,this.__.flags={},void(this.__.value=void 0)):new k(a,b)};k.prototype=Object.create(i.prototype),k.prototype.constructor=k,b.defineModelProperties(k,["filter","operator","value"]),g.Group=j,g.Rule=k;var l=g.utils={};l.iterateOptions=function(a,b){a&&($.isArray(a)?a.forEach(function(a){$.isPlainObject(a)?$.each(a,function(a,c){return b(a,c),!1}):b(a,a)}):$.each(a,function(a,c){b(a,c)}))},l.fmt=function(a,b){return Array.isArray(b)||(b=Array.prototype.slice.call(arguments,1)),a.replace(/{([0-9]+)}/g,function(a,c){return b[parseInt(c)]})},l.error=function(){var a=0,b="boolean"!=typeof arguments[a]||arguments[a++],c=arguments[a++],d=arguments[a++],e=Array.isArray(arguments[a])?arguments[a]:Array.prototype.slice.call(arguments,a);if(b){var f=new Error(l.fmt(d,e));throw f.name=c+"Error",f.args=e,f}console.error(c+"Error: "+l.fmt(d,e))},l.changeType=function(a,b,c){switch(b){case"integer":return parseInt(a);case"double":return parseFloat(a);case"boolean":var d="true"===a.trim().toLowerCase()||"1"===a.trim()||1===a;return c?d?1:0:d;default:return a}},l.escapeString=function(a){return"string"!=typeof a?a:a.replace(/[\0\n\r\b\\\'\"]/g,function(a){switch(a){case"\0":return"\\0";case"\n":return"\\n";case"\r":return"\\r";case"\b":return"\\b";default:return"\\"+a}}).replace(/\t/g,"\\t").replace(/\x1a/g,"\\Z")},l.escapeRegExp=function(a){return a.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")},l.escapeElementId=function(a){return a?a.replace(/(\\)?([:.\[\],])/g,function(a,b,c){return b?a:"\\"+c}):a},l.groupSort=function(a,b){var c=[],d=[];return a.forEach(function(a){var e;a[b]?(e=c.lastIndexOf(a[b]),e==-1?e=c.length:e++):e=c.length,c.splice(e,0,a[b]),d.splice(e,0,a)}),d},$.fn.queryBuilder=function(a){0===this.length&&l.error("Config","No target defined"),this.length>1&&l.error("Config","Unable to initialize on multiple target");var b=this.data("queryBuilder"),c="object"==typeof a&&a||{};return b||"destroy"!=a?(b||this.data("queryBuilder",new g(this,c)),"string"==typeof a?b[a].apply(b,Array.prototype.slice.call(arguments,1)):this):this},$.fn.queryBuilder.constructor=g,$.fn.queryBuilder.defaults=g.defaults,$.fn.queryBuilder.extend=g.extend,$.fn.queryBuilder.define=g.define,$.fn.queryBuilder.regional=g.regional,g.define("bt-checkbox",function(a){if("glyphicons"==a.font){var b=document.createElement("style");b.innerHTML='.checkbox input[type=checkbox]:checked + label:after { font-family: "Glyphicons Halflings"; content: "\\e013"; } .checkbox label:after { padding-left: 4px; padding-top: 2px; font-size: 9px; }',document.body.appendChild(b)}this.on("getRuleInput.filter",function(b,c,d){var e=c.filter;if(("radio"===e.input||"checkbox"===e.input)&&!e.plugin){b.value="",e.colors||(e.colors={}),e.color&&(e.colors._def_=e.color);var f=e.vertical?' style="display:block"':"",g=0;l.iterateOptions(e.values,function(c,h){var i=e.colors[c]||e.colors._def_||a.color,j=d+"_"+g++;b.value+=" "})}})},{font:"glyphicons",color:"default"}),g.define("bt-selectpicker",function(a){$.fn.selectpicker&&$.fn.selectpicker.Constructor||l.error("MissingLibrary",'Bootstrap Select is required to use "bt-selectpicker" plugin. Get it here: http://silviomoreto.github.io/bootstrap-select'),this.on("afterCreateRuleFilters",function(b,c){c.$el.find(h.rule_filter).removeClass("form-control").selectpicker(a)}),this.on("afterCreateRuleOperators",function(b,c){c.$el.find(h.rule_operator).removeClass("form-control").selectpicker(a)}),this.on("afterUpdateRuleFilter",function(a,b){b.$el.find(h.rule_filter).selectpicker("render")}),this.on("afterUpdateRuleOperator",function(a,b){b.$el.find(h.rule_operator).selectpicker("render")})},{container:"body",style:"btn-inverse btn-xs",width:"auto",showIcon:!1}),g.define("bt-tooltip-errors",function(a){$.fn.tooltip&&$.fn.tooltip.Constructor&&$.fn.tooltip.Constructor.prototype.fixTitle||l.error("MissingLibrary",'Bootstrap Tooltip is required to use "bt-tooltip-errors" plugin. Get it here: http://getbootstrap.com');var b=this;this.on("getRuleTemplate.filter getGroupTemplate.filter",function(a){var b=$(a.value);b.find(h.error_container).attr("data-toggle","tooltip"),a.value=b.prop("outerHTML")}),this.model.on("update",function(c,d,e){"error"==e&&b.settings.display_errors&&d.$el.find(h.error_container).eq(0).tooltip(a).tooltip("hide").tooltip("fixTitle")})},{placement:"right"}),g.extend({setFilters:function(a,b){var c=this;void 0===b&&(b=a,a=!1),b=this.checkFilters(b),b=this.change("setFilters",b);var d=b.map(function(a){return a.id});if(a||!function f(a){a.each(function(a){a.filter&&d.indexOf(a.filter.id)===-1&&l.error("ChangeFilter",'A rule is using filter "{0}"',a.filter.id)},f)}(this.model.root),this.filters=b,function g(a){a.each(!0,function(a){a.filter&&d.indexOf(a.filter.id)===-1?a.drop():(c.createRuleFilters(a),a.$el.find(h.rule_filter).val(a.filter?a.filter.id:"-1"),c.trigger("afterUpdateRuleFilter",a))},g)}(this.model.root),this.settings.plugins&&(this.settings.plugins["unique-filter"]&&this.updateDisabledFilters(),this.settings.plugins["bt-selectpicker"]&&this.$el.find(h.rule_filter).selectpicker("render")),this.settings.default_filter)try{this.getFilterById(this.settings.default_filter)}catch(e){this.settings.default_filter=null}this.trigger("afterSetFilters",b)},addFilter:function(a,b){void 0===b||"#end"==b?b=this.filters.length:"#start"==b&&(b=0),$.isArray(a)||(a=[a]);var c=$.extend(!0,[],this.filters);parseInt(b)==b?Array.prototype.splice.apply(c,[b,0].concat(a)):this.filters.some(function(a,c){if(a.id==b)return b=c+1,!0})?Array.prototype.splice.apply(c,[b,0].concat(a)):Array.prototype.push.apply(c,a),this.setFilters(c)},removeFilter:function(a,b){var c=$.extend(!0,[],this.filters);"string"==typeof a&&(a=[a]),c=c.filter(function(b){return a.indexOf(b.id)===-1}),this.setFilters(b,c)}}),g.define("filter-description",function(a){"inline"===a.mode?this.on("afterUpdateRuleFilter",function(b,c){var d=c.$el.find("p.filter-description");c.filter&&c.filter.description?(0===d.length?(d=$('

'),d.appendTo(c.$el)):d.show(),d.html(' '+c.filter.description)):d.hide()}):"popover"===a.mode?($.fn.popover&&$.fn.popover.Constructor&&$.fn.popover.Constructor.prototype.fixTitle||l.error("MissingLibrary",'Bootstrap Popover is required to use "filter-description" plugin. Get it here: http://getbootstrap.com'),this.on("afterUpdateRuleFilter",function(b,c){var d=c.$el.find("button.filter-description");c.filter&&c.filter.description?(0===d.length?(d=$(''),d.prependTo(c.$el.find(h.rule_actions)),d.popover({placement:"left",container:"body",html:!0}),d.on("mouseout",function(){d.popover("hide")})):d.show(),d.data("bs.popover").options.content=c.filter.description,d.attr("aria-describedby")&&d.popover("show")):(d.hide(),d.data("bs.popover")&&d.popover("hide"))})):"bootbox"===a.mode&&("bootbox"in window||l.error("MissingLibrary",'Bootbox is required to use "filter-description" plugin. Get it here: http://bootboxjs.com'),this.on("afterUpdateRuleFilter",function(b,c){var d=c.$el.find("button.filter-description");c.filter&&c.filter.description?(0===d.length&&(d=$(''),d.prependTo(c.$el.find(h.rule_actions)),d.on("click",function(){bootbox.alert(d.data("description"))})),d.data("description",c.filter.description)):d.hide()}))},{icon:"glyphicon glyphicon-info-sign",mode:"popover"}),g.defaults({operatorOpposites:{equal:"not_equal",not_equal:"equal","in":"not_in",not_in:"in",less:"greater_or_equal",less_or_equal:"greater",greater:"less_or_equal",greater_or_equal:"less",between:"not_between",not_between:"between",begins_with:"not_begins_with",not_begins_with:"begins_with",contains:"not_contains",not_contains:"contains",ends_with:"not_ends_with",not_ends_with:"ends_with",is_empty:"is_not_empty",is_not_empty:"is_empty",is_null:"is_not_null",is_not_null:"is_null"},conditionOpposites:{AND:"OR",OR:"AND"}}),g.define("invert",function(a){var c=this;this.on("afterInit",function(){c.$el.on("click.queryBuilder","[data-invert=group]",function(){var d=$(this).closest(h.group_container);c.invert(b(d),a)}),a.display_rules_button&&a.invert_rules&&c.$el.on("click.queryBuilder","[data-invert=rule]",function(){var d=$(this).closest(h.rule_container);c.invert(b(d),a)})}),this.on("getGroupTemplate.filter",function(b,d){var e=$(b.value);e.find(h.condition_container).after('"),b.value=e.prop("outerHTML")}),a.display_rules_button&&a.invert_rules&&this.on("getRuleTemplate.filter",function(b){var d=$(b.value);d.find(h.rule_actions).prepend('"),b.value=d.prop("outerHTML")})},{icon:"glyphicon glyphicon-random",recursive:!0,invert_rules:!0,display_rules_button:!1,silent_fail:!1}),g.extend({invert:function(a,b){if(!(a instanceof i)){if(!this.model.root)return;b=a,a=this.model.root}if("object"!=typeof b&&(b={}),void 0===b.recursive&&(b.recursive=!0),void 0===b.invert_rules&&(b.invert_rules=!0),void 0===b.silent_fail&&(b.silent_fail=!1),void 0===b.trigger&&(b.trigger=!0),a instanceof j){if(this.settings.conditionOpposites[a.condition]?a.condition=this.settings.conditionOpposites[a.condition]:b.silent_fail||l.error("InvertCondition",'Unknown inverse of condition "{0}"',a.condition),b.recursive){var c=$.extend({},b,{trigger:!1});a.each(function(a){b.invert_rules&&this.invert(a,c)},function(a){this.invert(a,c)},this)}}else if(a instanceof k&&a.operator&&!a.filter.no_invert)if(this.settings.operatorOpposites[a.operator.type]){var d=this.settings.operatorOpposites[a.operator.type];a.filter.operators&&a.filter.operators.indexOf(d)==-1||(a.operator=this.getOperatorByType(d))}else b.silent_fail||l.error("InvertOperator",'Unknown inverse of operator "{0}"',a.operator.type);b.trigger&&this.trigger("afterInvert",a,b)}}),g.defaults({mongoOperators:{equal:function(a){return a[0]},not_equal:function(a){return{$ne:a[0]}},"in":function(a){return{$in:a}},not_in:function(a){return{$nin:a}},less:function(a){return{$lt:a[0]}},less_or_equal:function(a){return{$lte:a[0]}},greater:function(a){return{$gt:a[0]}},greater_or_equal:function(a){return{$gte:a[0]}},between:function(a){return{$gte:a[0],$lte:a[1]}},not_between:function(a){return{$lt:a[0],$gt:a[1]}},begins_with:function(a){return{$regex:"^"+l.escapeRegExp(a[0])}},not_begins_with:function(a){return{$regex:"^(?!"+l.escapeRegExp(a[0])+")"}},contains:function(a){return{$regex:l.escapeRegExp(a[0])}},not_contains:function(a){return{$regex:"^((?!"+l.escapeRegExp(a[0])+").)*$",$options:"s"}},ends_with:function(a){return{$regex:l.escapeRegExp(a[0])+"$"}},not_ends_with:function(a){return{$regex:"(?0)d.push(c(a));else{var e=b.settings.mongoOperators[a.operator],f=b.getOperatorByType(a.operator),g=[];void 0===e&&l.error("UndefinedMongoOperator",'Unknown MongoDB operation for operator "{0}"',a.operator),0!==f.nb_inputs&&(a.value instanceof Array||(a.value=[a.value]),a.value.forEach(function(b){g.push(l.changeType(b,a.type,!1))}));var h={},i=b.change("getMongoDBField",a.field,a);h[i]=e.call(b,g),d.push(b.change("ruleToMongo",h,a,g,e))}});var e={};return e["$"+a.condition.toLowerCase()]=d,b.change("groupToMongo",e,a)}(a)},getRulesFromMongo:function(a){if(void 0===a||null===a)return null;var b=this;if(a=b.change("parseMongoNode",a),"rules"in a&&"condition"in a)return a;var e=d(a);return e||l.error("MongoParse","Invalid MongoDB query format"),function f(a,e){var g=a[e],h=[];return g.forEach(function(a){if(a=b.change("parseMongoNode",a),"rules"in a&&"condition"in a)return void h.push(a);if("id"in a&&"operator"in a&&"value"in a)return void h.push(a);var e=d(a);if(e)h.push(f(a,e));else{var g=Object.keys(a)[0],i=a[g],j=c(i,g);void 0===j&&l.error("MongoParse","Invalid MongoDB query format");var k=b.settings.mongoRuleOperators[j];void 0===k&&l.error("UndefinedMongoOperator",'JSON Rule operation unknown for operator "{0}"',j);var m=k.call(b,i),n=b.change("mongoToRule",{id:b.change("getMongoDBFieldID",g,i),field:g,operator:m.op,value:m.val},a);h.push(n)}}),b.change("mongoToGroup",{condition:e.replace("$","").toUpperCase(),rules:h},a)}(a,e)},setRulesFromMongo:function(a){this.setRules(this.getRulesFromMongo(a))}}),h.group_not=h.group_header+" [data-not=group]",b.defineModelProperties(j,["not"]),g.define("not-group",function(a){var c=this;this.on("afterInit",function(){c.$el.on("click.queryBuilder","[data-not=group]",function(){var a=$(this).closest(h.group_container),c=b(a);c.not=!c.not}),c.model.on("update",function(a,b,d){b instanceof j&&"not"===d&&c.updateGroupNot(b)})}),this.on("afterAddGroup",function(a,b){b.__.not=!1}),this.on("getGroupTemplate.filter",function(b,d){var e=$(b.value);e.find(h.condition_container).prepend('"),b.value=e.prop("outerHTML")}),this.on("groupToJson.filter",function(a,b){a.value.not=b.not}),this.on("jsonToGroup.filter",function(a,b){a.value.not=!!b.not}),this.on("groupToSQL.filter",function(a,b){b.not&&(a.value="NOT ( "+a.value+" )")}),this.on("parseSQLNode.filter",function(a){a.value.name&&"NOT"==a.value.name.toUpperCase()&&(a.value=a.value.arguments.value[0],a.value.not=!0)}),this.on("sqlToGroup.filter",function(a,b){a.value.not=!!b.not}),this.on("groupToMongo.filter",function(a,b){var c="$"+b.condition.toLowerCase();b.not&&a.value[c]&&(a.value={$nor:[a.value]})}),this.on("parseMongoNode.filter",function(a){var b=Object.keys(a.value);"$nor"==b[0]&&(a.value=a.value[b[0]][0],a.value.not=!0)}),this.on("mongoToGroup.filter",function(a,b){a.value.not=!!b.not})},{icon_unchecked:"glyphicon glyphicon-unchecked",icon_checked:"glyphicon glyphicon-check"}),g.extend({updateGroupNot:function(a){var b=this.plugins["not-group"];a.$el.find(">"+h.group_not).toggleClass("active",a.not).find("i").attr("class",a.not?b.icon_checked:b.icon_unchecked),this.trigger("afterUpdateGroupNot",a)}}),h.rule_and_group_containers=h.rule_container+", "+h.group_container,h.drag_handle=".drag-handle",g.defaults({default_rule_flags:{no_sortable:!1,no_drop:!1},default_group_flags:{no_sortable:!1,no_drop:!1}}),g.define("sortable",function(a){"interact"in window||l.error("MissingLibrary",'interact.js is required to use "sortable" plugin. Get it here: http://interactjs.io'),void 0!==a.default_no_sortable&&(l.error(!1,"Config",'Sortable plugin : "default_no_sortable" options is deprecated, use standard "default_rule_flags" and "default_group_flags" instead'),this.settings.default_rule_flags.no_sortable=this.settings.default_group_flags.no_sortable=a.default_no_sortable),interact.dynamicDrop(!0),interact.pointerMoveTolerance(10);var c,d,f;this.on("afterAddRule afterAddGroup",function(g,i){if(i!=c){var k=g.builder;a.inherit_no_sortable&&i.parent&&i.parent.flags.no_sortable&&(i.flags.no_sortable=!0),a.inherit_no_drop&&i.parent&&i.parent.flags.no_drop&&(i.flags.no_drop=!0),i.flags.no_sortable||interact(i.$el[0]).allowFrom(h.drag_handle).draggable({onstart:function(a){f=b(a.target),d=f.$el.clone().appendTo(f.$el.parent()).width(f.$el.outerWidth()).addClass("dragging");var e=$('
 
').height(f.$el.outerHeight());c=f.parent.addRule(e,f.getPos()),f.$el.hide()},onmove:function(a){d[0].style.top=a.clientY-15+"px",d[0].style.left=a.clientX-15+"px"},onend:function(){d.remove(),d=void 0,c.drop(),c=void 0,f.$el.show(),k.trigger("afterMove",f)}}),i.flags.no_drop||(interact(i.$el[0]).dropzone({accept:h.rule_and_group_containers,ondragenter:function(a){e(c,$(a.target))},ondrop:function(a){e(f,$(a.target),k)}}),i instanceof j&&interact(i.$el.find(h.group_header)[0]).dropzone({accept:h.rule_and_group_containers,ondragenter:function(a){e(c,$(a.target))},ondrop:function(a){e(f,$(a.target),k)}}))}}),this.on("beforeDeleteRule beforeDeleteGroup",function(a,b){a.isDefaultPrevented()||(interact(b.$el[0]).unset(),b instanceof j&&interact(b.$el.find(h.group_header)[0]).unset())}),this.on("afterApplyRuleFlags afterApplyGroupFlags",function(a,b){b.flags.no_sortable&&b.$el.find(".drag-handle").remove()}),this.on("getGroupTemplate.filter",function(b,c){if(c>1){var d=$(b.value);d.find(h.condition_container).after('
'),b.value=d.prop("outerHTML")}}),this.on("getRuleTemplate.filter",function(b){var c=$(b.value);c.find(h.rule_header).after('
'),b.value=c.prop("outerHTML")})},{inherit_no_sortable:!0,inherit_no_drop:!0,icon:"glyphicon glyphicon-sort"}),g.defaults({sqlOperators:{equal:{op:"= ?"},not_equal:{op:"!= ?"},"in":{op:"IN(?)",sep:", "},not_in:{op:"NOT IN(?)",sep:", "},less:{op:"< ?"},less_or_equal:{op:"<= ?"},greater:{op:"> ?"},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}"},is_empty:{op:"= ''"},is_not_empty:{op:"!= ''"},is_null:{op:"IS NULL"},is_not_null:{op:"IS NOT NULL"}},sqlRuleOperator:{"=":function(a){return{val:a,op:""===a?"is_empty":"equal"}},"!=":function(a){return{val:a,op:""===a?"is_not_empty":"not_equal"}},LIKE:function(a){return"%"==a.slice(0,1)&&"%"==a.slice(-1)?{val:a.slice(1,-1),op:"contains"}:"%"==a.slice(0,1)?{val:a.slice(1),op:"ends_with"}:"%"==a.slice(-1)?{val:a.slice(0,-1),op:"begins_with"}:void l.error("SQLParse",'Invalid value for LIKE operator "{0}"',a)},"NOT LIKE":function(a){return"%"==a.slice(0,1)&&"%"==a.slice(-1)?{val:a.slice(1,-1),op:"not_contains"}:"%"==a.slice(0,1)?{val:a.slice(1),op:"not_ends_with"}:"%"==a.slice(-1)?{val:a.slice(0,-1),op:"not_begins_with"}:void l.error("SQLParse",'Invalid value for NOT LIKE operator "{0}"',a)},IN:function(a){return{val:a,op:"in"}},"NOT IN":function(a){return{val:a,op:"not_in"}},"<":function(a){return{val:a,op:"less"}},"<=":function(a){return{val:a,op:"less_or_equal"}},">":function(a){return{val:a,op:"greater"}},">=":function(a){return{val:a,op:"greater_or_equal"}},BETWEEN:function(a){return{val:a,op:"between"}},"NOT BETWEEN":function(a){return{val:a,op:"not_between"}},IS:function(a){return null!==a&&l.error("SQLParse","Invalid value for IS operator"),{val:null,op:"is_null"}},"IS NOT":function(a){return null!==a&&l.error("SQLParse","Invalid value for IS operator"),{val:null,op:"is_not_null"}}},sqlStatements:{question_mark:function(){var a=[];return{add:function(b,c){return a.push(c),"?"},run:function(){return a}}},numbered:function(a){(!a||a.length>1)&&(a="$");var b=0,c=[];return{add:function(d,e){return c.push(e),b++,a+b},run:function(){return c}}},named:function(a){(!a||a.length>1)&&(a=":");var b={},c={};return{add:function(d,e){b[d.field]||(b[d.field]=1);var f=d.field+"_"+b[d.field]++;return c[f]=e,a+f},run:function(){return c}}}},sqlRuleStatement:{question_mark:function(a){var b=0;return{parse:function(c){return"?"==c?a[b++]:c},esc:function(a){return a.replace(/\?/g,"'?'")}}},numbered:function(a,b){(!b||b.length>1)&&(b="$");var c=new RegExp("^\\"+b+"[0-9]+$"),d=new RegExp("\\"+b+"([0-9]+)","g");return{parse:function(b){return c.test(b)?a[b.slice(1)-1]:b},esc:function(a){return a.replace(d,"'"+("$"==b?"$$":b)+"$1'")}}},named:function(a,b){(!b||b.length>1)&&(b=":");var c=new RegExp("^\\"+b),d=new RegExp("\\"+b+"("+Object.keys(a).join("|")+")","g");return{parse:function(b){return c.test(b)?a[b.slice(1)]:b},esc:function(a){return a.replace(d,"'"+("$"==b?"$$":b)+"$1'")}}}}}),g.extend({getSQL:function(a,b,c){if(c=void 0===c?this.getRules():c,b=b===!0?"\n":" ",a===!0&&(a="question_mark"),"string"==typeof a){var d=f(a);a=this.settings.sqlStatements[d[1]](d[2])}var e=this,g=function h(c){if(c.condition||(c.condition=e.settings.default_condition),["AND","OR"].indexOf(c.condition.toUpperCase())===-1&&l.error("UndefinedSQLCondition",'Unable to build SQL query with condition "{0}"',c.condition),!c.rules)return"";var d=[];c.rules.forEach(function(c){if(c.rules&&c.rules.length>0)d.push("("+b+h(c)+b+")"+b);else{var f=e.settings.sqlOperators[c.operator],g=e.getOperatorByType(c.operator),i="";void 0===f&&l.error("UndefinedSQLOperator",'Unknown SQL operation for operator "{0}"',c.operator),0!==g.nb_inputs&&(c.value instanceof Array||(c.value=[c.value]),c.value.forEach(function(b,d){d>0&&(i+=f.sep),"integer"==c.type||"double"==c.type||"boolean"==c.type?b=l.changeType(b,c.type,!0):a||(b=l.escapeString(b)),f.mod&&(b=l.fmt(f.mod,b)),a?i+=a.add(c,b):("string"==typeof b&&(b="'"+b+"'"),i+=b)}));var j=function(a){return f.op.replace(/\?/,a)},k=e.change("getSQLField",c.field,c)+" "+j(i);d.push(e.change("ruleToSQL",k,c,i,j))}});var f=d.join(" "+c.condition+b);return e.change("groupToSQL",f,c)}(c);return a?{sql:g,params:a.run()}:{sql:g}},getRulesFromSQL:function(a,b){"SQLParser"in window||l.error("MissingLibrary","SQLParser is required to parse SQL queries. Get it here https://github.com/mistic100/sql-parser");var c=this;if("string"==typeof a&&(a={sql:a}),b===!0&&(b="question_mark"),"string"==typeof b){var d=f(b);b=this.settings.sqlRuleStatement[d[1]](a.params,d[2])}b&&(a.sql=b.esc(a.sql)),0!==a.sql.toUpperCase().indexOf("SELECT")&&(a.sql="SELECT * FROM table WHERE "+a.sql);var e=SQLParser.parse(a.sql);if(e.where||l.error("SQLParse","No WHERE clause found"),a=c.change("parseSQLNode",e.where.conditions),"rules"in a&&"condition"in a)return a;var g=c.change("sqlToGroup",{condition:this.settings.default_condition,rules:[]},a),h=g;return function i(a,d){if(a=c.change("parseSQLNode",a),"rules"in a&&"condition"in a)return void h.rules.push(a);if("id"in a&&"operator"in a&&"value"in a)return void h.rules.push(a);if("left"in a&&"right"in a&&"operation"in a||l.error("SQLParse","Unable to parse WHERE clause"),["AND","OR"].indexOf(a.operation.toUpperCase())!==-1){if(d>0&&h.condition!=a.operation.toUpperCase()){var e=c.change("sqlToGroup",{condition:c.settings.default_condition,rules:[]},a);h.rules.push(e),h=e}h.condition=a.operation.toUpperCase(),d++;var f=h;i(a.left,d),h=f,i(a.right,d)}else{$.isPlainObject(a.right.value)&&l.error("SQLParse","Value format not supported for {0}.",a.left.value);var g;g=$.isArray(a.right.value)?a.right.value.map(function(a){return a.value}):a.right.value,b&&(g=$.isArray(g)?g.map(b.parse):b.parse(g));var j=a.operation.toUpperCase();"<>"==j&&(j="!=");var k=c.settings.sqlRuleOperator[j];void 0===k&&l.error("UndefinedSQLOperator",'Invalid SQL operation "{0}".',a.operation);var m=k.call(this,g,a.operation),n=a.left.values.join("."),o=c.change("sqlToRule",{id:c.change("getSQLFieldID",n,g),field:n,operator:m.op,value:m.val},a);h.rules.push(o)}}(a,0),g},setRulesFromSQL:function(a,b){this.setRules(this.getRulesFromSQL(a,b))}}),g.define("unique-filter",function(){this.status.used_filters={},this.on("afterUpdateRuleFilter",this.updateDisabledFilters),this.on("afterDeleteRule",this.updateDisabledFilters),this.on("afterCreateRuleFilters",this.applyDisabledFilters),this.on("afterReset",this.clearDisabledFilters),this.on("afterClear",this.clearDisabledFilters),this.on("getDefaultFilter.filter",function(a,b){var c=a.builder;if(c.updateDisabledFilters(),a.value.id in c.status.used_filters){var d=c.filters.some(function(d){if(!(d.id in c.status.used_filters)||c.status.used_filters[d.id].length>0&&c.status.used_filters[d.id].indexOf(b.parent)===-1)return a.value=d,!0});d||(l.error("UniqueFilter","No more non-unique filters available"),a.value=void 0)}})}),g.extend({updateDisabledFilters:function(a){var b=a?a.builder:this;b.status.used_filters={},b.model&&(!function c(a){a.each(function(a){a.filter&&a.filter.unique&&(b.status.used_filters[a.filter.id]||(b.status.used_filters[a.filter.id]=[]),"group"==a.filter.unique&&b.status.used_filters[a.filter.id].push(a.parent))},function(a){c(a)})}(b.model.root),b.applyDisabledFilters(a))},clearDisabledFilters:function(a){var b=a?a.builder:this;b.status.used_filters={},b.applyDisabledFilters(a)},applyDisabledFilters:function(a){var b=a?a.builder:this;b.$el.find(h.filter_container+" option").prop("disabled",!1),$.each(b.status.used_filters,function(a,c){0===c.length?b.$el.find(h.filter_container+' option[value="'+a+'"]:not(:selected)').prop("disabled",!0):c.forEach(function(b){b.each(function(b){ -b.$el.find(h.filter_container+' option[value="'+a+'"]:not(:selected)').prop("disabled",!0)})})}),b.settings.plugins&&b.settings.plugins["bt-selectpicker"]&&b.$el.find(h.rule_filter).selectpicker("render")}}),g.regional.en={__locale:"English (en)",__author:'Damien "Mistic" Sorel, http://www.strangeplanet.fr',add_rule:"Add rule",add_group:"Add group",delete_rule:"Delete",delete_group:"Delete",conditions:{AND:"AND",OR:"OR"},operators:{equal:"equal",not_equal:"not equal","in":"in",not_in:"not in",less:"less",less_or_equal:"less or equal",greater:"greater",greater_or_equal:"greater or equal",between:"between",not_between:"not between",begins_with:"begins with",not_begins_with:"doesn't begin with",contains:"contains",not_contains:"doesn't contain",ends_with:"ends with",not_ends_with:"doesn't end with",is_empty:"is empty",is_not_empty:"is not empty",is_null:"is null",is_not_null:"is not null"},errors:{no_filter:"No filter selected",empty_group:"The group is empty",radio_empty:"No value selected",checkbox_empty:"No value selected",select_empty:"No value selected",string_empty:"Empty value",string_exceed_min_length:"Must contain at least {0} characters",string_exceed_max_length:"Must not contain more than {0} characters",string_invalid_format:"Invalid format ({0})",number_nan:"Not a number",number_not_integer:"Not an integer",number_not_double:"Not a real number",number_exceed_min:"Must be greater than {0}",number_exceed_max:"Must be lower than {0}",number_wrong_step:"Must be a multiple of {0}",datetime_empty:"Empty value",datetime_invalid:"Invalid date format ({0})",datetime_exceed_min:"Must be after {0}",datetime_exceed_max:"Must be before {0}",boolean_not_valid:"Not a boolean",operator_not_multiple:"Operator {0} cannot accept multiple values"},invert:"Invert",NOT:"NOT"},g.defaults({lang_code:"en"})}); \ No newline at end of file +!function(a,b){"function"==typeof define&&define.amd?define(["jquery","doT","jQuery.extendext"],b):b(a.jQuery,a.doT)}(this,function($,a){"use strict";function b(){this.root=null,this.$=$(this)}function c(a){if(null!==a&&"object"==typeof a){var b=Object.keys(a);return 1===b.length?b[0]:void 0!==a.$gte&&void 0!==a.$lte?"between":void 0!==a.$lt&&void 0!==a.$gt?"not_between":void 0!==a.$regex?"$regex":void 0}return"eq"}function d(a){for(var b=Object.keys(a),c=0,d=b.length;c.rules-list",group_condition:".rules-group-header [name$=_cond]",rule_filter:".rule-filter-container [name$=_filter]",rule_operator:".rule-operator-container [name$=_operator]",rule_value:".rule-value-container [name*=_value_]",add_rule:"[data-add=rule]",delete_rule:"[data-delete=rule]",add_group:"[data-add=group]",delete_group:"[data-delete=group]"},g.templates={},g.regional={},g.OPERATORS={equal:{type:"equal",nb_inputs:1,multiple:!1,apply_to:["string","number","datetime","boolean"]},not_equal:{type:"not_equal",nb_inputs:1,multiple:!1,apply_to:["string","number","datetime","boolean"]},in:{type:"in",nb_inputs:1,multiple:!0,apply_to:["string","number","datetime"]},not_in:{type:"not_in",nb_inputs:1,multiple:!0,apply_to:["string","number","datetime"]},less:{type:"less",nb_inputs:1,multiple:!1,apply_to:["number","datetime"]},less_or_equal:{type:"less_or_equal",nb_inputs:1,multiple:!1,apply_to:["number","datetime"]},greater:{type:"greater",nb_inputs:1,multiple:!1,apply_to:["number","datetime"]},greater_or_equal:{type:"greater_or_equal",nb_inputs:1,multiple:!1,apply_to:["number","datetime"]},between:{type:"between",nb_inputs:2,multiple:!1,apply_to:["number","datetime"]},not_between:{type:"not_between",nb_inputs:2,multiple:!1,apply_to:["number","datetime"]},begins_with:{type:"begins_with",nb_inputs:1,multiple:!1,apply_to:["string"]},not_begins_with:{type:"not_begins_with",nb_inputs:1,multiple:!1,apply_to:["string"]},contains:{type:"contains",nb_inputs:1,multiple:!1,apply_to:["string"]},not_contains:{type:"not_contains",nb_inputs:1,multiple:!1,apply_to:["string"]},ends_with:{type:"ends_with",nb_inputs:1,multiple:!1,apply_to:["string"]},not_ends_with:{type:"not_ends_with",nb_inputs:1,multiple:!1,apply_to:["string"]},is_empty:{type:"is_empty",nb_inputs:0,multiple:!1,apply_to:["string"]},is_not_empty:{type:"is_not_empty",nb_inputs:0,multiple:!1,apply_to:["string"]},is_null:{type:"is_null",nb_inputs:0,multiple:!1,apply_to:["string","number","datetime","boolean"]},is_not_null:{type:"is_not_null",nb_inputs:0,multiple:!1,apply_to:["string","number","datetime","boolean"]}},g.DEFAULTS={filters:[],plugins:[],sort_filters:!1,display_errors:!0,allow_groups:-1,allow_empty:!1,conditions:["AND","OR"],default_condition:"AND",inputs_separator:" , ",select_placeholder:"------",display_empty_filter:!0,default_filter:null,optgroups:{},default_rule_flags:{filter_readonly:!1,operator_readonly:!1,value_readonly:!1,no_delete:!1},default_group_flags:{condition_readonly:!1,no_add_rule:!1,no_add_group:!1,no_delete:!1},templates:{group:null,rule:null,filterSelect:null,operatorSelect:null},lang_code:"en",lang:{},operators:["equal","not_equal","in","not_in","less","less_or_equal","greater","greater_or_equal","between","not_between","begins_with","not_begins_with","contains","not_contains","ends_with","not_ends_with","is_empty","is_not_empty","is_null","is_not_null"],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"}},g.plugins={},g.defaults=function(a){if("object"!=typeof a)return"string"==typeof a?"object"==typeof g.DEFAULTS[a]?$.extend(!0,{},g.DEFAULTS[a]):g.DEFAULTS[a]:$.extend(!0,{},g.DEFAULTS);$.extendext(!0,"replace",g.DEFAULTS,a)},g.define=function(a,b,c){g.plugins[a]={fct:b,def:c||{}}},g.extend=function(a){$.extend(g.prototype,a)},g.prototype.initPlugins=function(){if(this.plugins){if($.isArray(this.plugins)){var a={};this.plugins.forEach(function(b){a[b]=null}),this.plugins=a}Object.keys(this.plugins).forEach(function(a){a in g.plugins?(this.plugins[a]=$.extend(!0,{},g.plugins[a].def,this.plugins[a]||{}),g.plugins[a].fct.call(this,this.plugins[a])):k.error("Config",'Unable to find plugin "{0}"',a)},this)}},g.prototype.checkFilters=function(a){var b=[];if(a&&0!==a.length||k.error("Config","Missing filters list"),a.forEach(function(a,c){switch(a.id||k.error("Config","Missing filter {0} id",c),b.indexOf(a.id)!=-1&&k.error("Config",'Filter "{0}" already defined',a.id),b.push(a.id),a.type?g.types[a.type]||k.error("Config",'Invalid type "{0}"',a.type):a.type="string",a.input?"function"!=typeof a.input&&g.inputs.indexOf(a.input)==-1&&k.error("Config",'Invalid input "{0}"',a.input):a.input="number"===g.types[a.type]?"number":"text",a.operators&&a.operators.forEach(function(a){"string"!=typeof a&&k.error("Config","Filter operators must be global operators types (string)")}),a.field||(a.field=a.id),a.label||(a.label=a.field),a.optgroup?(this.status.has_optgroup=!0,this.settings.optgroups[a.optgroup]||(this.settings.optgroups[a.optgroup]=a.optgroup)):a.optgroup=null,a.input){case"radio":case"checkbox":(!a.values||a.values.length<1)&&k.error("Config",'Missing filter "{0}" values',a.id);break;case"select":a.placeholder&&(void 0===a.placeholder_value&&(a.placeholder_value=-1),k.iterateOptions(a.values,function(b){b==a.placeholder_value&&k.error("Config",'Placeholder of filter "{0}" overlaps with one of its values',a.id)}))}},this),this.settings.sort_filters)if("function"==typeof this.settings.sort_filters)a.sort(this.settings.sort_filters);else{var c=this;a.sort(function(a,b){return c.translate(a.label).localeCompare(c.translate(b.label))})}return this.status.has_optgroup&&(a=k.groupSort(a,"optgroup")),a},g.prototype.checkOperators=function(a){var b=[];return a.forEach(function(c,d){"string"==typeof c?(g.OPERATORS[c]||k.error("Config",'Unknown operator "{0}"',c),a[d]=c=$.extendext(!0,"replace",{},g.OPERATORS[c])):(c.type||k.error("Config",'Missing "type" for operator {0}',d),g.OPERATORS[c.type]&&(a[d]=c=$.extendext(!0,"replace",{},g.OPERATORS[c.type],c)),void 0!==c.nb_inputs&&void 0!==c.apply_to||k.error("Config",'Missing "nb_inputs" and/or "apply_to" for operator "{0}"',c.type)),b.indexOf(c.type)!=-1&&k.error("Config",'Operator "{0}" already defined',c.type),b.push(c.type),c.optgroup?(this.status.has_operator_optgroup=!0,this.settings.optgroups[c.optgroup]||(this.settings.optgroups[c.optgroup]=c.optgroup)):c.optgroup=null},this),this.status.has_operator_optgroup&&(a=k.groupSort(a,"optgroup")),a},g.prototype.bindEvents=function(){var a=this,b=g.selectors;this.$el.on("change.queryBuilder",b.group_condition,function(){if($(this).is(":checked")){var c=$(this).closest(b.group_container);a.getModel(c).condition=$(this).val()}}),this.$el.on("change.queryBuilder",b.rule_filter,function(){var c=$(this).closest(b.rule_container);a.getModel(c).filter=a.getFilterById($(this).val())}),this.$el.on("change.queryBuilder",b.rule_operator,function(){var c=$(this).closest(b.rule_container);a.getModel(c).operator=a.getOperatorByType($(this).val())}),this.$el.on("click.queryBuilder",b.add_rule,function(){var c=$(this).closest(b.group_container);a.addRule(a.getModel(c))}),this.$el.on("click.queryBuilder",b.delete_rule,function(){var c=$(this).closest(b.rule_container);a.deleteRule(a.getModel(c))}),0!==this.settings.allow_groups&&(this.$el.on("click.queryBuilder",b.add_group,function(){var c=$(this).closest(b.group_container);a.addGroup(a.getModel(c))}),this.$el.on("click.queryBuilder",b.delete_group,function(){var c=$(this).closest(b.group_container);a.deleteGroup(a.getModel(c))})),this.model.on({drop:function(b,c){c.$el.remove(),a.refreshGroupsConditions()},add:function(b,c,d,e){0===e?d.$el.prependTo(c.$el.find(">"+g.selectors.rules_list)):d.$el.insertAfter(c.rules[e-1].$el),a.refreshGroupsConditions()},move:function(b,c,d,e){c.$el.detach(),0===e?c.$el.prependTo(d.$el.find(">"+g.selectors.rules_list)):c.$el.insertAfter(d.rules[e-1].$el),a.refreshGroupsConditions()},update:function(b,c,d,e,f){if(c instanceof j)switch(d){case"error":a.updateError(c);break;case"flags":a.applyRuleFlags(c);break;case"filter":a.updateRuleFilter(c,f);break;case"operator":a.updateRuleOperator(c,f);break;case"value":a.updateRuleValue(c)}else switch(d){case"error":a.updateError(c);break;case"flags":a.applyGroupFlags(c);break;case"condition":a.updateGroupCondition(c)}}})},g.prototype.setRoot=function(a,b,c){a=void 0===a||a===!0;var d=this.nextGroupId(),e=$(this.getGroupTemplate(d,1));return this.$el.append(e),this.model.root=new i(null,e),this.model.root.model=this.model,this.model.root.data=b,this.model.root.__.flags=$.extend({},this.settings.default_group_flags,c),this.trigger("afterAddGroup",this.model.root),this.model.root.condition=this.settings.default_condition,a&&this.addRule(this.model.root),this.model.root},g.prototype.addGroup=function(a,b,c,d){b=void 0===b||b===!0;var e=a.level+1;if(this.trigger("beforeAddGroup",a,b,e).isDefaultPrevented())return null;var f=this.nextGroupId(),g=$(this.getGroupTemplate(f,e)),h=a.addGroup(g);return h.data=c,h.__.flags=$.extend({},this.settings.default_group_flags,d),this.trigger("afterAddGroup",h),h.condition=this.settings.default_condition,b&&this.addRule(h),h},g.prototype.deleteGroup=function(a){if(a.isRoot())return!1;if(this.trigger("beforeDeleteGroup",a).isDefaultPrevented())return!1;var b=!0;return a.each("reverse",function(a){b&=this.deleteRule(a)},function(a){b&=this.deleteGroup(a)},this),b&&(a.drop(),this.trigger("afterDeleteGroup")),b},g.prototype.updateGroupCondition=function(a){a.$el.find(">"+g.selectors.group_condition).each(function(){var b=$(this);b.prop("checked",b.val()===a.condition),b.parent().toggleClass("active",b.val()===a.condition)}),this.trigger("afterUpdateGroupCondition",a)},g.prototype.refreshGroupsConditions=function(){!function a(b){(!b.flags||b.flags&&!b.flags.condition_readonly)&&b.$el.find(">"+g.selectors.group_condition).prop("disabled",b.rules.length<=1).parent().toggleClass("disabled",b.rules.length<=1),b.each(null,function(b){a(b)},this)}(this.model.root)},g.prototype.addRule=function(a,b,c){if(this.trigger("beforeAddRule",a).isDefaultPrevented())return null;var d=this.nextRuleId(),e=$(this.getRuleTemplate(d)),f=a.addRule(e);return void 0!==b&&(f.data=b),f.__.flags=$.extend({},this.settings.default_rule_flags,c),this.trigger("afterAddRule",f),this.createRuleFilters(f),!this.settings.default_filter&&this.settings.display_empty_filter||(f.filter=this.change("getDefaultFilter",this.getFilterById(this.settings.default_filter||this.filters[0].id),f)),f},g.prototype.deleteRule=function(a){return!a.flags.no_delete&&(!this.trigger("beforeDeleteRule",a).isDefaultPrevented()&&(a.drop(),this.trigger("afterDeleteRule"),!0))},g.prototype.createRuleFilters=function(a){var b=this.change("getRuleFilters",this.filters,a),c=$(this.getRuleFilterSelect(a,b));a.$el.find(g.selectors.filter_container).html(c),this.trigger("afterCreateRuleFilters",a)},g.prototype.createRuleOperators=function(a){var b=a.$el.find(g.selectors.operator_container).empty();if(a.filter){var c=this.getOperators(a.filter),d=$(this.getRuleOperatorSelect(a,c));b.html(d),a.__.operator=c[0],this.trigger("afterCreateRuleOperators",a,c)}},g.prototype.createRuleInput=function(a){var b=a.$el.find(g.selectors.value_container).empty();if(a.__.value=void 0,a.filter&&a.operator&&0!==a.operator.nb_inputs){for(var c=this,d=$(),e=a.filter,f=0;f0&&b.append(this.settings.inputs_separator),b.append(h),d=d.add(h)}b.show(),d.on("change "+(e.input_event||""),function(){this._updating_input||(a._updating_value=!0,a.value=c.getRuleInputValue(a),a._updating_value=!1)}),e.plugin&&d[e.plugin](e.plugin_config||{}),this.trigger("afterCreateRuleInput",a),void 0!==e.default_value?a.value=e.default_value:(a._updating_value=!0,a.value=c.getRuleInputValue(a),a._updating_value=!1)}},g.prototype.updateRuleFilter=function(a,b){this.createRuleOperators(a),this.createRuleInput(a),a.$el.find(g.selectors.rule_filter).val(a.filter?a.filter.id:"-1"),b&&a.filter&&b.id!==a.filter.id&&(a.data=void 0),this.trigger("afterUpdateRuleFilter",a)},g.prototype.updateRuleOperator=function(a,b){var c=a.$el.find(g.selectors.value_container);a.operator&&0!==a.operator.nb_inputs?(c.show(),!c.is(":empty")&&b&&a.operator.nb_inputs===b.nb_inputs&&a.operator.optgroup===b.optgroup||this.createRuleInput(a)):(c.hide(),a.__.value=void 0),a.operator&&a.$el.find(g.selectors.rule_operator).val(a.operator.type),this.trigger("afterUpdateRuleOperator",a),this.updateRuleValue(a)},g.prototype.updateRuleValue=function(a){a._updating_value||this.setRuleInputValue(a,a.value),this.trigger("afterUpdateRuleValue",a)},g.prototype.applyRuleFlags=function(a){var b=a.flags,c=g.selectors;b.filter_readonly&&a.$el.find(c.rule_filter).prop("disabled",!0),b.operator_readonly&&a.$el.find(c.rule_operator).prop("disabled",!0),b.value_readonly&&a.$el.find(c.rule_value).prop("disabled",!0),b.no_delete&&a.$el.find(c.delete_rule).remove(),this.trigger("afterApplyRuleFlags",a)},g.prototype.applyGroupFlags=function(a){var b=a.flags,c=g.selectors;b.condition_readonly&&a.$el.find(">"+c.group_condition).prop("disabled",!0).parent().addClass("readonly"),b.no_add_rule&&a.$el.find(c.add_rule).remove(),b.no_add_group&&a.$el.find(c.add_group).remove(),b.no_delete&&a.$el.find(c.delete_group).remove(),this.trigger("afterApplyGroupFlags",a)},g.prototype.clearErrors=function(a){(a=a||this.model.root)&&(a.error=null,a instanceof i&&a.each(function(a){a.error=null},function(a){this.clearErrors(a)},this))},g.prototype.updateError=function(a){if(this.settings.display_errors)if(null===a.error)a.$el.removeClass("has-error");else{var b=this.translate("errors",a.error[0]);b=k.fmt(b,a.error.slice(1)),b=this.change("displayError",b,a.error,a),a.$el.addClass("has-error").find(g.selectors.error_container).eq(0).attr("title",b)}},g.prototype.triggerValidationError=function(a,b,c){$.isArray(b)||(b=[b]),this.trigger("validationError",a,b,c).isDefaultPrevented()||(a.error=b)},g.prototype.destroy=function(){this.trigger("beforeDestroy"),this.status.generated_id&&this.$el.removeAttr("id"),this.clear(),this.model=null,this.$el.off(".queryBuilder").removeClass("query-builder").removeData("queryBuilder"),delete this.$el[0].queryBuilder},g.prototype.reset=function(){this.trigger("beforeReset").isDefaultPrevented()||(this.status.group_id=1,this.status.rule_id=0,this.model.root.empty(),this.addRule(this.model.root),this.trigger("afterReset"))},g.prototype.clear=function(){this.trigger("beforeClear").isDefaultPrevented()||(this.status.group_id=0,this.status.rule_id=0,this.model.root&&(this.model.root.drop(),this.model.root=null),this.trigger("afterClear"))},g.prototype.setOptions=function(a){$.each(a,function(a,b){g.modifiable_options.indexOf(a)!==-1&&(this.settings[a]=b)}.bind(this))},g.prototype.getModel=function(a){return a?a instanceof h?a:$(a).data("queryBuilderModel"):this.model.root},g.prototype.validate=function(a){a=$.extend({skip_empty:!1},a),this.clearErrors();var b=this,c=function c(d){var e=0,f=0;return d.each(function(c){if(c.filter||!a.skip_empty){if(!c.filter)return b.triggerValidationError(c,"no_filter",null),void f++;if(!c.operator)return b.triggerValidationError(c,"no_operator",null),void f++;if(0!==c.operator.nb_inputs){var d=b.validateValue(c,c.value);if(d!==!0)return b.triggerValidationError(c,d,c.value),void f++}e++}},function(a){var b=c(a);b===!0?e++:b===!1&&f++}),!(f>0)&&(0===e&&!d.isRoot()&&a.skip_empty?null:!!(0!==e||b.settings.allow_empty&&d.isRoot())||(b.triggerValidationError(d,"empty_group",null),!1))}(this.model.root);return this.change("validate",c)},g.prototype.getRules=function(a){a=$.extend({get_flags:!1,allow_invalid:!1,skip_empty:!1},a);var b=this.validate(a);if(!b&&!a.allow_invalid)return null;var c=this,d=function b(d){var e={condition:d.condition,rules:[]};if(d.data&&(e.data=$.extendext(!0,"replace",{},d.data)),a.get_flags){var f=c.getGroupFlags(d.flags,"all"===a.get_flags);$.isEmptyObject(f)||(e.flags=f)}return d.each(function(b){if(b.filter||!a.skip_empty){var d=null;b.operator&&0===b.operator.nb_inputs||(d=b.value);var f={id:b.filter?b.filter.id:null,field:b.filter?b.filter.field:null,type:b.filter?b.filter.type:null,input:b.filter?b.filter.input:null,operator:b.operator?b.operator.type:null,value:d};if((b.filter&&b.filter.data||b.data)&&(f.data=$.extendext(!0,"replace",{},b.filter.data,b.data)),a.get_flags){var g=c.getRuleFlags(b.flags,"all"===a.get_flags);$.isEmptyObject(g)||(f.flags=g)}e.rules.push(c.change("ruleToJson",f,b))}},function(c){var d=b(c);0===d.rules.length&&a.skip_empty||e.rules.push(d)},this),c.change("groupToJson",e,d)}(this.model.root);return d.valid=b,this.change("getRules",d)},g.prototype.setRules=function(a,b){b=$.extend({allow_invalid:!1},b),$.isArray(a)&&(a={condition:this.settings.default_condition,rules:a}),a&&a.rules&&(0!==a.rules.length||this.settings.allow_empty)||k.error("RulesParse","Incorrect data object passed"),this.clear(),this.setRoot(!1,a.data,this.parseGroupFlags(a)),this.applyGroupFlags(this.model.root),a=this.change("setRules",a,b);var c=this;!function a(d,e){null!==e&&(void 0===d.condition?d.condition=c.settings.default_condition:c.settings.conditions.indexOf(d.condition)==-1&&(k.error(!b.allow_invalid,"UndefinedCondition",'Invalid condition "{0}"',d.condition),d.condition=c.settings.default_condition),e.condition=d.condition,d.rules.forEach(function(d){var f;if(void 0!==d.rules)if(c.settings.allow_groups!==-1&&c.settings.allow_groups1){i=["operator_not_multiple",f.type,this.translate("operators",f.type)];break}switch(e.input){case"radio":if(void 0===b[j]||0===b[j].length){h.allow_empty_value||(i=["radio_empty"]);break}break;case"checkbox":if(void 0===b[j]||0===b[j].length){h.allow_empty_value||(i=["checkbox_empty"]);break}break;case"select":if(void 0===b[j]||0===b[j].length||e.placeholder&&b[j]==e.placeholder_value){h.allow_empty_value||(i=["select_empty"]);break}break;default:d=$.isArray(b[j])?b[j]:[b[j]];for(var l=0;lparseInt(h.max)){i=[this.getValidationMessage(h,"max","string_exceed_max_length"),h.max];break}if(h.format&&("string"==typeof h.format&&(h.format=new RegExp(h.format)),!h.format.test(d[l]))){i=[this.getValidationMessage(h,"format","string_invalid_format"),h.format];break}break;case"number":if(void 0===d[l]||0===d[l].length){h.allow_empty_value||(i=["number_nan"]);break}if(isNaN(d[l])){i=["number_nan"];break}if("integer"==e.type){if(parseInt(d[l])!=d[l]){i=["number_not_integer"];break}}else if(parseFloat(d[l])!=d[l]){i=["number_not_double"];break}if(void 0!==h.min&&d[l]parseFloat(h.max)){i=[this.getValidationMessage(h,"max","number_exceed_max"),h.max];break}if(void 0!==h.step&&"any"!==h.step){var m=(d[l]/h.step).toPrecision(14);if(parseInt(m)!=m){i=[this.getValidationMessage(h,"step","number_wrong_step"),h.step];break}}break;case"datetime":if(void 0===d[l]||0===d[l].length){h.allow_empty_value||(i=["datetime_empty"]);break}if(h.format){"moment"in window||k.error("MissingLibrary","MomentJS is required for Date/Time validation. Get it here http://momentjs.com");var n=moment(d[l],h.format);if(!n.isValid()){i=[this.getValidationMessage(h,"format","datetime_invalid"),h.format];break}if(h.min&&nmoment(h.max,h.format)){i=[this.getValidationMessage(h,"max","datetime_exceed_max"),h.max];break}}break;case"boolean":if(void 0===d[l]||0===d[l].length){h.allow_empty_value||(i=["boolean_not_valid"]);break}if("true"!==(c=(""+d[l]).trim().toLowerCase())&&"false"!==c&&"1"!==c&&"0"!==c&&1!==d[l]&&0!==d[l]){i=["boolean_not_valid"];break}}if(i!==!0)break}}if(i!==!0)break}return i},g.prototype.nextGroupId=function(){return this.status.id+"_group_"+this.status.group_id++},g.prototype.nextRuleId=function(){return this.status.id+"_rule_"+this.status.rule_id++},g.prototype.getOperators=function(a){"string"==typeof a&&(a=this.getFilterById(a));for(var b=[],c=0,d=this.operators.length;c '+b+" "});break;case"select":g+='";break;case"textarea":g+=''; + h += '";break;default:switch(g.types[c.type]){case"number":h+='=f:e<=f},i=!1;h()&&(this.rules[e]instanceof j?void 0!==c&&(i=c.call(d,this.rules[e])===!1):i=b.call(d,this.rules[e])===!1,!i);e+=g);return!i},j.prototype.contains=function(a,b){return this.getNodePos(a)!==-1||!!b&&!this.each(function(a){return!0},function(b){return!b.contains(a,!0)})};var k=function(a,b){return this instanceof k?(i.call(this,a,b),this._updating_value=!1,this._updating_input=!1,this.__.filter=null,this.__.operator=null,this.__.flags={},void(this.__.value=void 0)):new k(a,b)};k.prototype=Object.create(i.prototype),k.prototype.constructor=k,b.defineModelProperties(k,["filter","operator","value"]),g.Group=j,g.Rule=k;var l=g.utils={};l.iterateOptions=function(a,b){a&&($.isArray(a)?a.forEach(function(a){$.isPlainObject(a)?$.each(a,function(a,c){return b(a,c),!1}):b(a,a)}):$.each(a,function(a,c){b(a,c)}))},l.fmt=function(a,b){return Array.isArray(b)||(b=Array.prototype.slice.call(arguments,1)),a.replace(/{([0-9]+)}/g,function(a,c){return b[parseInt(c)]})},l.error=function(){var a=0,b="boolean"!=typeof arguments[a]||arguments[a++],c=arguments[a++],d=arguments[a++],e=Array.isArray(arguments[a])?arguments[a]:Array.prototype.slice.call(arguments,a);if(b){var f=new Error(l.fmt(d,e));throw f.name=c+"Error",f.args=e,f}console.error(c+"Error: "+l.fmt(d,e))},l.changeType=function(a,b,c){switch(b){case"integer":return parseInt(a);case"double":return parseFloat(a);case"boolean":var d="true"===a.trim().toLowerCase()||"1"===a.trim()||1===a;return c?d?1:0:d;default:return a}},l.escapeString=function(a){return"string"!=typeof a?a:a.replace(/[\0\n\r\b\\\'\"]/g,function(a){switch(a){case"\0":return"\\0";case"\n":return"\\n";case"\r":return"\\r";case"\b":return"\\b";default:return"\\"+a}}).replace(/\t/g,"\\t").replace(/\x1a/g,"\\Z")},l.escapeRegExp=function(a){return a.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")},l.escapeElementId=function(a){return a?a.replace(/(\\)?([:.\[\],])/g,function(a,b,c){return b?a:"\\"+c}):a},l.groupSort=function(a,b){var c=[],d=[];return a.forEach(function(a){var e;a[b]?(e=c.lastIndexOf(a[b]),e==-1?e=c.length:e++):e=c.length,c.splice(e,0,a[b]),d.splice(e,0,a)}),d},$.fn.queryBuilder=function(a){0===this.length&&l.error("Config","No target defined"),this.length>1&&l.error("Config","Unable to initialize on multiple target");var b=this.data("queryBuilder"),c="object"==typeof a&&a||{};return b||"destroy"!=a?(b||this.data("queryBuilder",new g(this,c)),"string"==typeof a?b[a].apply(b,Array.prototype.slice.call(arguments,1)):this):this},$.fn.queryBuilder.constructor=g,$.fn.queryBuilder.defaults=g.defaults,$.fn.queryBuilder.extend=g.extend,$.fn.queryBuilder.define=g.define,$.fn.queryBuilder.regional=g.regional,g.define("bt-checkbox",function(a){if("glyphicons"==a.font){var b=document.createElement("style");b.innerHTML='.checkbox input[type=checkbox]:checked + label:after { font-family: "Glyphicons Halflings"; content: "\\e013"; } .checkbox label:after { padding-left: 4px; padding-top: 2px; font-size: 9px; }',document.body.appendChild(b)}this.on("getRuleInput.filter",function(b,c,d){var e=c.filter;if(("radio"===e.input||"checkbox"===e.input)&&!e.plugin){b.value="",e.colors||(e.colors={}),e.color&&(e.colors._def_=e.color);var f=e.vertical?' style="display:block"':"",g=0;l.iterateOptions(e.values,function(c,h){var i=e.colors[c]||e.colors._def_||a.color,j=d+"_"+g++;b.value+=" "})}})},{font:"glyphicons",color:"default"}),g.define("bt-selectpicker",function(a){$.fn.selectpicker&&$.fn.selectpicker.Constructor||l.error("MissingLibrary",'Bootstrap Select is required to use "bt-selectpicker" plugin. Get it here: http://silviomoreto.github.io/bootstrap-select'),this.on("afterCreateRuleFilters",function(b,c){c.$el.find(h.rule_filter).removeClass("form-control").selectpicker(a)}),this.on("afterCreateRuleOperators",function(b,c){c.$el.find(h.rule_operator).removeClass("form-control").selectpicker(a)}),this.on("afterUpdateRuleFilter",function(a,b){b.$el.find(h.rule_filter).selectpicker("render")}),this.on("afterUpdateRuleOperator",function(a,b){b.$el.find(h.rule_operator).selectpicker("render")})},{container:"body",style:"btn-inverse btn-xs",width:"auto",showIcon:!1}),g.define("bt-tooltip-errors",function(a){$.fn.tooltip&&$.fn.tooltip.Constructor&&$.fn.tooltip.Constructor.prototype.fixTitle||l.error("MissingLibrary",'Bootstrap Tooltip is required to use "bt-tooltip-errors" plugin. Get it here: http://getbootstrap.com');var b=this;this.on("getRuleTemplate.filter getGroupTemplate.filter",function(a){var b=$(a.value);b.find(h.error_container).attr("data-toggle","tooltip"),a.value=b.prop("outerHTML")}),this.model.on("update",function(c,d,e){"error"==e&&b.settings.display_errors&&d.$el.find(h.error_container).eq(0).tooltip(a).tooltip("hide").tooltip("fixTitle")})},{placement:"right"}),g.extend({setFilters:function(a,b){var c=this;void 0===b&&(b=a,a=!1),b=this.checkFilters(b),b=this.change("setFilters",b);var d=b.map(function(a){return a.id});if(a||!function f(a){a.each(function(a){a.filter&&d.indexOf(a.filter.id)===-1&&l.error("ChangeFilter",'A rule is using filter "{0}"',a.filter.id)},f)}(this.model.root),this.filters=b,function g(a){a.each(!0,function(a){a.filter&&d.indexOf(a.filter.id)===-1?a.drop():(c.createRuleFilters(a),a.$el.find(h.rule_filter).val(a.filter?a.filter.id:"-1"),c.trigger("afterUpdateRuleFilter",a))},g)}(this.model.root),this.settings.plugins&&(this.settings.plugins["unique-filter"]&&this.updateDisabledFilters(),this.settings.plugins["bt-selectpicker"]&&this.$el.find(h.rule_filter).selectpicker("render")),this.settings.default_filter)try{this.getFilterById(this.settings.default_filter)}catch(e){this.settings.default_filter=null}this.trigger("afterSetFilters",b)},addFilter:function(a,b){void 0===b||"#end"==b?b=this.filters.length:"#start"==b&&(b=0),$.isArray(a)||(a=[a]);var c=$.extend(!0,[],this.filters);parseInt(b)==b?Array.prototype.splice.apply(c,[b,0].concat(a)):this.filters.some(function(a,c){if(a.id==b)return b=c+1,!0})?Array.prototype.splice.apply(c,[b,0].concat(a)):Array.prototype.push.apply(c,a),this.setFilters(c)},removeFilter:function(a,b){var c=$.extend(!0,[],this.filters);"string"==typeof a&&(a=[a]),c=c.filter(function(b){return a.indexOf(b.id)===-1}),this.setFilters(b,c)}}),g.define("filter-description",function(a){"inline"===a.mode?this.on("afterUpdateRuleFilter",function(b,c){var d=c.$el.find("p.filter-description");c.filter&&c.filter.description?(0===d.length?(d=$('

'),d.appendTo(c.$el)):d.show(),d.html(' '+c.filter.description)):d.hide()}):"popover"===a.mode?($.fn.popover&&$.fn.popover.Constructor&&$.fn.popover.Constructor.prototype.fixTitle||l.error("MissingLibrary",'Bootstrap Popover is required to use "filter-description" plugin. Get it here: http://getbootstrap.com'),this.on("afterUpdateRuleFilter",function(b,c){var d=c.$el.find("button.filter-description");c.filter&&c.filter.description?(0===d.length?(d=$(''),d.prependTo(c.$el.find(h.rule_actions)),d.popover({placement:"left",container:"body",html:!0}),d.on("mouseout",function(){d.popover("hide")})):d.show(),d.data("bs.popover").options.content=c.filter.description,d.attr("aria-describedby")&&d.popover("show")):(d.hide(),d.data("bs.popover")&&d.popover("hide"))})):"bootbox"===a.mode&&("bootbox"in window||l.error("MissingLibrary",'Bootbox is required to use "filter-description" plugin. Get it here: http://bootboxjs.com'),this.on("afterUpdateRuleFilter",function(b,c){var d=c.$el.find("button.filter-description");c.filter&&c.filter.description?(0===d.length&&(d=$(''),d.prependTo(c.$el.find(h.rule_actions)),d.on("click",function(){bootbox.alert(d.data("description"))})),d.data("description",c.filter.description)):d.hide()}))},{icon:"glyphicon glyphicon-info-sign",mode:"popover"}),g.defaults({operatorOpposites:{equal:"not_equal",not_equal:"equal","in":"not_in",not_in:"in",less:"greater_or_equal",less_or_equal:"greater",greater:"less_or_equal",greater_or_equal:"less",between:"not_between",not_between:"between",begins_with:"not_begins_with",not_begins_with:"begins_with",contains:"not_contains",not_contains:"contains",ends_with:"not_ends_with",not_ends_with:"ends_with",is_empty:"is_not_empty",is_not_empty:"is_empty",is_null:"is_not_null",is_not_null:"is_null"},conditionOpposites:{AND:"OR",OR:"AND"}}),g.define("invert",function(a){var c=this;this.on("afterInit",function(){c.$el.on("click.queryBuilder","[data-invert=group]",function(){var d=$(this).closest(h.group_container);c.invert(b(d),a)}),a.display_rules_button&&a.invert_rules&&c.$el.on("click.queryBuilder","[data-invert=rule]",function(){var d=$(this).closest(h.rule_container);c.invert(b(d),a)})}),this.on("getGroupTemplate.filter",function(b,d){var e=$(b.value);e.find(h.condition_container).after('"),b.value=e.prop("outerHTML")}),a.display_rules_button&&a.invert_rules&&this.on("getRuleTemplate.filter",function(b){var d=$(b.value);d.find(h.rule_actions).prepend('"),b.value=d.prop("outerHTML")})},{icon:"glyphicon glyphicon-random",recursive:!0,invert_rules:!0,display_rules_button:!1,silent_fail:!1}),g.extend({invert:function(a,b){if(!(a instanceof i)){if(!this.model.root)return;b=a,a=this.model.root}if("object"!=typeof b&&(b={}),void 0===b.recursive&&(b.recursive=!0),void 0===b.invert_rules&&(b.invert_rules=!0),void 0===b.silent_fail&&(b.silent_fail=!1),void 0===b.trigger&&(b.trigger=!0),a instanceof j){if(this.settings.conditionOpposites[a.condition]?a.condition=this.settings.conditionOpposites[a.condition]:b.silent_fail||l.error("InvertCondition",'Unknown inverse of condition "{0}"',a.condition),b.recursive){var c=$.extend({},b,{trigger:!1});a.each(function(a){b.invert_rules&&this.invert(a,c)},function(a){this.invert(a,c)},this)}}else if(a instanceof k&&a.operator&&!a.filter.no_invert)if(this.settings.operatorOpposites[a.operator.type]){var d=this.settings.operatorOpposites[a.operator.type];a.filter.operators&&a.filter.operators.indexOf(d)==-1||(a.operator=this.getOperatorByType(d))}else b.silent_fail||l.error("InvertOperator",'Unknown inverse of operator "{0}"',a.operator.type);b.trigger&&this.trigger("afterInvert",a,b)}}),g.defaults({mongoOperators:{equal:function(a){return a[0]},not_equal:function(a){return{$ne:a[0]}},"in":function(a){return{$in:a}},not_in:function(a){return{$nin:a}},less:function(a){return{$lt:a[0]}},less_or_equal:function(a){return{$lte:a[0]}},greater:function(a){return{$gt:a[0]}},greater_or_equal:function(a){return{$gte:a[0]}},between:function(a){return{$gte:a[0],$lte:a[1]}},not_between:function(a){return{$lt:a[0],$gt:a[1]}},begins_with:function(a){return{$regex:"^"+l.escapeRegExp(a[0])}},not_begins_with:function(a){return{$regex:"^(?!"+l.escapeRegExp(a[0])+")"}},contains:function(a){return{$regex:l.escapeRegExp(a[0])}},not_contains:function(a){return{$regex:"^((?!"+l.escapeRegExp(a[0])+").)*$",$options:"s"}},ends_with:function(a){return{$regex:l.escapeRegExp(a[0])+"$"}},not_ends_with:function(a){return{$regex:"(?0)d.push(c(a));else{var e=b.settings.mongoOperators[a.operator],f=b.getOperatorByType(a.operator),g=[];void 0===e&&l.error("UndefinedMongoOperator",'Unknown MongoDB operation for operator "{0}"',a.operator),0!==f.nb_inputs&&(a.value instanceof Array||(a.value=[a.value]),a.value.forEach(function(b){g.push(l.changeType(b,a.type,!1))}));var h={},i=b.change("getMongoDBField",a.field,a);h[i]=e.call(b,g),d.push(b.change("ruleToMongo",h,a,g,e))}});var e={};return e["$"+a.condition.toLowerCase()]=d,b.change("groupToMongo",e,a)}(a)},getRulesFromMongo:function(a){if(void 0===a||null===a)return null;var b=this;if(a=b.change("parseMongoNode",a),"rules"in a&&"condition"in a)return a;var e=d(a);return e||l.error("MongoParse","Invalid MongoDB query format"),function f(a,e){var g=a[e],h=[];return g.forEach(function(a){if(a=b.change("parseMongoNode",a),"rules"in a&&"condition"in a)return void h.push(a);if("id"in a&&"operator"in a&&"value"in a)return void h.push(a);var e=d(a);if(e)h.push(f(a,e));else{var g=Object.keys(a)[0],i=a[g],j=c(i,g);void 0===j&&l.error("MongoParse","Invalid MongoDB query format");var k=b.settings.mongoRuleOperators[j];void 0===k&&l.error("UndefinedMongoOperator",'JSON Rule operation unknown for operator "{0}"',j);var m=k.call(b,i),n=b.change("mongoToRule",{id:b.change("getMongoDBFieldID",g,i),field:g,operator:m.op,value:m.val},a);h.push(n)}}),b.change("mongoToGroup",{condition:e.replace("$","").toUpperCase(),rules:h},a)}(a,e)},setRulesFromMongo:function(a){this.setRules(this.getRulesFromMongo(a))}}),h.group_not=h.group_header+" [data-not=group]",b.defineModelProperties(j,["not"]),g.define("not-group",function(a){var c=this;this.on("afterInit",function(){c.$el.on("click.queryBuilder","[data-not=group]",function(){var a=$(this).closest(h.group_container),c=b(a);c.not=!c.not}),c.model.on("update",function(a,b,d){b instanceof j&&"not"===d&&c.updateGroupNot(b)})}),this.on("afterAddGroup",function(a,b){b.__.not=!1}),this.on("getGroupTemplate.filter",function(b,d){var e=$(b.value);e.find(h.condition_container).prepend('"),b.value=e.prop("outerHTML")}),this.on("groupToJson.filter",function(a,b){a.value.not=b.not}),this.on("jsonToGroup.filter",function(a,b){a.value.not=!!b.not}),this.on("groupToSQL.filter",function(a,b){b.not&&(a.value="NOT ( "+a.value+" )")}),this.on("parseSQLNode.filter",function(a){a.value.name&&"NOT"==a.value.name.toUpperCase()&&(a.value=a.value.arguments.value[0],a.value.not=!0)}),this.on("sqlToGroup.filter",function(a,b){a.value.not=!!b.not}),this.on("groupToMongo.filter",function(a,b){var c="$"+b.condition.toLowerCase();b.not&&a.value[c]&&(a.value={$nor:[a.value]})}),this.on("parseMongoNode.filter",function(a){var b=Object.keys(a.value);"$nor"==b[0]&&(a.value=a.value[b[0]][0],a.value.not=!0)}),this.on("mongoToGroup.filter",function(a,b){a.value.not=!!b.not})},{icon_unchecked:"glyphicon glyphicon-unchecked",icon_checked:"glyphicon glyphicon-check"}),g.extend({updateGroupNot:function(a){var b=this.plugins["not-group"];a.$el.find(">"+h.group_not).toggleClass("active",a.not).find("i").attr("class",a.not?b.icon_checked:b.icon_unchecked),this.trigger("afterUpdateGroupNot",a)}}),h.rule_and_group_containers=h.rule_container+", "+h.group_container,h.drag_handle=".drag-handle",g.defaults({default_rule_flags:{no_sortable:!1,no_drop:!1},default_group_flags:{no_sortable:!1,no_drop:!1}}),g.define("sortable",function(a){"interact"in window||l.error("MissingLibrary",'interact.js is required to use "sortable" plugin. Get it here: http://interactjs.io'),void 0!==a.default_no_sortable&&(l.error(!1,"Config",'Sortable plugin : "default_no_sortable" options is deprecated, use standard "default_rule_flags" and "default_group_flags" instead'),this.settings.default_rule_flags.no_sortable=this.settings.default_group_flags.no_sortable=a.default_no_sortable),interact.dynamicDrop(!0),interact.pointerMoveTolerance(10);var c,d,f;this.on("afterAddRule afterAddGroup",function(g,i){if(i!=c){var k=g.builder;a.inherit_no_sortable&&i.parent&&i.parent.flags.no_sortable&&(i.flags.no_sortable=!0),a.inherit_no_drop&&i.parent&&i.parent.flags.no_drop&&(i.flags.no_drop=!0),i.flags.no_sortable||interact(i.$el[0]).allowFrom(h.drag_handle).draggable({onstart:function(a){f=b(a.target),d=f.$el.clone().appendTo(f.$el.parent()).width(f.$el.outerWidth()).addClass("dragging");var e=$('
 
').height(f.$el.outerHeight());c=f.parent.addRule(e,f.getPos()),f.$el.hide()},onmove:function(a){d[0].style.top=a.clientY-15+"px",d[0].style.left=a.clientX-15+"px"},onend:function(){d.remove(),d=void 0,c.drop(),c=void 0,f.$el.show(),k.trigger("afterMove",f)}}),i.flags.no_drop||(interact(i.$el[0]).dropzone({accept:h.rule_and_group_containers,ondragenter:function(a){e(c,$(a.target))},ondrop:function(a){e(f,$(a.target),k)}}),i instanceof j&&interact(i.$el.find(h.group_header)[0]).dropzone({accept:h.rule_and_group_containers,ondragenter:function(a){e(c,$(a.target))},ondrop:function(a){e(f,$(a.target),k)}}))}}),this.on("beforeDeleteRule beforeDeleteGroup",function(a,b){a.isDefaultPrevented()||(interact(b.$el[0]).unset(),b instanceof j&&interact(b.$el.find(h.group_header)[0]).unset())}),this.on("afterApplyRuleFlags afterApplyGroupFlags",function(a,b){b.flags.no_sortable&&b.$el.find(".drag-handle").remove()}),this.on("getGroupTemplate.filter",function(b,c){if(c>1){var d=$(b.value);d.find(h.condition_container).after('
'),b.value=d.prop("outerHTML")}}),this.on("getRuleTemplate.filter",function(b){var c=$(b.value);c.find(h.rule_header).after('
'),b.value=c.prop("outerHTML")})},{inherit_no_sortable:!0,inherit_no_drop:!0,icon:"glyphicon glyphicon-sort"}),g.defaults({sqlOperators:{equal:{op:"= ?"},not_equal:{op:"!= ?"},"in":{op:"IN(?)",sep:", "},not_in:{op:"NOT IN(?)",sep:", "},less:{op:"< ?"},less_or_equal:{op:"<= ?"},greater:{op:"> ?"},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}"},is_empty:{op:"= ''"},is_not_empty:{op:"!= ''"},is_null:{op:"IS NULL"},is_not_null:{op:"IS NOT NULL"}},sqlRuleOperator:{"=":function(a){return{val:a,op:""===a?"is_empty":"equal"}},"!=":function(a){return{val:a,op:""===a?"is_not_empty":"not_equal"}},LIKE:function(a){return"%"==a.slice(0,1)&&"%"==a.slice(-1)?{val:a.slice(1,-1),op:"contains"}:"%"==a.slice(0,1)?{val:a.slice(1),op:"ends_with"}:"%"==a.slice(-1)?{val:a.slice(0,-1),op:"begins_with"}:void l.error("SQLParse",'Invalid value for LIKE operator "{0}"',a)},"NOT LIKE":function(a){return"%"==a.slice(0,1)&&"%"==a.slice(-1)?{val:a.slice(1,-1),op:"not_contains"}:"%"==a.slice(0,1)?{val:a.slice(1),op:"not_ends_with"}:"%"==a.slice(-1)?{val:a.slice(0,-1),op:"not_begins_with"}:void l.error("SQLParse",'Invalid value for NOT LIKE operator "{0}"',a)},IN:function(a){return{val:a,op:"in"}},"NOT IN":function(a){return{val:a,op:"not_in"}},"<":function(a){return{val:a,op:"less"}},"<=":function(a){return{val:a,op:"less_or_equal"}},">":function(a){return{val:a,op:"greater"}},">=":function(a){return{val:a,op:"greater_or_equal"}},BETWEEN:function(a){return{val:a,op:"between"}},"NOT BETWEEN":function(a){return{val:a,op:"not_between"}},IS:function(a){return null!==a&&l.error("SQLParse","Invalid value for IS operator"),{val:null,op:"is_null"}},"IS NOT":function(a){return null!==a&&l.error("SQLParse","Invalid value for IS operator"),{val:null,op:"is_not_null"}}},sqlStatements:{question_mark:function(){var a=[];return{add:function(b,c){return a.push(c),"?"},run:function(){return a}}},numbered:function(a){(!a||a.length>1)&&(a="$");var b=0,c=[];return{add:function(d,e){return c.push(e),b++,a+b},run:function(){return c}}},named:function(a){(!a||a.length>1)&&(a=":");var b={},c={};return{add:function(d,e){b[d.field]||(b[d.field]=1);var f=d.field+"_"+b[d.field]++;return c[f]=e,a+f},run:function(){return c}}}},sqlRuleStatement:{question_mark:function(a){var b=0;return{parse:function(c){return"?"==c?a[b++]:c},esc:function(a){return a.replace(/\?/g,"'?'")}}},numbered:function(a,b){(!b||b.length>1)&&(b="$");var c=new RegExp("^\\"+b+"[0-9]+$"),d=new RegExp("\\"+b+"([0-9]+)","g");return{parse:function(b){return c.test(b)?a[b.slice(1)-1]:b},esc:function(a){return a.replace(d,"'"+("$"==b?"$$":b)+"$1'")}}},named:function(a,b){(!b||b.length>1)&&(b=":");var c=new RegExp("^\\"+b),d=new RegExp("\\"+b+"("+Object.keys(a).join("|")+")","g");return{parse:function(b){return c.test(b)?a[b.slice(1)]:b},esc:function(a){return a.replace(d,"'"+("$"==b?"$$":b)+"$1'")}}}}}),g.extend({getSQL:function(a,b,c){if(c=void 0===c?this.getRules():c,b=b===!0?"\n":" ",a===!0&&(a="question_mark"),"string"==typeof a){var d=f(a);a=this.settings.sqlStatements[d[1]](d[2])}var e=this,g=function h(c){if(c.condition||(c.condition=e.settings.default_condition), -["AND","OR"].indexOf(c.condition.toUpperCase())===-1&&l.error("UndefinedSQLCondition",'Unable to build SQL query with condition "{0}"',c.condition),!c.rules)return"";var d=[];c.rules.forEach(function(c){if(c.rules&&c.rules.length>0)d.push("("+b+h(c)+b+")"+b);else{var f=e.settings.sqlOperators[c.operator],g=e.getOperatorByType(c.operator),i="";void 0===f&&l.error("UndefinedSQLOperator",'Unknown SQL operation for operator "{0}"',c.operator),0!==g.nb_inputs&&(c.value instanceof Array||(c.value=[c.value]),c.value.forEach(function(b,d){d>0&&(i+=f.sep),"integer"==c.type||"double"==c.type||"boolean"==c.type?b=l.changeType(b,c.type,!0):a||(b=l.escapeString(b)),f.mod&&(b=l.fmt(f.mod,b)),a?i+=a.add(c,b):("string"==typeof b&&(b="'"+b+"'"),i+=b)}));var j=function(a){return f.op.replace(/\?/,a)},k=e.change("getSQLField",c.field,c)+" "+j(i);d.push(e.change("ruleToSQL",k,c,i,j))}});var f=d.join(" "+c.condition+b);return e.change("groupToSQL",f,c)}(c);return a?{sql:g,params:a.run()}:{sql:g}},getRulesFromSQL:function(a,b){"SQLParser"in window||l.error("MissingLibrary","SQLParser is required to parse SQL queries. Get it here https://github.com/mistic100/sql-parser");var c=this;if("string"==typeof a&&(a={sql:a}),b===!0&&(b="question_mark"),"string"==typeof b){var d=f(b);b=this.settings.sqlRuleStatement[d[1]](a.params,d[2])}b&&(a.sql=b.esc(a.sql)),0!==a.sql.toUpperCase().indexOf("SELECT")&&(a.sql="SELECT * FROM table WHERE "+a.sql);var e=SQLParser.parse(a.sql);if(e.where||l.error("SQLParse","No WHERE clause found"),a=c.change("parseSQLNode",e.where.conditions),"rules"in a&&"condition"in a)return a;var g=c.change("sqlToGroup",{condition:this.settings.default_condition,rules:[]},a),h=g;return function i(a,d){if(a=c.change("parseSQLNode",a),"rules"in a&&"condition"in a)return void h.rules.push(a);if("id"in a&&"operator"in a&&"value"in a)return void h.rules.push(a);if("left"in a&&"right"in a&&"operation"in a||l.error("SQLParse","Unable to parse WHERE clause"),["AND","OR"].indexOf(a.operation.toUpperCase())!==-1){if(d>0&&h.condition!=a.operation.toUpperCase()){var e=c.change("sqlToGroup",{condition:c.settings.default_condition,rules:[]},a);h.rules.push(e),h=e}h.condition=a.operation.toUpperCase(),d++;var f=h;i(a.left,d),h=f,i(a.right,d)}else{$.isPlainObject(a.right.value)&&l.error("SQLParse","Value format not supported for {0}.",a.left.value);var g;g=$.isArray(a.right.value)?a.right.value.map(function(a){return a.value}):a.right.value,b&&(g=$.isArray(g)?g.map(b.parse):b.parse(g));var j=a.operation.toUpperCase();"<>"==j&&(j="!=");var k=c.settings.sqlRuleOperator[j];void 0===k&&l.error("UndefinedSQLOperator",'Invalid SQL operation "{0}".',a.operation);var m=k.call(this,g,a.operation),n=a.left.values.join("."),o=c.change("sqlToRule",{id:c.change("getSQLFieldID",n,g),field:n,operator:m.op,value:m.val},a);h.rules.push(o)}}(a,0),g},setRulesFromSQL:function(a,b){this.setRules(this.getRulesFromSQL(a,b))}}),g.define("unique-filter",function(){this.status.used_filters={},this.on("afterUpdateRuleFilter",this.updateDisabledFilters),this.on("afterDeleteRule",this.updateDisabledFilters),this.on("afterCreateRuleFilters",this.applyDisabledFilters),this.on("afterReset",this.clearDisabledFilters),this.on("afterClear",this.clearDisabledFilters),this.on("getDefaultFilter.filter",function(a,b){var c=a.builder;if(c.updateDisabledFilters(),a.value.id in c.status.used_filters){var d=c.filters.some(function(d){if(!(d.id in c.status.used_filters)||c.status.used_filters[d.id].length>0&&c.status.used_filters[d.id].indexOf(b.parent)===-1)return a.value=d,!0});d||(l.error("UniqueFilter","No more non-unique filters available"),a.value=void 0)}})}),g.extend({updateDisabledFilters:function(a){var b=a?a.builder:this;b.status.used_filters={},b.model&&(!function c(a){a.each(function(a){a.filter&&a.filter.unique&&(b.status.used_filters[a.filter.id]||(b.status.used_filters[a.filter.id]=[]),"group"==a.filter.unique&&b.status.used_filters[a.filter.id].push(a.parent))},function(a){c(a)})}(b.model.root),b.applyDisabledFilters(a))},clearDisabledFilters:function(a){var b=a?a.builder:this;b.status.used_filters={},b.applyDisabledFilters(a)},applyDisabledFilters:function(a){var b=a?a.builder:this;b.$el.find(h.filter_container+" option").prop("disabled",!1),$.each(b.status.used_filters,function(a,c){0===c.length?b.$el.find(h.filter_container+' option[value="'+a+'"]:not(:selected)').prop("disabled",!0):c.forEach(function(b){b.each(function(b){b.$el.find(h.filter_container+' option[value="'+a+'"]:not(:selected)').prop("disabled",!0)})})}),b.settings.plugins&&b.settings.plugins["bt-selectpicker"]&&b.$el.find(h.rule_filter).selectpicker("render")}}),g.regional.en={__locale:"English (en)",__author:'Damien "Mistic" Sorel, http://www.strangeplanet.fr',add_rule:"Add rule",add_group:"Add group",delete_rule:"Delete",delete_group:"Delete",conditions:{AND:"AND",OR:"OR"},operators:{equal:"equal",not_equal:"not equal","in":"in",not_in:"not in",less:"less",less_or_equal:"less or equal",greater:"greater",greater_or_equal:"greater or equal",between:"between",not_between:"not between",begins_with:"begins with",not_begins_with:"doesn't begin with",contains:"contains",not_contains:"doesn't contain",ends_with:"ends with",not_ends_with:"doesn't end with",is_empty:"is empty",is_not_empty:"is not empty",is_null:"is null",is_not_null:"is not null"},errors:{no_filter:"No filter selected",empty_group:"The group is empty",radio_empty:"No value selected",checkbox_empty:"No value selected",select_empty:"No value selected",string_empty:"Empty value",string_exceed_min_length:"Must contain at least {0} characters",string_exceed_max_length:"Must not contain more than {0} characters",string_invalid_format:"Invalid format ({0})",number_nan:"Not a number",number_not_integer:"Not an integer",number_not_double:"Not a real number",number_exceed_min:"Must be greater than {0}",number_exceed_max:"Must be lower than {0}",number_wrong_step:"Must be a multiple of {0}",datetime_empty:"Empty value",datetime_invalid:"Invalid date format ({0})",datetime_exceed_min:"Must be after {0}",datetime_exceed_max:"Must be before {0}",boolean_not_valid:"Not a boolean",operator_not_multiple:"Operator {0} cannot accept multiple values"},invert:"Invert",NOT:"NOT"},g.defaults({lang_code:"en"})}); \ No newline at end of file +!function(a,b){"function"==typeof define&&define.amd?define("jQuery.extendext",["jquery"],b):"object"==typeof module&&module.exports?module.exports=b(require("jquery")):b(a.jQuery)}(this,function($){"use strict";$.extendext=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1,k="default";for("boolean"==typeof g&&(j=g,g=arguments[h++]||{}),"string"==typeof g&&(k=g.toLowerCase(),"concat"!==k&&"replace"!==k&&"extend"!==k&&(k="default"),g=arguments[h++]||{}),"object"==typeof g||$.isFunction(g)||(g={}),h===i&&(g=this,h--);h":">",'"':""","'":"'","/":"/"},c=a?/[&<>"'\/]/g:/&(?!#?\w+;)|<|>|"|'|\//g;return function(a){return a?a.toString().replace(c,function(a){return b[a]||a}):""}},c=function(){return this||(0,eval)("this")}(),"undefined"!=typeof module&&module.exports?module.exports=d:"function"==typeof define&&define.amd?define("doT",function(){return d}):c.doT=d;var e={append:{start:"'+(",end:")+'",startencode:"'+encodeHTML("},split:{start:"';out+=(",end:");out+='",startencode:"';out+=encodeHTML("}},f=/$^/;d.template=function(g,h,i){h=h||d.templateSettings;var j,k,l=h.append?e.append:e.split,m=0,n=h.use||h.define?a(h,g,i||{}):g;n=("var out='"+(h.strip?n.replace(/(^|\r|\n)\t* +| +\t*(\r|\n|$)/g," ").replace(/\r|\n|\t|\/\*[\s\S]*?\*\//g,""):n).replace(/'|\\/g,"\\$&").replace(h.interpolate||f,function(a,c){return l.start+b(c)+l.end}).replace(h.encode||f,function(a,c){return j=!0,l.startencode+b(c)+l.end}).replace(h.conditional||f,function(a,c,d){return c?d?"';}else if("+b(d)+"){out+='":"';}else{out+='":d?"';if("+b(d)+"){out+='":"';}out+='"}).replace(h.iterate||f,function(a,c,d,e){return c?(m+=1,k=e||"i"+m,c=b(c),"';var arr"+m+"="+c+";if(arr"+m+"){var "+d+","+k+"=-1,l"+m+"=arr"+m+".length-1;while("+k+".rules-list",group_condition:".rules-group-header [name$=_cond]",rule_filter:".rule-filter-container [name$=_filter]",rule_operator:".rule-operator-container [name$=_operator]",rule_value:".rule-value-container [name*=_value_]",add_rule:"[data-add=rule]",delete_rule:"[data-delete=rule]",add_group:"[data-add=group]",delete_group:"[data-delete=group]"},g.templates={},g.regional={},g.OPERATORS={equal:{type:"equal",nb_inputs:1,multiple:!1,apply_to:["string","number","datetime","boolean"]},not_equal:{type:"not_equal",nb_inputs:1,multiple:!1,apply_to:["string","number","datetime","boolean"]},in:{type:"in",nb_inputs:1,multiple:!0,apply_to:["string","number","datetime"]},not_in:{type:"not_in",nb_inputs:1,multiple:!0,apply_to:["string","number","datetime"]},less:{type:"less",nb_inputs:1,multiple:!1,apply_to:["number","datetime"]},less_or_equal:{type:"less_or_equal",nb_inputs:1,multiple:!1,apply_to:["number","datetime"]},greater:{type:"greater",nb_inputs:1,multiple:!1,apply_to:["number","datetime"]},greater_or_equal:{type:"greater_or_equal",nb_inputs:1,multiple:!1,apply_to:["number","datetime"]},between:{type:"between",nb_inputs:2,multiple:!1,apply_to:["number","datetime"]},not_between:{type:"not_between",nb_inputs:2,multiple:!1,apply_to:["number","datetime"]},begins_with:{type:"begins_with",nb_inputs:1,multiple:!1,apply_to:["string"]},not_begins_with:{type:"not_begins_with",nb_inputs:1,multiple:!1,apply_to:["string"]},contains:{type:"contains",nb_inputs:1,multiple:!1,apply_to:["string"]},not_contains:{type:"not_contains",nb_inputs:1,multiple:!1,apply_to:["string"]},ends_with:{type:"ends_with",nb_inputs:1,multiple:!1,apply_to:["string"]},not_ends_with:{type:"not_ends_with",nb_inputs:1,multiple:!1,apply_to:["string"]},is_empty:{type:"is_empty",nb_inputs:0,multiple:!1,apply_to:["string"]},is_not_empty:{type:"is_not_empty",nb_inputs:0,multiple:!1,apply_to:["string"]},is_null:{type:"is_null",nb_inputs:0,multiple:!1,apply_to:["string","number","datetime","boolean"]},is_not_null:{type:"is_not_null",nb_inputs:0,multiple:!1,apply_to:["string","number","datetime","boolean"]}},g.DEFAULTS={filters:[],plugins:[],sort_filters:!1,display_errors:!0,allow_groups:-1,allow_empty:!1,conditions:["AND","OR"],default_condition:"AND",inputs_separator:" , ",select_placeholder:"------",display_empty_filter:!0,default_filter:null,optgroups:{},default_rule_flags:{filter_readonly:!1,operator_readonly:!1,value_readonly:!1,no_delete:!1},default_group_flags:{condition_readonly:!1,no_add_rule:!1,no_add_group:!1,no_delete:!1},templates:{group:null,rule:null,filterSelect:null,operatorSelect:null},lang_code:"en",lang:{},operators:["equal","not_equal","in","not_in","less","less_or_equal","greater","greater_or_equal","between","not_between","begins_with","not_begins_with","contains","not_contains","ends_with","not_ends_with","is_empty","is_not_empty","is_null","is_not_null"],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"}},g.plugins={},g.defaults=function(a){if("object"!=typeof a)return"string"==typeof a?"object"==typeof g.DEFAULTS[a]?$.extend(!0,{},g.DEFAULTS[a]):g.DEFAULTS[a]:$.extend(!0,{},g.DEFAULTS);$.extendext(!0,"replace",g.DEFAULTS,a)},g.define=function(a,b,c){g.plugins[a]={fct:b,def:c||{}}},g.extend=function(a){$.extend(g.prototype,a)},g.prototype.initPlugins=function(){if(this.plugins){if($.isArray(this.plugins)){var a={};this.plugins.forEach(function(b){a[b]=null}),this.plugins=a}Object.keys(this.plugins).forEach(function(a){a in g.plugins?(this.plugins[a]=$.extend(!0,{},g.plugins[a].def,this.plugins[a]||{}),g.plugins[a].fct.call(this,this.plugins[a])):k.error("Config",'Unable to find plugin "{0}"',a)},this)}},g.prototype.checkFilters=function(a){var b=[];if(a&&0!==a.length||k.error("Config","Missing filters list"),a.forEach(function(a,c){switch(a.id||k.error("Config","Missing filter {0} id",c),b.indexOf(a.id)!=-1&&k.error("Config",'Filter "{0}" already defined',a.id),b.push(a.id),a.type?g.types[a.type]||k.error("Config",'Invalid type "{0}"',a.type):a.type="string",a.input?"function"!=typeof a.input&&g.inputs.indexOf(a.input)==-1&&k.error("Config",'Invalid input "{0}"',a.input):a.input="number"===g.types[a.type]?"number":"text",a.operators&&a.operators.forEach(function(a){"string"!=typeof a&&k.error("Config","Filter operators must be global operators types (string)")}),a.field||(a.field=a.id),a.label||(a.label=a.field),a.optgroup?(this.status.has_optgroup=!0,this.settings.optgroups[a.optgroup]||(this.settings.optgroups[a.optgroup]=a.optgroup)):a.optgroup=null,a.input){case"radio":case"checkbox":(!a.values||a.values.length<1)&&k.error("Config",'Missing filter "{0}" values',a.id);break;case"select":a.placeholder&&(void 0===a.placeholder_value&&(a.placeholder_value=-1),k.iterateOptions(a.values,function(b){b==a.placeholder_value&&k.error("Config",'Placeholder of filter "{0}" overlaps with one of its values',a.id)}))}},this),this.settings.sort_filters)if("function"==typeof this.settings.sort_filters)a.sort(this.settings.sort_filters);else{var c=this;a.sort(function(a,b){return c.translate(a.label).localeCompare(c.translate(b.label))})}return this.status.has_optgroup&&(a=k.groupSort(a,"optgroup")),a},g.prototype.checkOperators=function(a){var b=[];return a.forEach(function(c,d){"string"==typeof c?(g.OPERATORS[c]||k.error("Config",'Unknown operator "{0}"',c),a[d]=c=$.extendext(!0,"replace",{},g.OPERATORS[c])):(c.type||k.error("Config",'Missing "type" for operator {0}',d),g.OPERATORS[c.type]&&(a[d]=c=$.extendext(!0,"replace",{},g.OPERATORS[c.type],c)),void 0!==c.nb_inputs&&void 0!==c.apply_to||k.error("Config",'Missing "nb_inputs" and/or "apply_to" for operator "{0}"',c.type)),b.indexOf(c.type)!=-1&&k.error("Config",'Operator "{0}" already defined',c.type),b.push(c.type),c.optgroup?(this.status.has_operator_optgroup=!0,this.settings.optgroups[c.optgroup]||(this.settings.optgroups[c.optgroup]=c.optgroup)):c.optgroup=null},this),this.status.has_operator_optgroup&&(a=k.groupSort(a,"optgroup")),a},g.prototype.bindEvents=function(){var a=this,b=g.selectors;this.$el.on("change.queryBuilder",b.group_condition,function(){if($(this).is(":checked")){var c=$(this).closest(b.group_container);a.getModel(c).condition=$(this).val()}}),this.$el.on("change.queryBuilder",b.rule_filter,function(){var c=$(this).closest(b.rule_container);a.getModel(c).filter=a.getFilterById($(this).val())}),this.$el.on("change.queryBuilder",b.rule_operator,function(){var c=$(this).closest(b.rule_container);a.getModel(c).operator=a.getOperatorByType($(this).val())}),this.$el.on("click.queryBuilder",b.add_rule,function(){var c=$(this).closest(b.group_container);a.addRule(a.getModel(c))}),this.$el.on("click.queryBuilder",b.delete_rule,function(){var c=$(this).closest(b.rule_container);a.deleteRule(a.getModel(c))}),0!==this.settings.allow_groups&&(this.$el.on("click.queryBuilder",b.add_group,function(){var c=$(this).closest(b.group_container);a.addGroup(a.getModel(c))}),this.$el.on("click.queryBuilder",b.delete_group,function(){var c=$(this).closest(b.group_container);a.deleteGroup(a.getModel(c))})),this.model.on({drop:function(b,c){c.$el.remove(),a.refreshGroupsConditions()},add:function(b,c,d,e){0===e?d.$el.prependTo(c.$el.find(">"+g.selectors.rules_list)):d.$el.insertAfter(c.rules[e-1].$el),a.refreshGroupsConditions()},move:function(b,c,d,e){c.$el.detach(),0===e?c.$el.prependTo(d.$el.find(">"+g.selectors.rules_list)):c.$el.insertAfter(d.rules[e-1].$el),a.refreshGroupsConditions()},update:function(b,c,d,e,f){if(c instanceof j)switch(d){case"error":a.updateError(c);break;case"flags":a.applyRuleFlags(c);break;case"filter":a.updateRuleFilter(c,f);break;case"operator":a.updateRuleOperator(c,f);break;case"value":a.updateRuleValue(c)}else switch(d){case"error":a.updateError(c);break;case"flags":a.applyGroupFlags(c);break;case"condition":a.updateGroupCondition(c)}}})},g.prototype.setRoot=function(a,b,c){a=void 0===a||a===!0;var d=this.nextGroupId(),e=$(this.getGroupTemplate(d,1));return this.$el.append(e),this.model.root=new i(null,e),this.model.root.model=this.model,this.model.root.data=b,this.model.root.__.flags=$.extend({},this.settings.default_group_flags,c),this.trigger("afterAddGroup",this.model.root),this.model.root.condition=this.settings.default_condition,a&&this.addRule(this.model.root),this.model.root},g.prototype.addGroup=function(a,b,c,d){b=void 0===b||b===!0;var e=a.level+1;if(this.trigger("beforeAddGroup",a,b,e).isDefaultPrevented())return null;var f=this.nextGroupId(),g=$(this.getGroupTemplate(f,e)),h=a.addGroup(g);return h.data=c,h.__.flags=$.extend({},this.settings.default_group_flags,d),this.trigger("afterAddGroup",h),h.condition=this.settings.default_condition,b&&this.addRule(h),h},g.prototype.deleteGroup=function(a){if(a.isRoot())return!1;if(this.trigger("beforeDeleteGroup",a).isDefaultPrevented())return!1;var b=!0;return a.each("reverse",function(a){b&=this.deleteRule(a)},function(a){b&=this.deleteGroup(a)},this),b&&(a.drop(),this.trigger("afterDeleteGroup")),b},g.prototype.updateGroupCondition=function(a){a.$el.find(">"+g.selectors.group_condition).each(function(){var b=$(this);b.prop("checked",b.val()===a.condition),b.parent().toggleClass("active",b.val()===a.condition)}),this.trigger("afterUpdateGroupCondition",a)},g.prototype.refreshGroupsConditions=function(){!function a(b){(!b.flags||b.flags&&!b.flags.condition_readonly)&&b.$el.find(">"+g.selectors.group_condition).prop("disabled",b.rules.length<=1).parent().toggleClass("disabled",b.rules.length<=1),b.each(null,function(b){a(b)},this)}(this.model.root)},g.prototype.addRule=function(a,b,c){if(this.trigger("beforeAddRule",a).isDefaultPrevented())return null;var d=this.nextRuleId(),e=$(this.getRuleTemplate(d)),f=a.addRule(e);return void 0!==b&&(f.data=b),f.__.flags=$.extend({},this.settings.default_rule_flags,c),this.trigger("afterAddRule",f),this.createRuleFilters(f),!this.settings.default_filter&&this.settings.display_empty_filter||(f.filter=this.change("getDefaultFilter",this.getFilterById(this.settings.default_filter||this.filters[0].id),f)),f},g.prototype.deleteRule=function(a){return!a.flags.no_delete&&(!this.trigger("beforeDeleteRule",a).isDefaultPrevented()&&(a.drop(),this.trigger("afterDeleteRule"),!0))},g.prototype.createRuleFilters=function(a){var b=this.change("getRuleFilters",this.filters,a),c=$(this.getRuleFilterSelect(a,b));a.$el.find(g.selectors.filter_container).html(c),this.trigger("afterCreateRuleFilters",a)},g.prototype.createRuleOperators=function(a){var b=a.$el.find(g.selectors.operator_container).empty();if(a.filter){var c=this.getOperators(a.filter),d=$(this.getRuleOperatorSelect(a,c));b.html(d),a.__.operator=c[0],this.trigger("afterCreateRuleOperators",a,c)}},g.prototype.createRuleInput=function(a){var b=a.$el.find(g.selectors.value_container).empty();if(a.__.value=void 0,a.filter&&a.operator&&0!==a.operator.nb_inputs){for(var c=this,d=$(),e=a.filter,f=0;f0&&b.append(this.settings.inputs_separator),b.append(h),d=d.add(h)}b.show(),d.on("change "+(e.input_event||""),function(){this._updating_input||(a._updating_value=!0,a.value=c.getRuleInputValue(a),a._updating_value=!1)}),e.plugin&&d[e.plugin](e.plugin_config||{}),this.trigger("afterCreateRuleInput",a),void 0!==e.default_value?a.value=e.default_value:(a._updating_value=!0,a.value=c.getRuleInputValue(a),a._updating_value=!1)}},g.prototype.updateRuleFilter=function(a,b){this.createRuleOperators(a),this.createRuleInput(a),a.$el.find(g.selectors.rule_filter).val(a.filter?a.filter.id:"-1"),b&&a.filter&&b.id!==a.filter.id&&(a.data=void 0),this.trigger("afterUpdateRuleFilter",a)},g.prototype.updateRuleOperator=function(a,b){var c=a.$el.find(g.selectors.value_container);a.operator&&0!==a.operator.nb_inputs?(c.show(),!c.is(":empty")&&b&&a.operator.nb_inputs===b.nb_inputs&&a.operator.optgroup===b.optgroup||this.createRuleInput(a)):(c.hide(),a.__.value=void 0),a.operator&&a.$el.find(g.selectors.rule_operator).val(a.operator.type),this.trigger("afterUpdateRuleOperator",a),this.updateRuleValue(a)},g.prototype.updateRuleValue=function(a){a._updating_value||this.setRuleInputValue(a,a.value),this.trigger("afterUpdateRuleValue",a)},g.prototype.applyRuleFlags=function(a){var b=a.flags,c=g.selectors;b.filter_readonly&&a.$el.find(c.rule_filter).prop("disabled",!0),b.operator_readonly&&a.$el.find(c.rule_operator).prop("disabled",!0),b.value_readonly&&a.$el.find(c.rule_value).prop("disabled",!0),b.no_delete&&a.$el.find(c.delete_rule).remove(),this.trigger("afterApplyRuleFlags",a)},g.prototype.applyGroupFlags=function(a){var b=a.flags,c=g.selectors;b.condition_readonly&&a.$el.find(">"+c.group_condition).prop("disabled",!0).parent().addClass("readonly"),b.no_add_rule&&a.$el.find(c.add_rule).remove(),b.no_add_group&&a.$el.find(c.add_group).remove(),b.no_delete&&a.$el.find(c.delete_group).remove(),this.trigger("afterApplyGroupFlags",a)},g.prototype.clearErrors=function(a){(a=a||this.model.root)&&(a.error=null,a instanceof i&&a.each(function(a){a.error=null},function(a){this.clearErrors(a)},this))},g.prototype.updateError=function(a){if(this.settings.display_errors)if(null===a.error)a.$el.removeClass("has-error");else{var b=this.translate("errors",a.error[0]);b=k.fmt(b,a.error.slice(1)),b=this.change("displayError",b,a.error,a),a.$el.addClass("has-error").find(g.selectors.error_container).eq(0).attr("title",b)}},g.prototype.triggerValidationError=function(a,b,c){$.isArray(b)||(b=[b]),this.trigger("validationError",a,b,c).isDefaultPrevented()||(a.error=b)},g.prototype.destroy=function(){this.trigger("beforeDestroy"),this.status.generated_id&&this.$el.removeAttr("id"),this.clear(),this.model=null,this.$el.off(".queryBuilder").removeClass("query-builder").removeData("queryBuilder"),delete this.$el[0].queryBuilder},g.prototype.reset=function(){this.trigger("beforeReset").isDefaultPrevented()||(this.status.group_id=1,this.status.rule_id=0,this.model.root.empty(),this.addRule(this.model.root),this.trigger("afterReset"))},g.prototype.clear=function(){this.trigger("beforeClear").isDefaultPrevented()||(this.status.group_id=0,this.status.rule_id=0,this.model.root&&(this.model.root.drop(),this.model.root=null),this.trigger("afterClear"))},g.prototype.setOptions=function(a){$.each(a,function(a,b){g.modifiable_options.indexOf(a)!==-1&&(this.settings[a]=b)}.bind(this))},g.prototype.getModel=function(a){return a?a instanceof h?a:$(a).data("queryBuilderModel"):this.model.root},g.prototype.validate=function(a){a=$.extend({skip_empty:!1},a),this.clearErrors();var b=this,c=function c(d){var e=0,f=0;return d.each(function(c){if(c.filter||!a.skip_empty){if(!c.filter)return b.triggerValidationError(c,"no_filter",null),void f++;if(!c.operator)return b.triggerValidationError(c,"no_operator",null),void f++;if(0!==c.operator.nb_inputs){var d=b.validateValue(c,c.value);if(d!==!0)return b.triggerValidationError(c,d,c.value),void f++}e++}},function(a){var b=c(a);b===!0?e++:b===!1&&f++}),!(f>0)&&(0===e&&!d.isRoot()&&a.skip_empty?null:!!(0!==e||b.settings.allow_empty&&d.isRoot())||(b.triggerValidationError(d,"empty_group",null),!1))}(this.model.root);return this.change("validate",c)},g.prototype.getRules=function(a){a=$.extend({get_flags:!1,allow_invalid:!1,skip_empty:!1},a);var b=this.validate(a);if(!b&&!a.allow_invalid)return null;var c=this,d=function b(d){var e={condition:d.condition,rules:[]};if(d.data&&(e.data=$.extendext(!0,"replace",{},d.data)),a.get_flags){var f=c.getGroupFlags(d.flags,"all"===a.get_flags);$.isEmptyObject(f)||(e.flags=f)}return d.each(function(b){if(b.filter||!a.skip_empty){var d=null;b.operator&&0===b.operator.nb_inputs||(d=b.value);var f={id:b.filter?b.filter.id:null,field:b.filter?b.filter.field:null,type:b.filter?b.filter.type:null,input:b.filter?b.filter.input:null,operator:b.operator?b.operator.type:null,value:d};if((b.filter&&b.filter.data||b.data)&&(f.data=$.extendext(!0,"replace",{},b.filter.data,b.data)),a.get_flags){var g=c.getRuleFlags(b.flags,"all"===a.get_flags);$.isEmptyObject(g)||(f.flags=g)}e.rules.push(c.change("ruleToJson",f,b))}},function(c){var d=b(c);0===d.rules.length&&a.skip_empty||e.rules.push(d)},this),c.change("groupToJson",e,d)}(this.model.root);return d.valid=b,this.change("getRules",d)},g.prototype.setRules=function(a,b){b=$.extend({allow_invalid:!1},b),$.isArray(a)&&(a={condition:this.settings.default_condition,rules:a}),a&&a.rules&&(0!==a.rules.length||this.settings.allow_empty)||k.error("RulesParse","Incorrect data object passed"),this.clear(),this.setRoot(!1,a.data,this.parseGroupFlags(a)),this.applyGroupFlags(this.model.root),a=this.change("setRules",a,b);var c=this;!function a(d,e){null!==e&&(void 0===d.condition?d.condition=c.settings.default_condition:c.settings.conditions.indexOf(d.condition)==-1&&(k.error(!b.allow_invalid,"UndefinedCondition",'Invalid condition "{0}"',d.condition),d.condition=c.settings.default_condition),e.condition=d.condition,d.rules.forEach(function(d){var f;if(void 0!==d.rules)if(c.settings.allow_groups!==-1&&c.settings.allow_groups1){i=["operator_not_multiple",f.type,this.translate("operators",f.type)];break}switch(e.input){case"radio":if(void 0===b[j]||0===b[j].length){h.allow_empty_value||(i=["radio_empty"]);break}break;case"checkbox":if(void 0===b[j]||0===b[j].length){h.allow_empty_value||(i=["checkbox_empty"]);break}break;case"select":if(void 0===b[j]||0===b[j].length||e.placeholder&&b[j]==e.placeholder_value){h.allow_empty_value||(i=["select_empty"]);break}break;default:d=$.isArray(b[j])?b[j]:[b[j]];for(var l=0;lparseInt(h.max)){i=[this.getValidationMessage(h,"max","string_exceed_max_length"),h.max];break}if(h.format&&("string"==typeof h.format&&(h.format=new RegExp(h.format)),!h.format.test(d[l]))){i=[this.getValidationMessage(h,"format","string_invalid_format"),h.format];break}break;case"number":if(void 0===d[l]||0===d[l].length){h.allow_empty_value||(i=["number_nan"]);break}if(isNaN(d[l])){i=["number_nan"];break}if("integer"==e.type){if(parseInt(d[l])!=d[l]){i=["number_not_integer"];break}}else if(parseFloat(d[l])!=d[l]){i=["number_not_double"];break}if(void 0!==h.min&&d[l]parseFloat(h.max)){i=[this.getValidationMessage(h,"max","number_exceed_max"),h.max];break}if(void 0!==h.step&&"any"!==h.step){var m=(d[l]/h.step).toPrecision(14);if(parseInt(m)!=m){i=[this.getValidationMessage(h,"step","number_wrong_step"),h.step];break}}break;case"datetime":if(void 0===d[l]||0===d[l].length){h.allow_empty_value||(i=["datetime_empty"]);break}if(h.format){"moment"in window||k.error("MissingLibrary","MomentJS is required for Date/Time validation. Get it here http://momentjs.com");var n=moment(d[l],h.format);if(!n.isValid()){i=[this.getValidationMessage(h,"format","datetime_invalid"),h.format];break}if(h.min&&nmoment(h.max,h.format)){i=[this.getValidationMessage(h,"max","datetime_exceed_max"),h.max];break}}break;case"boolean":if(void 0===d[l]||0===d[l].length){h.allow_empty_value||(i=["boolean_not_valid"]);break}if("true"!==(c=(""+d[l]).trim().toLowerCase())&&"false"!==c&&"1"!==c&&"0"!==c&&1!==d[l]&&0!==d[l]){i=["boolean_not_valid"];break}}if(i!==!0)break}}if(i!==!0)break}return i},g.prototype.nextGroupId=function(){return this.status.id+"_group_"+this.status.group_id++},g.prototype.nextRuleId=function(){return this.status.id+"_rule_"+this.status.rule_id++},g.prototype.getOperators=function(a){"string"==typeof a&&(a=this.getFilterById(a));for(var b=[],c=0,d=this.operators.length;c '+b+" "});break;case"select":g+='";break;case"textarea":g+=''; + h += '