Skip to content

Commit 1e6194c

Browse files
committed
Fix #642 Add optgroups support for values
1 parent 621041b commit 1e6194c

6 files changed

Lines changed: 146 additions & 30 deletions

File tree

examples/index.html

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -284,14 +284,42 @@ <h3>Output</h3>
284284
input: 'select',
285285
optgroup: 'core',
286286
placeholder: 'Select something',
287-
values: {
288-
'eur': 'Europe',
289-
'asia': 'Asia',
290-
'oce': 'Oceania',
291-
'afr': 'Africa',
292-
'na': 'North America',
293-
'sa': 'South America'
294-
},
287+
values: [
288+
{
289+
label: 'Europe',
290+
value: 'eur',
291+
optgroup: 'North'
292+
},
293+
{
294+
label: 'Asia',
295+
value: 'asia',
296+
optgroup: 'North'
297+
},
298+
{
299+
label: 'Oceania',
300+
value: 'oce',
301+
optgroup: 'South'
302+
},
303+
{
304+
label: 'Africa',
305+
value: 'afr',
306+
optgroup: 'South'
307+
},
308+
{
309+
label: 'North America',
310+
value: 'na',
311+
optgroup: 'North'
312+
},
313+
{
314+
label: 'South America',
315+
value: 'sa',
316+
optgroup: 'South'
317+
},
318+
{
319+
label: 'Mordor',
320+
value: 'mrd'
321+
}
322+
],
295323
operators: ['equal', 'not_equal', 'is_null', 'is_not_null']
296324
},
297325
/*

src/core.js

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,40 @@ QueryBuilder.prototype.checkFilters = function(filters) {
9393
break;
9494

9595
case 'select':
96+
var cleanValues = [];
97+
filter.has_optgroup = false;
98+
99+
Utils.iterateOptions(filter.values, function(value, label, optgroup) {
100+
cleanValues.push({
101+
value: value,
102+
label: label,
103+
optgroup: optgroup || null
104+
});
105+
106+
if (optgroup) {
107+
filter.has_optgroup = true;
108+
109+
// register optgroup if needed
110+
if (!this.settings.optgroups[optgroup]) {
111+
this.settings.optgroups[optgroup] = optgroup;
112+
}
113+
}
114+
}.bind(this));
115+
116+
if (filter.has_optgroup) {
117+
filter.values = Utils.groupSort(cleanValues, 'optgroup');
118+
}
119+
else {
120+
filter.values = cleanValues;
121+
}
122+
96123
if (filter.placeholder) {
97124
if (filter.placeholder_value === undefined) {
98125
filter.placeholder_value = -1;
99126
}
100-
Utils.iterateOptions(filter.values, function(key) {
101-
if (key == filter.placeholder_value) {
127+
128+
filter.values.forEach(function(entry) {
129+
if (entry.value == filter.placeholder_value) {
102130
Utils.error('Config', 'Placeholder of filter "{0}" overlaps with one of its values', filter.id);
103131
}
104132
});

src/defaults.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,8 @@ QueryBuilder.DEFAULTS = {
156156
group: null,
157157
rule: null,
158158
filterSelect: null,
159-
operatorSelect: null
159+
operatorSelect: null,
160+
ruleValueSelect: null
160161
},
161162

162163
lang_code: 'en',

src/template.js

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,24 @@ QueryBuilder.templates.operatorSelect = '\
8787
{{? optgroup !== null }}</optgroup>{{?}} \
8888
</select>';
8989

90+
QueryBuilder.templates.ruleValueSelect = '\
91+
{{ var optgroup = null; }} \
92+
<select class="form-control" name="{{= it.name }}" {{? it.rule.filter.multiple }}multiple{{?}}> \
93+
{{? it.rule.filter.placeholder }} \
94+
<option value="{{= it.rule.filter.placeholder_value }}" disabled selected>{{= it.rule.filter.placeholder }}</option> \
95+
{{?}} \
96+
{{~ it.rule.filter.values: entry }} \
97+
{{? optgroup !== entry.optgroup }} \
98+
{{? optgroup !== null }}</optgroup>{{?}} \
99+
{{? (optgroup = entry.optgroup) !== null }} \
100+
<optgroup label="{{= it.translate(it.settings.optgroups[optgroup]) }}"> \
101+
{{?}} \
102+
{{?}} \
103+
<option value="{{= entry.value }}">{{= entry.label }}</option> \
104+
{{~}} \
105+
{{? optgroup !== null }}</optgroup>{{?}} \
106+
</select>';
107+
90108
/**
91109
* Returns group's HTML
92110
* @param {string} group_id
@@ -203,6 +221,36 @@ QueryBuilder.prototype.getRuleOperatorSelect = function(rule, operators) {
203221
return this.change('getRuleOperatorSelect', h, rule, operators);
204222
};
205223

224+
/**
225+
* Returns the rule's value select HTML
226+
* @param {string} name
227+
* @param {Rule} rule
228+
* @returns {string}
229+
* @fires QueryBuilder.changer:getRuleValueSelect
230+
* @private
231+
*/
232+
QueryBuilder.prototype.getRuleValueSelect = function(name, rule) {
233+
var h = this.templates.ruleValueSelect({
234+
builder: this,
235+
name: name,
236+
rule: rule,
237+
icons: this.icons,
238+
settings: this.settings,
239+
translate: this.translate.bind(this)
240+
});
241+
242+
/**
243+
* Modifies the raw HTML of the rule's value dropdown (in case of a "select filter)
244+
* @event changer:getRuleValueSelect
245+
* @memberof QueryBuilder
246+
* @param {string} html
247+
* @param [string} name
248+
* @param {Rule} rule
249+
* @returns {string}
250+
*/
251+
return this.change('getRuleValueSelect', h, name, rule);
252+
};
253+
206254
/**
207255
* Returns the rule's value HTML
208256
* @param {Rule} rule
@@ -231,14 +279,7 @@ QueryBuilder.prototype.getRuleInput = function(rule, value_id) {
231279
break;
232280

233281
case 'select':
234-
h += '<select class="form-control" name="' + name + '"' + (filter.multiple ? ' multiple' : '') + '>';
235-
if (filter.placeholder) {
236-
h += '<option value="' + filter.placeholder_value + '" disabled selected>' + filter.placeholder + '</option>';
237-
}
238-
Utils.iterateOptions(filter.values, function(key, val) {
239-
h += '<option value="' + key + '">' + val + '</option> ';
240-
});
241-
h += '</select>';
282+
h = this.getRuleValueSelect(name, rule);
242283
break;
243284

244285
case 'textarea':

src/utils.js

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@ QueryBuilder.utils = Utils;
1414
* @callback Utils#OptionsIteratee
1515
* @param {string} key
1616
* @param {string} value
17+
* @param {string} [optgroup]
1718
*/
1819

1920
/**
20-
* Iterates over radio/checkbox/selection options, it accept three formats
21+
* Iterates over radio/checkbox/selection options, it accept four formats
2122
*
2223
* @example
2324
* // array of values
@@ -28,6 +29,9 @@ QueryBuilder.utils = Utils;
2829
* @example
2930
* // array of 1-element maps
3031
* options = [{1: 'one'}, {2: 'two'}, {3: 'three'}]
32+
* @example
33+
* // array of elements
34+
* options = [{value: 1, label: 'one', optgroup: 'group'}, {value: 2, label: 'two'}]
3135
*
3236
* @param {object|array} options
3337
* @param {Utils#OptionsIteratee} tpl
@@ -36,12 +40,18 @@ Utils.iterateOptions = function(options, tpl) {
3640
if (options) {
3741
if ($.isArray(options)) {
3842
options.forEach(function(entry) {
39-
// array of one-element maps
4043
if ($.isPlainObject(entry)) {
41-
$.each(entry, function(key, val) {
42-
tpl(key, val);
43-
return false; // break after first entry
44-
});
44+
// array of elements
45+
if ('value' in entry) {
46+
tpl(entry.value, entry.label || entry.value, entry.optgroup);
47+
}
48+
// array of one-element maps
49+
else {
50+
$.each(entry, function(key, val) {
51+
tpl(key, val);
52+
return false; // break after first entry
53+
});
54+
}
4555
}
4656
// array of values
4757
else {

tests/utils.module.js

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,31 +7,39 @@ $(function () {
77
*/
88
QUnit.test('iterateOptions', function (assert) {
99
var output;
10-
function callback(a, b) {
11-
output.push(a, b);
10+
function callback(a, b, c) {
11+
output.push(a, b, c);
1212
}
1313

1414
output = [];
1515
Utils.iterateOptions(['one', 'foo', 'bar'], callback);
1616
assert.deepEqual(
1717
output,
18-
['one', 'one', 'foo', 'foo', 'bar', 'bar'],
18+
['one', 'one', undefined, 'foo', 'foo', undefined, 'bar', 'bar', undefined],
1919
'Should iterate simple array'
2020
);
2121

2222
output = [];
2323
Utils.iterateOptions({1: 'one', 2: 'foo', 3: 'bar'}, callback);
2424
assert.deepEqual(
2525
output,
26-
['1', 'one', '2', 'foo', '3', 'bar'],
26+
['1', 'one', undefined, '2', 'foo', undefined, '3', 'bar', undefined],
2727
'Should iterate simple hash-map'
2828
);
2929

3030
output = [];
3131
Utils.iterateOptions([{1: 'one'}, {2: 'foo'}, {3: 'bar'}], callback);
3232
assert.deepEqual(
3333
output,
34-
['1', 'one', '2', 'foo', '3', 'bar'],
34+
['1', 'one', undefined, '2', 'foo', undefined, '3', 'bar', undefined],
35+
'Should iterate array of one element hash-maps'
36+
);
37+
38+
output = [];
39+
Utils.iterateOptions([{value: 1, label: 'one', optgroup: 'group'}, {value: 2, label: 'foo'}, {value: 3, label: 'bar', optgroup: 'group'}], callback);
40+
assert.deepEqual(
41+
output,
42+
[1, 'one', 'group', 2, 'foo', undefined, 3, 'bar', 'group'],
3543
'Should iterate array of hash-maps'
3644
);
3745
});

0 commit comments

Comments
 (0)