Skip to content

Commit

Permalink
preserve order of options outside of optgroups. fixes #603
Browse files Browse the repository at this point in the history
  • Loading branch information
oyejorge committed Oct 22, 2023
1 parent dee6989 commit 2b2353d
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 55 deletions.
4 changes: 4 additions & 0 deletions src/getSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export default function getSettings( input:TomInput, settings_user:RecursivePart
var options = settings_element.options;
var optionsMap:{[key:string]:any} = {};
var group_count = 1;
let $order = 0;

var readData = (el:HTMLElement):TomOption => {

Expand Down Expand Up @@ -94,6 +95,7 @@ export default function getSettings( input:TomInput, settings_user:RecursivePart
option_data[field_disabled] = option_data[field_disabled] || option.disabled;
option_data[field_optgroup] = option_data[field_optgroup] || group;
option_data.$option = option;
option_data.$order = option_data.$order || ++$order;

optionsMap[value] = option_data;
options.push(option_data);
Expand All @@ -111,6 +113,8 @@ export default function getSettings( input:TomInput, settings_user:RecursivePart
optgroup_data[field_optgroup_label] = optgroup_data[field_optgroup_label] || optgroup.getAttribute('label') || '';
optgroup_data[field_optgroup_value] = optgroup_data[field_optgroup_value] || group_count++;
optgroup_data[field_disabled] = optgroup_data[field_disabled] || optgroup.disabled;
optgroup_data.$order = optgroup_data.$order || ++$order;

settings_element.optgroups.push(optgroup_data);

id = optgroup_data[field_optgroup_value];
Expand Down
59 changes: 41 additions & 18 deletions src/tom-select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1380,9 +1380,11 @@ export default class TomSelect extends MicroPlugin(MicroEvent){
refreshOptions( triggerDropdown:boolean = true ){
var i, j, k, n, optgroup, optgroups, html:DocumentFragment, has_create_option, active_group;
var create;
const groups: {[key:string]:DocumentFragment} = {};

const groups_order:string[] = [];
type Group = {fragment:DocumentFragment,order:number,optgroup:string}
const groups: {[key:string]:number} = {};
const groups_order:Group[] = [];

var self = this;
var query = self.inputValue();
const same_query = query === self.lastQuery || (query == '' && self.lastQuery == null);
Expand Down Expand Up @@ -1410,6 +1412,25 @@ export default class TomSelect extends MicroPlugin(MicroEvent){
show_dropdown = true;
}

// get fragment for group and the position of the group in group_order
const getGroupFragment = (optgroup:string,order:number):[number,DocumentFragment] => {

let group_order_i = groups[optgroup];

if( group_order_i !== undefined ){
let order_group = groups_order[group_order_i];
if( order_group !== undefined ){
return [group_order_i,order_group.fragment];
}
}

let group_fragment = document.createDocumentFragment();
group_order_i = groups_order.length;
groups_order.push({fragment:group_fragment,order,optgroup});

return [group_order_i,group_fragment]
}

// render and group available options individually
for (i = 0; i < n; i++) {

Expand All @@ -1432,18 +1453,21 @@ export default class TomSelect extends MicroPlugin(MicroEvent){

optgroup = option[self.settings.optgroupField] || '';
optgroups = Array.isArray(optgroup) ? optgroup : [optgroup];


for (j = 0, k = optgroups && optgroups.length; j < k; j++) {
optgroup = optgroups[j];
if (!self.optgroups.hasOwnProperty(optgroup)) {

let order = option.$order;
let self_optgroup = self.optgroups[optgroup];
if( self_optgroup === undefined ){
optgroup = '';
}else{
order = self_optgroup.$order;
}

let group_fragment = groups[optgroup];
if( group_fragment === undefined ){
group_fragment = document.createDocumentFragment();
groups_order.push(optgroup);
}
const [group_order_i,group_fragment] = getGroupFragment(optgroup,order);


// nodes can only have one parent, so if the option is in mutple groups, we need a clone
if( j > 0 ){
Expand All @@ -1459,29 +1483,28 @@ export default class TomSelect extends MicroPlugin(MicroEvent){
active_option = option_el;
}
}
}

}
group_fragment.appendChild(option_el);
groups[optgroup] = group_fragment;
if( optgroup != '' ){
groups[optgroup] = group_order_i;
}
}
}

// sort optgroups
if( self.settings.lockOptgroupOrder ){
groups_order.sort((a, b) => {
const grp_a = self.optgroups[a];
const grp_b = self.optgroups[b];
const a_order = grp_a && grp_a.$order || 0;
const b_order = grp_b && grp_b.$order || 0;
return a_order - b_order;
return a.order - b.order;
});
}

// render optgroup headers & join groups
html = document.createDocumentFragment();
iterate( groups_order, (optgroup:string) => {
iterate( groups_order, (group_order:Group) => {

let group_fragment = groups[optgroup];
let group_fragment = group_order.fragment;
let optgroup = group_order.optgroup

if( !group_fragment || !group_fragment.children.length ) return;

Expand Down
80 changes: 43 additions & 37 deletions test/tests/setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,16 +129,16 @@
var test = setup_test('<select></select>', {});
});
it_n('should allow for values optgroups with duplicated options', function() {
var test = setup_test(['<select>',
'<optgroup data-val="Group 1" label="Group 1">',
'<option value="a">Item A</option>',
'<option value="b">Item B</option>',
'</optgroup>',
'<optgroup data-val="Group 2" label="Group 2">',
'<option value="a">Item A</option>',
'<option value="b">Item B</option>',
'</optgroup>',
'</select>'].join(''), {
var test = setup_test(`<select>
<optgroup data-val="Group 1" label="Group 1">
<option value="a">Item A</option>
<option value="b">Item B</option>
</optgroup>
<optgroup data-val="Group 2" label="Group 2">
<option value="a">Item A</option>
<option value="b">Item B</option>
</optgroup>
</select>`, {
optgroupValueField: 'val',
optgroupField: 'grp',
disabledField: 'dis'
Expand All @@ -147,32 +147,32 @@
assert.equal( test.instance.options['a'].text,'Item A');
assert.equal( test.instance.options['a'].value,'a');
assert.deepEqual( test.instance.options['a'].grp,['Group 1', 'Group 2']);
assert.equal( test.instance.options['a'].$order,1);
assert.equal( test.instance.options['a'].$order,2);
assert.equal( test.instance.options['a'].dis,false);

assert.equal( test.instance.options['b'].text,'Item B');
assert.equal( test.instance.options['b'].value,'b');
assert.deepEqual( test.instance.options['b'].grp,['Group 1', 'Group 2']);
assert.equal( test.instance.options['b'].$order,2);
assert.equal( test.instance.options['b'].$order,3);
assert.equal( test.instance.options['b'].dis,false);


assert.deepEqual(test.instance.optgroups, {
'Group 1': {label: 'Group 1', val: 'Group 1', $order: 3, dis: false},
'Group 1': {label: 'Group 1', val: 'Group 1', $order: 1, dis: false},
'Group 2': {label: 'Group 2', val: 'Group 2', $order: 4, dis: false}
}, '2');
});
it_n('should respect disabled flags of option and optgroup', function() {
var test = setup_test(['<select>',
'<optgroup data-val="Group 1" label="Group 1">',
'<option value="a" disabled>Item A</option>',
'<option value="b">Item B</option>',
'</optgroup>',
'<optgroup data-val="Group 2" label="Group 2" disabled>',
'<option value="a">Item A</option>',
'<option value="b">Item B</option>',
'</optgroup>',
'</select>'].join(''), {
var test = setup_test(`<select>
<optgroup data-val="Group 1" label="Group 1">
<option value="a" disabled>Item A</option>
<option value="b">Item B</option>
</optgroup>
<optgroup data-val="Group 2" label="Group 2" disabled>
<option value="a">Item A</option>
<option value="b">Item B</option>
</optgroup>
</select>`, {
optgroupValueField: 'val',
optgroupField: 'grp',
disabledField: 'dis'
Expand All @@ -181,17 +181,17 @@
assert.equal( test.instance.options['a'].text,'Item A');
assert.equal( test.instance.options['a'].value,'a');
assert.deepEqual( test.instance.options['a'].grp,['Group 1', 'Group 2']);
assert.equal( test.instance.options['a'].$order,1);
assert.equal( test.instance.options['a'].$order,2);
assert.equal( test.instance.options['a'].dis,true);

assert.equal( test.instance.options['b'].text,'Item B');
assert.equal( test.instance.options['b'].value,'b');
assert.deepEqual( test.instance.options['b'].grp,['Group 1', 'Group 2']);
assert.equal( test.instance.options['b'].$order,2);
assert.equal( test.instance.options['b'].$order,3);
assert.equal( test.instance.options['b'].dis,false);

assert.deepEqual(test.instance.optgroups, {
'Group 1': {label: 'Group 1', val: 'Group 1', $order: 3, dis: false},
'Group 1': {label: 'Group 1', val: 'Group 1', $order: 1, dis: false},
'Group 2': {label: 'Group 2', val: 'Group 2', $order: 4, dis: true}
}, '2');
});
Expand All @@ -215,19 +215,25 @@

it_n('display non-optgroup items and optgroups with lockOptgroupOrder = true', function(done) {
var test = setup_test(`<select>
<option>Item</option>
<optgroup label="Group 1">
<option value="a">Item A</option>
<option value="b">Item B</option>
</optgroup>
<optgroup label="Group 2">
<option value="a">Item A</option>
<option value="b">Item B</option>
</optgroup>
</select>`,{lockOptgroupOrder:true});
<option value="first">First</option>
<optgroup label="Group 1">
<option value="a">Item A</option>
<option value="b">Item B</option>
</optgroup>
<optgroup label="Group 2">
<option value="a">Item A</option>
<option value="b">Item B</option>
</optgroup>
<option value="last">Last</option>
</select>`,{
lockOptgroupOrder:true
});
test.instance.refreshOptions(true);
assert.equal(test.instance.dropdown_content.querySelectorAll('.optgroup').length, 2, 'expect 2 optgroups');
assert.equal(test.instance.dropdown_content.querySelectorAll('.option').length, 5, 'expect 5 options');
assert.equal(test.instance.dropdown_content.querySelectorAll('.option').length, 6, 'expect 6 options');
assert.equal( test.instance.dropdown_content.querySelector('.option[data-value=last]').nextElementSibling, null,'should preserve order of options outside of optgroups');
assert.equal( test.instance.dropdown_content.querySelector('.option[data-value=first]').previousElementSibling, null,'should preserve order of options outside of optgroups');

done();
});

Expand Down

0 comments on commit 2b2353d

Please sign in to comment.