Skip to content

Commit 926c82c

Browse files
authored
feat: add new preFilter callback option (#356)
* feat: add new `preFilter` callback option
1 parent e4689a4 commit 926c82c

File tree

8 files changed

+298
-5
lines changed

8 files changed

+298
-5
lines changed

packages/demo/src/app-routing.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ import Options36 from './options/options36.js';
6868
import Options37 from './options/options37.js';
6969
import Options38 from './options/options38.js';
7070
import Options39 from './options/options39.js';
71+
import Options40 from './options/options40.js';
7172

7273
export const navbarRouting = [
7374
{ name: 'getting-started', view: '/src/getting-started.html', viewModel: GettingStarted, title: 'Getting Started' },
@@ -138,6 +139,7 @@ export const exampleRouting = [
138139
{ name: 'options37', view: '/src/options/options37.html', viewModel: Options37, title: 'Navigation Highlight' },
139140
{ name: 'options38', view: '/src/options/options38.html', viewModel: Options38, title: 'Dark Mode' },
140141
{ 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' },
141143
],
142144
},
143145
{
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+
Filter 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/options40.html"
11+
>html</a
12+
>
13+
|
14+
<a target="_blank" href="https://github.com/ghiscoding/multiple-select-vanilla/blob/main/packages/demo/src/options/options40.ts"
15+
>ts</a
16+
>
17+
</span>
18+
</span>
19+
</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>
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" title="January, March are filtered out">
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" title="The entire Group 2 is filtered out">
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" title="February, April are filtered out">
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" title="Q1 group is filtered out">
84+
</select>
85+
</div>
86+
</div>
87+
</div>
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
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+
preFilter: dataItem => {
13+
// filter out values 1,3 which are January and March
14+
return dataItem.value !== '1' && dataItem.value !== '3';
15+
},
16+
}) as MultipleSelectInstance;
17+
18+
this.ms2 = multipleSelect('select[data-test=select2]', {
19+
filter: true,
20+
preFilter: dataItem => {
21+
// filter out the entire Group2
22+
return (dataItem as OptGroupRowData).label !== 'Group 2';
23+
},
24+
}) as MultipleSelectInstance;
25+
26+
this.ms3 = multipleSelect('select[data-test=select3]', {
27+
data: {
28+
1: 'January',
29+
2: 'February',
30+
3: 'March',
31+
4: 'April',
32+
5: 'May',
33+
6: 'June',
34+
7: 'July',
35+
8: 'August',
36+
9: 'September',
37+
10: 'October',
38+
11: 'November',
39+
12: 'December',
40+
},
41+
preFilter: dataItem => {
42+
// filter out values 2,4 which are February and April
43+
return dataItem.value !== '2' && dataItem.value !== '4';
44+
},
45+
}) as MultipleSelectInstance;
46+
47+
this.ms4 = multipleSelect('select[data-test=select4]', {
48+
data: [
49+
{
50+
type: 'optgroup',
51+
label: 'Q1',
52+
children: [
53+
{
54+
text: 'January',
55+
value: 1,
56+
selected: true,
57+
},
58+
{
59+
text: 'February',
60+
value: 2,
61+
},
62+
{
63+
text: 'March',
64+
value: 3,
65+
},
66+
],
67+
},
68+
{
69+
type: 'optgroup',
70+
label: 'Q2',
71+
children: [
72+
{
73+
text: 'April',
74+
value: 4,
75+
},
76+
{
77+
text: 'May',
78+
value: 5,
79+
},
80+
{
81+
text: 'June',
82+
value: 6,
83+
},
84+
],
85+
},
86+
{
87+
type: 'optgroup',
88+
label: 'Q3',
89+
children: [
90+
{
91+
text: 'July',
92+
value: 7,
93+
},
94+
{
95+
text: 'August',
96+
value: 8,
97+
},
98+
{
99+
text: 'September',
100+
value: 9,
101+
},
102+
],
103+
},
104+
{
105+
type: 'optgroup',
106+
label: 'Q4',
107+
children: [
108+
{
109+
text: 'October',
110+
value: 10,
111+
},
112+
{
113+
text: 'November',
114+
value: 11,
115+
},
116+
{
117+
text: 'December',
118+
value: 12,
119+
},
120+
],
121+
},
122+
],
123+
preFilter: dataItem => {
124+
// filter out the Q1 group
125+
return (dataItem as OptGroupRowData).label !== 'Q1';
126+
},
127+
}) as MultipleSelectInstance;
128+
}
129+
130+
unmount() {
131+
// destroy ms instance(s) to avoid DOM leaks
132+
this.ms1?.destroy();
133+
this.ms2?.destroy();
134+
this.ms3?.destroy();
135+
this.ms4?.destroy();
136+
this.ms1 = undefined;
137+
this.ms2 = undefined;
138+
this.ms3 = undefined;
139+
this.ms4 = undefined;
140+
}
141+
}

packages/multiple-select-vanilla/build-watch.mjs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import { compile as sassCompile } from 'sass';
88
const env = process.env.NODE_ENV;
99

1010
// Start the compilation process
11-
runCompilation(process.env.LERNA_FILE_CHANGES.split(','));
11+
const files = process.env.LERNA_FILE_CHANGES.split(',') || [];
12+
runCompilation(files);
1213

1314
function runBuild(options) {
1415
const startTime = new Date().getTime();
@@ -22,8 +23,7 @@ function runBuild(options) {
2223
target: 'es2021',
2324
sourcemap: true,
2425
logLevel: 'error',
25-
// outfile: env === 'production' ? './dist/multiple-select.min.js' : './dist/multiple-select.js',
26-
outfile: 'dist/multiple-select.js',
26+
outfile: 'dist/index.js',
2727
},
2828
...options,
2929
};

packages/multiple-select-vanilla/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,11 @@
5353
"clean": "rimraf dist",
5454
"build": "pnpm build:all && pnpm build:types:prod",
5555
"postbuild": "pnpm sass:build && pnpm sass:copy",
56-
"dev:init": "pnpm sass:build && pnpm sass:copy && pnpm build:all",
56+
"dev:init": "pnpm sass:build && pnpm sass:copy && pnpm build:all && pnpm build:types:prod",
5757
"build:all": "node build-prod.mjs",
5858
"build:watch": "cross-env NODE_ENV=development node build-watch.mjs",
5959
"build:locales": "esbuild src/locales/all-locales-index.ts --bundle --minify --format=iife --target=es2021 --sourcemap --outfile=dist/locales/multiple-select-all-locales.js",
60-
"build:esm": "esbuild src/index.ts --bundle --minify --format=esm --target=es2021 --sourcemap --outfile=dist/multiple-select.js",
60+
"build:esm": "esbuild src/index.ts --bundle --minify --format=esm --target=es2021 --sourcemap --outfile=dist/index.js",
6161
"build:types": "tsc --emitDeclarationOnly --incremental --declarationMap false --outDir dist",
6262
"build:types:prod": "tsc --emitDeclarationOnly --incremental --declarationMap --outDir dist",
6363
"sass:build": "sass src/styles:dist/styles/css --style=compressed --quiet-deps --no-source-map",

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

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

319+
// user might filter its data prior to rendering the list
320+
if (this.data && this.options.preFilter) {
321+
this.data = this.data.filter(this.options.preFilter);
322+
}
323+
319324
this.dataTotal = setDataKeys(this.data || []);
320325
}
321326

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,9 @@ 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`. */
173+
preFilter?: (dataItem: OptionRowData | OptGroupRowData) => boolean;
174+
172175
/** Defaults to False, should we render option labels as html? */
173176
renderOptionLabelAsHtml?: boolean;
174177

playwright/e2e/options40.spec.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { test, expect } from '@playwright/test';
2+
3+
test.describe('Options 40 - Filter Data', () => {
4+
test('all select dropdown should have data filtered out', async ({ page }) => {
5+
await page.goto('#/options40');
6+
7+
// 1st select
8+
await page.locator('div[data-test=select1].ms-parent').click();
9+
await expect(await page.getByRole('option', { name: 'January' })).toHaveCount(0);
10+
await expect(await page.getByRole('option', { name: 'February' })).toHaveCount(1);
11+
await expect(await page.getByRole('option', { name: 'March' })).toHaveCount(0);
12+
await expect(await page.getByRole('option', { name: 'April' })).toHaveCount(1);
13+
await expect(await page.getByRole('option', { name: 'May' })).toHaveCount(1);
14+
const select1LiElms = await page.locator('div[data-test=select1] li[role="option"]');
15+
await expect(select1LiElms).toHaveCount(10);
16+
await page.locator('div[data-test=select1].ms-parent').click();
17+
18+
// 2nd select
19+
await page.locator('div[data-test=select2].ms-parent').click();
20+
const select2GroupElms = await page.locator('div[data-test=select2] li.group');
21+
const select2LiElms = await page.locator('div[data-test=select2] li');
22+
await expect(select2GroupElms).toHaveCount(2);
23+
await expect(await select2GroupElms.getByText('Group 1')).toHaveCount(1);
24+
await expect(await select2LiElms.getByText('Option 1')).toHaveCount(1);
25+
await expect(await select2GroupElms.getByText('Group 2')).toHaveCount(0);
26+
await expect(await select2LiElms.getByText('Option 4')).toHaveCount(0);
27+
await expect(await select2GroupElms.getByText('Group 3')).toHaveCount(1);
28+
await expect(await select2LiElms.getByText('Option 7')).toHaveCount(1);
29+
await page.locator('div[data-test=select2].ms-parent').click();
30+
31+
// 3rd select
32+
await page.locator('div[data-test=select3].ms-parent').click();
33+
await expect(await page.getByRole('option', { name: 'January' })).toHaveCount(1);
34+
await expect(await page.getByRole('option', { name: 'February' })).toHaveCount(0);
35+
await expect(await page.getByRole('option', { name: 'March' })).toHaveCount(1);
36+
await expect(await page.getByRole('option', { name: 'April' })).toHaveCount(0);
37+
await expect(await page.getByRole('option', { name: 'May' })).toHaveCount(1);
38+
const select3LiElms = await page.locator('div[data-test=select1] li[role="option"]');
39+
await expect(select3LiElms).toHaveCount(10);
40+
await page.locator('div[data-test=select3].ms-parent').click();
41+
42+
// 4th select
43+
await page.locator('div[data-test=select4].ms-parent').click();
44+
const select4GroupElms = await page.locator('div[data-test=select4] li.group');
45+
const select4LiElms = await page.locator('div[data-test=select4] li');
46+
await expect(select4GroupElms).toHaveCount(3);
47+
await expect(await select4GroupElms.getByText('Q1')).toHaveCount(0);
48+
await expect(await select4LiElms.getByText('January')).toHaveCount(0);
49+
await expect(await select4GroupElms.getByText('Q2')).toHaveCount(1);
50+
await expect(await select4LiElms.getByText('April')).toHaveCount(1);
51+
await expect(await select4GroupElms.getByText('Q3')).toHaveCount(1);
52+
await expect(await select4LiElms.getByText('July')).toHaveCount(1);
53+
await page.locator('div[data-test=select4].ms-parent').click();
54+
});
55+
});

0 commit comments

Comments
 (0)