diff --git a/webapp/src/main/webapp/templates/freemarker/body/search/search-error.ftl b/webapp/src/main/webapp/templates/freemarker/body/search/search-error.ftl deleted file mode 100644 index 3f08f55d..00000000 --- a/webapp/src/main/webapp/templates/freemarker/body/search/search-error.ftl +++ /dev/null @@ -1,16 +0,0 @@ -<#-- $This file is distributed under the terms of the license in LICENSE$ --> - -<#-- Template for displaying search error message --> - -<#if title??> -
-

${title?html}

-
- -
-

- ${message?html} -

-
-<#include "searchSelector.ftl"> -<#include "search-help.ftl" > diff --git a/webapp/src/main/webapp/templates/freemarker/body/search/search-help.ftl b/webapp/src/main/webapp/templates/freemarker/body/search/search-help.ftl deleted file mode 100644 index e892f96d..00000000 --- a/webapp/src/main/webapp/templates/freemarker/body/search/search-help.ftl +++ /dev/null @@ -1,49 +0,0 @@ -<#-- $This file is distributed under the terms of the license in LICENSE$ --> - -<#if origination?has_content && origination == "helpLink"> -
-

Search Tips

-
- - Back to results - -<#else> -
-

Search Tips

-
- - - -
-

Advanced Tips

-
- - -${stylesheets.add('')} - diff --git a/webapp/src/main/webapp/templates/freemarker/body/search/search-pagedResults.ftl b/webapp/src/main/webapp/templates/freemarker/body/search/search-pagedResults.ftl deleted file mode 100644 index 83c45e9f..00000000 --- a/webapp/src/main/webapp/templates/freemarker/body/search/search-pagedResults.ftl +++ /dev/null @@ -1,252 +0,0 @@ -<#-- $This file is distributed under the terms of the license in LICENSE$ --> - -<#-- Template for displaying paged search results --> - -

- -<#include "searchSelector.ftl"> - -<#escape x as x?html> -
${i18n().search_results_for} '${querytext}'
-
<#if classGroupName?has_content>${i18n().limited_to_type} '${classGroupName}'
-
<#if typeName?has_content>${i18n().limited_to_type} '${typeName}'
- - - - ${i18n().download_results} -<#-- --> -

- -${i18n().not_expected_results} -
- - <#-- Refinement links --> - <#if classGroupLinks?has_content && classGroupLinks?size gt 1> -
-

${i18n().display_only}

- -
- - - <#if classLinks?has_content && classLinks?size gt 1 > -
- <#if classGroupName?has_content> -

${i18n().limit} ${classGroupName} ${i18n().to}

- <#else> -

${i18n().limit_to}

- - -
- - - -
- - <#if user.loggedIn> - - -
- - - - <#-- Search results --> - - - - <#-- Paging controls --> - <#if (pagingLinks?size > 0)> -
- ${i18n().pages}: - <#if prevPage??> - <#list pagingLinks as link> - <#if link.url??> - ${link.text} - <#else> - ${link.text} <#-- no link if current page --> - - - <#if nextPage??> -
- -
- - <#-- VIVO OpenSocial Extension by UCSF --> - <#if openSocial??> - <#if openSocial.visible> -

OpenSocial

- - - - - - -
- - - -${stylesheets.add('', - '', - '')} - -${headScripts.add('', - '', - '' - )} - -${scripts.add('')} diff --git a/webapp/src/main/webapp/themes/iph/css/iph.css b/webapp/src/main/webapp/themes/iph/css/iph.css index 637c7189..c020011a 100644 --- a/webapp/src/main/webapp/themes/iph/css/iph.css +++ b/webapp/src/main/webapp/themes/iph/css/iph.css @@ -35,10 +35,10 @@ body { max-width: 1920px; width: 100%; height: 200px; - display: flex; + /*display: flex; flex-direction: column; flex-wrap: nowrap; - justify-content: center; + justify-content: center;*/ } a { color: #000000; @@ -584,7 +584,11 @@ ul#header-nav a.log-out { margin-left: auto; margin-right: auto; } -#switch-to-extended-searchform { +#goToExtendedSearchForm { + width: 100%; +} + +#goToExtendedSearch { border:none; background-color:unset; color: #8B4513; @@ -597,6 +601,7 @@ ul#header-nav a.log-out { font-family: "Roboto"; font-style: normal; } + #custom-search { width: 100%; display: flex; @@ -1194,7 +1199,6 @@ p.external-auth a.blue:hover { /* HOME SEARCH ------> */ #search-home { /*float: left;*/ - margin-bottom: 30px; margin-top: 10px; width: 100%; display: flex; diff --git a/webapp/src/main/webapp/themes/iph/js/query-builder-config.js b/webapp/src/main/webapp/themes/iph/js/query-builder-config.js deleted file mode 100644 index e69de29b..00000000 diff --git a/webapp/src/main/webapp/themes/iph/js/query-builder.ru.js b/webapp/src/main/webapp/themes/iph/js/query-builder.ru.js deleted file mode 100644 index c53708b7..00000000 --- a/webapp/src/main/webapp/themes/iph/js/query-builder.ru.js +++ /dev/null @@ -1,77 +0,0 @@ -/*! - * jQuery QueryBuilder 2.5.2 - * Locale: Russian (ru) - * Licensed under MIT (https://opensource.org/licenses/MIT) - */ - -(function(root, factory) { - if (typeof define == 'function' && define.amd) { - define(['jquery', 'query-builder'], factory); - } - else { - factory(root.jQuery); - } -}(this, function($) { -"use strict"; - -var QueryBuilder = $.fn.queryBuilder; - -QueryBuilder.regional['ru'] = { - "__locale": "Russian (ru)", - "add_rule": "Добавить условие", - "add_group": "Добавить группу", - "delete_rule": "Удалить", - "delete_group": "Удалить", - "conditions": { - "AND": "И", - "OR": "ИЛИ" - }, - "operators": { - "equal": "равно", - "not_equal": "не равно", - "in": "из указанных", - "not_in": "не из указанных", - "less": "меньше", - "less_or_equal": "меньше или равно", - "greater": "больше", - "greater_or_equal": "больше или равно", - "between": "между", - "begins_with": "начинается с", - "not_begins_with": "не начинается с", - "contains": "содержит", - "not_contains": "не содержит", - "ends_with": "оканчивается на", - "not_ends_with": "не оканчивается на", - "is_empty": "пустая строка", - "is_not_empty": "не пустая строка", - "is_null": "пусто", - "is_not_null": "не пусто" - }, - "errors": { - "no_filter": "Фильтр не выбран", - "empty_group": "Группа пуста", - "radio_empty": "Не выбранно значение", - "checkbox_empty": "Не выбранно значение", - "select_empty": "Не выбранно значение", - "string_empty": "Не заполненно", - "string_exceed_min_length": "Должен содержать больше {0} символов", - "string_exceed_max_length": "Должен содержать меньше {0} символов", - "string_invalid_format": "Неверный формат ({0})", - "number_nan": "Не число", - "number_not_integer": "Не число", - "number_not_double": "Не число", - "number_exceed_min": "Должно быть больше {0}", - "number_exceed_max": "Должно быть меньше, чем {0}", - "number_wrong_step": "Должно быть кратно {0}", - "datetime_empty": "Не заполненно", - "datetime_invalid": "Неверный формат даты ({0})", - "datetime_exceed_min": "Должно быть, после {0}", - "datetime_exceed_max": "Должно быть, до {0}", - "boolean_not_valid": "Не логическое", - "operator_not_multiple": "Оператор \"{1}\" не поддерживает много значений" - }, - "invert": "Инвертировать" -}; - -QueryBuilder.defaults({ lang_code: 'ru' }); -})); diff --git a/webapp/src/main/webapp/themes/iph/js/query-builder.standalone.js b/webapp/src/main/webapp/themes/iph/js/query-builder.standalone.js deleted file mode 100644 index c899f05b..00000000 --- a/webapp/src/main/webapp/themes/iph/js/query-builder.standalone.js +++ /dev/null @@ -1,6477 +0,0 @@ -/*! - * jQuery.extendext 0.1.2 - * - * Copyright 2014-2016 Damien "Mistic" Sorel (http://www.strangeplanet.fr) - * Licensed under MIT (http://opensource.org/licenses/MIT) - * - * Based on jQuery.extend by jQuery Foundation, Inc. and other contributors - */ - -(function (root, factory) { - if (typeof define === 'function' && define.amd) { - define('jQuery.extendext', ['jquery'], factory); - } - else if (typeof module === 'object' && module.exports) { - module.exports = factory(require('jquery')); - } - else { - factory(root.jQuery); - } -}(this, function ($) { - "use strict"; - - $.extendext = function () { - var options, name, src, copy, copyIsArray, clone, - target = arguments[0] || {}, - i = 1, - length = arguments.length, - deep = false, - arrayMode = 'default'; - - // Handle a deep copy situation - if (typeof target === "boolean") { - deep = target; - - // Skip the boolean and the target - target = arguments[i++] || {}; - } - - // Handle array mode parameter - if (typeof target === "string") { - arrayMode = target.toLowerCase(); - if (arrayMode !== 'concat' && arrayMode !== 'replace' && arrayMode !== 'extend') { - arrayMode = 'default'; - } - - // Skip the string param - target = arguments[i++] || {}; - } - - // Handle case when target is a string or something (possible in deep copy) - if (typeof target !== "object" && !$.isFunction(target)) { - target = {}; - } - - // Extend jQuery itself if only one argument is passed - if (i === length) { - target = this; - i--; - } - - for (; i < length; i++) { - // Only deal with non-null/undefined values - if ((options = arguments[i]) !== null) { - // Special operations for arrays - if ($.isArray(options) && arrayMode !== 'default') { - clone = target && $.isArray(target) ? target : []; - - switch (arrayMode) { - case 'concat': - target = clone.concat($.extend(deep, [], options)); - break; - - case 'replace': - target = $.extend(deep, [], options); - break; - - case 'extend': - options.forEach(function (e, i) { - if (typeof e === 'object') { - var type = $.isArray(e) ? [] : {}; - clone[i] = $.extendext(deep, arrayMode, clone[i] || type, e); - - } else if (clone.indexOf(e) === -1) { - clone.push(e); - } - }); - - target = clone; - break; - } - - } else { - // Extend the base object - for (name in options) { - src = target[name]; - copy = options[name]; - - // Prevent never-ending loop - if (target === copy) { - continue; - } - - // Recurse if we're merging plain objects or arrays - if (deep && copy && ( $.isPlainObject(copy) || - (copyIsArray = $.isArray(copy)) )) { - - if (copyIsArray) { - copyIsArray = false; - clone = src && $.isArray(src) ? src : []; - - } else { - clone = src && $.isPlainObject(src) ? src : {}; - } - - // Never move original objects, clone them - target[name] = $.extendext(deep, arrayMode, clone, copy); - - // Don't bring in undefined values - } else if (copy !== undefined) { - target[name] = copy; - } - } - } - } - } - - // Return the modified object - return target; - }; -})); - -// doT.js -// 2011-2014, Laura Doktorova, https://github.com/olado/doT -// Licensed under the MIT license. - -(function () { - "use strict"; - - var doT = { - name: "doT", - version: "1.1.1", - templateSettings: { - evaluate: /\{\{([\s\S]+?(\}?)+)\}\}/g, - interpolate: /\{\{=([\s\S]+?)\}\}/g, - encode: /\{\{!([\s\S]+?)\}\}/g, - use: /\{\{#([\s\S]+?)\}\}/g, - useParams: /(^|[^\w$])def(?:\.|\[[\'\"])([\w$\.]+)(?:[\'\"]\])?\s*\:\s*([\w$\.]+|\"[^\"]+\"|\'[^\']+\'|\{[^\}]+\})/g, - define: /\{\{##\s*([\w\.$]+)\s*(\:|=)([\s\S]+?)#\}\}/g, - defineParams:/^\s*([\w$]+):([\s\S]+)/, - conditional: /\{\{\?(\?)?\s*([\s\S]*?)\s*\}\}/g, - iterate: /\{\{~\s*(?:\}\}|([\s\S]+?)\s*\:\s*([\w$]+)\s*(?:\:\s*([\w$]+))?\s*\}\})/g, - varname: "it", - strip: true, - append: true, - selfcontained: false, - doNotSkipEncoded: false - }, - template: undefined, //fn, compile template - compile: undefined, //fn, for express - log: true - }, _globals; - - doT.encodeHTMLSource = function(doNotSkipEncoded) { - var encodeHTMLRules = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'", "/": "/" }, - matchHTML = doNotSkipEncoded ? /[&<>"'\/]/g : /&(?!#?\w+;)|<|>|"|'|\//g; - return function(code) { - return code ? code.toString().replace(matchHTML, function(m) {return encodeHTMLRules[m] || m;}) : ""; - }; - }; - - _globals = (function(){ return this || (0,eval)("this"); }()); - - /* istanbul ignore else */ - if (typeof module !== "undefined" && module.exports) { - module.exports = doT; - } else if (typeof define === "function" && define.amd) { - define('doT', function(){return doT;}); - } else { - _globals.doT = doT; - } - - var startend = { - append: { start: "'+(", end: ")+'", startencode: "'+encodeHTML(" }, - split: { start: "';out+=(", end: ");out+='", startencode: "';out+=encodeHTML(" } - }, skip = /$^/; - - function resolveDefs(c, block, def) { - return ((typeof block === "string") ? block : block.toString()) - .replace(c.define || skip, function(m, code, assign, value) { - if (code.indexOf("def.") === 0) { - code = code.substring(4); - } - if (!(code in def)) { - if (assign === ":") { - if (c.defineParams) value.replace(c.defineParams, function(m, param, v) { - def[code] = {arg: param, text: v}; - }); - if (!(code in def)) def[code]= value; - } else { - new Function("def", "def['"+code+"']=" + value)(def); - } - } - return ""; - }) - .replace(c.use || skip, function(m, code) { - if (c.useParams) code = code.replace(c.useParams, function(m, s, d, param) { - if (def[d] && def[d].arg && param) { - var rw = (d+":"+param).replace(/'|\\/g, "_"); - def.__exp = def.__exp || {}; - def.__exp[rw] = def[d].text.replace(new RegExp("(^|[^\\w$])" + def[d].arg + "([^\\w$])", "g"), "$1" + param + "$2"); - return s + "def.__exp['"+rw+"']"; - } - }); - var v = new Function("def", "return " + code)(def); - return v ? resolveDefs(c, v, def) : v; - }); - } - - function unescape(code) { - return code.replace(/\\('|\\)/g, "$1").replace(/[\r\t\n]/g, " "); - } - - doT.template = function(tmpl, c, def) { - c = c || doT.templateSettings; - var cse = c.append ? startend.append : startend.split, needhtmlencode, sid = 0, indv, - str = (c.use || c.define) ? resolveDefs(c, tmpl, def || {}) : tmpl; - - str = ("var out='" + (c.strip ? str.replace(/(^|\r|\n)\t* +| +\t*(\r|\n|$)/g," ") - .replace(/\r|\n|\t|\/\*[\s\S]*?\*\//g,""): str) - .replace(/'|\\/g, "\\$&") - .replace(c.interpolate || skip, function(m, code) { - return cse.start + unescape(code) + cse.end; - }) - .replace(c.encode || skip, function(m, code) { - needhtmlencode = true; - return cse.startencode + unescape(code) + cse.end; - }) - .replace(c.conditional || skip, function(m, elsecase, code) { - return elsecase ? - (code ? "';}else if(" + unescape(code) + "){out+='" : "';}else{out+='") : - (code ? "';if(" + unescape(code) + "){out+='" : "';}out+='"); - }) - .replace(c.iterate || skip, function(m, iterate, vname, iname) { - if (!iterate) return "';} } out+='"; - sid+=1; indv=iname || "i"+sid; iterate=unescape(iterate); - return "';var arr"+sid+"="+iterate+";if(arr"+sid+"){var "+vname+","+indv+"=-1,l"+sid+"=arr"+sid+".length-1;while("+indv+"} - * @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(); -}; - -$.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), { - builder: this - }); - - this.$el.triggerHandler(event, Array.prototype.slice.call(arguments, 1)); - - 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); - 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); - 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); - return this; - }, - - /** - * 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(' '); - } -}); - - -/** - * Allowed types and their internal representation - * @type {object.} - * @readonly - * @private - */ -QueryBuilder.types = { - 'string': 'string', - 'integer': 'number', - 'double': 'number', - 'date': 'datetime', - 'time': 'datetime', - 'datetime': 'datetime', - 'boolean': 'boolean' -}; - -/** - * Allowed inputs - * @type {string[]} - * @readonly - * @private - */ -QueryBuilder.inputs = [ - 'text', - 'number', - 'textarea', - 'radio', - 'checkbox', - 'select' -]; - -/** - * Runtime modifiable options with `setOptions` method - * @type {string[]} - * @readonly - * @private - */ -QueryBuilder.modifiable_options = [ - 'display_errors', - 'allow_groups', - 'allow_empty', - 'default_condition', - 'default_filter' -]; - -/** - * CSS selectors for common components - * @type {object.} - * @readonly - */ -QueryBuilder.selectors = { - group_container: '.rules-group-container', - rule_container: '.rule-container', - filter_container: '.rule-filter-container', - operator_container: '.rule-operator-container', - value_container: '.rule-value-container', - error_container: '.error-container', - condition_container: '.rules-group-header .group-conditions', - - rule_header: '.rule-header', - group_header: '.rules-group-header', - group_actions: '.group-actions', - rule_actions: '.rule-actions', - - rules_list: '.rules-group-body>.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]' -}; - -/** - * Template strings (see template.js) - * @type {object.} - * @readonly - */ -QueryBuilder.templates = {}; - -/** - * 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'] }, - not_equal: { type: 'not_equal', nb_inputs: 1, multiple: false, apply_to: ['string', 'number', 'datetime', 'boolean'] }, - in: { type: 'in', nb_inputs: 1, multiple: true, apply_to: ['string', 'number', 'datetime'] }, - not_in: { type: 'not_in', nb_inputs: 1, multiple: true, apply_to: ['string', 'number', 'datetime'] }, - less: { type: 'less', nb_inputs: 1, multiple: false, apply_to: ['number', 'datetime'] }, - less_or_equal: { type: 'less_or_equal', nb_inputs: 1, multiple: false, apply_to: ['number', 'datetime'] }, - greater: { type: 'greater', nb_inputs: 1, multiple: false, apply_to: ['number', 'datetime'] }, - greater_or_equal: { type: 'greater_or_equal', nb_inputs: 1, multiple: false, apply_to: ['number', 'datetime'] }, - between: { type: 'between', nb_inputs: 2, multiple: false, apply_to: ['number', 'datetime'] }, - not_between: { type: 'not_between', nb_inputs: 2, multiple: false, apply_to: ['number', 'datetime'] }, - begins_with: { type: 'begins_with', nb_inputs: 1, multiple: false, apply_to: ['string'] }, - not_begins_with: { type: 'not_begins_with', nb_inputs: 1, multiple: false, apply_to: ['string'] }, - contains: { type: 'contains', nb_inputs: 1, multiple: false, apply_to: ['string'] }, - not_contains: { type: 'not_contains', nb_inputs: 1, multiple: false, apply_to: ['string'] }, - ends_with: { type: 'ends_with', nb_inputs: 1, multiple: false, apply_to: ['string'] }, - not_ends_with: { type: 'not_ends_with', nb_inputs: 1, multiple: false, apply_to: ['string'] }, - is_empty: { type: 'is_empty', nb_inputs: 0, multiple: false, apply_to: ['string'] }, - is_not_empty: { type: 'is_not_empty', nb_inputs: 0, multiple: false, apply_to: ['string'] }, - is_null: { type: 'is_null', nb_inputs: 0, multiple: false, apply_to: ['string', 'number', 'datetime', 'boolean'] }, - is_not_null: { type: 'is_not_null', nb_inputs: 0, multiple: false, apply_to: ['string', 'number', 'datetime', 'boolean'] } -}; - -/** - * Default configuration - * @type {object} - * @readonly - */ -QueryBuilder.DEFAULTS = { - filters: [], - plugins: [], - - sort_filters: false, - display_errors: true, - allow_groups: -1, - allow_empty: false, - conditions: ['AND', 'OR'], - default_condition: 'AND', - inputs_separator: ' , ', - select_placeholder: '------', - display_empty_filter: true, - default_filter: null, - optgroups: {}, - - default_rule_flags: { - filter_readonly: false, - operator_readonly: false, - value_readonly: false, - no_delete: false - }, - - default_group_flags: { - condition_readonly: false, - no_add_rule: false, - no_add_group: false, - no_delete: false - }, - - templates: { - group: null, - rule: null, - filterSelect: null, - operatorSelect: null, - ruleValueSelect: 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' - } -}; - - -/** - * @module plugins - */ - -/** - * Definition of available plugins - * @type {object.} - */ -QueryBuilder.plugins = {}; - -/** - * Gets or extends the default configuration - * @param {object} [options] - new configuration - * @returns {undefined|object} nothing or configuration object (copy) - */ -QueryBuilder.defaults = function(options) { - if (typeof options == 'object') { - $.extendext(true, 'replace', QueryBuilder.DEFAULTS, options); - } - else if (typeof options == 'string') { - if (typeof QueryBuilder.DEFAULTS[options] == 'object') { - return $.extend(true, {}, QueryBuilder.DEFAULTS[options]); - } - else { - return QueryBuilder.DEFAULTS[options]; - } - } - else { - return $.extend(true, {}, QueryBuilder.DEFAULTS); - } -}; - -/** - * Registers a new plugin - * @param {string} name - * @param {function} fct - init function - * @param {object} [def] - default options - */ -QueryBuilder.define = function(name, fct, def) { - QueryBuilder.plugins[name] = { - fct: fct, - def: def || {} - }; -}; - -/** - * Adds new methods to QueryBuilder prototype - * @param {object.} methods - */ -QueryBuilder.extend = function(methods) { - $.extend(QueryBuilder.prototype, methods); -}; - -/** - * Initializes plugins for an instance - * @throws ConfigError - * @private - */ -QueryBuilder.prototype.initPlugins = function() { - if (!this.plugins) { - return; - } - - if ($.isArray(this.plugins)) { - var tmp = {}; - this.plugins.forEach(function(plugin) { - tmp[plugin] = null; - }); - this.plugins = tmp; - } - - Object.keys(this.plugins).forEach(function(plugin) { - if (plugin in QueryBuilder.plugins) { - this.plugins[plugin] = $.extend(true, {}, - QueryBuilder.plugins[plugin].def, - this.plugins[plugin] || {} - ); - - QueryBuilder.plugins[plugin].fct.call(this, this.plugins[plugin]); - } - else { - Utils.error('Config', 'Unable to find plugin "{0}"', plugin); - } - }, this); -}; - -/** - * Returns the config of a plugin, if the plugin is not loaded, returns the default config. - * @param {string} name - * @param {string} [property] - * @throws ConfigError - * @returns {*} - */ -QueryBuilder.prototype.getPluginOptions = function(name, property) { - var plugin; - if (this.plugins && this.plugins[name]) { - plugin = this.plugins[name]; - } - else if (QueryBuilder.plugins[name]) { - plugin = QueryBuilder.plugins[name].def; - } - - if (plugin) { - if (property) { - return plugin[property]; - } - else { - return plugin; - } - } - else { - Utils.error('Config', 'Unable to find plugin "{0}"', name); - } -}; - - -/** - * Final initialisation of the builder - * @param {object} [rules] - * @fires QueryBuilder.afterInit - * @private - */ -QueryBuilder.prototype.init = function(rules) { - /** - * When the initilization is done, just before creating the root group - * @event afterInit - * @memberof QueryBuilder - */ - this.trigger('afterInit'); - - if (rules) { - this.setRules(rules); - delete this.settings.rules; - } - else { - this.setRoot(true); - } -}; - -/** - * Checks the configuration of each filter - * @param {QueryBuilder.Filter[]} filters - * @returns {QueryBuilder.Filter[]} - * @throws ConfigError - */ -QueryBuilder.prototype.checkFilters = function(filters) { - var definedFilters = []; - - if (!filters || filters.length === 0) { - Utils.error('Config', 'Missing filters list'); - } - - filters.forEach(function(filter, i) { - if (!filter.id) { - Utils.error('Config', 'Missing filter {0} id', i); - } - if (definedFilters.indexOf(filter.id) != -1) { - Utils.error('Config', 'Filter "{0}" already defined', filter.id); - } - definedFilters.push(filter.id); - - if (!filter.type) { - filter.type = 'string'; - } - else if (!QueryBuilder.types[filter.type]) { - Utils.error('Config', 'Invalid type "{0}"', filter.type); - } - - if (!filter.input) { - 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); - } - - if (filter.operators) { - filter.operators.forEach(function(operator) { - if (typeof operator != 'string') { - Utils.error('Config', 'Filter operators must be global operators types (string)'); - } - }); - } - - if (!filter.field) { - filter.field = filter.id; - } - if (!filter.label) { - filter.label = filter.field; - } - - if (!filter.optgroup) { - filter.optgroup = null; - } - else { - this.status.has_optgroup = true; - - // register optgroup if needed - if (!this.settings.optgroups[filter.optgroup]) { - this.settings.optgroups[filter.optgroup] = filter.optgroup; - } - } - - switch (filter.input) { - case 'radio': - case 'checkbox': - if (!filter.values || filter.values.length < 1) { - Utils.error('Config', 'Missing filter "{0}" values', filter.id); - } - break; - - case 'select': - var cleanValues = []; - filter.has_optgroup = false; - - Utils.iterateOptions(filter.values, function(value, label, optgroup) { - cleanValues.push({ - value: value, - label: label, - optgroup: optgroup || null - }); - - if (optgroup) { - filter.has_optgroup = true; - - // register optgroup if needed - if (!this.settings.optgroups[optgroup]) { - this.settings.optgroups[optgroup] = optgroup; - } - } - }.bind(this)); - - if (filter.has_optgroup) { - filter.values = Utils.groupSort(cleanValues, 'optgroup'); - } - else { - filter.values = cleanValues; - } - - if (filter.placeholder) { - if (filter.placeholder_value === undefined) { - filter.placeholder_value = -1; - } - - filter.values.forEach(function(entry) { - if (entry.value == filter.placeholder_value) { - Utils.error('Config', 'Placeholder of filter "{0}" overlaps with one of its values', filter.id); - } - }); - } - break; - } - }, this); - - if (this.settings.sort_filters) { - if (typeof this.settings.sort_filters == 'function') { - filters.sort(this.settings.sort_filters); - } - else { - var self = this; - filters.sort(function(a, b) { - return self.translate(a.label).localeCompare(self.translate(b.label)); - }); - } - } - - if (this.status.has_optgroup) { - filters = Utils.groupSort(filters, 'optgroup'); - } - - return filters; -}; - -/** - * Checks the configuration of each operator - * @param {QueryBuilder.Operator[]} operators - * @returns {QueryBuilder.Operator[]} - * @throws ConfigError - */ -QueryBuilder.prototype.checkOperators = function(operators) { - var definedOperators = []; - - operators.forEach(function(operator, i) { - if (typeof operator == 'string') { - if (!QueryBuilder.OPERATORS[operator]) { - Utils.error('Config', 'Unknown operator "{0}"', operator); - } - - operators[i] = operator = $.extendext(true, 'replace', {}, QueryBuilder.OPERATORS[operator]); - } - else { - if (!operator.type) { - Utils.error('Config', 'Missing "type" for operator {0}', i); - } - - if (QueryBuilder.OPERATORS[operator.type]) { - operators[i] = operator = $.extendext(true, 'replace', {}, QueryBuilder.OPERATORS[operator.type], operator); - } - - if (operator.nb_inputs === undefined || operator.apply_to === undefined) { - Utils.error('Config', 'Missing "nb_inputs" and/or "apply_to" for operator "{0}"', operator.type); - } - } - - if (definedOperators.indexOf(operator.type) != -1) { - Utils.error('Config', 'Operator "{0}" already defined', operator.type); - } - definedOperators.push(operator.type); - - if (!operator.optgroup) { - operator.optgroup = null; - } - else { - this.status.has_operator_optgroup = true; - - // register optgroup if needed - if (!this.settings.optgroups[operator.optgroup]) { - this.settings.optgroups[operator.optgroup] = operator.optgroup; - } - } - }, this); - - if (this.status.has_operator_optgroup) { - operators = Utils.groupSort(operators, 'optgroup'); - } - - return operators; -}; - -/** - * 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); - 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); - 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); - 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(self.getModel($group)); - }); - - // delete rule button - this.$el.on('click.queryBuilder', Selectors.delete_rule, function() { - var $rule = $(this).closest(Selectors.rule_container); - 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(self.getModel($group)); - }); - - // delete group button - this.$el.on('click.queryBuilder', Selectors.delete_group, function() { - var $group = $(this).closest(Selectors.group_container); - self.deleteGroup(self.getModel($group)); - }); - } - - // model events - this.model.on({ - 'drop': function(e, node) { - node.$el.remove(); - self.refreshGroupsConditions(); - }, - 'add': function(e, parent, node, index) { - if (index === 0) { - node.$el.prependTo(parent.$el.find('>' + QueryBuilder.selectors.rules_list)); - } - else { - node.$el.insertAfter(parent.rules[index - 1].$el); - } - self.refreshGroupsConditions(); - }, - 'move': function(e, node, group, index) { - node.$el.detach(); - - if (index === 0) { - node.$el.prependTo(group.$el.find('>' + QueryBuilder.selectors.rules_list)); - } - else { - node.$el.insertAfter(group.rules[index - 1].$el); - } - self.refreshGroupsConditions(); - }, - 'update': function(e, node, field, value, oldValue) { - if (node instanceof Rule) { - switch (field) { - case 'error': - self.updateError(node); - break; - - case 'flags': - self.applyRuleFlags(node); - break; - - case 'filter': - self.updateRuleFilter(node, oldValue); - break; - - case 'operator': - self.updateRuleOperator(node, oldValue); - break; - - case 'value': - self.updateRuleValue(node, oldValue); - break; - } - } - else { - switch (field) { - case 'error': - self.updateError(node); - break; - - case 'flags': - self.applyGroupFlags(node); - break; - - case 'condition': - self.updateGroupCondition(node, oldValue); - break; - } - } - } - }); -}; - -/** - * 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); - - var group_id = this.nextGroupId(); - var $group = $(this.getGroupTemplate(group_id, 1)); - - this.$el.append($group); - this.model.root = new Group(null, $group); - this.model.root.model = this.model; - - this.model.root.data = data; - this.model.root.flags = $.extend({}, this.settings.default_group_flags, flags); - this.model.root.condition = this.settings.default_condition; - - this.trigger('afterAddGroup', this.model.root); - - if (addRule) { - this.addRule(this.model.root); - } - - return this.model.root; -}; - -/** - * 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; - } - - var group_id = this.nextGroupId(); - var $group = $(this.getGroupTemplate(group_id, level)); - var model = parent.addGroup($group); - - model.data = data; - model.flags = $.extend({}, this.settings.default_group_flags, flags); - model.condition = this.settings.default_condition; - - /** - * Just after adding a group - * @event afterAddGroup - * @memberof QueryBuilder - * @param {Group} group - */ - this.trigger('afterAddGroup', model); - - /** - * After any change in the rules - * @event rulesChanged - * @memberof QueryBuilder - */ - this.trigger('rulesChanged'); - - if (addRule) { - this.addRule(model); - } - - return model; -}; - -/** - * 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; - } - - var del = true; - - group.each('reverse', function(rule) { - del &= this.deleteRule(rule); - }, function(group) { - del &= this.deleteGroup(group); - }, this); - - if (del) { - group.drop(); - - /** - * Just after deleting a group - * @event afterDeleteGroup - * @memberof QueryBuilder - */ - this.trigger('afterDeleteGroup'); - - this.trigger('rulesChanged'); - } - - return del; -}; - -/** - * Performs actions when a group's condition changes - * @param {Group} group - * @param {object} previousCondition - * @fires QueryBuilder.afterUpdateGroupCondition - * @private - */ -QueryBuilder.prototype.updateGroupCondition = function(group, previousCondition) { - 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 - * @param {object} previousCondition - */ - this.trigger('afterUpdateGroupCondition', group, previousCondition); - - this.trigger('rulesChanged'); -}; - -/** - * 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('>' + QueryBuilder.selectors.group_condition).prop('disabled', group.rules.length <= 1) - .parent().toggleClass('disabled', group.rules.length <= 1); - } - - group.each(null, function(group) { - walk(group); - }, this); - }(this.model.root)); -}; - -/** - * 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; - } - - var rule_id = this.nextRuleId(); - var $rule = $(this.getRuleTemplate(rule_id)); - var model = parent.addRule($rule); - - model.data = data; - 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.trigger('rulesChanged'); - - 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 - ); - } - - return model; -}; - -/** - * 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; - } - - rule.drop(); - - /** - * Just after deleting a rule - * @event afterDeleteRule - * @memberof QueryBuilder - */ - this.trigger('afterDeleteRule'); - - this.trigger('rulesChanged'); - - return true; -}; - -/** - * Creates the filters for a rule - * @param {Rule} rule - * @fires QueryBuilder.changer:getRuleFilters - * @fires QueryBuilder.afterCreateRuleFilters - * @private - */ -QueryBuilder.prototype.createRuleFilters = function(rule) { - /** - * Modifies the list a filters available for a rule - * @event changer:getRuleFilters - * @memberof QueryBuilder - * @param {QueryBuilder.Filter[]} filters - * @param {Rule} rule - * @returns {QueryBuilder.Filter[]} - */ - var filters = this.change('getRuleFilters', this.filters, rule); - var $filterSelect = $(this.getRuleFilterSelect(rule, filters)); - - rule.$el.find(QueryBuilder.selectors.filter_container).html($filterSelect); - - /** - * After creating the dropdown for filters - * @event afterCreateRuleFilters - * @memberof QueryBuilder - * @param {Rule} rule - */ - this.trigger('afterCreateRuleFilters', rule); - - this.applyRuleFlags(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(QueryBuilder.selectors.operator_container).empty(); - - if (!rule.filter) { - return; - } - - var operators = this.getOperators(rule.filter); - var $operatorSelect = $(this.getRuleOperatorSelect(rule, operators)); - - $operatorContainer.html($operatorSelect); - - // set the operator without triggering update event - if (rule.filter.default_operator) { - rule.__.operator = this.getOperatorByType(rule.filter.default_operator); - } - else { - rule.__.operator = operators[0]; - } - - rule.$el.find(QueryBuilder.selectors.rule_operator).val(rule.operator.type); - - /** - * 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); - - this.applyRuleFlags(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(QueryBuilder.selectors.value_container).empty(); - - rule.__.value = undefined; - - if (!rule.filter || !rule.operator || rule.operator.nb_inputs === 0) { - return; - } - - var self = this; - var $inputs = $(); - var filter = rule.filter; - - for (var i = 0; i < rule.operator.nb_inputs; i++) { - var $ruleInput = $(this.getRuleInput(rule, i)); - if (i > 0) $valueContainer.append(this.settings.inputs_separator); - $valueContainer.append($ruleInput); - $inputs = $inputs.add($ruleInput); - } - - $valueContainer.css('display', ''); - - $inputs.on('change ' + (filter.input_event || ''), function() { - if (!rule._updating_input) { - rule._updating_value = true; - rule.value = self.getRuleInputValue(rule); - rule._updating_value = false; - } - }); - - if (filter.plugin) { - $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) { - rule.value = filter.default_value; - } - else { - rule._updating_value = true; - rule.value = self.getRuleInputValue(rule); - rule._updating_value = false; - } - - this.applyRuleFlags(rule); -}; - -/** - * 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(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 - * @param {object} previousFilter - */ - this.trigger('afterUpdateRuleFilter', rule, previousFilter); - - this.trigger('rulesChanged'); -}; - -/** - * 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(QueryBuilder.selectors.value_container); - - if (!rule.operator || rule.operator.nb_inputs === 0) { - $valueContainer.hide(); - - rule.__.value = undefined; - } - else { - $valueContainer.css('display', ''); - - 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(QueryBuilder.selectors.rule_operator).val(rule.operator.type); - - // refresh value if the format changed for this operator - rule.__.value = this.getRuleInputValue(rule); - } - - /** - * After the operator has been updated and the input optionally re-created - * @event afterUpdateRuleOperator - * @memberof QueryBuilder - * @param {Rule} rule - * @param {object} previousOperator - */ - this.trigger('afterUpdateRuleOperator', rule, previousOperator); - - this.trigger('rulesChanged'); -}; - -/** - * Performs actions when rule's value changes - * @param {Rule} rule - * @param {object} previousValue - * @fires QueryBuilder.afterUpdateRuleValue - * @private - */ -QueryBuilder.prototype.updateRuleValue = function(rule, previousValue) { - if (!rule._updating_value) { - this.setRuleInputValue(rule, rule.value); - } - - /** - * After the rule value has been modified - * @event afterUpdateRuleValue - * @memberof QueryBuilder - * @param {Rule} rule - * @param {*} previousValue - */ - this.trigger('afterUpdateRuleValue', rule, previousValue); - - this.trigger('rulesChanged'); -}; - -/** - * 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; - - rule.$el.find(Selectors.rule_filter).prop('disabled', flags.filter_readonly); - rule.$el.find(Selectors.rule_operator).prop('disabled', flags.operator_readonly); - rule.$el.find(Selectors.rule_value).prop('disabled', flags.value_readonly); - - if (flags.no_delete) { - 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); -}; - -/** - * 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; - - group.$el.find('>' + Selectors.group_condition).prop('disabled', flags.condition_readonly) - .parent().toggleClass('readonly', flags.condition_readonly); - - if (flags.no_add_rule) { - group.$el.find(Selectors.add_rule).remove(); - } - if (flags.no_add_group) { - group.$el.find(Selectors.add_group).remove(); - } - if (flags.no_delete) { - 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); -}; - -/** - * Clears all errors markers - * @param {Node} [node] default is root Group - */ -QueryBuilder.prototype.clearErrors = function(node) { - node = node || this.model.root; - - if (!node) { - return; - } - - node.error = null; - - if (node instanceof Group) { - node.each(function(rule) { - rule.error = null; - }, function(group) { - this.clearErrors(group); - }, this); - } -}; - -/** - * Adds/Removes error on a Rule or Group - * @param {Node} node - * @fires QueryBuilder.changer:displayError - * @private - */ -QueryBuilder.prototype.updateError = function(node) { - if (this.settings.display_errors) { - if (node.error === null) { - node.$el.removeClass('has-error'); - } - else { - 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(QueryBuilder.selectors.error_container).eq(0) - .attr('title', errorMessage); - } - } -}; - -/** - * 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; - } -}; - - -/** - * 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) { - this.$el.removeAttr('id'); - } - - this.clear(); - this.model = null; - - this.$el - .off('.queryBuilder') - .removeClass('query-builder') - .removeData('queryBuilder'); - - delete this.$el[0].queryBuilder; -}; - -/** - * 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; - - this.model.root.empty(); - - this.model.root.data = undefined; - this.model.root.flags = $.extend({}, this.settings.default_group_flags); - this.model.root.condition = this.settings.default_condition; - - this.addRule(this.model.root); - - /** - * After the {@link QueryBuilder#reset} method - * @event afterReset - * @memberof QueryBuilder - */ - this.trigger('afterReset'); - - this.trigger('rulesChanged'); -}; - -/** - * 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; - - if (this.model.root) { - this.model.root.drop(); - this.model.root = null; - } - - /** - * After the {@link QueryBuilder#clear} method - * @event afterClear - * @memberof QueryBuilder - */ - this.trigger('afterClear'); - - this.trigger('rulesChanged'); -}; - -/** - * Modifies the builder configuration.
- * Only options defined in QueryBuilder.modifiable_options are modifiable - * @param {object} options - */ -QueryBuilder.prototype.setOptions = function(options) { - $.each(options, function(opt, value) { - if (QueryBuilder.modifiable_options.indexOf(opt) !== -1) { - this.settings[opt] = value; - } - }.bind(this)); -}; - -/** - * Returns the model associated to a DOM object, or the root model - * @param {jQuery} [target] - * @returns {Node} - */ -QueryBuilder.prototype.getModel = function(target) { - if (!target) { - return this.model.root; - } - else if (target instanceof Node) { - return target; - } - else { - return $(target).data('queryBuilderModel'); - } -}; - -/** - * 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(options) { - options = $.extend({ - skip_empty: false - }, options); - - this.clearErrors(); - - var self = this; - - var valid = (function parse(group) { - var done = 0; - var errors = 0; - - group.each(function(rule) { - if (!rule.filter && options.skip_empty) { - return; - } - - if (!rule.filter) { - self.triggerValidationError(rule, 'no_filter', null); - errors++; - return; - } - - if (!rule.operator) { - self.triggerValidationError(rule, 'no_operator', null); - errors++; - return; - } - - if (rule.operator.nb_inputs !== 0) { - var valid = self.validateValue(rule, rule.value); - - if (valid !== true) { - self.triggerValidationError(rule, valid, rule.value); - errors++; - return; - } - } - - done++; - - }, function(group) { - var res = parse(group); - if (res === true) { - done++; - } - else if (res === false) { - errors++; - } - }); - - 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; - } - - return true; - - }(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); -}; - -/** - * 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, - skip_empty: false - }, options); - - var valid = this.validate(options); - if (!valid && !options.allow_invalid) { - return null; - } - - var self = this; - - var out = (function parse(group) { - var groupData = { - condition: group.condition, - rules: [] - }; - - if (group.data) { - groupData.data = $.extendext(true, 'replace', {}, group.data); - } - - if (options.get_flags) { - var flags = self.getGroupFlags(group.flags, options.get_flags === 'all'); - if (!$.isEmptyObject(flags)) { - groupData.flags = flags; - } - } - - 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; - } - - var ruleData = { - id: rule.filter ? rule.filter.id : null, - field: rule.filter ? rule.filter.field : null, - type: rule.filter ? rule.filter.type : null, - input: rule.filter ? rule.filter.input : null, - operator: rule.operator ? rule.operator.type : null, - value: value - }; - - if (rule.filter && rule.filter.data || rule.data) { - ruleData.data = $.extendext(true, 'replace', {}, rule.filter.data, rule.data); - } - - if (options.get_flags) { - var flags = self.getRuleFlags(rule.flags, options.get_flags === 'all'); - if (!$.isEmptyObject(flags)) { - ruleData.flags = flags; - } - } - - /** - * 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) { - 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); -}; - -/** - * 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 - * @fires QueryBuilder.changer:setRules - * @fires QueryBuilder.changer:jsonToRule - * @fires QueryBuilder.changer:jsonToGroup - * @fires QueryBuilder.afterSetRules - */ -QueryBuilder.prototype.setRules = function(data, options) { - options = $.extend({ - allow_invalid: false - }, options); - - if ($.isArray(data)) { - data = { - condition: this.settings.default_condition, - rules: data - }; - } - - if (!data || !data.rules || (data.rules.length === 0 && !this.settings.allow_empty)) { - Utils.error('RulesParse', 'Incorrect data object passed'); - } - - this.clear(); - this.setRoot(false, data.data, this.parseGroupFlags(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; - - (function add(data, group) { - if (group === null) { - return; - } - - if (data.condition === undefined) { - data.condition = self.settings.default_condition; - } - else if (self.settings.conditions.indexOf(data.condition) == -1) { - Utils.error(!options.allow_invalid, 'UndefinedCondition', 'Invalid condition "{0}"', data.condition); - data.condition = self.settings.default_condition; - } - - group.condition = data.condition; - - data.rules.forEach(function(item) { - var model; - - if (item.rules !== undefined) { - if (self.settings.allow_groups !== -1 && self.settings.allow_groups < group.level) { - Utils.error(!options.allow_invalid, 'RulesParse', 'No more than {0} groups are allowed', self.settings.allow_groups); - self.reset(); - } - else { - model = self.addGroup(group, false, item.data, self.parseGroupFlags(item)); - if (model === null) { - return; - } - - add(item, model); - } - } - else { - if (!item.empty) { - if (item.id === undefined) { - Utils.error(!options.allow_invalid, 'RulesParse', 'Missing rule field id'); - item.empty = true; - } - if (item.operator === undefined) { - item.operator = 'equal'; - } - } - - model = self.addRule(group, item.data, self.parseRuleFlags(item)); - if (model === null) { - return; - } - - if (!item.empty) { - model.filter = self.getFilterById(item.id, !options.allow_invalid); - } - - if (model.filter) { - model.operator = self.getOperatorByType(item.operator, !options.allow_invalid); - - if (!model.operator) { - model.operator = self.getOperators(model.filter)[0]; - } - } - - if (model.operator && model.operator.nb_inputs !== 0) { - if (item.value !== undefined) { - model.value = item.value; - } - else if (model.filter.default_value !== undefined) { - model.value = model.filter.default_value; - } - } - - /** - * Modifies the Rule object generated from the JSON - * @event 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'); -}; - - -/** - * 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 || {}; - var result = true; - - if (validation.callback) { - result = validation.callback.call(this, value, rule); - } - else { - 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 - * @private - */ -QueryBuilder.prototype._validateValue = function(rule, value) { - var filter = rule.filter; - var operator = rule.operator; - var validation = filter.validation || {}; - var result = true; - 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) { - if (!validation.allow_empty_value) { - result = ['radio_empty']; - } - break; - } - break; - - case 'checkbox': - if (value[i] === undefined || value[i].length === 0) { - if (!validation.allow_empty_value) { - result = ['checkbox_empty']; - } - break; - } - break; - - case 'select': - if (value[i] === undefined || value[i].length === 0 || (filter.placeholder && value[i] == filter.placeholder_value)) { - if (!validation.allow_empty_value) { - result = ['select_empty']; - } - break; - } - break; - - 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.min !== undefined) { - if (tempValue[j].length < parseInt(validation.min)) { - result = [this.getValidationMessage(validation, 'min', 'string_exceed_min_length'), validation.min]; - break; - } - } - if (validation.max !== undefined) { - if (tempValue[j].length > parseInt(validation.max)) { - result = [this.getValidationMessage(validation, 'max', 'string_exceed_max_length'), validation.max]; - break; - } - } - 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; - - case 'number': - if (tempValue[j] === undefined || tempValue[j].length === 0) { - if (!validation.allow_empty_value) { - result = ['number_nan']; - } - break; - } - if (isNaN(tempValue[j])) { - result = ['number_nan']; - break; - } - if (filter.type == 'integer') { - if (parseInt(tempValue[j]) != tempValue[j]) { - result = ['number_not_integer']; - break; - } - } - else { - if (parseFloat(tempValue[j]) != tempValue[j]) { - result = ['number_not_double']; - break; - } - } - if (validation.min !== undefined) { - if (tempValue[j] < parseFloat(validation.min)) { - result = [this.getValidationMessage(validation, 'min', 'number_exceed_min'), validation.min]; - break; - } - } - if (validation.max !== undefined) { - if (tempValue[j] > parseFloat(validation.max)) { - result = [this.getValidationMessage(validation, 'max', 'number_exceed_max'), validation.max]; - break; - } - } - 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; - - case 'datetime': - if (tempValue[j] === undefined || tempValue[j].length === 0) { - if (!validation.allow_empty_value) { - result = ['datetime_empty']; - } - 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; - } - 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; - - 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; - } - } - - if (result !== true) { - break; - } - } - } - - if (result !== true) { - break; - } - } - - if ((rule.operator.type === 'between' || rule.operator.type === 'not_between') && value.length === 2) { - switch (QueryBuilder.types[filter.type]) { - case 'number': - if (value[0] > value[1]) { - result = ['number_between_invalid', value[0], value[1]]; - } - break; - - case 'datetime': - // 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 (moment(value[0], validation.format).isAfter(moment(value[1], validation.format))) { - result = ['datetime_between_invalid', value[0], value[1]]; - } - } - break; - } - } - - return result; -}; - -/** - * Returns an incremented group ID - * @returns {string} - * @private - */ -QueryBuilder.prototype.nextGroupId = function() { - return this.status.id + '_group_' + (this.status.group_id++); -}; - -/** - * Returns an incremented rule ID - * @returns {string} - * @private - */ -QueryBuilder.prototype.nextRuleId = function() { - return this.status.id + '_rule_' + (this.status.rule_id++); -}; - -/** - * Returns the operators for a filter - * @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') { - filter = this.getFilterById(filter); - } - - var result = []; - - for (var i = 0, l = this.operators.length; i < l; i++) { - // filter operators check - if (filter.operators) { - if (filter.operators.indexOf(this.operators[i].type) == -1) { - continue; - } - } - // type check - else if (this.operators[i].apply_to.indexOf(QueryBuilder.types[filter.type]) == -1) { - continue; - } - - result.push(this.operators[i]); - } - - // keep sort order defined for the filter - if (filter.operators) { - result.sort(function(a, b) { - return filter.operators.indexOf(a.type) - filter.operators.indexOf(b.type); - }); - } - - /** - * 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 - * @private - */ -QueryBuilder.prototype.getFilterById = function(id, doThrow) { - if (id == '-1') { - return null; - } - - for (var i = 0, l = this.filters.length; i < l; i++) { - if (this.filters[i].id == id) { - return this.filters[i]; - } - } - - Utils.error(doThrow !== false, 'UndefinedFilter', 'Undefined filter "{0}"', id); - - return null; -}; - -/** - * Returns a particular operator by its type - * @param {string} type - * @param {boolean} [doThrow=true] - * @returns {object|null} - * @throws UndefinedOperatorError - * @private - */ -QueryBuilder.prototype.getOperatorByType = function(type, doThrow) { - if (type == '-1') { - return null; - } - - for (var i = 0, l = this.operators.length; i < l; i++) { - if (this.operators[i].type == type) { - return this.operators[i]; - } - } - - Utils.error(doThrow !== false, 'UndefinedOperator', 'Undefined operator "{0}"', type); - - return null; -}; - -/** - * Returns rule's current input value - * @param {Rule} rule - * @returns {*} - * @fires QueryBuilder.changer:getRuleValue - * @private - */ -QueryBuilder.prototype.getRuleInputValue = function(rule) { - var filter = rule.filter; - var operator = rule.operator; - var value = []; - - if (filter.valueGetter) { - value = filter.valueGetter.call(this, rule); - } - else { - 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); - var tmp; - - switch (filter.input) { - case 'radio': - value.push($value.find('[name=' + name + ']:checked').val()); - break; - - 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 { - value.push($value.find('[name=' + name + '] option:selected').val()); - } - break; - - default: - value.push($value.find('[name=' + name + ']').val()); - } - } - - value = value.map(function(val) { - if (operator.multiple && filter.value_separator && typeof val == 'string') { - val = val.split(filter.value_separator); - } - - if ($.isArray(val)) { - return val.map(function(subval) { - return Utils.changeType(subval, filter.type); - }); - } - else { - return Utils.changeType(val, filter.type); - } - }); - - if (operator.nb_inputs === 1) { - value = value[0]; - } - - // @deprecated - if (filter.valueParser) { - value = filter.valueParser.call(this, rule, value); - } - } - - /** - * 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 - * @private - */ -QueryBuilder.prototype.setRuleInputValue = function(rule, value) { - var filter = rule.filter; - var operator = rule.operator; - - if (!filter || !operator) { - return; - } - - rule._updating_input = true; - - if (filter.valueSetter) { - filter.valueSetter.call(this, rule, value); - } - else { - var $value = rule.$el.find(QueryBuilder.selectors.value_container); - - if (operator.nb_inputs == 1) { - value = [value]; - } - - for (var i = 0; i < operator.nb_inputs; i++) { - var name = Utils.escapeElementId(rule.id + '_value_' + i); - - switch (filter.input) { - case 'radio': - $value.find('[name=' + name + '][value="' + value[i] + '"]').prop('checked', true).trigger('change'); - break; - - case 'checkbox': - 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: - if (operator.multiple && filter.value_separator && $.isArray(value[i])) { - value[i] = value[i].join(filter.value_separator); - } - $value.find('[name=' + name + ']').val(value[i]).trigger('change'); - break; - } - } - } - - rule._updating_input = false; -}; - -/** - * 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); - - if (rule.readonly) { - $.extend(flags, { - filter_readonly: true, - operator_readonly: true, - value_readonly: true, - no_delete: true - }); - } - - if (rule.flags) { - $.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); -}; - -/** - * Gets a copy of flags of a rule - * @param {object} flags - * @param {boolean} [all=false] - return all flags or only changes from default flags - * @returns {object} - * @private - */ -QueryBuilder.prototype.getRuleFlags = function(flags, all) { - if (all) { - return $.extend({}, flags); - } - else { - var ret = {}; - $.each(this.settings.default_rule_flags, function(key, value) { - if (flags[key] !== value) { - ret[key] = flags[key]; - } - }); - return ret; - } -}; - -/** - * 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); - - if (group.readonly) { - $.extend(flags, { - condition_readonly: true, - no_add_rule: true, - no_add_group: true, - no_delete: true - }); - } - - if (group.flags) { - $.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); -}; - -/** - * Gets a copy of flags of a group - * @param {object} flags - * @param {boolean} [all=false] - return all flags or only changes from default flags - * @returns {object} - * @private - */ -QueryBuilder.prototype.getGroupFlags = function(flags, all) { - if (all) { - return $.extend({}, flags); - } - else { - var ret = {}; - $.each(this.settings.default_group_flags, function(key, value) { - if (flags[key] !== value) { - ret[key] = flags[key]; - } - }); - return ret; - } -}; - -/** - * 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.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); -}; - -/** - * 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; -}; - - -QueryBuilder.templates.group = '\ -
\ -
\ -
\ - \ - {{? it.settings.allow_groups===-1 || it.settings.allow_groups>=it.level }} \ - \ - {{?}} \ - {{? it.level>1 }} \ - \ - {{?}} \ -
\ -
\ - {{~ it.conditions: condition }} \ - \ - {{~}} \ -
\ - {{? it.settings.display_errors }} \ -
\ - {{?}} \ -
\ -
\ -
\ -
\ -
'; - -QueryBuilder.templates.rule = '\ -
\ -
\ -
\ - \ -
\ -
\ - {{? it.settings.display_errors }} \ -
\ - {{?}} \ -
\ -
\ -
\ -
'; - -QueryBuilder.templates.filterSelect = '\ -{{ var optgroup = null; }} \ -'; - -QueryBuilder.templates.operatorSelect = '\ -{{? it.operators.length === 1 }} \ - \ -{{= it.translate("operators", it.operators[0].type) }} \ - \ -{{?}} \ -{{ var optgroup = null; }} \ -'; - -QueryBuilder.templates.ruleValueSelect = '\ -{{ var optgroup = null; }} \ -'; - -/** - * 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({ - builder: this, - group_id: group_id, - level: level, - conditions: this.settings.conditions, - icons: this.icons, - 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'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, - 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's filter HTML - * @param {Rule} rule - * @param {object[]} filters - * @returns {string} - * @fires QueryBuilder.changer:getRuleFilterTemplate - * @private - */ -QueryBuilder.prototype.getRuleFilterSelect = function(rule, filters) { - var h = this.templates.filterSelect({ - builder: this, - rule: rule, - filters: filters, - icons: this.icons, - settings: this.settings, - translate: this.translate.bind(this) - }); - - /** - * Modifies the raw HTML of the rule's filter dropdown - * @event changer:getRuleFilterSelect - * @memberof QueryBuilder - * @param {string} html - * @param {Rule} rule - * @param {QueryBuilder.Filter[]} filters - * @returns {string} - */ - return this.change('getRuleFilterSelect', h, rule, filters); -}; - -/** - * 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({ - builder: this, - rule: rule, - operators: operators, - icons: this.icons, - settings: this.settings, - translate: this.translate.bind(this) - }); - - /** - * Modifies the raw HTML of the rule's operator dropdown - * @event changer:getRuleOperatorSelect - * @memberof QueryBuilder - * @param {string} html - * @param {Rule} rule - * @param {QueryBuilder.Operator[]} operators - * @returns {string} - */ - return this.change('getRuleOperatorSelect', h, rule, operators); -}; - -/** - * Returns the rule's value select HTML - * @param {string} name - * @param {Rule} rule - * @returns {string} - * @fires QueryBuilder.changer:getRuleValueSelect - * @private - */ -QueryBuilder.prototype.getRuleValueSelect = function(name, rule) { - var h = this.templates.ruleValueSelect({ - builder: this, - name: name, - rule: rule, - icons: this.icons, - settings: this.settings, - translate: this.translate.bind(this) - }); - - /** - * Modifies the raw HTML of the rule's value dropdown (in case of a "select filter) - * @event changer:getRuleValueSelect - * @memberof QueryBuilder - * @param {string} html - * @param [string} name - * @param {Rule} rule - * @returns {string} - */ - return this.change('getRuleValueSelect', h, name, rule); -}; - -/** - * 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; - var validation = rule.filter.validation || {}; - var name = rule.id + '_value_' + value_id; - var c = filter.vertical ? ' class=block' : ''; - var h = ''; - - if (typeof filter.input == 'function') { - h = filter.input.call(this, rule, name); - } - else { - switch (filter.input) { - case 'radio': - case 'checkbox': - Utils.iterateOptions(filter.values, function(key, val) { - h += ' ' + val + ' '; - }); - break; - - case 'select': - h = this.getRuleValueSelect(name, rule); - break; - - case 'textarea': - h += '";break;case"number":l+=' "})}})},{font:"glyphicons",color:"default"}),c.define("bt-selectpicker",function(r){$.fn.selectpicker&&$.fn.selectpicker.Constructor||h.error("MissingLibrary",'Bootstrap Select is required to use "bt-selectpicker" plugin. Get it here: http://silviomoreto.github.io/bootstrap-select');var n=c.selectors;this.on("afterCreateRuleFilters",function(e,t){t.$el.find(n.rule_filter).removeClass("form-control").selectpicker(r)}),this.on("afterCreateRuleOperators",function(e,t){t.$el.find(n.rule_operator).removeClass("form-control").selectpicker(r)}),this.on("afterUpdateRuleFilter",function(e,t){t.$el.find(n.rule_filter).selectpicker("render")}),this.on("afterUpdateRuleOperator",function(e,t){t.$el.find(n.rule_operator).selectpicker("render")}),this.on("beforeDeleteRule",function(e,t){t.$el.find(n.rule_filter).selectpicker("destroy"),t.$el.find(n.rule_operator).selectpicker("destroy")})},{container:"body",style:"btn-inverse btn-xs",width:"auto",showIcon:!1}),c.define("bt-tooltip-errors",function(n){$.fn.tooltip&&$.fn.tooltip.Constructor&&$.fn.tooltip.Constructor.prototype.fixTitle||h.error("MissingLibrary",'Bootstrap Tooltip is required to use "bt-tooltip-errors" plugin. Get it here: http://getbootstrap.com');var i=this;this.on("getRuleTemplate.filter getGroupTemplate.filter",function(e){var t=$(e.value);t.find(c.selectors.error_container).attr("data-toggle","tooltip"),e.value=t.prop("outerHTML")}),this.model.on("update",function(e,t,r){"error"==r&&i.settings.display_errors&&t.$el.find(c.selectors.error_container).eq(0).tooltip(n).tooltip("hide").tooltip("fixTitle")})},{placement:"right"}),c.extend({setFilters:function(e,t){var r=this;void 0===t&&(t=e,e=!1),t=this.checkFilters(t);var n=(t=this.change("setFilters",t)).map(function(e){return e.id});if(e||function e(t){t.each(function(e){e.filter&&-1===n.indexOf(e.filter.id)&&h.error("ChangeFilter",'A rule is using filter "{0}"',e.filter.id)},e)}(this.model.root),this.filters=t,function e(t){t.each(!0,function(e){e.filter&&-1===n.indexOf(e.filter.id)?(e.drop(),r.trigger("rulesChanged")):(r.createRuleFilters(e),e.$el.find(c.selectors.rule_filter).val(e.filter?e.filter.id:"-1"),r.trigger("afterUpdateRuleFilter",e))},e)}(this.model.root),this.settings.plugins&&(this.settings.plugins["unique-filter"]&&this.updateDisabledFilters(),this.settings.plugins["bt-selectpicker"]&&this.$el.find(c.selectors.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",t)},addFilter:function(e,r){void 0===r||"#end"==r?r=this.filters.length:"#start"==r&&(r=0),$.isArray(e)||(e=[e]);var t=$.extend(!0,[],this.filters);parseInt(r)==r?Array.prototype.splice.apply(t,[r,0].concat(e)):this.filters.some(function(e,t){if(e.id==r)return r=t+1,!0})?Array.prototype.splice.apply(t,[r,0].concat(e)):Array.prototype.push.apply(t,e),this.setFilters(t)},removeFilter:function(t,e){var r=$.extend(!0,[],this.filters);"string"==typeof t&&(t=[t]),r=r.filter(function(e){return-1===t.indexOf(e.id)}),this.setFilters(e,r)}}),c.define("chosen-selectpicker",function(r){$.fn.chosen||h.error("MissingLibrary",'chosen is required to use "chosen-selectpicker" plugin. Get it here: https://github.com/harvesthq/chosen'),this.settings.plugins["bt-selectpicker"]&&h.error("Conflict","bt-selectpicker is already selected as the dropdown plugin. Please remove chosen-selectpicker from the plugin list");var n=c.selectors;this.on("afterCreateRuleFilters",function(e,t){t.$el.find(n.rule_filter).removeClass("form-control").chosen(r)}),this.on("afterCreateRuleOperators",function(e,t){t.$el.find(n.rule_operator).removeClass("form-control").chosen(r)}),this.on("afterUpdateRuleFilter",function(e,t){t.$el.find(n.rule_filter).trigger("chosen:updated")}),this.on("afterUpdateRuleOperator",function(e,t){t.$el.find(n.rule_operator).trigger("chosen:updated")}),this.on("beforeDeleteRule",function(e,t){t.$el.find(n.rule_filter).chosen("destroy"),t.$el.find(n.rule_operator).chosen("destroy")})}),c.define("filter-description",function(i){"inline"===i.mode?this.on("afterUpdateRuleFilter afterUpdateRuleOperator",function(e,t){var r=t.$el.find("p.filter-description"),n=e.builder.getFilterDescription(t.filter,t);n?(0===r.length?(r=$('

')).appendTo(t.$el):r.css("display",""),r.html(' '+n)):r.hide()}):"popover"===i.mode?($.fn.popover&&$.fn.popover.Constructor&&$.fn.popover.Constructor.prototype.fixTitle||h.error("MissingLibrary",'Bootstrap Popover is required to use "filter-description" plugin. Get it here: http://getbootstrap.com'),this.on("afterUpdateRuleFilter afterUpdateRuleOperator",function(e,t){var r=t.$el.find("button.filter-description"),n=e.builder.getFilterDescription(t.filter,t);n?(0===r.length?((r=$('')).prependTo(t.$el.find(c.selectors.rule_actions)),r.popover({placement:"left",container:"body",html:!0}),r.on("mouseout",function(){r.popover("hide")})):r.css("display",""),r.data("bs.popover").options.content=n,r.attr("aria-describedby")&&r.popover("show")):(r.hide(),r.data("bs.popover")&&r.popover("hide"))})):"bootbox"===i.mode&&("bootbox"in window||h.error("MissingLibrary",'Bootbox is required to use "filter-description" plugin. Get it here: http://bootboxjs.com'),this.on("afterUpdateRuleFilter afterUpdateRuleOperator",function(e,t){var r=t.$el.find("button.filter-description"),n=e.builder.getFilterDescription(t.filter,t);n?(0===r.length?((r=$('')).prependTo(t.$el.find(c.selectors.rule_actions)),r.on("click",function(){bootbox.alert(r.data("description"))})):r.css("display",""),r.data("description",n)):r.hide()}))},{icon:"glyphicon glyphicon-info-sign",mode:"popover"}),c.extend({getFilterDescription:function(e,t){return e?"function"==typeof e.description?e.description.call(this,t):e.description:void 0}}),c.define("invert",function(r){var n=this,i=c.selectors;this.on("afterInit",function(){n.$el.on("click.queryBuilder","[data-invert=group]",function(){var e=$(this).closest(i.group_container);n.invert(n.getModel(e),r)}),r.display_rules_button&&r.invert_rules&&n.$el.on("click.queryBuilder","[data-invert=rule]",function(){var e=$(this).closest(i.rule_container);n.invert(n.getModel(e),r)})}),r.disable_template||(this.on("getGroupTemplate.filter",function(e){var t=$(e.value);t.find(i.condition_container).after('"),e.value=t.prop("outerHTML")}),r.display_rules_button&&r.invert_rules&&this.on("getRuleTemplate.filter",function(e){var t=$(e.value);t.find(i.rule_actions).prepend('"),e.value=t.prop("outerHTML")}))},{icon:"glyphicon glyphicon-random",recursive:!0,invert_rules:!0,display_rules_button:!1,silent_fail:!1,disable_template:!1}),c.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"}}),c.extend({invert:function(e,t){if(!(e instanceof i)){if(!this.model.root)return;t=e,e=this.model.root}if("object"!=typeof t&&(t={}),void 0===t.recursive&&(t.recursive=!0),void 0===t.invert_rules&&(t.invert_rules=!0),void 0===t.silent_fail&&(t.silent_fail=!1),void 0===t.trigger&&(t.trigger=!0),e instanceof a){if(this.settings.conditionOpposites[e.condition]?e.condition=this.settings.conditionOpposites[e.condition]:t.silent_fail||h.error("InvertCondition",'Unknown inverse of condition "{0}"',e.condition),t.recursive){var r=$.extend({},t,{trigger:!1});e.each(function(e){t.invert_rules&&this.invert(e,r)},function(e){this.invert(e,r)},this)}}else if(e instanceof l&&e.operator&&!e.filter.no_invert)if(this.settings.operatorOpposites[e.operator.type]){var n=this.settings.operatorOpposites[e.operator.type];e.filter.operators&&-1==e.filter.operators.indexOf(n)||(e.operator=this.getOperatorByType(n))}else t.silent_fail||h.error("InvertOperator",'Unknown inverse of operator "{0}"',e.operator.type);t.trigger&&(this.trigger("afterInvert",e,t),this.trigger("rulesChanged"))}}),c.defaults({mongoOperators:{equal:function(e){return e[0]},not_equal:function(e){return{$ne:e[0]}},in:function(e){return{$in:e}},not_in:function(e){return{$nin:e}},less:function(e){return{$lt:e[0]}},less_or_equal:function(e){return{$lte:e[0]}},greater:function(e){return{$gt:e[0]}},greater_or_equal:function(e){return{$gte:e[0]}},between:function(e){return{$gte:e[0],$lte:e[1]}},not_between:function(e){return{$lt:e[0],$gt:e[1]}},begins_with:function(e){return{$regex:"^"+h.escapeRegExp(e[0])}},not_begins_with:function(e){return{$regex:"^(?!"+h.escapeRegExp(e[0])+")"}},contains:function(e){return{$regex:h.escapeRegExp(e[0])}},not_contains:function(e){return{$regex:"^((?!"+h.escapeRegExp(e[0])+").)*$",$options:"s"}},ends_with:function(e){return{$regex:h.escapeRegExp(e[0])+"$"}},not_ends_with:function(e){return{$regex:"(? '+n.translate("NOT")+""),e.value=t.prop("outerHTML")}),this.on("groupToJson.filter",function(e,t){e.value.not=t.not}),this.on("jsonToGroup.filter",function(e,t){e.value.not=!!t.not}),this.on("groupToSQL.filter",function(e,t){t.not&&(e.value="NOT ( "+e.value+" )")}),this.on("parseSQLNode.filter",function(e){e.value.name&&"NOT"==e.value.name.toUpperCase()&&(e.value=e.value.arguments.value[0],-1===["AND","OR"].indexOf(e.value.operation.toUpperCase())&&(e.value=new SQLParser.nodes.Op(n.settings.default_condition,e.value,null)),e.value.not=!0)}),this.on("sqlGroupsDistinct.filter",function(e,t,r,n){r.not&&0"+c.selectors.group_not).toggleClass("active",e.not).find("i").attr("class",e.not?t.icon_checked:t.icon_unchecked),this.trigger("afterUpdateGroupNot",e),this.trigger("rulesChanged")}}),c.define("sortable",function(n){var i,o,l,s;"interact"in window||h.error("MissingLibrary",'interact.js is required to use "sortable" plugin. Get it here: http://interactjs.io'),void 0!==n.default_no_sortable&&(h.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=n.default_no_sortable),interact.dynamicDrop(!0),interact.pointerMoveTolerance(10),this.on("afterAddRule afterAddGroup",function(e,t){if(t!=i){var r=e.builder;n.inherit_no_sortable&&t.parent&&t.parent.flags.no_sortable&&(t.flags.no_sortable=!0),n.inherit_no_drop&&t.parent&&t.parent.flags.no_drop&&(t.flags.no_drop=!0),t.flags.no_sortable||interact(t.$el[0]).draggable({allowFrom:c.selectors.drag_handle,onstart:function(e){s=!1,l=r.getModel(e.target),o=l.$el.clone().appendTo(l.$el.parent()).width(l.$el.outerWidth()).addClass("dragging");var t=$('
 
').height(l.$el.outerHeight());i=l.parent.addRule(t,l.getPos()),l.$el.hide()},onmove:function(e){o[0].style.top=e.clientY-15+"px",o[0].style.left=e.clientX-15+"px"},onend:function(e){e.dropzone&&(u(l,$(e.relatedTarget),r),s=!0),o.remove(),o=void 0,i.drop(),i=void 0,l.$el.css("display",""),r.trigger("afterMove",l),r.trigger("rulesChanged")}}),t.flags.no_drop||(interact(t.$el[0]).dropzone({accept:c.selectors.rule_and_group_containers,ondragenter:function(e){u(i,$(e.target),r)},ondrop:function(e){s||u(l,$(e.target),r)}}),t instanceof a&&interact(t.$el.find(c.selectors.group_header)[0]).dropzone({accept:c.selectors.rule_and_group_containers,ondragenter:function(e){u(i,$(e.target),r)},ondrop:function(e){s||u(l,$(e.target),r)}}))}}),this.on("beforeDeleteRule beforeDeleteGroup",function(e,t){e.isDefaultPrevented()||(interact(t.$el[0]).unset(),t instanceof a&&interact(t.$el.find(c.selectors.group_header)[0]).unset())}),this.on("afterApplyRuleFlags afterApplyGroupFlags",function(e,t){t.flags.no_sortable&&t.$el.find(".drag-handle").remove()}),n.disable_template||(this.on("getGroupTemplate.filter",function(e,t){if(1'),e.value=r.prop("outerHTML")}}),this.on("getRuleTemplate.filter",function(e){var t=$(e.value);t.find(c.selectors.rule_header).after('
'),e.value=t.prop("outerHTML")}))},{inherit_no_sortable:!0,inherit_no_drop:!0,icon:"glyphicon glyphicon-sort",disable_template:!1}),c.selectors.rule_and_group_containers=c.selectors.rule_container+", "+c.selectors.group_container,c.selectors.drag_handle=".drag-handle",c.defaults({default_rule_flags:{no_sortable:!1,no_drop:!1},default_group_flags:{no_sortable:!1,no_drop:!1}}),c.define("sql-support",function(e){},{boolean_as_integer:!0}),c.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(e){return{val:e,op:""===e?"is_empty":"equal"}},"!=":function(e){return{val:e,op:""===e?"is_not_empty":"not_equal"}},LIKE:function(e){return"%"==e.slice(0,1)&&"%"==e.slice(-1)?{val:e.slice(1,-1),op:"contains"}:"%"==e.slice(0,1)?{val:e.slice(1),op:"ends_with"}:"%"==e.slice(-1)?{val:e.slice(0,-1),op:"begins_with"}:void h.error("SQLParse",'Invalid value for LIKE operator "{0}"',e)},"NOT LIKE":function(e){return"%"==e.slice(0,1)&&"%"==e.slice(-1)?{val:e.slice(1,-1),op:"not_contains"}:"%"==e.slice(0,1)?{val:e.slice(1),op:"not_ends_with"}:"%"==e.slice(-1)?{val:e.slice(0,-1),op:"not_begins_with"}:void h.error("SQLParse",'Invalid value for NOT LIKE operator "{0}"',e)},IN:function(e){return{val:e,op:"in"}},"NOT IN":function(e){return{val:e,op:"not_in"}},"<":function(e){return{val:e,op:"less"}},"<=":function(e){return{val:e,op:"less_or_equal"}},">":function(e){return{val:e,op:"greater"}},">=":function(e){return{val:e,op:"greater_or_equal"}},BETWEEN:function(e){return{val:e,op:"between"}},"NOT BETWEEN":function(e){return{val:e,op:"not_between"}},IS:function(e){return null!==e&&h.error("SQLParse","Invalid value for IS operator"),{val:null,op:"is_null"}},"IS NOT":function(e){return null!==e&&h.error("SQLParse","Invalid value for IS operator"),{val:null,op:"is_not_null"}}},sqlStatements:{question_mark:function(){var r=[];return{add:function(e,t){return r.push(t),"?"},run:function(){return r}}},numbered:function(r){(!r||1"==l&&(l="!=");var s=f.settings.sqlRuleOperator[l];void 0===s&&h.error("UndefinedSQLOperator",'Invalid SQL operation "{0}".',t.operation);var a,u=s.call(this,o,t.operation);"values"in t.left?a=t.left.values.join("."):"value"in t.left?a=t.left.value:h.error("SQLParse","Cannot find field name in {0}",JSON.stringify(t.left));var p=f.getSQLFieldID(a,o),d=f.change("sqlToRule",{id:p,field:a,operator:u.op,value:u.val},t);g.rules.push(d)}}(n,0),i},setRulesFromSQL:function(e,t){this.setRules(this.getRulesFromSQL(e,t))},getSQLFieldID:function(t,e){var r=this.filters.filter(function(e){return e.field.toLowerCase()===t.toLowerCase()});return 1===r.length?r[0].id:this.change("getSQLFieldID",t,e)}}),c.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(t,r){var n=t.builder;(n.updateDisabledFilters(),t.value.id in n.status.used_filters)&&(n.filters.some(function(e){if(!(e.id in n.status.used_filters)||0 +
diff --git a/webapp/src/main/webapp/themes/iph/templates/customSearchForm.ftl b/webapp/src/main/webapp/themes/iph/templates/customSearchForm.ftl index c6697646..916baba3 100644 --- a/webapp/src/main/webapp/themes/iph/templates/customSearchForm.ftl +++ b/webapp/src/main/webapp/themes/iph/templates/customSearchForm.ftl @@ -1,5 +1,5 @@
- + - - -<#include "developer.ftl"> - \ No newline at end of file + + +<#include "developer.ftl"> diff --git a/webapp/src/main/webapp/themes/iph/templates/page-home.ftl b/webapp/src/main/webapp/themes/iph/templates/page-home.ftl index b533b050..026edf12 100644 --- a/webapp/src/main/webapp/themes/iph/templates/page-home.ftl +++ b/webapp/src/main/webapp/themes/iph/templates/page-home.ftl @@ -44,7 +44,7 @@
${i18n().search_form}
- <#include "searchSelector.ftl"> + <#include "simpleSearchForm.ftl">
diff --git a/webapp/src/main/webapp/themes/iph/templates/page.ftl b/webapp/src/main/webapp/themes/iph/templates/page.ftl index 224f8745..69eb70e8 100644 --- a/webapp/src/main/webapp/themes/iph/templates/page.ftl +++ b/webapp/src/main/webapp/themes/iph/templates/page.ftl @@ -17,7 +17,7 @@ <#include "rightPanel.ftl"> <#include "contentWrapper.ftl"> - <#if currentServlet != "individual" && currentServlet != "login" && currentServlet != "display" && currentServlet != "customsearch" && currentServlet != "entity"> + <#if currentServlet != "individual" && currentServlet != "login" && currentServlet != "display" && currentServlet != "extendedsearch" && currentServlet != "entity">

filteredSearch

@@ -25,7 +25,7 @@ ${i18n().search_form} - <#include "searchSelector.ftl"> + <#include "simpleSearchForm.ftl">
diff --git a/webapp/src/main/webapp/themes/iph/templates/queryBuilder.ftl b/webapp/src/main/webapp/themes/iph/templates/queryBuilder.ftl deleted file mode 100644 index 81062b49..00000000 --- a/webapp/src/main/webapp/themes/iph/templates/queryBuilder.ftl +++ /dev/null @@ -1,288 +0,0 @@ - - - - -<#macro freeField field > - { - id: '${field.field}', - label: '${field.name}', - type: 'string', - operators: ['contains', 'not_contains'] - }, - - -<#macro multivalueField field > - { - id: '${field.field}', - label: '${field.name}', - type: 'string', - input: 'select', - values: { - - <#if searchFields??> - <#list searchFilters as filter> - <#if filter.field == field.field> - '"${filter.id}"':'${filter.name}', - - - <#else> - { - id: 'ALLTEXT', - label: 'Everywhere', - type: 'string', - operators: ['contains', 'not_contains'] - }, - - }, - operators: ['contains', 'not_contains'] - }, - - -<#macro selectHitsPerPage> - <#if !hitsPerPage?? > - <#assign hitsPerPage = 20 > - - <#assign hitsValues= [20,40,60,80,100]> - - diff --git a/webapp/src/main/webapp/themes/iph/templates/searchSelector.ftl b/webapp/src/main/webapp/themes/iph/templates/searchSelector.ftl deleted file mode 100644 index 7c950df7..00000000 --- a/webapp/src/main/webapp/themes/iph/templates/searchSelector.ftl +++ /dev/null @@ -1,18 +0,0 @@ -
- <#include "customSearchForm.ftl"> - -
-<#include "queryBuilder.ftl"> - - - diff --git a/webapp/src/main/webapp/themes/iph/templates/simpleSearchForm.ftl b/webapp/src/main/webapp/themes/iph/templates/simpleSearchForm.ftl new file mode 100644 index 00000000..25707f84 --- /dev/null +++ b/webapp/src/main/webapp/themes/iph/templates/simpleSearchForm.ftl @@ -0,0 +1,9 @@ +
+ <#include "customSearchForm.ftl"> + + + +
+ + +