Skip to content

Commit 0491b8d

Browse files
authored
feat: add new preSort callback option, fixes #355 (#358)
* feat: add new `preSort` callback option, fixes #355
1 parent 06242ac commit 0491b8d

File tree

8 files changed

+315
-8
lines changed

8 files changed

+315
-8
lines changed

packages/demo/src/app-routing.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ import Options37 from './options/options37.js';
6969
import Options38 from './options/options38.js';
7070
import Options39 from './options/options39.js';
7171
import Options40 from './options/options40.js';
72+
import Options41 from './options/options41.js';
7273

7374
export const navbarRouting = [
7475
{ name: 'getting-started', view: '/src/getting-started.html', viewModel: GettingStarted, title: 'Getting Started' },
@@ -139,7 +140,8 @@ export const exampleRouting = [
139140
{ name: 'options37', view: '/src/options/options37.html', viewModel: Options37, title: 'Navigation Highlight' },
140141
{ name: 'options38', view: '/src/options/options38.html', viewModel: Options38, title: 'Dark Mode' },
141142
{ name: 'options39', view: '/src/options/options39.html', viewModel: Options39, title: 'Label Id (aria-labelledby)' },
142-
{ name: 'options40', view: '/src/options/options40.html', viewModel: Options40, title: 'Filter Data' },
143+
{ name: 'options40', view: '/src/options/options40.html', viewModel: Options40, title: 'Pre-Filter Data' },
144+
{ name: 'options41', view: '/src/options/options41.html', viewModel: Options41, title: 'Pre-Sort Data' },
143145
],
144146
},
145147
{

packages/demo/src/options/options40.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<div class="row mb-2">
22
<div class="col-md-12 title-desc">
33
<h2 class="bd-title">
4-
Filter Data
4+
Pre-Filter Data
55
<span class="float-end links">
66
Code <span class="fa fa-link"></span>
77
<span class="small">
@@ -17,7 +17,7 @@ <h2 class="bd-title">
1717
</span>
1818
</span>
1919
</h2>
20-
<div class="demo-subtitle">Use <code>preFilter(predicate)</code> to pre-filter the data collection before rendering the select dropdown in the UI.</div>
20+
<div class="demo-subtitle">Use <code>preFilter: predicate</code> to pre-filter the data collection before rendering the select dropdown in the UI.</div>
2121
</div>
2222
</div>
2323

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<div class="row mb-2">
2+
<div class="col-md-12 title-desc">
3+
<h2 class="bd-title">
4+
Pre-Sort Data
5+
<span class="float-end links">
6+
Code <span class="fa fa-link"></span>
7+
<span class="small">
8+
<a
9+
target="_blank"
10+
href="https://github.com/ghiscoding/multiple-select-vanilla/blob/main/packages/demo/src/options/options41.html"
11+
>html</a
12+
>
13+
|
14+
<a target="_blank" href="https://github.com/ghiscoding/multiple-select-vanilla/blob/main/packages/demo/src/options/options41.ts"
15+
>ts</a
16+
>
17+
</span>
18+
</span>
19+
</h2>
20+
<div class="demo-subtitle">Use <code>preSort: comparer</code> to pre-sort the data collection before rendering the select dropdown in the UI.</div>
21+
</div>
22+
</div>
23+
24+
<div>
25+
<div class="mb-3 row">
26+
<label class="col-sm-2">Basic Select</label>
27+
28+
<div class="col-sm-10">
29+
<select multiple="multiple" class="full-width" data-test="select1">
30+
<option value="1">January</option>
31+
<option value="2">February</option>
32+
<option value="3">March</option>
33+
<option value="4">April</option>
34+
<option value="5">May</option>
35+
<option value="6">June</option>
36+
<option value="7">July</option>
37+
<option value="8">August</option>
38+
<option value="9">September</option>
39+
<option value="10">October</option>
40+
<option value="11">November</option>
41+
<option value="12">December</option>
42+
</select>
43+
</div>
44+
</div>
45+
46+
<div class="mb-3 row">
47+
<label class="col-sm-2"> Group Select </label>
48+
49+
<div class="col-sm-10">
50+
<select multiple="multiple" class="full-width" data-test="select2">
51+
<optgroup label="Group 1">
52+
<option value="1">Option 1</option>
53+
<option value="2">Option 2</option>
54+
<option value="3">Option 3</option>
55+
</optgroup>
56+
<optgroup label="Group 2">
57+
<option value="4">Option 4</option>
58+
<option value="5">Option 5</option>
59+
<option value="6">Option 6</option>
60+
</optgroup>
61+
<optgroup label="Group 3">
62+
<option value="7">Option 7</option>
63+
<option value="8">Option 8</option>
64+
<option value="9">Option 9</option>
65+
</optgroup>
66+
</select>
67+
</div>
68+
</div>
69+
70+
<div class="mb-3 row">
71+
<label class="col-sm-2">Basic Select (from data)</label>
72+
73+
<div class="col-sm-10">
74+
<select multiple="multiple" class="full-width" data-test="select3">
75+
</select>
76+
</div>
77+
</div>
78+
79+
<div class="mb-3 row">
80+
<label class="col-sm-2"> Group Select (from data)</label>
81+
82+
<div class="col-sm-10">
83+
<select multiple="multiple" class="full-width" data-test="select4">
84+
</select>
85+
</div>
86+
</div>
87+
</div>
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import { type MultipleSelectInstance, type OptGroupRowData, multipleSelect } from 'multiple-select-vanilla';
2+
3+
export default class Example {
4+
ms1?: MultipleSelectInstance;
5+
ms2?: MultipleSelectInstance;
6+
ms3?: MultipleSelectInstance;
7+
ms4?: MultipleSelectInstance;
8+
9+
mount() {
10+
this.ms1 = multipleSelect('select[data-test=select1]', {
11+
filter: true,
12+
preSort: (item1, item2) => {
13+
// sort by value in reverse order
14+
const direction = -1; // reverse order
15+
return (+item1.value! - +item2.value!) * direction;
16+
},
17+
}) as MultipleSelectInstance;
18+
19+
this.ms2 = multipleSelect('select[data-test=select2]', {
20+
filter: true,
21+
preSort: (item1, item2) => {
22+
// sort by value in reverse order
23+
const direction = -1; // reverse order
24+
// @ts-ignore
25+
if (direction === 1) {
26+
return (item1 as OptGroupRowData).label < (item2 as OptGroupRowData).label ? -1 : 1;
27+
}
28+
return (item1 as OptGroupRowData).label < (item2 as OptGroupRowData).label ? 1 : -1;
29+
},
30+
}) as MultipleSelectInstance;
31+
32+
this.ms3 = multipleSelect('select[data-test=select3]', {
33+
data: {
34+
1: 'January',
35+
2: 'February',
36+
3: 'March',
37+
4: 'April',
38+
5: 'May',
39+
6: 'June',
40+
7: 'July',
41+
8: 'August',
42+
9: 'September',
43+
10: 'October',
44+
11: 'November',
45+
12: 'December',
46+
},
47+
preSort: (item1, item2) => {
48+
// sort by value in reverse order
49+
const direction = -1; // reverse order
50+
return (+item1.value! - +item2.value!) * direction;
51+
},
52+
}) as MultipleSelectInstance;
53+
54+
this.ms4 = multipleSelect('select[data-test=select4]', {
55+
data: [
56+
{
57+
type: 'optgroup',
58+
label: 'Q1',
59+
children: [
60+
{
61+
text: 'January',
62+
value: 1,
63+
selected: true,
64+
},
65+
{
66+
text: 'February',
67+
value: 2,
68+
},
69+
{
70+
text: 'March',
71+
value: 3,
72+
},
73+
],
74+
},
75+
{
76+
type: 'optgroup',
77+
label: 'Q2',
78+
children: [
79+
{
80+
text: 'April',
81+
value: 4,
82+
},
83+
{
84+
text: 'May',
85+
value: 5,
86+
},
87+
{
88+
text: 'June',
89+
value: 6,
90+
},
91+
],
92+
},
93+
{
94+
type: 'optgroup',
95+
label: 'Q3',
96+
children: [
97+
{
98+
text: 'July',
99+
value: 7,
100+
},
101+
{
102+
text: 'August',
103+
value: 8,
104+
},
105+
{
106+
text: 'September',
107+
value: 9,
108+
},
109+
],
110+
},
111+
{
112+
type: 'optgroup',
113+
label: 'Q4',
114+
children: [
115+
{
116+
text: 'October',
117+
value: 10,
118+
},
119+
{
120+
text: 'November',
121+
value: 11,
122+
},
123+
{
124+
text: 'December',
125+
value: 12,
126+
},
127+
],
128+
},
129+
],
130+
preSort: (item1, item2) => {
131+
// sort by value in reverse order
132+
const direction = -1; // reverse order
133+
// @ts-ignore
134+
if (direction === 1) {
135+
return (item1 as OptGroupRowData).label < (item2 as OptGroupRowData).label ? -1 : 1;
136+
}
137+
return (item1 as OptGroupRowData).label < (item2 as OptGroupRowData).label ? 1 : -1;
138+
},
139+
}) as MultipleSelectInstance;
140+
}
141+
142+
unmount() {
143+
// destroy ms instance(s) to avoid DOM leaks
144+
this.ms1?.destroy();
145+
this.ms2?.destroy();
146+
this.ms3?.destroy();
147+
this.ms4?.destroy();
148+
this.ms1 = undefined;
149+
this.ms2 = undefined;
150+
this.ms3 = undefined;
151+
this.ms4 = undefined;
152+
}
153+
}

packages/multiple-select-vanilla/src/MultipleSelectInstance.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,11 +316,16 @@ export class MultipleSelectInstance {
316316
this.fromHtml = true;
317317
}
318318

319-
// user might filter its data prior to rendering the list
319+
// user might pre-filter its data prior to rendering the list
320320
if (this.data && this.options.preFilter) {
321321
this.data = this.data.filter(this.options.preFilter);
322322
}
323323

324+
// user might pre-sort its data prior to rendering the list
325+
if (this.data && this.options.preSort) {
326+
this.data = this.data.sort(this.options.preSort);
327+
}
328+
324329
this.dataTotal = setDataKeys(this.data || []);
325330
}
326331

packages/multiple-select-vanilla/src/models/multipleSelectOption.interface.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,9 +169,18 @@ export interface MultipleSelectOption extends MultipleSelectLocale {
169169
/** Defines the position of select dropdown, can only be bottom or top. By default this option is set to bottom. */
170170
position: 'bottom' | 'top';
171171

172-
/** Custom Filter predicate function to pre-filter the data collection before rendering the select dropdown. Executed before `preSort`. */
172+
/**
173+
* Custom Filter predicate function callback to pre-filter the data collection before rendering the select dropdown.
174+
* Note that this is executed before `preSort` (when defined).
175+
*/
173176
preFilter?: (dataItem: OptionRowData | OptGroupRowData) => boolean;
174177

178+
/**
179+
* Custom Sort Comparer function callback to pre-sort the data collection before rendering the select dropdown.
180+
* Note that this is executed after `preSort` (when defined).
181+
*/
182+
preSort?: (item1: OptionRowData | OptGroupRowData, item2: OptionRowData | OptGroupRowData) => number;
183+
175184
/** Defaults to False, should we render option labels as html? */
176185
renderOptionLabelAsHtml?: boolean;
177186

playwright/e2e/options40.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { test, expect } from '@playwright/test';
22

3-
test.describe('Options 40 - Filter Data', () => {
4-
test('all select dropdown should have data filtered out', async ({ page }) => {
3+
test.describe('Options 40 - Pre-Filter Data', () => {
4+
test('all select dropdown should have data pre-filtered', async ({ page }) => {
55
await page.goto('#/options40');
66

77
// 1st select
@@ -35,7 +35,7 @@ test.describe('Options 40 - Filter Data', () => {
3535
await expect(await page.getByRole('option', { name: 'March' })).toHaveCount(1);
3636
await expect(await page.getByRole('option', { name: 'April' })).toHaveCount(0);
3737
await expect(await page.getByRole('option', { name: 'May' })).toHaveCount(1);
38-
const select3LiElms = await page.locator('div[data-test=select1] li[role="option"]');
38+
const select3LiElms = await page.locator('div[data-test=select3] li[role="option"]');
3939
await expect(select3LiElms).toHaveCount(10);
4040
await page.locator('div[data-test=select3].ms-parent').click();
4141

playwright/e2e/options41.spec.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { test, expect } from '@playwright/test';
2+
3+
test.describe('Options 41 - Pre-Sort Data', () => {
4+
test('all select dropdown should have data pre-filtered', async ({ page }) => {
5+
await page.goto('#/options41');
6+
7+
// 1st select
8+
await page.locator('div[data-test=select1].ms-parent').click();
9+
const select1LiElms = await page.locator('div[data-test=select1] li[role="option"]');
10+
await expect(select1LiElms).toHaveCount(12);
11+
const select1li1 = await page.locator('div[data-test=select1] .ms-drop li').nth(0);
12+
await expect(await select1li1.locator('label')).toHaveText('December');
13+
const select1li2 = await page.locator('div[data-test=select1] .ms-drop li').nth(1);
14+
await expect(await select1li2.locator('label')).toHaveText('November');
15+
const select1li3 = await page.locator('div[data-test=select1] .ms-drop li').nth(2);
16+
await expect(await select1li3.locator('label')).toHaveText('October');
17+
await page.locator('div[data-test=select1].ms-parent').click();
18+
19+
// 2nd select
20+
await page.locator('div[data-test=select2].ms-parent').click();
21+
const select2GroupElms = await page.locator('div[data-test=select2] li.group');
22+
const select2li1 = await page.locator('div[data-test=select2] .ms-drop li.group').nth(0);
23+
await expect(await select2li1.locator('label.optgroup')).toHaveText('Group 3');
24+
const select2li2 = await page.locator('div[data-test=select2] .ms-drop li.group').nth(1);
25+
await expect(await select2li2.locator('label.optgroup')).toHaveText('Group 2');
26+
await expect(select2GroupElms).toHaveCount(3);
27+
await page.locator('div[data-test=select2].ms-parent').click();
28+
29+
// 3rd select
30+
await page.locator('div[data-test=select3].ms-parent').click();
31+
const select3LiElms = await page.locator('div[data-test=select3] li[role="option"]');
32+
await expect(select3LiElms).toHaveCount(12);
33+
const select3li1 = await page.locator('div[data-test=select3] .ms-drop li').nth(0);
34+
await expect(await select3li1.locator('label')).toHaveText('December');
35+
const select3li2 = await page.locator('div[data-test=select3] .ms-drop li').nth(1);
36+
await expect(await select3li2.locator('label')).toHaveText('November');
37+
const select3li3 = await page.locator('div[data-test=select3] .ms-drop li').nth(2);
38+
await expect(await select3li3.locator('label')).toHaveText('October');
39+
await page.locator('div[data-test=select3].ms-parent').click();
40+
41+
// 4th select
42+
await page.locator('div[data-test=select4].ms-parent').click();
43+
const select4GroupElms = await page.locator('div[data-test=select4] li.group');
44+
const select4li1 = await page.locator('div[data-test=select4] .ms-drop li.group').nth(0);
45+
await expect(await select4li1.locator('label.optgroup')).toHaveText('Q4');
46+
const select4li2 = await page.locator('div[data-test=select4] .ms-drop li.group').nth(1);
47+
await expect(await select4li2.locator('label.optgroup')).toHaveText('Q3');
48+
await expect(select4GroupElms).toHaveCount(4);
49+
await page.locator('div[data-test=select4].ms-parent').click();
50+
});
51+
});

0 commit comments

Comments
 (0)