diff --git a/bower.json b/bower.json index 7fe5fab0..4ca570e2 100644 --- a/bower.json +++ b/bower.json @@ -7,8 +7,10 @@ }], "description": "jQuery plugin for user friendly query/filter creator", "main": [ - "dist/query-builder.js", - "dist/query-builder.css" + "dist/query-builder.min.js", + "dist/query-builder.min.css", + "src/query-builder.js", + "src/query-builder.css" ], "dependencies" : { "jquery": ">= 1.9.0", @@ -33,5 +35,11 @@ "bower_components", "test", "tests" - ] + ], + "devDependencies": { + "chai": "~1.8.0", + "mocha": "~1.20.0", + "sinon": "http://sinonjs.org/releases/sinon-1.9.0.js", + "lodash": "~2.4.1" + } } diff --git a/src/ads-query-builder.css b/src/ads-query-builder.css new file mode 100644 index 00000000..764e626f --- /dev/null +++ b/src/ads-query-builder.css @@ -0,0 +1,143 @@ +/*! + * jQuery QueryBuilder + * Copyright 2014 Damien "Mistic" Sorel (http://www.strangeplanet.fr) + * Licensed under MIT (http://opensource.org/licenses/MIT) + */ + +.rule-container, +.rules-group-container, +.rule-placeholder { + margin:4px 0; + border-radius:5px; + //padding:5px; + //border:1px solid #eee; + background:#fff; + background:rgba(255, 255, 255, 0.9); + +} + +/* GROUPS */ +.rules-group-container { + //padding:10px 10px 5px 10px; + border:1px dashed #66afe9; + //background:#FCF9ED; + //background:rgba(250, 240, 210, 0.5); +} + .rules-group-header { + margin-bottom:10px; + } + .rules-group-header input[name$=_cond] { + display:none; + } + +/* RULES */ +.rules-list { + list-style:none; + padding:0 0 0 20px; + margin:0; +} + .rule-container {} + .rule-container.has-error { + background:#fdd; + border-color:#f99; + } + .rule-container>div:not(.rule-header) { + display:inline-block; + margin:0 5px 0 0; + vertical-align:top; + } + .rule-value-container:not(:empty) { + border-left:1px solid #ddd; + padding-left:5px; + } + .rule-value-container label { + margin-bottom:0; + } + .rule-value-container label.block { + display:block; + } + .rule-container select, + .rule-container input[type=text], + .rule-container input[type=number] { + padding:1px; + } + +/* TICKS */ +.rules-list>* { + position:relative; +} + .rules-list>*:before, + .rules-list>*:after { + content:''; + position:absolute; + left:-15px; + width:15px; + height:calc(50% + 4px); + border-color:#ccc; + //border-style:solid; + } + + .rules-list>*:before { + top:-2px; + border-width:0 0 2px 2px; + } + .rules-list>*:after { + top:50%; + border-width:0 0 0 2px; + } + + .rules-list>*:first-child:before { + top:-12px; + height:calc(50% + 14px); + } + .rules-list>*:last-child:before { + border-radius:0 0 0 4px; + } + .rules-list>*:last-child:after { + display:none; + } + +/* DRAG & DROP */ +.rule-container .drag-handle, +.rules-group-container .drag-handle { + cursor:move; + display:inline-block; +} + +.rule-container .dragged, +.rules-group-container .dragged { + opacity:0.5; +} + +.rule-placeholder { + border:1px dashed #bbb; + opacity:0.7; +} + +/* hide first delete button and drag handle */ +.query-builder>.rules-group-container>.rules-group-header [data-delete], +.query-builder>.rules-group-container>.rules-group-header .drag-handle { + display:none; +} + +.btn:active, .btn.active { + color:#66afe9; + background-color: white; +} + +.query-builder > .rules-group-container > .rules-group-header > .btn-group > .btn-primary { + display:none; +} +.btn-danger { + color: #d9534f; + background-color: white; + border-color: white; +} +.btn-success { + color: #5cb85c; + background-color: white; + border-color: white; +} +div.query-builder > .rules-group-container { + border:1px solid white; +} \ No newline at end of file diff --git a/src/query-builder.js b/src/query-builder.js index 0eeeb1be..bb5d41ed 100644 --- a/src/query-builder.js +++ b/src/query-builder.js @@ -2,6 +2,9 @@ * jQuery QueryBuilder * Copyright 2014 Damien "Mistic" Sorel (http://www.strangeplanet.fr) * Licensed under MIT (http://opensource.org/licenses/MIT) + * + * Enhanced, fixed and tested by rca, while at Harvard Center for Astrophysics (ADSLabs), + * 2014 - https://github.com/romanchyla/jQuery-QueryBuilder */ /*jshint multistr:true */ @@ -32,6 +35,12 @@ var QueryBuilder = function($el, options) { var that = this; + + if ('extend' in options) { + $.extend(this, options.extend); + delete options['extend']; + } + // global variables this.$el = $el; this.settings = $.extend(true, {}, QueryBuilder.DEFAULTS, options); @@ -138,6 +147,9 @@ onAfterAddGroup: null, onAfterAddRule: null, + conditions: ['AND', 'OR'], + default_condition: 'AND', + sortable: false, filters: [], @@ -327,12 +339,19 @@ $ul = $group.find('>.rules-group-body>.rules-list'), $buttons = $group.find('>.rules-group-header input[name$=_cond]'); + if (!data.rules) { + throw new Error("Missing data.rules element"); + } + if (!data.condition) { - data.condition = 'AND'; + data.condition = that.settings.default_condition; } - $buttons.filter('[value=AND]').prop('checked', data.condition.toUpperCase() == 'AND'); - $buttons.filter('[value=OR]').prop('checked', data.condition.toUpperCase() == 'OR'); + var l = that.settings.conditions.length, cond; + for (var i=0; i < l; i++) { + cond = that.settings.conditions[i]; + $buttons.filter('[value=' + cond + ']').prop('checked', data.condition.toUpperCase() == cond); + } $buttons.trigger('change'); $.each(data.rules, function(i, rule) { @@ -356,7 +375,17 @@ $value = $rule.find('.rule-value-container'); $rule.find('.rule-filter-container select[name$=_filter]').val(rule.id).trigger('change'); - $rule.find('.rule-operator-container select[name$=_operator]').val(rule.operator).trigger('change'); + + var $operator = $rule.find('.rule-operator-container select[name$=_operator]'); + if ($operator.children('option[value="' + rule.operator + '"]').length == 0) { + if (filter.createOperatorIfNecessary) { + $operator.append($('')); + } + else { + throw 'Unknown operator: ' + rule.operator; + } + } + $operator.val(rule.operator).trigger('change'); if (operator.accept_values) { switch (filter.input) { @@ -858,17 +887,30 @@ var res = []; + var op; + if (filter.operators && filter.operators.length > 0) { + for (var i= 0, l=filter.operators.length; i -1) { + res.push({ + type: op, + label: this.lang['operator_'+op] + }); + break; + } + } + } + } + + if (res.length > 0) + return res; + for (var i=0, l=this.operators.length; i< l; i++) { + cond = this.settings.conditions[i]; + conditions.push(''); + } + conditions = conditions.join('\n'); + var h = '\
\
\ @@ -994,8 +1045,7 @@ \ \
\ - \ - \ + ' + conditions + '\
\ '+ (this.settings.sortable ? '
' : '') +' \
\ @@ -1129,7 +1179,7 @@ // =============================== $.fn.queryBuilder = function(option) { if (this.length > 1) { - $.error('Unable to initialize on multiple target'); + $.error('Unable to initialize multiple instances on the same target'); } var data = this.data('queryBuilder'), diff --git a/tests/mocha/ads.html b/tests/mocha/ads.html new file mode 100644 index 00000000..26f3b827 --- /dev/null +++ b/tests/mocha/ads.html @@ -0,0 +1,338 @@ + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+ + + +
+ +
+

Output

+ +
+ +
+
+ +
+ + + + + + + + + + diff --git a/tests/mocha/basic-functionality.js b/tests/mocha/basic-functionality.js new file mode 100644 index 00000000..3989ce09 --- /dev/null +++ b/tests/mocha/basic-functionality.js @@ -0,0 +1,127 @@ +// globals: $ +describe("QueryBuilder (Basic API)", function () { + + + it("can set a different language interface", function (done) { + expect($.fn.queryBuilder.defaults.get().lang.delete_group).to.eql('Delete'); + + var original = $.fn.queryBuilder.defaults.get(); + + var head = document.getElementsByTagName('head')[0]; + var script = document.createElement('script'); + script.type = 'text/javascript'; + script.src = '../../src/i18n/fr.js'; + + var checkit = function () { + expect($.fn.queryBuilder.defaults.get().lang.delete_group).to.eql('Supprimer'); + $.fn.queryBuilder.defaults.set(original); + expect($.fn.queryBuilder.defaults.get().lang.delete_group).to.eql('Delete'); + done(); + }; + + script.onreadystatechange = function () { + if (this.readyState == 'complete') checkit(); + }; + script.onload= checkit; + + head.appendChild(script); + + }); + + it("respects the order of operators", function() { + $('#builder').queryBuilder('destroy'); + $('#builder').queryBuilder({filters:[ + { + id: 'name', + label: 'Name', + type: 'string', + operators: ['not_equal', 'contains', 'equal'] + } + ] + }); + + $('#builder').queryBuilder('setRules', { + "condition": "AND", + "rules": [ + { + "id": "name", + "field": "name", + "type": "string", + "input": "text", + "operator": "equal", + "value": "x" + } + ] + }); + + var vals = _.map($('#builder').find('#builder_rule_0>div.rule-operator-container>select>option'), function(x) {return $(x).val()}); + expect(vals).to.eql(['not_equal', 'contains', 'equal']); + }); + + it("allows to change conditions", function() { + $('#builder').queryBuilder('destroy'); + $('#builder').queryBuilder({ + conditions: ['AND', 'OR', 'DEFOP'], + filters:[ + { + id: 'name', + label: 'Name', + type: 'string', + operators: ['not_equal', 'contains', 'equal'] + } + ] + }); + + $('#builder').queryBuilder('setRules', { + "condition": "DEFOP", + "rules": [ + { + "id": "name", + "field": "name", + "type": "string", + "input": "text", + "operator": "equal", + "value": "x" + } + ] + }); + + var vals = _.map($.find('label.btn-primary'), function(x) {return $(x).text()}); + expect(vals).to.eql(['AND', 'OR', 'DEFOP']); + expect($('#builder').find('label.btn-primary.active').text()).to.be.eql('DEFOP'); + }); + + it("allows to insert non-existing operator", function() { + $('#builder').queryBuilder('destroy'); + $('#builder').queryBuilder({ + filters:[ + { + id: 'name', + label: 'Name', + type: 'string', + operators: ['not_equal', 'contains', 'equal'], + createOperatorIfNecessary: true + } + ] + }); + + $('#builder').queryBuilder('setRules', { + "condition": "DEFOP", + "rules": [ + { + "id": "name", + "field": "name", + "type": "string", + "input": "text", + "operator": "in", + "value": "x" + } + ] + }); + + var vals = _.map($.find('.rule-operator-container option'), function(x) {return $(x).val()}); + expect(vals).to.eql(["not_equal", "contains", "equal", "in"]); + expect($($.find('.rule-operator-container select')).val()).to.be.eql('in'); + }); + +}); \ No newline at end of file diff --git a/tests/mocha/index.html b/tests/mocha/index.html new file mode 100644 index 00000000..0852a169 --- /dev/null +++ b/tests/mocha/index.html @@ -0,0 +1,230 @@ + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+ + + +
+ +
+

Output

+

+        
+
+
+ +
+ + + + + + + + + +