{"version":3,"names":["root","factory","define","amd","module","exports","require","jQuery","this","$","extendext","options","name","src","copy","copyIsArray","clone","target","arguments","i","length","deep","arrayMode","toLowerCase","isFunction","isArray","concat","extend","forEach","e","type","indexOf","push","isPlainObject","Array","undefined","QueryBuilder","$el","queryBuilder","settings","DEFAULTS","model","Model","status","id","generated_id","group_id","rule_id","has_optgroup","has_operator_optgroup","filters","icons","operators","templates","plugins","lang","regional","Utils","error","lang_code","allow_groups","Object","keys","tpl","Error","attr","Math","floor","random","addClass","checkFilters","checkOperators","bindEvents","initPlugins","prototype","trigger","event","Event","_tojQueryEvent","builder","triggerHandler","slice","call","change","value","on","cb","off","once","one","filter","split","map","join","types","string","integer","double","date","time","datetime","boolean","inputs","modifiable_options","selectors","group_container","rule_container","filter_container","operator_container","value_container","error_container","condition_container","rule_header","group_header","group_actions","rule_actions","rules_list","group_condition","rule_filter","rule_operator","rule_value","add_rule","delete_rule","add_group","delete_group","OPERATORS","equal","nb_inputs","multiple","apply_to","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","sort_filters","display_errors","allow_empty","conditions","default_condition","inputs_separator","select_placeholder","display_empty_filter","default_filter","optgroups","default_rule_flags","filter_readonly","operator_readonly","value_readonly","no_delete","default_group_flags","condition_readonly","no_add_rule","no_add_group","group","rule","filterSelect","operatorSelect","ruleValueSelect","ruleValueCheck","remove_group","remove_rule","defaults","fct","def","methods","tmp","plugin","getPluginOptions","property","init","rules","setRules","setRoot","definedFilters","input","operator","field","label","optgroup","values","cleanValues","iterateOptions","bind","groupSort","placeholder","placeholder_value","entry","sort","self","a","b","translate","localeCompare","definedOperators","Selectors","is","$group","closest","getModel","condition","val","$rule","getFilterById","getOperatorByType","addRule","deleteRule","addGroup","deleteGroup","drop","node","remove","refreshGroupsConditions","add","parent","index","prependTo","find","insertAfter","move","detach","update","oldValue","Rule","updateError","applyRuleFlags","updateRuleFilter","updateRuleOperator","updateRuleValue","applyGroupFlags","updateGroupCondition","data","flags","nextGroupId","parseHTML","getGroupTemplate","append","Group","tooltip","placement","level","isDefaultPrevented","isRoot","del","each","previousCondition","$this","prop","toggleClass","walk","nextRuleId","getRuleTemplate","createRuleFilters","$filterSelect","getRuleFilterSelect","html","createRuleOperators","$operatorContainer","empty","getOperators","$operatorSelect","getRuleOperatorSelect","default_operator","__","createRuleInput","$valueContainer","$inputs","$ruleInput","getRuleInput","trim","document","css","input_event","_updating_input","_updating_value","getRuleInputValue","plugin_config","default_value","previousFilter","previousOperator","hide","previousValue","setRuleInputValue","clearErrors","removeClass","errorMessage","fmt","eq","triggerValidationError","destroy","removeAttr","clear","removeData","reset","setOptions","opt","Node","validate","skip_empty","valid","parse","done","errors","validateValue","res","getRules","get_flags","allow_invalid","out","groupData","getGroupFlags","isEmptyObject","ruleData","getRuleFlags","parseGroupFlags","item","parseRuleFlags","validation","result","callback","_validateValue","tempValue","allow_empty_value","j","min","parseInt","getValidationMessage","max","format","RegExp","test","isNaN","parseFloat","step","v","toPrecision","window","DateTime","fromFormat","isValid","l","doThrow","valueGetter","$value","escapeElementId","value_separator","subval","changeType","valueSetter","readonly","all","ret","key","category","translation","messages","icon","vertical","h","getRuleValueSelect","getRuleValueCheck","value_id","size","rows","utils","str","args","replace","m","message","err","console","ew","parseNumber","parseDateTime","toSQLDate","convertToBool","escapeString","additionalEscape","escaped","s","escapeRegExp","$0","$1","$2","items","newItems","idx","lastIndexOf","splice","defineModelProperties","obj","fields","defineProperty","enumerable","get","set","apply","getPos","getNodePos","removeNode","moveAfter","moveAtBegin","moveAtEnd","insertNode","create","constructor","reverse","cbRule","cbGroup","context","c","stop","recursive","getStmtConfig","stmt","config","match","fn","option","setFilters","deleteOrphans","filtersIds","checkOrphans","updateBuilder","updateDisabledFilters","selectpicker","addFilter","newFilters","position","some","removeFilter","filterIds","mode","$p","description","getFilterDescription","appendTo","$b","after","dir","container","title","invert","display_rules_button","invert_rules","disable_template","$h","silent_fail","operatorOpposites","conditionOpposites","AND","OR","tempOpts","no_invert","not","updateGroupNot","before","json","toUpperCase","operation","SQLParser","nodes","Op","$nor","applyToFilter","select2","filterSelect2Options","applyToOperator","operatorSelect2Options","applyToValue","valueSelect2Options","boolean_as_integer","sqlOperators","op","sep","mod","escape","sqlRuleOperator","LIKE","IN","BETWEEN","IS","sqlStatements","question_mark","params","run","numbered","char","named","indexes","sqlRuleStatement","esc","sql","regex1","regex2","getSQL","nl","parts","ope","sqlFn","ruleExpression","groupExpression","getRulesFromSQL","query","parsed","where","curr","flatten","next","left","right","sqlrl","opVal","JSON","stringify","finalValue","searchChars","getSQLFieldID","setRulesFromSQL","matchingFilters","used_filters","applyDisabledFilters","clearDisabledFilters","unique","filterId","groups","__locale","__author","no_filter","empty_group","radio_empty","checkbox_empty","select_empty","string_empty","string_exceed_min_length","string_exceed_max_length","string_invalid_format","number_nan","number_not_integer","number_not_double","number_exceed_min","number_exceed_max","number_wrong_step","number_between_invalid","datetime_empty","datetime_invalid","datetime_exceed_min","datetime_exceed_max","datetime_between_invalid","boolean_not_valid","operator_not_multiple","NOT"],"sources":["../template/php2024/jquery/query-builder.js"],"sourcesContent":["/*!\n * jQuery.extendext 1.0.0\n *\n * Copyright 2014-2019 Damien \"Mistic\" Sorel (http://www.strangeplanet.fr)\n * Licensed under MIT (http://opensource.org/licenses/MIT)\n *\n * Based on jQuery.extend by jQuery Foundation, Inc. and other contributors\n */\n\n(function (root, factory) {\n if (typeof define === 'function' && define.amd) {\n define('jquery-extendext', ['jquery'], factory);\n }\n else if (typeof module === 'object' && module.exports) {\n module.exports = factory(require('jquery'));\n }\n else {\n factory(root.jQuery);\n }\n}(this, function ($) {\n \"use strict\";\n\n $.extendext = function () {\n var options, name, src, copy, copyIsArray, clone,\n target = arguments[0] || {},\n i = 1,\n length = arguments.length,\n deep = false,\n arrayMode = 'default';\n\n // Handle a deep copy situation\n if (typeof target === \"boolean\") {\n deep = target;\n\n // Skip the boolean and the target\n target = arguments[i++] || {};\n }\n\n // Handle array mode parameter\n if (typeof target === \"string\") {\n arrayMode = target.toLowerCase();\n if (arrayMode !== 'concat' && arrayMode !== 'replace' && arrayMode !== 'extend') {\n arrayMode = 'default';\n }\n\n // Skip the string param\n target = arguments[i++] || {};\n }\n\n // Handle case when target is a string or something (possible in deep copy)\n if (typeof target !== \"object\" && !$.isFunction(target)) {\n target = {};\n }\n\n // Extend jQuery itself if only one argument is passed\n if (i === length) {\n target = this;\n i--;\n }\n\n for (; i < length; i++) {\n // Only deal with non-null/undefined values\n if ((options = arguments[i]) !== null) {\n // Special operations for arrays\n if ($.isArray(options) && arrayMode !== 'default') {\n clone = target && $.isArray(target) ? target : [];\n\n switch (arrayMode) {\n case 'concat':\n target = clone.concat($.extend(deep, [], options));\n break;\n\n case 'replace':\n target = $.extend(deep, [], options);\n break;\n\n case 'extend':\n options.forEach(function (e, i) {\n if (typeof e === 'object') {\n var type = $.isArray(e) ? [] : {};\n clone[i] = $.extendext(deep, arrayMode, clone[i] || type, e);\n\n } else if (clone.indexOf(e) === -1) {\n clone.push(e);\n }\n });\n\n target = clone;\n break;\n }\n\n } else {\n // Extend the base object\n for (name in options) {\n copy = options[name];\n\n // Prevent never-ending loop\n if (name === '__proto__' || target === copy) {\n continue;\n }\n\n // Recurse if we're merging plain objects or arrays\n if (deep && copy && ( $.isPlainObject(copy) ||\n (copyIsArray = $.isArray(copy)) )) {\n src = target[name];\n\n // Ensure proper type for the source value\n if ( copyIsArray && !Array.isArray( src ) ) {\n clone = [];\n } else if ( !copyIsArray && !$.isPlainObject( src ) ) {\n clone = {};\n } else {\n clone = src;\n }\n copyIsArray = false;\n\n // Never move original objects, clone them\n target[name] = $.extendext(deep, arrayMode, clone, copy);\n\n // Don't bring in undefined values\n } else if (copy !== undefined) {\n target[name] = copy;\n }\n }\n }\n }\n }\n\n // Return the modified object\n return target;\n };\n}));\n\n\n/*!\n * jQuery QueryBuilder 2.7.0\n * Copyright 2014-2023 Damien \"Mistic\" Sorel (http://www.strangeplanet.fr)\n * Licensed under MIT (https://opensource.org/licenses/MIT)\n */\n\n(function(root, factory) {\n if (typeof define == 'function' && define.amd) {\n define('query-builder', ['jquery', 'jquery-extendext'], factory);\n }\n else if (typeof module === 'object' && module.exports) {\n module.exports = factory(require('jquery'), require('jquery-extendext'));\n }\n else {\n factory(root.jQuery);\n }\n}(this, function($) {\n\"use strict\";\n\n/**\n * @typedef {object} Filter\n * @memberof QueryBuilder\n * @description See {@link http://querybuilder.js.org/index.html#filters}\n */\n\n/**\n * @typedef {object} Operator\n * @memberof QueryBuilder\n * @description See {@link http://querybuilder.js.org/index.html#operators}\n */\n\n/**\n * @param {jQuery} $el\n * @param {object} options - see {@link http://querybuilder.js.org/#options}\n * @constructor\n */\nvar QueryBuilder = function($el, options) {\n $el[0].queryBuilder = this;\n\n /**\n * Element container\n * @member {jQuery}\n * @readonly\n */\n this.$el = $el;\n\n /**\n * Configuration object\n * @member {object}\n * @readonly\n */\n this.settings = $.extendext(true, 'replace', {}, QueryBuilder.DEFAULTS, options);\n\n /**\n * Internal model\n * @member {Model}\n * @readonly\n */\n this.model = new Model();\n\n /**\n * Internal status\n * @member {object}\n * @property {string} id - id of the container\n * @property {boolean} generated_id - if the container id has been generated\n * @property {int} group_id - current group id\n * @property {int} rule_id - current rule id\n * @property {boolean} has_optgroup - if filters have optgroups\n * @property {boolean} has_operator_optgroup - if operators have optgroups\n * @readonly\n * @private\n */\n this.status = {\n id: null,\n generated_id: false,\n group_id: 0,\n rule_id: 0,\n has_optgroup: false,\n has_operator_optgroup: false\n };\n\n /**\n * List of filters\n * @member {QueryBuilder.Filter[]}\n * @readonly\n */\n this.filters = this.settings.filters;\n\n /**\n * List of icons\n * @member {object.}\n * @readonly\n */\n this.icons = this.settings.icons;\n\n /**\n * List of operators\n * @member {QueryBuilder.Operator[]}\n * @readonly\n */\n this.operators = this.settings.operators;\n\n /**\n * List of templates\n * @member {object.}\n * @readonly\n */\n this.templates = this.settings.templates;\n\n /**\n * Plugins configuration\n * @member {object.}\n * @readonly\n */\n this.plugins = this.settings.plugins;\n\n /**\n * Translations object\n * @member {object}\n * @readonly\n */\n this.lang = null;\n\n // translations : english << 'lang_code' << custom\n if (QueryBuilder.regional['en'] === undefined) {\n Utils.error('Config', '\"i18n/en.js\" not loaded.');\n }\n this.lang = $.extendext(true, 'replace', {}, QueryBuilder.regional['en'], QueryBuilder.regional[this.settings.lang_code], this.settings.lang);\n\n // \"allow_groups\" can be boolean or int\n if (this.settings.allow_groups === false) {\n this.settings.allow_groups = 0;\n }\n else if (this.settings.allow_groups === true) {\n this.settings.allow_groups = -1;\n }\n\n // init templates\n Object.keys(this.templates).forEach(function(tpl) {\n if (!this.templates[tpl]) {\n this.templates[tpl] = QueryBuilder.templates[tpl];\n }\n if (typeof this.templates[tpl] !== 'function') {\n throw new Error(`Template ${tpl} must be a function`);\n }\n }, this);\n\n // ensure we have a container id\n if (!this.$el.attr('id')) {\n this.$el.attr('id', 'qb_' + Math.floor(Math.random() * 99999));\n this.status.generated_id = true;\n }\n this.status.id = this.$el.attr('id');\n\n // INIT\n this.$el.addClass('query-builder form-inline');\n\n this.filters = this.checkFilters(this.filters);\n this.operators = this.checkOperators(this.operators);\n this.bindEvents();\n this.initPlugins();\n};\n\n$.extend(QueryBuilder.prototype, /** @lends QueryBuilder.prototype */ {\n /**\n * Triggers an event on the builder container\n * @param {string} type\n * @returns {$.Event}\n */\n trigger: function(type) {\n var event = new $.Event(this._tojQueryEvent(type), {\n builder: this\n });\n\n this.$el.triggerHandler(event, Array.prototype.slice.call(arguments, 1));\n\n return event;\n },\n\n /**\n * Triggers an event on the builder container and returns the modified value\n * @param {string} type\n * @param {*} value\n * @returns {*}\n */\n change: function(type, value) {\n var event = new $.Event(this._tojQueryEvent(type, true), {\n builder: this,\n value: value\n });\n\n this.$el.triggerHandler(event, Array.prototype.slice.call(arguments, 2));\n\n return event.value;\n },\n\n /**\n * Attaches an event listener on the builder container\n * @param {string} type\n * @param {function} cb\n * @returns {QueryBuilder}\n */\n on: function(type, cb) {\n this.$el.on(this._tojQueryEvent(type), cb);\n return this;\n },\n\n /**\n * Removes an event listener from the builder container\n * @param {string} type\n * @param {function} [cb]\n * @returns {QueryBuilder}\n */\n off: function(type, cb) {\n this.$el.off(this._tojQueryEvent(type), cb);\n return this;\n },\n\n /**\n * Attaches an event listener called once on the builder container\n * @param {string} type\n * @param {function} cb\n * @returns {QueryBuilder}\n */\n once: function(type, cb) {\n this.$el.one(this._tojQueryEvent(type), cb);\n return this;\n },\n\n /**\n * Appends `.queryBuilder` and optionally `.filter` to the events names\n * @param {string} name\n * @param {boolean} [filter=false]\n * @returns {string}\n * @private\n */\n _tojQueryEvent: function(name, filter) {\n return name.split(' ').map(function(type) {\n return type + '.queryBuilder' + (filter ? '.filter' : '');\n }).join(' ');\n }\n});\n\n\n/**\n * Allowed types and their internal representation\n * @type {object.}\n * @readonly\n * @private\n */\nQueryBuilder.types = {\n 'string': 'string',\n 'integer': 'number',\n 'double': 'number',\n 'date': 'datetime',\n 'time': 'datetime',\n 'datetime': 'datetime',\n 'boolean': 'boolean'\n};\n\n/**\n * Allowed inputs\n * @type {string[]}\n * @readonly\n * @private\n */\nQueryBuilder.inputs = [\n 'text',\n 'number',\n 'textarea',\n 'radio',\n 'checkbox',\n 'select'\n];\n\n/**\n * Runtime modifiable options with `setOptions` method\n * @type {string[]}\n * @readonly\n * @private\n */\nQueryBuilder.modifiable_options = [\n 'display_errors',\n 'allow_groups',\n 'allow_empty',\n 'default_condition',\n 'default_filter'\n];\n\n/**\n * CSS selectors for common components\n * @type {object.}\n * @readonly\n */\nQueryBuilder.selectors = {\n group_container: '.rules-group-container',\n rule_container: '.rule-container',\n filter_container: '.rule-filter-container',\n operator_container: '.rule-operator-container',\n value_container: '.rule-value-container',\n error_container: '.error-container',\n condition_container: '.rules-group-header .group-conditions',\n\n rule_header: '.rule-header',\n group_header: '.rules-group-header',\n group_actions: '.group-actions',\n rule_actions: '.rule-actions',\n\n rules_list: '.rules-group-body>.rules-list',\n\n group_condition: '.rules-group-header [name$=_cond]',\n rule_filter: '.rule-filter-container [name$=_filter]',\n rule_operator: '.rule-operator-container [name$=_operator]',\n rule_value: '.rule-value-container [name*=_value_]',\n\n add_rule: '[data-add=rule]',\n delete_rule: '[data-delete=rule]',\n add_group: '[data-add=group]',\n delete_group: '[data-delete=group]'\n};\n\n/**\n * Template strings (see template.js)\n * @type {object.}\n * @readonly\n */\nQueryBuilder.templates = {};\n\n/**\n * Localized strings (see i18n/)\n * @type {object.}\n * @readonly\n */\nQueryBuilder.regional = {};\n\n/**\n * Default operators\n * @type {object.}\n * @readonly\n */\nQueryBuilder.OPERATORS = {\n equal: { type: 'equal', nb_inputs: 1, multiple: false, apply_to: ['string', 'number', 'datetime', 'boolean'] },\n not_equal: { type: 'not_equal', nb_inputs: 1, multiple: false, apply_to: ['string', 'number', 'datetime', 'boolean'] },\n in: { type: 'in', nb_inputs: 1, multiple: true, apply_to: ['string', 'number', 'datetime'] },\n not_in: { type: 'not_in', nb_inputs: 1, multiple: true, apply_to: ['string', 'number', 'datetime'] },\n less: { type: 'less', nb_inputs: 1, multiple: false, apply_to: ['number', 'datetime'] },\n less_or_equal: { type: 'less_or_equal', nb_inputs: 1, multiple: false, apply_to: ['number', 'datetime'] },\n greater: { type: 'greater', nb_inputs: 1, multiple: false, apply_to: ['number', 'datetime'] },\n greater_or_equal: { type: 'greater_or_equal', nb_inputs: 1, multiple: false, apply_to: ['number', 'datetime'] },\n between: { type: 'between', nb_inputs: 2, multiple: false, apply_to: ['number', 'datetime'] },\n not_between: { type: 'not_between', nb_inputs: 2, multiple: false, apply_to: ['number', 'datetime'] },\n begins_with: { type: 'begins_with', nb_inputs: 1, multiple: false, apply_to: ['string'] },\n not_begins_with: { type: 'not_begins_with', nb_inputs: 1, multiple: false, apply_to: ['string'] },\n contains: { type: 'contains', nb_inputs: 1, multiple: false, apply_to: ['string'] },\n not_contains: { type: 'not_contains', nb_inputs: 1, multiple: false, apply_to: ['string'] },\n ends_with: { type: 'ends_with', nb_inputs: 1, multiple: false, apply_to: ['string'] },\n not_ends_with: { type: 'not_ends_with', nb_inputs: 1, multiple: false, apply_to: ['string'] },\n is_empty: { type: 'is_empty', nb_inputs: 0, multiple: false, apply_to: ['string'] },\n is_not_empty: { type: 'is_not_empty', nb_inputs: 0, multiple: false, apply_to: ['string'] },\n is_null: { type: 'is_null', nb_inputs: 0, multiple: false, apply_to: ['string', 'number', 'datetime', 'boolean'] },\n is_not_null: { type: 'is_not_null', nb_inputs: 0, multiple: false, apply_to: ['string', 'number', 'datetime', 'boolean'] }\n};\n\n/**\n * Default configuration\n * @type {object}\n * @readonly\n */\nQueryBuilder.DEFAULTS = {\n filters: [],\n plugins: [],\n\n sort_filters: false,\n display_errors: true,\n allow_groups: -1,\n allow_empty: false,\n conditions: ['AND', 'OR'],\n default_condition: 'AND',\n inputs_separator: ' , ',\n select_placeholder: '------',\n display_empty_filter: true,\n default_filter: null,\n optgroups: {},\n\n default_rule_flags: {\n filter_readonly: false,\n operator_readonly: false,\n value_readonly: false,\n no_delete: false\n },\n\n default_group_flags: {\n condition_readonly: false,\n no_add_rule: false,\n no_add_group: false,\n no_delete: false\n },\n\n templates: {\n group: null,\n rule: null,\n filterSelect: null,\n operatorSelect: null,\n ruleValueSelect: null,\n ruleValueCheck: null\n },\n\n lang_code: 'en',\n lang: {},\n\n operators: [\n 'equal',\n 'not_equal',\n 'in',\n 'not_in',\n 'less',\n 'less_or_equal',\n 'greater',\n 'greater_or_equal',\n 'between',\n 'not_between',\n 'begins_with',\n 'not_begins_with',\n 'contains',\n 'not_contains',\n 'ends_with',\n 'not_ends_with',\n 'is_empty',\n 'is_not_empty',\n 'is_null',\n 'is_not_null'\n ],\n\n icons: {\n add_group: 'fa-solid fa-circle-plus',\n add_rule: 'fa-solid fa-plus',\n remove_group: 'fa-solid fa-xmark',\n remove_rule: 'fa-solid fa-xmark',\n error: 'fa-solid fa-circle-exclamation'\n }\n};\n\n\n/**\n * @module plugins\n */\n\n/**\n * Definition of available plugins\n * @type {object.}\n */\nQueryBuilder.plugins = {};\n\n/**\n * Gets or extends the default configuration\n * @param {object} [options] - new configuration\n * @returns {undefined|object} nothing or configuration object (copy)\n */\nQueryBuilder.defaults = function(options) {\n if (typeof options == 'object') {\n $.extendext(true, 'replace', QueryBuilder.DEFAULTS, options);\n }\n else if (typeof options == 'string') {\n if (typeof QueryBuilder.DEFAULTS[options] == 'object') {\n return $.extend(true, {}, QueryBuilder.DEFAULTS[options]);\n }\n else {\n return QueryBuilder.DEFAULTS[options];\n }\n }\n else {\n return $.extend(true, {}, QueryBuilder.DEFAULTS);\n }\n};\n\n/**\n * Registers a new plugin\n * @param {string} name\n * @param {function} fct - init function\n * @param {object} [def] - default options\n */\nQueryBuilder.define = function(name, fct, def) {\n QueryBuilder.plugins[name] = {\n fct: fct,\n def: def || {}\n };\n};\n\n/**\n * Adds new methods to QueryBuilder prototype\n * @param {object.} methods\n */\nQueryBuilder.extend = function(methods) {\n $.extend(QueryBuilder.prototype, methods);\n};\n\n/**\n * Initializes plugins for an instance\n * @throws ConfigError\n * @private\n */\nQueryBuilder.prototype.initPlugins = function() {\n if (!this.plugins) {\n return;\n }\n\n if (Array.isArray(this.plugins)) {\n var tmp = {};\n this.plugins.forEach(function(plugin) {\n tmp[plugin] = null;\n });\n this.plugins = tmp;\n }\n\n Object.keys(this.plugins).forEach(function(plugin) {\n if (plugin in QueryBuilder.plugins) {\n this.plugins[plugin] = $.extend(true, {},\n QueryBuilder.plugins[plugin].def,\n this.plugins[plugin] || {}\n );\n\n QueryBuilder.plugins[plugin].fct.call(this, this.plugins[plugin]);\n }\n else {\n Utils.error('Config', 'Unable to find plugin \"{0}\"', plugin);\n }\n }, this);\n};\n\n/**\n * Returns the config of a plugin, if the plugin is not loaded, returns the default config.\n * @param {string} name\n * @param {string} [property]\n * @throws ConfigError\n * @returns {*}\n */\nQueryBuilder.prototype.getPluginOptions = function(name, property) {\n var plugin;\n if (this.plugins && this.plugins[name]) {\n plugin = this.plugins[name];\n }\n else if (QueryBuilder.plugins[name]) {\n plugin = QueryBuilder.plugins[name].def;\n }\n\n if (plugin) {\n if (property) {\n return plugin[property];\n }\n else {\n return plugin;\n }\n }\n else {\n Utils.error('Config', 'Unable to find plugin \"{0}\"', name);\n }\n};\n\n\n/**\n * Final initialisation of the builder\n * @param {object} [rules]\n * @fires QueryBuilder.afterInit\n * @private\n */\nQueryBuilder.prototype.init = function(rules) {\n /**\n * When the initilization is done, just before creating the root group\n * @event afterInit\n * @memberof QueryBuilder\n */\n this.trigger('afterInit');\n\n if (rules) {\n this.setRules(rules);\n delete this.settings.rules;\n }\n else {\n this.setRoot(true);\n }\n};\n\n/**\n * Checks the configuration of each filter\n * @param {QueryBuilder.Filter[]} filters\n * @returns {QueryBuilder.Filter[]}\n * @throws ConfigError\n */\nQueryBuilder.prototype.checkFilters = function(filters) {\n var definedFilters = [];\n\n if (!filters || filters.length === 0) {\n Utils.error('Config', 'Missing filters list');\n }\n\n filters.forEach(function(filter, i) {\n if (!filter.id) {\n Utils.error('Config', 'Missing filter {0} id', i);\n }\n if (definedFilters.indexOf(filter.id) != -1) {\n Utils.error('Config', 'Filter \"{0}\" already defined', filter.id);\n }\n definedFilters.push(filter.id);\n\n if (!filter.type) {\n filter.type = 'string';\n }\n else if (!QueryBuilder.types[filter.type]) {\n Utils.error('Config', 'Invalid type \"{0}\"', filter.type);\n }\n\n if (!filter.input) {\n filter.input = QueryBuilder.types[filter.type] === 'number' ? 'number' : 'text';\n }\n else if (typeof filter.input != 'function' && QueryBuilder.inputs.indexOf(filter.input) == -1) {\n Utils.error('Config', 'Invalid input \"{0}\"', filter.input);\n }\n\n if (filter.operators) {\n filter.operators.forEach(function(operator) {\n if (typeof operator != 'string') {\n Utils.error('Config', 'Filter operators must be global operators types (string)');\n }\n });\n }\n\n if (!filter.field) {\n filter.field = filter.id;\n }\n if (!filter.label) {\n filter.label = filter.field;\n }\n\n if (!filter.optgroup) {\n filter.optgroup = null;\n }\n else {\n this.status.has_optgroup = true;\n\n // register optgroup if needed\n if (!this.settings.optgroups[filter.optgroup]) {\n this.settings.optgroups[filter.optgroup] = filter.optgroup;\n }\n }\n\n switch (filter.input) {\n case 'radio':\n case 'checkbox':\n if (!filter.values || filter.values.length < 1) {\n Utils.error('Config', 'Missing filter \"{0}\" values', filter.id);\n }\n break;\n\n case 'select':\n var cleanValues = [];\n filter.has_optgroup = false;\n\n Utils.iterateOptions(filter.values, function(value, label, optgroup) {\n cleanValues.push({\n value: value,\n label: label,\n optgroup: optgroup || null\n });\n\n if (optgroup) {\n filter.has_optgroup = true;\n\n // register optgroup if needed\n if (!this.settings.optgroups[optgroup]) {\n this.settings.optgroups[optgroup] = optgroup;\n }\n }\n }.bind(this));\n\n if (filter.has_optgroup) {\n filter.values = Utils.groupSort(cleanValues, 'optgroup');\n }\n else {\n filter.values = cleanValues;\n }\n\n if (filter.placeholder) {\n if (filter.placeholder_value === undefined) {\n filter.placeholder_value = -1;\n }\n\n filter.values.forEach(function(entry) {\n if (entry.value == filter.placeholder_value) {\n Utils.error('Config', 'Placeholder of filter \"{0}\" overlaps with one of its values', filter.id);\n }\n });\n }\n break;\n }\n }, this);\n\n if (this.settings.sort_filters) {\n if (typeof this.settings.sort_filters == 'function') {\n filters.sort(this.settings.sort_filters);\n }\n else {\n var self = this;\n filters.sort(function(a, b) {\n return self.translate(a.label).localeCompare(self.translate(b.label));\n });\n }\n }\n\n if (this.status.has_optgroup) {\n filters = Utils.groupSort(filters, 'optgroup');\n }\n\n return filters;\n};\n\n/**\n * Checks the configuration of each operator\n * @param {QueryBuilder.Operator[]} operators\n * @returns {QueryBuilder.Operator[]}\n * @throws ConfigError\n */\nQueryBuilder.prototype.checkOperators = function(operators) {\n var definedOperators = [];\n\n operators.forEach(function(operator, i) {\n if (typeof operator == 'string') {\n if (!QueryBuilder.OPERATORS[operator]) {\n Utils.error('Config', 'Unknown operator \"{0}\"', operator);\n }\n\n operators[i] = operator = $.extendext(true, 'replace', {}, QueryBuilder.OPERATORS[operator]);\n }\n else {\n if (!operator.type) {\n Utils.error('Config', 'Missing \"type\" for operator {0}', i);\n }\n\n if (QueryBuilder.OPERATORS[operator.type]) {\n operators[i] = operator = $.extendext(true, 'replace', {}, QueryBuilder.OPERATORS[operator.type], operator);\n }\n\n if (operator.nb_inputs === undefined || operator.apply_to === undefined) {\n Utils.error('Config', 'Missing \"nb_inputs\" and/or \"apply_to\" for operator \"{0}\"', operator.type);\n }\n }\n\n if (definedOperators.indexOf(operator.type) != -1) {\n Utils.error('Config', 'Operator \"{0}\" already defined', operator.type);\n }\n definedOperators.push(operator.type);\n\n if (!operator.optgroup) {\n operator.optgroup = null;\n }\n else {\n this.status.has_operator_optgroup = true;\n\n // register optgroup if needed\n if (!this.settings.optgroups[operator.optgroup]) {\n this.settings.optgroups[operator.optgroup] = operator.optgroup;\n }\n }\n }, this);\n\n if (this.status.has_operator_optgroup) {\n operators = Utils.groupSort(operators, 'optgroup');\n }\n\n return operators;\n};\n\n/**\n * Adds all events listeners to the builder\n * @private\n */\nQueryBuilder.prototype.bindEvents = function() {\n var self = this;\n var Selectors = QueryBuilder.selectors;\n\n // group condition change\n this.$el.on('change.queryBuilder', Selectors.group_condition, function() {\n if ($(this).is(':checked')) {\n var $group = $(this).closest(Selectors.group_container);\n self.getModel($group).condition = $(this).val();\n }\n });\n\n // rule filter change\n this.$el.on('change.queryBuilder', Selectors.rule_filter, function() {\n var $rule = $(this).closest(Selectors.rule_container);\n self.getModel($rule).filter = self.getFilterById($(this).val());\n });\n\n // rule operator change\n this.$el.on('change.queryBuilder', Selectors.rule_operator, function() {\n var $rule = $(this).closest(Selectors.rule_container);\n self.getModel($rule).operator = self.getOperatorByType($(this).val());\n });\n\n // add rule button\n this.$el.on('click.queryBuilder', Selectors.add_rule, function() {\n var $group = $(this).closest(Selectors.group_container);\n self.addRule(self.getModel($group));\n });\n\n // delete rule button\n this.$el.on('click.queryBuilder', Selectors.delete_rule, function() {\n var $rule = $(this).closest(Selectors.rule_container);\n self.deleteRule(self.getModel($rule));\n });\n\n if (this.settings.allow_groups !== 0) {\n // add group button\n this.$el.on('click.queryBuilder', Selectors.add_group, function() {\n var $group = $(this).closest(Selectors.group_container);\n self.addGroup(self.getModel($group));\n });\n\n // delete group button\n this.$el.on('click.queryBuilder', Selectors.delete_group, function() {\n var $group = $(this).closest(Selectors.group_container);\n self.deleteGroup(self.getModel($group));\n });\n }\n\n // model events\n this.model.on({\n 'drop': function(e, node) {\n node.$el.remove();\n self.refreshGroupsConditions();\n },\n 'add': function(e, parent, node, index) {\n if (index === 0) {\n node.$el.prependTo(parent.$el.find('>' + QueryBuilder.selectors.rules_list));\n }\n else {\n node.$el.insertAfter(parent.rules[index - 1].$el);\n }\n self.refreshGroupsConditions();\n },\n 'move': function(e, node, group, index) {\n node.$el.detach();\n\n if (index === 0) {\n node.$el.prependTo(group.$el.find('>' + QueryBuilder.selectors.rules_list));\n }\n else {\n node.$el.insertAfter(group.rules[index - 1].$el);\n }\n self.refreshGroupsConditions();\n },\n 'update': function(e, node, field, value, oldValue) {\n if (node instanceof Rule) {\n switch (field) {\n case 'error':\n self.updateError(node);\n break;\n\n case 'flags':\n self.applyRuleFlags(node);\n break;\n\n case 'filter':\n self.updateRuleFilter(node, oldValue);\n break;\n\n case 'operator':\n self.updateRuleOperator(node, oldValue);\n break;\n\n case 'value':\n self.updateRuleValue(node, oldValue);\n break;\n }\n }\n else {\n switch (field) {\n case 'error':\n self.updateError(node);\n break;\n\n case 'flags':\n self.applyGroupFlags(node);\n break;\n\n case 'condition':\n self.updateGroupCondition(node, oldValue);\n break;\n }\n }\n }\n });\n};\n\n/**\n * Creates the root group\n * @param {boolean} [addRule=true] - adds a default empty rule\n * @param {object} [data] - group custom data\n * @param {object} [flags] - flags to apply to the group\n * @returns {Group} root group\n * @fires QueryBuilder.afterAddGroup\n */\nQueryBuilder.prototype.setRoot = function(addRule, data, flags) {\n addRule = (addRule === undefined || addRule === true);\n\n var group_id = this.nextGroupId();\n var $group = $($.parseHTML(this.getGroupTemplate(group_id, 1)));\n\n this.$el.append($group);\n this.model.root = new Group(null, $group);\n this.model.root.model = this.model;\n\n this.model.root.data = data;\n this.model.root.flags = $.extend({}, this.settings.default_group_flags, flags);\n this.model.root.condition = this.settings.default_condition;\n\n this.trigger('afterAddGroup', this.model.root);\n\n this.model.root.$el.find(\"[data-bs-toggle=tooltip]\").tooltip({\n //*** container: this.$el[0], // Note: Do not put in this.$el[0] or the position of the tooltip will be affected after size change (e.g. after adding a rule)\n trigger: \"hover\",\n placement: \"bottom\"\n });\n\n if (addRule) {\n this.addRule(this.model.root);\n }\n\n return this.model.root;\n};\n\n/**\n * Adds a new group\n * @param {Group} parent\n * @param {boolean} [addRule=true] - adds a default empty rule\n * @param {object} [data] - group custom data\n * @param {object} [flags] - flags to apply to the group\n * @returns {Group}\n * @fires QueryBuilder.beforeAddGroup\n * @fires QueryBuilder.afterAddGroup\n */\nQueryBuilder.prototype.addGroup = function(parent, addRule, data, flags) {\n addRule = (addRule === undefined || addRule === true);\n\n var level = parent.level + 1;\n\n /**\n * Just before adding a group, can be prevented.\n * @event beforeAddGroup\n * @memberof QueryBuilder\n * @param {Group} parent\n * @param {boolean} addRule - if an empty rule will be added in the group\n * @param {int} level - nesting level of the group, 1 is the root group\n */\n var e = this.trigger('beforeAddGroup', parent, addRule, level);\n if (e.isDefaultPrevented()) {\n return null;\n }\n\n var group_id = this.nextGroupId();\n var $group = $(this.getGroupTemplate(group_id, level));\n var model = parent.addGroup($group);\n\n model.data = data;\n model.flags = $.extend({}, this.settings.default_group_flags, flags);\n model.condition = this.settings.default_condition;\n\n /**\n * Just after adding a group\n * @event afterAddGroup\n * @memberof QueryBuilder\n * @param {Group} group\n */\n this.trigger('afterAddGroup', model);\n\n model.$el.find(\"[data-bs-toggle=tooltip]\").tooltip({\n //*** container: this.$el[0],\n trigger: \"hover\",\n placement: \"bottom\"\n });\n\n /**\n * After any change in the rules\n * @event rulesChanged\n * @memberof QueryBuilder\n */\n this.trigger('rulesChanged');\n\n if (addRule) {\n this.addRule(model);\n }\n\n return model;\n};\n\n/**\n * Tries to delete a group. The group is not deleted if at least one rule is flagged `no_delete`.\n * @param {Group} group\n * @returns {boolean} if the group has been deleted\n * @fires QueryBuilder.beforeDeleteGroup\n * @fires QueryBuilder.afterDeleteGroup\n */\nQueryBuilder.prototype.deleteGroup = function(group) {\n if (group.isRoot()) {\n return false;\n }\n\n /**\n * Just before deleting a group, can be prevented\n * @event beforeDeleteGroup\n * @memberof QueryBuilder\n * @param {Group} parent\n */\n var e = this.trigger('beforeDeleteGroup', group);\n if (e.isDefaultPrevented()) {\n return false;\n }\n\n var del = true;\n\n group.each('reverse', function(rule) {\n del &= this.deleteRule(rule);\n }, function(group) {\n del &= this.deleteGroup(group);\n }, this);\n\n if (del) {\n group.drop();\n\n /**\n * Just after deleting a group\n * @event afterDeleteGroup\n * @memberof QueryBuilder\n */\n this.trigger('afterDeleteGroup');\n\n this.trigger('rulesChanged');\n }\n\n return del;\n};\n\n/**\n * Performs actions when a group's condition changes\n * @param {Group} group\n * @param {object} previousCondition\n * @fires QueryBuilder.afterUpdateGroupCondition\n * @private\n */\nQueryBuilder.prototype.updateGroupCondition = function(group, previousCondition) {\n group.$el.find('>' + QueryBuilder.selectors.group_condition).each(function() {\n var $this = $(this);\n $this.prop('checked', $this.val() === group.condition);\n $this.parent().toggleClass('active', $this.val() === group.condition);\n });\n\n /**\n * After the group condition has been modified\n * @event afterUpdateGroupCondition\n * @memberof QueryBuilder\n * @param {Group} group\n * @param {object} previousCondition\n */\n this.trigger('afterUpdateGroupCondition', group, previousCondition);\n\n this.trigger('rulesChanged');\n};\n\n/**\n * Updates the visibility of conditions based on number of rules inside each group\n * @private\n */\nQueryBuilder.prototype.refreshGroupsConditions = function() {\n (function walk(group) {\n if (!group.flags || (group.flags && !group.flags.condition_readonly)) {\n group.$el.find('>' + QueryBuilder.selectors.group_condition).prop('disabled', group.rules.length <= 1)\n .parent().toggleClass('disabled', group.rules.length <= 1);\n }\n\n group.each(null, function(group) {\n walk(group);\n }, this);\n }(this.model.root));\n};\n\n/**\n * Adds a new rule\n * @param {Group} parent\n * @param {object} [data] - rule custom data\n * @param {object} [flags] - flags to apply to the rule\n * @returns {Rule}\n * @fires QueryBuilder.beforeAddRule\n * @fires QueryBuilder.afterAddRule\n * @fires QueryBuilder.changer:getDefaultFilter\n */\nQueryBuilder.prototype.addRule = function(parent, data, flags) {\n /**\n * Just before adding a rule, can be prevented\n * @event beforeAddRule\n * @memberof QueryBuilder\n * @param {Group} parent\n */\n var e = this.trigger('beforeAddRule', parent);\n if (e.isDefaultPrevented()) {\n return null;\n }\n\n var rule_id = this.nextRuleId();\n var $rule = $($.parseHTML(this.getRuleTemplate(rule_id)));\n var model = parent.addRule($rule);\n\n model.data = data;\n model.flags = $.extend({}, this.settings.default_rule_flags, flags);\n\n /**\n * Just after adding a rule\n * @event afterAddRule\n * @memberof QueryBuilder\n * @param {Rule} rule\n */\n this.trigger('afterAddRule', model);\n\n model.$el.find(\"[data-bs-toggle=tooltip]\").tooltip({\n //*** container: this.$el[0],\n trigger: \"hover\",\n placement: \"bottom\"\n });\n\n this.trigger('rulesChanged');\n\n this.createRuleFilters(model);\n\n if (this.settings.default_filter || !this.settings.display_empty_filter) {\n /**\n * Modifies the default filter for a rule\n * @event changer:getDefaultFilter\n * @memberof QueryBuilder\n * @param {QueryBuilder.Filter} filter\n * @param {Rule} rule\n * @returns {QueryBuilder.Filter}\n */\n model.filter = this.change('getDefaultFilter',\n this.getFilterById(this.settings.default_filter || this.filters[0].id),\n model\n );\n }\n\n return model;\n};\n\n/**\n * Tries to delete a rule\n * @param {Rule} rule\n * @returns {boolean} if the rule has been deleted\n * @fires QueryBuilder.beforeDeleteRule\n * @fires QueryBuilder.afterDeleteRule\n */\nQueryBuilder.prototype.deleteRule = function(rule) {\n if (rule.flags.no_delete) {\n return false;\n }\n\n /**\n * Just before deleting a rule, can be prevented\n * @event beforeDeleteRule\n * @memberof QueryBuilder\n * @param {Rule} rule\n */\n var e = this.trigger('beforeDeleteRule', rule);\n if (e.isDefaultPrevented()) {\n return false;\n }\n\n rule.drop();\n\n /**\n * Just after deleting a rule\n * @event afterDeleteRule\n * @memberof QueryBuilder\n */\n this.trigger('afterDeleteRule');\n\n this.trigger('rulesChanged');\n\n return true;\n};\n\n/**\n * Creates the filters for a rule\n * @param {Rule} rule\n * @fires QueryBuilder.changer:getRuleFilters\n * @fires QueryBuilder.afterCreateRuleFilters\n * @private\n */\nQueryBuilder.prototype.createRuleFilters = function(rule) {\n /**\n * Modifies the list a filters available for a rule\n * @event changer:getRuleFilters\n * @memberof QueryBuilder\n * @param {QueryBuilder.Filter[]} filters\n * @param {Rule} rule\n * @returns {QueryBuilder.Filter[]}\n */\n var filters = this.change('getRuleFilters', this.filters, rule);\n var $filterSelect = $($.parseHTML(this.getRuleFilterSelect(rule, filters)));\n\n rule.$el.find(QueryBuilder.selectors.filter_container).html($filterSelect);\n\n /**\n * After creating the dropdown for filters\n * @event afterCreateRuleFilters\n * @memberof QueryBuilder\n * @param {Rule} rule\n */\n this.trigger('afterCreateRuleFilters', rule);\n\n this.applyRuleFlags(rule);\n};\n\n/**\n * Creates the operators for a rule and init the rule operator\n * @param {Rule} rule\n * @fires QueryBuilder.afterCreateRuleOperators\n * @private\n */\nQueryBuilder.prototype.createRuleOperators = function(rule) {\n var $operatorContainer = rule.$el.find(QueryBuilder.selectors.operator_container).empty();\n\n if (!rule.filter) {\n return;\n }\n\n var operators = this.getOperators(rule.filter);\n var $operatorSelect = $($.parseHTML(this.getRuleOperatorSelect(rule, operators)));\n\n $operatorContainer.html($operatorSelect);\n\n // set the operator without triggering update event\n if (rule.filter.default_operator) {\n rule.__.operator = this.getOperatorByType(rule.filter.default_operator);\n }\n else {\n rule.__.operator = operators[0];\n }\n\n rule.$el.find(QueryBuilder.selectors.rule_operator).val(rule.operator.type);\n\n /**\n * After creating the dropdown for operators\n * @event afterCreateRuleOperators\n * @memberof QueryBuilder\n * @param {Rule} rule\n * @param {QueryBuilder.Operator[]} operators - allowed operators for this rule\n */\n this.trigger('afterCreateRuleOperators', rule, operators);\n\n this.applyRuleFlags(rule);\n};\n\n/**\n * Creates the main input for a rule\n * @param {Rule} rule\n * @fires QueryBuilder.afterCreateRuleInput\n * @private\n */\nQueryBuilder.prototype.createRuleInput = function(rule) {\n var $valueContainer = rule.$el.find(QueryBuilder.selectors.value_container).empty();\n\n rule.__.value = undefined;\n\n if (!rule.filter || !rule.operator || rule.operator.nb_inputs === 0) {\n return;\n }\n\n var self = this;\n var $inputs = $();\n var filter = rule.filter;\n\n for (var i = 0; i < rule.operator.nb_inputs; i++) {\n var $ruleInput = $($.parseHTML(this.getRuleInput(rule, i).trim(), document, true)); // Keep scripts //***\n if (i > 0) $valueContainer.append(this.settings.inputs_separator);\n $valueContainer.append($ruleInput);\n $inputs = $inputs.add($ruleInput);\n }\n\n $valueContainer.css('display', '');\n\n $inputs.on('change ' + (filter.input_event || ''), function() {\n if (!rule._updating_input) {\n rule._updating_value = true;\n rule.value = self.getRuleInputValue(rule);\n rule._updating_value = false;\n }\n });\n\n if (filter.plugin) {\n $inputs[filter.plugin](filter.plugin_config || {});\n }\n\n /**\n * After creating the input for a rule and initializing optional plugin\n * @event afterCreateRuleInput\n * @memberof QueryBuilder\n * @param {Rule} rule\n */\n this.trigger('afterCreateRuleInput', rule);\n\n if (filter.default_value !== undefined) {\n rule.value = filter.default_value;\n }\n else {\n rule._updating_value = true;\n rule.value = self.getRuleInputValue(rule);\n rule._updating_value = false;\n }\n\n this.applyRuleFlags(rule);\n};\n\n/**\n * Performs action when a rule's filter changes\n * @param {Rule} rule\n * @param {object} previousFilter\n * @fires QueryBuilder.afterUpdateRuleFilter\n * @private\n */\nQueryBuilder.prototype.updateRuleFilter = function(rule, previousFilter) {\n this.createRuleOperators(rule);\n this.createRuleInput(rule);\n\n rule.$el.find(QueryBuilder.selectors.rule_filter).val(rule.filter ? rule.filter.id : '-1');\n\n // clear rule data if the filter changed\n if (previousFilter && rule.filter && previousFilter.id !== rule.filter.id) {\n rule.data = undefined;\n }\n\n /**\n * After the filter has been updated and the operators and input re-created\n * @event afterUpdateRuleFilter\n * @memberof QueryBuilder\n * @param {Rule} rule\n * @param {object} previousFilter\n */\n this.trigger('afterUpdateRuleFilter', rule, previousFilter);\n\n this.trigger('rulesChanged');\n};\n\n/**\n * Performs actions when a rule's operator changes\n * @param {Rule} rule\n * @param {object} previousOperator\n * @fires QueryBuilder.afterUpdateRuleOperator\n * @private\n */\nQueryBuilder.prototype.updateRuleOperator = function(rule, previousOperator) {\n var $valueContainer = rule.$el.find(QueryBuilder.selectors.value_container);\n\n if (!rule.operator || rule.operator.nb_inputs === 0) {\n $valueContainer.hide();\n\n rule.__.value = undefined;\n }\n else {\n $valueContainer.css('display', '');\n\n if ($valueContainer.is(':empty') || !previousOperator ||\n rule.operator.nb_inputs !== previousOperator.nb_inputs ||\n rule.operator.optgroup !== previousOperator.optgroup\n ) {\n this.createRuleInput(rule);\n }\n }\n\n if (rule.operator) {\n rule.$el.find(QueryBuilder.selectors.rule_operator).val(rule.operator.type);\n\n // refresh value if the format changed for this operator\n rule.__.value = this.getRuleInputValue(rule);\n }\n\n /**\n * After the operator has been updated and the input optionally re-created\n * @event afterUpdateRuleOperator\n * @memberof QueryBuilder\n * @param {Rule} rule\n * @param {object} previousOperator\n */\n this.trigger('afterUpdateRuleOperator', rule, previousOperator);\n\n this.trigger('rulesChanged');\n};\n\n/**\n * Performs actions when rule's value changes\n * @param {Rule} rule\n * @param {object} previousValue\n * @fires QueryBuilder.afterUpdateRuleValue\n * @private\n */\nQueryBuilder.prototype.updateRuleValue = function(rule, previousValue) {\n if (!rule._updating_value) {\n this.setRuleInputValue(rule, rule.value);\n }\n\n /**\n * After the rule value has been modified\n * @event afterUpdateRuleValue\n * @memberof QueryBuilder\n * @param {Rule} rule\n * @param {*} previousValue\n */\n this.trigger('afterUpdateRuleValue', rule, previousValue);\n\n this.trigger('rulesChanged');\n};\n\n/**\n * Changes a rule's properties depending on its flags\n * @param {Rule} rule\n * @fires QueryBuilder.afterApplyRuleFlags\n * @private\n */\nQueryBuilder.prototype.applyRuleFlags = function(rule) {\n var flags = rule.flags;\n var Selectors = QueryBuilder.selectors;\n\n rule.$el.find(Selectors.rule_filter).prop('disabled', flags.filter_readonly);\n rule.$el.find(Selectors.rule_operator).prop('disabled', flags.operator_readonly);\n rule.$el.find(Selectors.rule_value).prop('disabled', flags.value_readonly);\n\n if (flags.no_delete) {\n rule.$el.find(Selectors.delete_rule).remove();\n }\n\n /**\n * After rule's flags has been applied\n * @event afterApplyRuleFlags\n * @memberof QueryBuilder\n * @param {Rule} rule\n */\n this.trigger('afterApplyRuleFlags', rule);\n};\n\n/**\n * Changes group's properties depending on its flags\n * @param {Group} group\n * @fires QueryBuilder.afterApplyGroupFlags\n * @private\n */\nQueryBuilder.prototype.applyGroupFlags = function(group) {\n var flags = group.flags;\n var Selectors = QueryBuilder.selectors;\n\n group.$el.find('>' + Selectors.group_condition).prop('disabled', flags.condition_readonly)\n .parent().toggleClass('readonly', flags.condition_readonly);\n\n if (flags.no_add_rule) {\n group.$el.find(Selectors.add_rule).remove();\n }\n if (flags.no_add_group) {\n group.$el.find(Selectors.add_group).remove();\n }\n if (flags.no_delete) {\n group.$el.find(Selectors.delete_group).remove();\n }\n\n /**\n * After group's flags has been applied\n * @event afterApplyGroupFlags\n * @memberof QueryBuilder\n * @param {Group} group\n */\n this.trigger('afterApplyGroupFlags', group);\n};\n\n/**\n * Clears all errors markers\n * @param {Node} [node] default is root Group\n */\nQueryBuilder.prototype.clearErrors = function(node) {\n node = node || this.model.root;\n\n if (!node) {\n return;\n }\n\n node.error = null;\n\n if (node instanceof Group) {\n node.each(function(rule) {\n rule.error = null;\n }, function(group) {\n this.clearErrors(group);\n }, this);\n }\n};\n\n/**\n * Adds/Removes error on a Rule or Group\n * @param {Node} node\n * @fires QueryBuilder.changer:displayError\n * @private\n */\nQueryBuilder.prototype.updateError = function(node) {\n if (this.settings.display_errors) {\n if (node.error === null) {\n node.$el.removeClass('has-error');\n }\n else {\n var errorMessage = this.translate('errors', node.error[0]);\n errorMessage = Utils.fmt(errorMessage, node.error.slice(1));\n\n /**\n * Modifies an error message before display\n * @event changer:displayError\n * @memberof QueryBuilder\n * @param {string} errorMessage - the error message (translated and formatted)\n * @param {array} error - the raw error array (error code and optional arguments)\n * @param {Node} node\n * @returns {string}\n */\n errorMessage = this.change('displayError', errorMessage, node.error, node);\n\n if (errorMessage)\n node.$el.addClass('has-error').find(QueryBuilder.selectors.error_container).eq(0).html(errorMessage); //***\n }\n }\n};\n\n/**\n * Triggers a validation error event\n * @param {Node} node\n * @param {string|array} error\n * @param {*} value\n * @fires QueryBuilder.validationError\n * @private\n */\nQueryBuilder.prototype.triggerValidationError = function(node, error, value) {\n if (!Array.isArray(error)) {\n error = [error];\n }\n\n /**\n * Fired when a validation error occurred, can be prevented\n * @event validationError\n * @memberof QueryBuilder\n * @param {Node} node\n * @param {string} error\n * @param {*} value\n */\n var e = this.trigger('validationError', node, error, value);\n if (!e.isDefaultPrevented()) {\n node.error = error;\n }\n};\n\n\n/**\n * Destroys the builder\n * @fires QueryBuilder.beforeDestroy\n */\nQueryBuilder.prototype.destroy = function() {\n /**\n * Before the {@link QueryBuilder#destroy} method\n * @event beforeDestroy\n * @memberof QueryBuilder\n */\n this.trigger('beforeDestroy');\n\n if (this.status.generated_id) {\n this.$el.removeAttr('id');\n }\n\n this.clear();\n this.model = null;\n\n this.$el\n .off('.queryBuilder')\n .removeClass('query-builder')\n .removeData('queryBuilder');\n\n delete this.$el[0].queryBuilder;\n};\n\n/**\n * Clear all rules and resets the root group\n * @fires QueryBuilder.beforeReset\n * @fires QueryBuilder.afterReset\n */\nQueryBuilder.prototype.reset = function() {\n /**\n * Before the {@link QueryBuilder#reset} method, can be prevented\n * @event beforeReset\n * @memberof QueryBuilder\n */\n var e = this.trigger('beforeReset');\n if (e.isDefaultPrevented()) {\n return;\n }\n\n this.status.group_id = 1;\n this.status.rule_id = 0;\n\n this.model.root.empty();\n\n this.model.root.data = undefined;\n this.model.root.flags = $.extend({}, this.settings.default_group_flags);\n this.model.root.condition = this.settings.default_condition;\n\n this.addRule(this.model.root);\n\n /**\n * After the {@link QueryBuilder#reset} method\n * @event afterReset\n * @memberof QueryBuilder\n */\n this.trigger('afterReset');\n\n this.trigger('rulesChanged');\n};\n\n/**\n * Clears all rules and removes the root group\n * @fires QueryBuilder.beforeClear\n * @fires QueryBuilder.afterClear\n */\nQueryBuilder.prototype.clear = function() {\n /**\n * Before the {@link QueryBuilder#clear} method, can be prevented\n * @event beforeClear\n * @memberof QueryBuilder\n */\n var e = this.trigger('beforeClear');\n if (e.isDefaultPrevented()) {\n return;\n }\n\n this.status.group_id = 0;\n this.status.rule_id = 0;\n\n if (this.model.root) {\n this.model.root.drop();\n this.model.root = null;\n }\n\n /**\n * After the {@link QueryBuilder#clear} method\n * @event afterClear\n * @memberof QueryBuilder\n */\n this.trigger('afterClear');\n\n this.trigger('rulesChanged');\n};\n\n/**\n * Modifies the builder configuration.
\n * Only options defined in QueryBuilder.modifiable_options are modifiable\n * @param {object} options\n */\nQueryBuilder.prototype.setOptions = function(options) {\n $.each(options, function(opt, value) {\n if (QueryBuilder.modifiable_options.indexOf(opt) !== -1) {\n this.settings[opt] = value;\n }\n }.bind(this));\n};\n\n/**\n * Returns the model associated to a DOM object, or the root model\n * @param {jQuery} [target]\n * @returns {Node}\n */\nQueryBuilder.prototype.getModel = function(target) {\n if (!target) {\n return this.model.root;\n }\n else if (target instanceof Node) {\n return target;\n }\n else {\n return $(target).data('queryBuilderModel');\n }\n};\n\n/**\n * Validates the whole builder\n * @param {object} [options]\n * @param {boolean} [options.skip_empty=false] - skips validating rules that have no filter selected\n * @returns {boolean}\n * @fires QueryBuilder.changer:validate\n */\nQueryBuilder.prototype.validate = function(options) {\n options = $.extend({\n skip_empty: false\n }, options);\n\n this.clearErrors();\n\n var self = this;\n\n var valid = (function parse(group) {\n var done = 0;\n var errors = 0;\n\n group.each(function(rule) {\n if (!rule.filter && options.skip_empty) {\n return;\n }\n\n if (!rule.filter) {\n self.triggerValidationError(rule, 'no_filter', null);\n errors++;\n return;\n }\n\n if (!rule.operator) {\n self.triggerValidationError(rule, 'no_operator', null);\n errors++;\n return;\n }\n\n if (rule.operator.nb_inputs !== 0) {\n var valid = self.validateValue(rule, rule.value);\n\n if (valid !== true) {\n self.triggerValidationError(rule, valid, rule.value);\n errors++;\n return;\n }\n }\n\n done++;\n\n }, function(group) {\n var res = parse(group);\n if (res === true) {\n done++;\n }\n else if (res === false) {\n errors++;\n }\n });\n\n if (errors > 0) {\n return false;\n }\n else if (done === 0 && !group.isRoot() && options.skip_empty) {\n return null;\n }\n else if (done === 0 && (!self.settings.allow_empty || !group.isRoot())) {\n self.triggerValidationError(group, 'empty_group', null);\n return false;\n }\n\n return true;\n\n }(this.model.root));\n\n /**\n * Modifies the result of the {@link QueryBuilder#validate} method\n * @event changer:validate\n * @memberof QueryBuilder\n * @param {boolean} valid\n * @returns {boolean}\n */\n return this.change('validate', valid);\n};\n\n/**\n * Gets an object representing current rules\n * @param {object} [options]\n * @param {boolean|string} [options.get_flags=false] - export flags, true: only changes from default flags or 'all'\n * @param {boolean} [options.allow_invalid=false] - returns rules even if they are invalid\n * @param {boolean} [options.skip_empty=false] - remove rules that have no filter selected\n * @returns {object}\n * @fires QueryBuilder.changer:ruleToJson\n * @fires QueryBuilder.changer:groupToJson\n * @fires QueryBuilder.changer:getRules\n */\nQueryBuilder.prototype.getRules = function(options) {\n options = $.extend({\n get_flags: false,\n allow_invalid: false,\n skip_empty: false\n }, options);\n\n var valid = this.validate(options);\n if (!valid && !options.allow_invalid) {\n return null;\n }\n\n var self = this;\n\n var out = (function parse(group) {\n var groupData = {\n condition: group.condition,\n rules: []\n };\n\n if (group.data) {\n groupData.data = $.extendext(true, 'replace', {}, group.data);\n }\n\n if (options.get_flags) {\n var flags = self.getGroupFlags(group.flags, options.get_flags === 'all');\n if (!$.isEmptyObject(flags)) {\n groupData.flags = flags;\n }\n }\n\n group.each(function(rule) {\n if ((!rule.filter || rule.value === undefined) && options.skip_empty) { //***\n return;\n }\n\n var value = null;\n if (!rule.operator || rule.operator.nb_inputs !== 0) {\n value = rule.value;\n }\n\n var ruleData = {\n id: rule.filter ? rule.filter.id : null,\n field: rule.filter ? rule.filter.field : null,\n type: rule.filter ? rule.filter.type : null,\n input: rule.filter ? rule.filter.input : null,\n operator: rule.operator ? rule.operator.type : null,\n value: value\n };\n\n if (rule.filter && rule.filter.data || rule.data) {\n ruleData.data = $.extendext(true, 'replace', {}, rule.filter ? rule.filter.data : {}, rule.data);\n }\n\n if (options.get_flags) {\n var flags = self.getRuleFlags(rule.flags, options.get_flags === 'all');\n if (!$.isEmptyObject(flags)) {\n ruleData.flags = flags;\n }\n }\n\n /**\n * Modifies the JSON generated from a Rule object\n * @event changer:ruleToJson\n * @memberof QueryBuilder\n * @param {object} json\n * @param {Rule} rule\n * @returns {object}\n */\n groupData.rules.push(self.change('ruleToJson', ruleData, rule));\n\n }, function(model) {\n var data = parse(model);\n if (data.rules.length !== 0 || !options.skip_empty) {\n groupData.rules.push(data);\n }\n }, this);\n\n /**\n * Modifies the JSON generated from a Group object\n * @event changer:groupToJson\n * @memberof QueryBuilder\n * @param {object} json\n * @param {Group} group\n * @returns {object}\n */\n return self.change('groupToJson', groupData, group);\n\n }(this.model.root));\n\n out.valid = valid;\n\n /**\n * Modifies the result of the {@link QueryBuilder#getRules} method\n * @event changer:getRules\n * @memberof QueryBuilder\n * @param {object} json\n * @returns {object}\n */\n return this.change('getRules', out);\n};\n\n/**\n * Sets rules from object\n * @param {object} data\n * @param {object} [options]\n * @param {boolean} [options.allow_invalid=false] - silent-fail if the data are invalid\n * @throws RulesError, UndefinedConditionError\n * @fires QueryBuilder.changer:setRules\n * @fires QueryBuilder.changer:jsonToRule\n * @fires QueryBuilder.changer:jsonToGroup\n * @fires QueryBuilder.afterSetRules\n */\nQueryBuilder.prototype.setRules = function(data, options) {\n options = $.extend({\n allow_invalid: false\n }, options);\n\n if (Array.isArray(data)) {\n data = {\n condition: this.settings.default_condition,\n rules: data\n };\n }\n\n if (!data || !data.rules || (data.rules.length === 0 && !this.settings.allow_empty)) {\n Utils.error('RulesParse', 'Incorrect data object passed');\n }\n\n this.clear();\n this.setRoot(false, data.data, this.parseGroupFlags(data));\n\n /**\n * Modifies data before the {@link QueryBuilder#setRules} method\n * @event changer:setRules\n * @memberof QueryBuilder\n * @param {object} json\n * @param {object} options\n * @returns {object}\n */\n data = this.change('setRules', data, options);\n\n var self = this;\n\n (function add(data, group) {\n if (group === null) {\n return;\n }\n\n if (data.condition === undefined) {\n data.condition = self.settings.default_condition;\n }\n else if (self.settings.conditions.indexOf(data.condition) == -1) {\n Utils.error(!options.allow_invalid, 'UndefinedCondition', 'Invalid condition \"{0}\"', data.condition);\n data.condition = self.settings.default_condition;\n }\n\n group.condition = data.condition;\n\n data.rules.forEach(function(item) {\n var model;\n\n if (item.rules !== undefined) {\n if (self.settings.allow_groups !== -1 && self.settings.allow_groups < group.level) {\n Utils.error(!options.allow_invalid, 'RulesParse', 'No more than {0} groups are allowed', self.settings.allow_groups);\n self.reset();\n }\n else {\n model = self.addGroup(group, false, item.data, self.parseGroupFlags(item));\n if (model === null) {\n return;\n }\n\n add(item, model);\n }\n }\n else {\n if (!item.empty) {\n if (item.id === undefined) {\n Utils.error(!options.allow_invalid, 'RulesParse', 'Missing rule field id');\n item.empty = true;\n }\n if (item.operator === undefined) {\n item.operator = 'equal';\n }\n }\n\n model = self.addRule(group, item.data, self.parseRuleFlags(item));\n if (model === null) {\n return;\n }\n\n if (!item.empty) {\n model.filter = self.getFilterById(item.id, !options.allow_invalid);\n }\n\n if (model.filter) {\n model.operator = self.getOperatorByType(item.operator, !options.allow_invalid);\n\n if (!model.operator) {\n model.operator = self.getOperators(model.filter)[0];\n }\n }\n\n if (model.operator && model.operator.nb_inputs !== 0) {\n if (item.value !== undefined) {\n model.value = item.value;\n }\n else if (model.filter.default_value !== undefined) {\n model.value = model.filter.default_value;\n }\n }\n\n /**\n * Modifies the Rule object generated from the JSON\n * @event changer:jsonToRule\n * @memberof QueryBuilder\n * @param {Rule} rule\n * @param {object} json\n * @returns {Rule} the same rule\n */\n if (self.change('jsonToRule', model, item) != model) {\n Utils.error('RulesParse', 'Plugin tried to change rule reference');\n }\n }\n });\n\n /**\n * Modifies the Group object generated from the JSON\n * @event changer:jsonToGroup\n * @memberof QueryBuilder\n * @param {Group} group\n * @param {object} json\n * @returns {Group} the same group\n */\n if (self.change('jsonToGroup', group, data) != group) {\n Utils.error('RulesParse', 'Plugin tried to change group reference');\n }\n\n }(data, this.model.root));\n\n /**\n * After the {@link QueryBuilder#setRules} method\n * @event afterSetRules\n * @memberof QueryBuilder\n */\n this.trigger('afterSetRules');\n};\n\n\n/**\n * Performs value validation\n * @param {Rule} rule\n * @param {string|string[]} value\n * @returns {array|boolean} true or error array\n * @fires QueryBuilder.changer:validateValue\n */\nQueryBuilder.prototype.validateValue = function(rule, value) {\n var validation = rule.filter.validation || {};\n var result = true;\n\n if (validation.callback) {\n result = validation.callback.call(this, value, rule);\n }\n else {\n result = this._validateValue(rule, value);\n }\n\n /**\n * Modifies the result of the rule validation method\n * @event changer:validateValue\n * @memberof QueryBuilder\n * @param {array|boolean} result - true or an error array\n * @param {*} value\n * @param {Rule} rule\n * @returns {array|boolean}\n */\n return this.change('validateValue', result, value, rule);\n};\n\n/**\n * Default validation function\n * @param {Rule} rule\n * @param {string|string[]} value\n * @returns {array|boolean} true or error array\n * @throws ConfigError\n * @private\n */\nQueryBuilder.prototype._validateValue = function(rule, value) {\n var filter = rule.filter;\n var operator = rule.operator;\n var validation = filter.validation || {};\n var result = true;\n var tmp, tempValue;\n\n if (rule.operator.nb_inputs === 1) {\n value = [value];\n }\n\n for (var i = 0; i < operator.nb_inputs; i++) {\n if (!operator.multiple && Array.isArray(value[i]) && value[i].length > 1) {\n result = ['operator_not_multiple', operator.type, this.translate('operators', operator.type)];\n break;\n }\n\n switch (filter.input) {\n case 'radio':\n if (value[i] === undefined || value[i].length === 0) {\n if (!validation.allow_empty_value) {\n result = ['radio_empty'];\n }\n break;\n }\n break;\n\n case 'checkbox':\n if (value[i] === undefined || value[i].length === 0) {\n if (!validation.allow_empty_value) {\n result = ['checkbox_empty'];\n }\n break;\n }\n break;\n\n case 'select':\n if (value[i] === undefined || value[i].length === 0 || (filter.placeholder && value[i] == filter.placeholder_value)) {\n if (!validation.allow_empty_value) {\n result = ['select_empty'];\n }\n break;\n }\n break;\n\n default:\n tempValue = Array.isArray(value[i]) ? value[i] : [value[i]];\n\n for (var j = 0; j < tempValue.length; j++) {\n switch (QueryBuilder.types[filter.type]) {\n case 'string':\n if (tempValue[j] === undefined || tempValue[j].length === 0) {\n if (!validation.allow_empty_value) {\n result = ['string_empty'];\n }\n break;\n }\n if (validation.min !== undefined) {\n if (tempValue[j].length < parseInt(validation.min)) {\n result = [this.getValidationMessage(validation, 'min', 'string_exceed_min_length'), validation.min];\n break;\n }\n }\n if (validation.max !== undefined) {\n if (tempValue[j].length > parseInt(validation.max)) {\n result = [this.getValidationMessage(validation, 'max', 'string_exceed_max_length'), validation.max];\n break;\n }\n }\n if (validation.format) {\n if (typeof validation.format == 'string') {\n validation.format = new RegExp(validation.format);\n }\n if (!validation.format.test(tempValue[j])) {\n result = [this.getValidationMessage(validation, 'format', 'string_invalid_format'), validation.format];\n break;\n }\n }\n break;\n\n case 'number':\n if (tempValue[j] === undefined || tempValue[j].length === 0) {\n if (!validation.allow_empty_value) {\n result = ['number_nan'];\n }\n break;\n }\n if (isNaN(tempValue[j])) {\n result = ['number_nan'];\n break;\n }\n if (filter.type == 'integer') {\n if (parseInt(tempValue[j]) != tempValue[j]) {\n result = ['number_not_integer'];\n break;\n }\n }\n else {\n if (parseFloat(tempValue[j]) != tempValue[j]) {\n result = ['number_not_double'];\n break;\n }\n }\n if (validation.min !== undefined) {\n if (tempValue[j] < parseFloat(validation.min)) {\n result = [this.getValidationMessage(validation, 'min', 'number_exceed_min'), validation.min];\n break;\n }\n }\n if (validation.max !== undefined) {\n if (tempValue[j] > parseFloat(validation.max)) {\n result = [this.getValidationMessage(validation, 'max', 'number_exceed_max'), validation.max];\n break;\n }\n }\n if (validation.step !== undefined && validation.step !== 'any') {\n var v = (tempValue[j] / validation.step).toPrecision(14);\n if (parseInt(v) != v) {\n result = [this.getValidationMessage(validation, 'step', 'number_wrong_step'), validation.step];\n break;\n }\n }\n break;\n\n case 'datetime':\n if (tempValue[j] === undefined || tempValue[j].length === 0) {\n if (!validation.allow_empty_value) {\n result = ['datetime_empty'];\n }\n break;\n }\n\n // we need Luxon //***\n if (validation.format) {\n if (!('luxon' in window)) {\n Utils.error('MissingLibrary', 'Luxon is required for Date/Time validation.'); //***\n }\n\n var datetime = DateTime.fromFormat(tempValue[j], validation.format, validation.options); //***\n if (!datetime.isValid()) {\n result = [this.getValidationMessage(validation, 'format', 'datetime_invalid'), validation.format];\n break;\n }\n else {\n if (validation.min) {\n if (datetime < DateTime.fromFormat(validation.min, validation.format, validation.options)) { //***\n result = [this.getValidationMessage(validation, 'min', 'datetime_exceed_min'), validation.min];\n break;\n }\n }\n if (validation.max) {\n if (datetime > DateTime.fromFormat(validation.max, validation.format, validation.options)) { //***\n result = [this.getValidationMessage(validation, 'max', 'datetime_exceed_max'), validation.max];\n break;\n }\n }\n }\n }\n break;\n\n case 'boolean':\n if (tempValue[j] === undefined || tempValue[j].length === 0) {\n if (!validation.allow_empty_value) {\n result = ['boolean_not_valid'];\n }\n break;\n }\n tmp = ('' + tempValue[j]).trim().toLowerCase();\n if (tmp !== 'true' && tmp !== 'false' &&\n tmp !== 't' && tmp !== 'f' && tmp !== 'y' && tmp !== 'n' && //***\n tmp !== '1' && tmp !== '0' && tempValue[j] !== 1 && tempValue[j] !== 0) {\n result = ['boolean_not_valid'];\n break;\n }\n }\n\n if (result !== true) {\n break;\n }\n }\n }\n\n if (result !== true) {\n break;\n }\n }\n\n if ((rule.operator.type === 'between' || rule.operator.type === 'not_between') && value.length === 2) {\n switch (QueryBuilder.types[filter.type]) {\n case 'number':\n if (value[0] > value[1]) {\n result = ['number_between_invalid', value[0], value[1]];\n }\n break;\n\n case 'datetime':\n // we need Luxon //***\n if (validation.format) {\n if (!('luxon' in window)) {\n Utils.error('MissingLibrary', 'Luxon is required for Date/Time validation.'); //***\n }\n\n if (DateTime.fromFormat(value[0], validation.format, validation.options) > DateTime.fromFormat(value[1], validation.format, validation.options)) { //***\n result = ['datetime_between_invalid', value[0], value[1]];\n }\n }\n break;\n }\n }\n\n return result;\n};\n\n/**\n * Returns an incremented group ID\n * @returns {string}\n * @private\n */\nQueryBuilder.prototype.nextGroupId = function() {\n return this.status.id + '_group_' + (this.status.group_id++);\n};\n\n/**\n * Returns an incremented rule ID\n * @returns {string}\n * @private\n */\nQueryBuilder.prototype.nextRuleId = function() {\n return this.status.id + '_rule_' + (this.status.rule_id++);\n};\n\n/**\n * Returns the operators for a filter\n * @param {string|object} filter - filter id or filter object\n * @returns {object[]}\n * @fires QueryBuilder.changer:getOperators\n */\nQueryBuilder.prototype.getOperators = function(filter) {\n if (typeof filter == 'string') {\n filter = this.getFilterById(filter);\n }\n\n var result = [];\n\n for (var i = 0, l = this.operators.length; i < l; i++) {\n // filter operators check\n if (filter.operators) {\n if (filter.operators.indexOf(this.operators[i].type) == -1) {\n continue;\n }\n }\n // type check\n else if (this.operators[i].apply_to.indexOf(QueryBuilder.types[filter.type]) == -1) {\n continue;\n }\n\n result.push(this.operators[i]);\n }\n\n // keep sort order defined for the filter\n if (filter.operators) {\n result.sort(function(a, b) {\n return filter.operators.indexOf(a.type) - filter.operators.indexOf(b.type);\n });\n }\n\n /**\n * Modifies the operators available for a filter\n * @event changer:getOperators\n * @memberof QueryBuilder\n * @param {QueryBuilder.Operator[]} operators\n * @param {QueryBuilder.Filter} filter\n * @returns {QueryBuilder.Operator[]}\n */\n return this.change('getOperators', result, filter);\n};\n\n/**\n * Returns a particular filter by its id\n * @param {string} id\n * @param {boolean} [doThrow=true]\n * @returns {object|null}\n * @throws UndefinedFilterError\n */\nQueryBuilder.prototype.getFilterById = function(id, doThrow) {\n if (id == '-1') {\n return null;\n }\n\n for (var i = 0, l = this.filters.length; i < l; i++) {\n if (this.filters[i].id == id) {\n return this.filters[i];\n }\n }\n\n Utils.error(doThrow !== false, 'UndefinedFilter', 'Undefined filter \"{0}\"', id);\n\n return null;\n};\n\n/**\n * Returns a particular operator by its type\n * @param {string} type\n * @param {boolean} [doThrow=true]\n * @returns {object|null}\n * @throws UndefinedOperatorError\n */\nQueryBuilder.prototype.getOperatorByType = function(type, doThrow) {\n if (type == '-1') {\n return null;\n }\n\n for (var i = 0, l = this.operators.length; i < l; i++) {\n if (this.operators[i].type == type) {\n return this.operators[i];\n }\n }\n\n Utils.error(doThrow !== false, 'UndefinedOperator', 'Undefined operator \"{0}\"', type);\n\n return null;\n};\n\n/**\n * Returns rule's current input value\n * @param {Rule} rule\n * @returns {*}\n * @fires QueryBuilder.changer:getRuleValue\n * @private\n */\nQueryBuilder.prototype.getRuleInputValue = function(rule) {\n var filter = rule.filter;\n var operator = rule.operator;\n var value = [];\n\n if (filter.valueGetter) {\n value = filter.valueGetter.call(this, rule);\n }\n else {\n var $value = rule.$el.find(QueryBuilder.selectors.value_container);\n\n for (var i = 0; i < operator.nb_inputs; i++) {\n var name = Utils.escapeElementId(rule.id + '_value_' + i);\n var tmp;\n\n switch (filter.input) {\n case 'radio':\n value.push($value.find('[name=' + name + ']:checked').val());\n break;\n\n case 'checkbox':\n tmp = [];\n $value.find('[name=' + name + ']:checked').each(function() {\n tmp.push($(this).val());\n });\n value.push(tmp);\n break;\n\n case 'select':\n if (filter.multiple) {\n tmp = [];\n $value.find('[name=' + name + '] option:selected').each(function() {\n tmp.push($(this).val());\n });\n value.push(tmp);\n }\n else {\n value.push($value.find('[name=' + name + '] option:selected').val());\n }\n break;\n\n default:\n value.push($value.find('[name=' + name + ']').val());\n }\n }\n\n value = value.map(function(val) {\n if (operator.multiple && filter.value_separator && typeof val == 'string') {\n val = val.split(filter.value_separator);\n }\n\n if (Array.isArray(val)) {\n return val.map(function(subval) {\n return Utils.changeType(subval, filter); //***\n });\n }\n else {\n return Utils.changeType(val, filter); //***\n }\n });\n\n if (operator.nb_inputs === 1) {\n value = value[0];\n }\n\n // @deprecated\n // if (filter.valueParser) {\n // value = filter.valueParser.call(this, rule, value);\n // }\n }\n\n /**\n * Modifies the rule's value grabbed from the DOM\n * @event changer:getRuleValue\n * @memberof QueryBuilder\n * @param {*} value\n * @param {Rule} rule\n * @returns {*}\n */\n return this.change('getRuleValue', value, rule);\n};\n\n/**\n * Sets the value of a rule's input\n * @param {Rule} rule\n * @param {*} value\n * @private\n */\nQueryBuilder.prototype.setRuleInputValue = function(rule, value) {\n var filter = rule.filter;\n var operator = rule.operator;\n\n if (!filter || !operator) {\n return;\n }\n\n rule._updating_input = true;\n\n if (filter.valueSetter) {\n filter.valueSetter.call(this, rule, value);\n }\n else {\n var $value = rule.$el.find(QueryBuilder.selectors.value_container);\n\n if (operator.nb_inputs == 1) {\n value = [value];\n }\n\n for (var i = 0; i < operator.nb_inputs; i++) {\n var name = Utils.escapeElementId(rule.id + '_value_' + i);\n\n switch (filter.input) {\n case 'radio':\n $value.find('[name=' + name + '][value=\"' + value[i] + '\"]').prop('checked', true).trigger('change');\n break;\n\n case 'checkbox':\n if (!Array.isArray(value[i])) {\n value[i] = [value[i]];\n }\n value[i].forEach(function(value) {\n $value.find('[name=' + name + '][value=\"' + value + '\"]').prop('checked', true).trigger('change');\n });\n break;\n\n default:\n if (operator.multiple && filter.value_separator && Array.isArray(value[i])) {\n value[i] = value[i].join(filter.value_separator);\n }\n $value.find('[name=' + name + ']').val(value[i]).trigger('change');\n break;\n }\n }\n }\n\n rule._updating_input = false;\n};\n\n/**\n * Parses rule flags\n * @param {object} rule\n * @returns {object}\n * @fires QueryBuilder.changer:parseRuleFlags\n * @private\n */\nQueryBuilder.prototype.parseRuleFlags = function(rule) {\n var flags = $.extend({}, this.settings.default_rule_flags);\n\n if (rule.readonly) {\n $.extend(flags, {\n filter_readonly: true,\n operator_readonly: true,\n value_readonly: true,\n no_delete: true\n });\n }\n\n if (rule.flags) {\n $.extend(flags, rule.flags);\n }\n\n /**\n * Modifies the consolidated rule's flags\n * @event changer:parseRuleFlags\n * @memberof QueryBuilder\n * @param {object} flags\n * @param {object} rule - not a Rule object\n * @returns {object}\n */\n return this.change('parseRuleFlags', flags, rule);\n};\n\n/**\n * Gets a copy of flags of a rule\n * @param {object} flags\n * @param {boolean} [all=false] - return all flags or only changes from default flags\n * @returns {object}\n * @private\n */\nQueryBuilder.prototype.getRuleFlags = function(flags, all) {\n if (all) {\n return $.extend({}, flags);\n }\n else {\n var ret = {};\n $.each(this.settings.default_rule_flags, function(key, value) {\n if (flags[key] !== value) {\n ret[key] = flags[key];\n }\n });\n return ret;\n }\n};\n\n/**\n * Parses group flags\n * @param {object} group\n * @returns {object}\n * @fires QueryBuilder.changer:parseGroupFlags\n * @private\n */\nQueryBuilder.prototype.parseGroupFlags = function(group) {\n var flags = $.extend({}, this.settings.default_group_flags);\n\n if (group.readonly) {\n $.extend(flags, {\n condition_readonly: true,\n no_add_rule: true,\n no_add_group: true,\n no_delete: true\n });\n }\n\n if (group.flags) {\n $.extend(flags, group.flags);\n }\n\n /**\n * Modifies the consolidated group's flags\n * @event changer:parseGroupFlags\n * @memberof QueryBuilder\n * @param {object} flags\n * @param {object} group - not a Group object\n * @returns {object}\n */\n return this.change('parseGroupFlags', flags, group);\n};\n\n/**\n * Gets a copy of flags of a group\n * @param {object} flags\n * @param {boolean} [all=false] - return all flags or only changes from default flags\n * @returns {object}\n * @private\n */\nQueryBuilder.prototype.getGroupFlags = function(flags, all) {\n if (all) {\n return $.extend({}, flags);\n }\n else {\n var ret = {};\n $.each(this.settings.default_group_flags, function(key, value) {\n if (flags[key] !== value) {\n ret[key] = flags[key];\n }\n });\n return ret;\n }\n};\n\n/**\n * Translate a label either by looking in the `lang` object or in itself if it's an object where keys are language codes\n * @param {string} [category]\n * @param {string|object} key\n * @returns {string}\n * @fires QueryBuilder.changer:translate\n */\nQueryBuilder.prototype.translate = function(category, key) {\n if (key === undefined) { //***\n key = category;\n category = undefined;\n }\n\n var translation;\n if (typeof key === 'object') {\n translation = key[this.settings.lang_code] || key['en'];\n }\n else {\n translation = (category ? this.lang[category] : this.lang)[key] || key;\n }\n\n /**\n * Modifies the translated label\n * @event changer:translate\n * @memberof QueryBuilder\n * @param {string} translation\n * @param {string|object} key\n * @param {string} [category]\n * @returns {string}\n */\n return this.change('translate', translation, key, category);\n};\n\n/**\n * Returns a validation message\n * @param {object} validation\n * @param {string} type\n * @param {string} def\n * @returns {string}\n * @private\n */\nQueryBuilder.prototype.getValidationMessage = function(validation, type, def) {\n return validation.messages && validation.messages[type] || def;\n};\n\n\nQueryBuilder.templates.group = ({ group_id, level, conditions, icons, settings, translate, builder }) => {\n return `\n
\n
\n ${level > 1 ? `\n \n ` : ''}\n
\\\n \n ${settings.allow_groups === -1 || settings.allow_groups >= level ? `\n \n ` : ''}\n
\n
\n ${conditions.map((condition, i) => `\n \\\n \n `).join('\\n')}\n
\n ${settings.display_errors ? `\n
\n ` : ''}\n
\n
\n
\n
\n
`;\n};\n\nQueryBuilder.templates.rule = ({ rule_id, icons, settings, translate, builder }) => {\n return `\n
\n
\n
\n \n
\n
\n
\n
\n
\n
`;\n};\n\nQueryBuilder.templates.filterSelect = ({ rule, filters, icons, settings, translate, builder }) => {\n let optgroup = null;\n return `\n`;\n};\n\nQueryBuilder.templates.operatorSelect = ({ rule, operators, icons, settings, translate, builder }) => {\n let optgroup = null;\n return `\n${operators.length === 1 ? `\n\n${translate(\"operators\", operators[0].type)}\n\n` : ''}\n${operators.length > 1 ? `\n\n` : ''}`;\n};\n\nQueryBuilder.templates.ruleValueSelect = ({ name, rule, icons, settings, translate, builder }) => {\n let optgroup = null;\n return `\n `;\n};\n\nQueryBuilder.templates.ruleValueCheck = ({ name, rule, icons, settings, translate, builder }) => rule.filter.values.map((entry, i) => `\n
\n \n \n
\n`).join('');\n\n/**\n * Returns group's HTML\n * @param {string} group_id\n * @param {int} level\n * @returns {string}\n * @fires QueryBuilder.changer:getGroupTemplate\n * @private\n */\nQueryBuilder.prototype.getGroupTemplate = function(group_id, level) {\n var h = this.templates.group({\n builder: this,\n group_id: group_id,\n level: level,\n conditions: this.settings.conditions,\n icons: this.icons,\n settings: this.settings,\n translate: this.translate.bind(this)\n }).trim();\n\n /**\n * Modifies the raw HTML of a group\n * @event changer:getGroupTemplate\n * @memberof QueryBuilder\n * @param {string} html\n * @param {int} level\n * @returns {string}\n */\n return this.change('getGroupTemplate', h, level);\n};\n\n/**\n * Returns rule's HTML\n * @param {string} rule_id\n * @returns {string}\n * @fires QueryBuilder.changer:getRuleTemplate\n * @private\n */\nQueryBuilder.prototype.getRuleTemplate = function(rule_id) {\n var h = this.templates.rule({\n builder: this,\n rule_id: rule_id,\n icons: this.icons,\n settings: this.settings,\n translate: this.translate.bind(this)\n }).trim();\n\n /**\n * Modifies the raw HTML of a rule\n * @event changer:getRuleTemplate\n * @memberof QueryBuilder\n * @param {string} html\n * @returns {string}\n */\n return this.change('getRuleTemplate', h);\n};\n\n/**\n * Returns rule's filter HTML\n * @param {Rule} rule\n * @param {object[]} filters\n * @returns {string}\n * @fires QueryBuilder.changer:getRuleFilterTemplate\n * @private\n */\nQueryBuilder.prototype.getRuleFilterSelect = function(rule, filters) {\n var h = this.templates.filterSelect({\n builder: this,\n rule: rule,\n filters: filters,\n icons: this.icons,\n settings: this.settings,\n translate: this.translate.bind(this)\n }).trim();\n\n /**\n * Modifies the raw HTML of the rule's filter dropdown\n * @event changer:getRuleFilterSelect\n * @memberof QueryBuilder\n * @param {string} html\n * @param {Rule} rule\n * @param {QueryBuilder.Filter[]} filters\n * @returns {string}\n */\n return this.change('getRuleFilterSelect', h, rule, filters);\n};\n\n/**\n * Returns rule's operator HTML\n * @param {Rule} rule\n * @param {object[]} operators\n * @returns {string}\n * @fires QueryBuilder.changer:getRuleOperatorTemplate\n * @private\n */\nQueryBuilder.prototype.getRuleOperatorSelect = function(rule, operators) {\n var h = this.templates.operatorSelect({\n builder: this,\n rule: rule,\n operators: operators,\n icons: this.icons,\n settings: this.settings,\n translate: this.translate.bind(this)\n }).trim();\n\n /**\n * Modifies the raw HTML of the rule's operator dropdown\n * @event changer:getRuleOperatorSelect\n * @memberof QueryBuilder\n * @param {string} html\n * @param {Rule} rule\n * @param {QueryBuilder.Operator[]} operators\n * @returns {string}\n */\n return this.change('getRuleOperatorSelect', h, rule, operators);\n};\n\n/**\n * Returns the rule's value select HTML\n * @param {string} name\n * @param {Rule} rule\n * @returns {string}\n * @fires QueryBuilder.changer:getRuleValueSelect\n * @private\n */\nQueryBuilder.prototype.getRuleValueSelect = function(name, rule) {\n var h = this.templates.ruleValueSelect({\n builder: this,\n name: name,\n rule: rule,\n icons: this.icons,\n settings: this.settings,\n translate: this.translate.bind(this)\n }).trim();\n\n /**\n * Modifies the raw HTML of the rule's value dropdown (in case of a \"select\" filter)\n * @event changer:getRuleValueSelect\n * @memberof QueryBuilder\n * @param {string} html\n * @param [string} name\n * @param {Rule} rule\n * @returns {string}\n */\n return this.change('getRuleValueSelect', h, name, rule);\n};\n\n/**\n * Returns the rule's value check/radio HTML\n * @param {string} name\n * @param {Rule} rule\n * @returns {string}\n * @fires QueryBuilder.changer:getRuleValueCheck\n * @private\n */\n QueryBuilder.prototype.getRuleValueCheck = function(name, rule) { //***\n var h = this.templates.ruleValueCheck({\n builder: this,\n name: name,\n rule: rule,\n icons: this.icons,\n settings: this.settings,\n translate: this.translate.bind(this)\n });\n\n /**\n * Modifies the raw HTML of the rule's value dropdown (in case of a \"checkbox\" or \"radio\" filter)\n * @event changer:getRuleValueCheck\n * @memberof QueryBuilder\n * @param {string} html\n * @param [string} name\n * @param {Rule} rule\n * @returns {string}\n */\n return this.change('getRuleValueCheck', h, name, rule);\n};\n\n/**\n * Returns the rule's value HTML\n * @param {Rule} rule\n * @param {int} value_id\n * @returns {string}\n * @fires QueryBuilder.changer:getRuleInput\n * @private\n */\nQueryBuilder.prototype.getRuleInput = function(rule, value_id) {\n var filter = rule.filter;\n var validation = rule.filter.validation || {};\n var name = rule.id + '_value_' + value_id;\n // var c = filter.vertical ? ' class=block' : '';\n var h = '';\n var placeholder = Array.isArray(filter.placeholder) ? filter.placeholder[value_id] : filter.placeholder;\n\n if (typeof filter.input == 'function') {\n h = filter.input.call(this, rule, name);\n }\n else {\n switch (filter.input) {\n case 'radio':\n case 'checkbox':\n h = this.getRuleValueCheck(name, rule); //***\n break;\n\n case 'select':\n h = this.getRuleValueSelect(name, rule);\n break;\n\n case 'textarea':\n h += '