From e3d76fc392a752e2121a6bd842a5f8e2249406b8 Mon Sep 17 00:00:00 2001 From: Ris Adams Date: Thu, 17 Nov 2022 12:10:47 -0500 Subject: [PATCH] Adding Dynamic option groups feature * Reviving PR #1226 from @jackbentley * Addresses #1025 * Addresses #151 --- CHANGELOG.md | 9 +- examples/optgroups.html | 436 ++++++++++--------- src/defaults.js | 42 +- src/selectize.js | 8 +- test/setup.js | 926 +++++++++++++++++++++------------------- 5 files changed, 762 insertions(+), 659 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2f2eaa3e..5f705493c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,14 @@ +# Changelog + -## v0.15.0 · 03 11 2022 +## v0.15.1 · 17 11 2022 + +- New feature: dynamically add option groups + _@jackbentley_ + +## v0.15.0 · 14 11 2022 ### Breaking changes diff --git a/examples/optgroups.html b/examples/optgroups.html index 174d11873..3f76fc40f 100644 --- a/examples/optgroups.html +++ b/examples/optgroups.html @@ -2,201 +2,253 @@ - - - - - Selectize.js Demo - - - - - - - - - - - -
-

Selectize.js

+ + + -
-

Optgroups (basic)

-
- - -
- -
+ + + + Selectize.js Demo + + + + + + + + + + -
-

Optgroups (programmatic)

-
- - -
- -
+ + < id="wrapper"> +

Selectize.js

+ +
+

Optgroups (basic)

+
+ + +
+ +
+ +
+

Optgroups (disabled)

+
+ + +
+ +
+ +
+

Optgroups (repeated options)

+
+ + +
+ +
+ +
+

Optgroups (programmatic)

+
+ + +
+ +
+ +
+

Plugin: "optgroup_columns"

+
+ +
+ +
+ + +
+

Optgroups (dynamic)

+
+ + +
+ +
+ -
-

Plugin: "optgroup_columns"

-
- -
- -
-
- diff --git a/src/defaults.js b/src/defaults.js index d69f7701f..34098ee2a 100644 --- a/src/defaults.js +++ b/src/defaults.js @@ -62,26 +62,28 @@ Selectize.defaults = { normalize: false, ignoreOnDropwdownHeight: 'img, i', search: true, - /* - load : null, // function(query, callback) { ... } - score : null, // function(search) { ... } - formatValueToKey : null, // function(key) { ... } - onInitialize : null, // function() { ... } - onChange : null, // function(value) { ... } - onItemAdd : null, // function(value, $item) { ... } - onItemRemove : null, // function(value, $item) { ... } - onClear : null, // function() { ... } - onOptionAdd : null, // function(value, data) { ... } - onOptionRemove : null, // function(value) { ... } - onOptionClear : null, // function() { ... } - onOptionGroupAdd : null, // function(id, data) { ... } - onOptionGroupRemove : null, // function(id) { ... } - onOptionGroupClear : null, // function() { ... } - onDropdownOpen : null, // function($dropdown) { ... } - onDropdownClose : null, // function($dropdown) { ... } - onType : null, // function(str) { ... } - onDelete : null, // function(values) { ... } - */ + + /* + load : null, // function(query, callback) { ... } + score : null, // function(search) { ... } + formatValueToKey : null, // function(key) { ... } + optionGroupRegister : null, // function(optgroup) to register dynamically created option groups + onInitialize : null, // function() { ... } + onChange : null, // function(value) { ... } + onItemAdd : null, // function(value, $item) { ... } + onItemRemove : null, // function(value, $item) { ... } + onClear : null, // function() { ... } + onOptionAdd : null, // function(value, data) { ... } + onOptionRemove : null, // function(value) { ... } + onOptionClear : null, // function() { ... } + onOptionGroupAdd : null, // function(id, data) { ... } + onOptionGroupRemove : null, // function(id) { ... } + onOptionGroupClear : null, // function() { ... } + onDropdownOpen : null, // function($dropdown) { ... } + onDropdownClose : null, // function($dropdown) { ... } + onType : null, // function(str) { ... } + onDelete : null, // function(values) { ... } + */ render: { /* diff --git a/src/selectize.js b/src/selectize.js index 4aef82d83..fde40d962 100644 --- a/src/selectize.js +++ b/src/selectize.js @@ -1172,7 +1172,13 @@ $.extend(Selectize.prototype, { for (j = 0, k = optgroups && optgroups.length; j < k; j++) { optgroup = optgroups[j]; - if (!self.optgroups.hasOwnProperty(optgroup)) { + if (!self.optgroups.hasOwnProperty(optgroup) && typeof self.settings.optionGroupRegister === 'function') { + var regGroup; + if (regGroup = self.settings.optionGroupRegister.apply(self, [optgroup])) { + self.registerOptionGroup(regGroup); + } + } + if (!self.optgroups.hasOwnProperty(optgroup)) { optgroup = ''; } if (!groups.hasOwnProperty(optgroup)) { diff --git a/test/setup.js b/test/setup.js index 9f8cbf892..4a64fdded 100644 --- a/test/setup.js +++ b/test/setup.js @@ -1,226 +1,262 @@ -(function() { - - describe('Setup', function() { - - it('should not allow duplicate initialization', function() { - var instance_before, instance_after, test; - - test = setup_test('', {}); - instance_before = test.$select[0].selectize; - test.$select.selectize(); - instance_after = test.$select[0].selectize; - - expect(instance_before).to.be.equal(instance_after); - }); - - describe('', function() { - it('should complete without exceptions', function() { - var test = setup_test('', {}); - }); - it('should populate items,options from "dataAttr" if available', function() { - var data = [{val: 'a', lbl: 'Hello'}, {val: 'b', lbl: 'World'}]; - var test = setup_test('', { - dataAttr: 'data-hydrate', - valueField: 'val', - labelField: 'lbl' - }); - expect(test.selectize.getValue()).to.be.equal('a,b'); - assert.deepEqual(test.selectize.items, ['a','b']); - assert.deepEqual(test.selectize.options, { - 'a': {val: 'a', lbl: 'Hello', $order: 1}, - 'b': {val: 'b', lbl: 'World', $order: 2} - }); - }); - describe('getValue()', function() { - it('should return value as a string', function() { - var test = setup_test('', {delimiter: ','}); - expect(test.selectize.getValue()).to.be.a('string'); - }); - it('should return "" when empty', function() { - var test = setup_test('', {delimiter: ','}); - expect(test.selectize.getValue()).to.be.equal(''); - }); - it('should return proper value when not empty', function() { - var test = setup_test('', {delimiter: ','}); - expect(test.selectize.getValue()).to.be.equal('a,b'); - }); - }); - describe('', function() { - it('should propagate original input attributes to the generated input', function() { - var test = setup_test('', {}); - expect(test.selectize.$control_input.attr('autocorrect')).to.be.equal('off'); - expect(test.selectize.$control_input.attr('autocapitalize')).to.be.equal('none'); - }); - it('should not add attributes if not present in the original', function() { - var test = setup_test('', {}); - expect(test.selectize.$control_input.attr('autocorrect')).to.be.equal(undefined); - expect(test.selectize.$control_input.attr('autocapitalize')).to.be.equal(undefined); - }); - }); - }); - - describe('', function() { - it('should complete without exceptions', function(done) { - var test = setup_test('', {}); - window.setTimeout(function() { - assert.equal(test.selectize.$control_input.attr('type'), 'number'); - done(); - }, 0); - }); - }); - - describe('', {}); - }); - it('should allow for values optgroups with duplicated options', function() { - var test = setup_test([''].join(''), { - optgroupValueField: 'val', - optgroupField: 'grp', - disabledField: 'dis' +(function () { + + describe('Setup', function () { + + it('should not allow duplicate initialization', function () { + var instance_before, instance_after, test; + + test = setup_test('', {}); + instance_before = test.$select[0].selectize; + test.$select.selectize(); + instance_after = test.$select[0].selectize; + + expect(instance_before).to.be.equal(instance_after); + }); + + describe('', function () { + it('should complete without exceptions', function () { + var test = setup_test('', {}); + }); + it('should populate items,options from "dataAttr" if available', function () { + var data = [{ val: 'a', lbl: 'Hello' }, { val: 'b', lbl: 'World' }]; + var test = setup_test('', { + dataAttr: 'data-hydrate', + valueField: 'val', + labelField: 'lbl' + }); + expect(test.selectize.getValue()).to.be.equal('a,b'); + assert.deepEqual(test.selectize.items, ['a', 'b']); + assert.deepEqual(test.selectize.options, { + 'a': { val: 'a', lbl: 'Hello', $order: 1 }, + 'b': { val: 'b', lbl: 'World', $order: 2 } + }); + }); + describe('getValue()', function () { + it('should return value as a string', function () { + var test = setup_test('', { delimiter: ',' }); + expect(test.selectize.getValue()).to.be.a('string'); + }); + it('should return "" when empty', function () { + var test = setup_test('', { delimiter: ',' }); + expect(test.selectize.getValue()).to.be.equal(''); + }); + it('should return proper value when not empty', function () { + var test = setup_test('', { delimiter: ',' }); + expect(test.selectize.getValue()).to.be.equal('a,b'); + }); + }); + describe('', function () { + it('should propagate original input attributes to the generated input', function () { + var test = setup_test('', {}); + expect(test.selectize.$control_input.attr('autocorrect')).to.be.equal('off'); + expect(test.selectize.$control_input.attr('autocapitalize')).to.be.equal('none'); + }); + it('should not add attributes if not present in the original', function () { + var test = setup_test('', {}); + expect(test.selectize.$control_input.attr('autocorrect')).to.be.equal(undefined); + expect(test.selectize.$control_input.attr('autocapitalize')).to.be.equal(undefined); + }); + }); + }); + + describe('', function () { + it('should complete without exceptions', function (done) { + var test = setup_test('', {}); + window.setTimeout(function () { + assert.equal(test.selectize.$control_input.attr('type'), 'number'); + done(); + }, 0); + }); + }); + + describe('', {}); + }); + it('should allow for values optgroups with duplicated options', function () { + var test = setup_test([''].join(''), { + optgroupValueField: 'val', + optgroupField: 'grp', + disabledField: 'dis' + }); + assert.deepEqual(test.selectize.options, { + 'a': { text: 'Item A', value: 'a', grp: ['Group 1', 'Group 2'], $order: 1, dis: false, styles: '', classes: '' }, + 'b': { text: 'Item B', value: 'b', grp: ['Group 1', 'Group 2'], $order: 2, dis: false, styles: '', classes: '' } + }); + assert.deepEqual(test.selectize.optgroups, { + 'Group 1': { label: 'Group 1', val: 'Group 1', $order: 3, dis: false }, + 'Group 2': { label: 'Group 2', val: 'Group 2', $order: 4, dis: false } + }, '2'); + }); + it('should allow respect disabled flags of option and optgroup', function () { + var test = setup_test([''].join(''), { + optgroupValueField: 'val', + optgroupField: 'grp', + disabledField: 'dis' }); - assert.deepEqual(test.selectize.options, { - 'a': {text: 'Item A', value: 'a', grp: ['Group 1', 'Group 2'], $order: 1, dis: false, styles: '', classes: ''}, - 'b': {text: 'Item B', value: 'b', grp: ['Group 1', 'Group 2'], $order: 2, dis: false, styles: '', classes: ''} - }); - assert.deepEqual(test.selectize.optgroups, { - 'Group 1': {label: 'Group 1', val: 'Group 1', $order: 3, dis: false}, - 'Group 2': {label: 'Group 2', val: 'Group 2', $order: 4, dis: false} - }, '2'); - }); - it('should allow respect disabled flags of option and optgroup', function() { - var test = setup_test([''].join(''), { - optgroupValueField: 'val', - optgroupField: 'grp', - disabledField: 'dis' + assert.deepEqual(test.selectize.options, { + 'a': { text: 'Item A', value: 'a', grp: ['Group 1', 'Group 2'], $order: 1, dis: true, styles: '', classes: '' }, + 'b': { text: 'Item B', value: 'b', grp: ['Group 1', 'Group 2'], $order: 2, dis: false, styles: '', classes: '' } }); - assert.deepEqual(test.selectize.options, { - 'a': {text: 'Item A', value: 'a', grp: ['Group 1', 'Group 2'], $order: 1, dis: true, styles: '', classes: ''}, - 'b': {text: 'Item B', value: 'b', grp: ['Group 1', 'Group 2'], $order: 2, dis: false, styles: '', classes: ''} - }); - assert.deepEqual(test.selectize.optgroups, { - 'Group 1': {label: 'Group 1', val: 'Group 1', $order: 3, dis: false}, - 'Group 2': {label: 'Group 2', val: 'Group 2', $order: 4, dis: true} - }, '2'); - }); - it('should add options in text form (no html entities)', function() { - var test = setup_test('', {}); - expect(test.selectize.options['a'].text).to.be.equal(''); - }); - it('should keep options in original order if no sort given', function(done) { - var test = setup_test([ - '' - ].join(), {}); - - var order_expected = ['AL','AK','AZ','AR','CO','CT','DE','DC','FL','GA','HI','ID','IL','IN','IA','KS','KY','LA','ME','MD','MA','MI','MN','MS','MO','MT','NE','NV','NH','NJ','NM','NY','NC','ND','OH','OK','OR','PA','RI','SC','SD','TN','TX','UT','VT','VA','WA','WV','WI','01','10']; - var order_actual = []; - - test.selectize.refreshOptions(true); - window.setTimeout(function() { - test.selectize.$dropdown.find('[data-value]').each(function(i, el) { - order_actual.push($(el).attr('data-value')); - }); - - expect(order_actual).to.eql(order_expected); - done(); - }, 0); - }); - it('should respect option disabled flag', function (done) { - var test = setup_test([''].join(''), {}); - - test.selectize.refreshOptions(true); - window.setTimeout(function() { - expect(test.selectize.$dropdown.find('.option')).to.has.length(2); - expect(test.selectize.$dropdown.find('[data-selectable]')).to.has.length(1); - done(); - }, 0); + assert.deepEqual(test.selectize.optgroups, { + 'Group 1': { label: 'Group 1', val: 'Group 1', $order: 3, dis: false }, + 'Group 2': { label: 'Group 2', val: 'Group 2', $order: 4, dis: true } + }, '2'); + }); + it('should add options in text form (no html entities)', function () { + var test = setup_test('', {}); + expect(test.selectize.options['a'].text).to.be.equal(''); + }); + it('should keep options in original order if no sort given', function (done) { + var test = setup_test([ + '' + ].join(), {}); + + var order_expected = ['AL', 'AK', 'AZ', 'AR', 'CO', 'CT', 'DE', 'DC', 'FL', 'GA', 'HI', 'ID', 'IL', 'IN', 'IA', 'KS', 'KY', 'LA', 'ME', 'MD', 'MA', 'MI', 'MN', 'MS', 'MO', 'MT', 'NE', 'NV', 'NH', 'NJ', 'NM', 'NY', 'NC', 'ND', 'OH', 'OK', 'OR', 'PA', 'RI', 'SC', 'SD', 'TN', 'TX', 'UT', 'VT', 'VA', 'WA', 'WV', 'WI', '01', '10']; + var order_actual = []; + + test.selectize.refreshOptions(true); + window.setTimeout(function () { + test.selectize.$dropdown.find('[data-value]').each(function (i, el) { + order_actual.push($(el).attr('data-value')); + }); + + expect(order_actual).to.eql(order_expected); + done(); + }, 0); + }); + it('should register should not care optionGroupRegister is not set', function () { + var test = setup_test('', { + options: [ + { value: 'a', grp: 'someGroup' }, + { value: 'b', grp: 'anotherGroup' }, + { value: 'c', grp: 'anotherGroup' } + ], + optgroupValueField: 'val', + optgroupField: 'grp', + optionGroupRegister: function (optgroup) { + var group = {}; + group['label'] = optgroup; + group['val'] = optgroup; + + return group; + } + }); + test.selectize.refreshOptions(); + assert.deepEqual(test.selectize.optgroups, { + 'someGroup': { label: 'someGroup', val: 'someGroup', $order: 4 }, + 'anotherGroup': { label: 'anotherGroup', val: 'anotherGroup', $order: 5 } + }, '2'); + }); + it('should respect option disabled flag', function (done) { + var test = setup_test([''].join(''), {}); + + test.selectize.refreshOptions(true); + window.setTimeout(function () { + expect(test.selectize.$dropdown.find('.option')).to.has.length(2); + expect(test.selectize.$dropdown.find('[data-selectable]')).to.has.length(1); + done(); + }, 0); }); it('should respect option style / class', function () { var test; - beforeEach(function() { + beforeEach(function () { test = setup_test(''); + ''); }); - it('should dropdown height to be equal 100', function() { + it('should dropdown height to be equal 100', function () { test.selectize.focus(); window.setTimeout(function () { @@ -233,10 +269,10 @@ it('should respect input readonly (option search = false)', function () { var test; - beforeEach(function() { + beforeEach(function () { test = setup_test('', { search: false }); + '', { search: false }); }); it('should readonly on input and cursor pointer on input and control element', function () { @@ -247,245 +283,245 @@ }, 0); }); }); - describe('getValue()', function() { - it('should return "" when empty', function() { - var test = setup_test('', {}); - expect(test.selectize.getValue()).to.be.equal('a'); - }); - }); - }); - - describe('', {}); - }); - describe('getValue()', function() { - it('should return [] when empty', function() { - var test = setup_test('', {}); - expect(test.selectize.getValue()).to.deep.equal(['a']); - }); - }); - }); - - describe('', {}); - }); - it('should have "disabled" class', function() { - expect(test.selectize.$control.hasClass('disabled')).to.be.equal(true); - }); - it('should have isDisabled property set to true', function() { - expect(test.selectize.isDisabled).to.be.equal(true); - }); - }); - - describe('' + - '' + - '' + - '', {}); - $form = test.$select.parents('form'); - $button = $('