Skip to content

Commit 48ef7ea

Browse files
authored
feat(filtered-list): Add ? and * wildcards to filtered-search, closes openscd#1006. (openscd#1007)
* Add ? and * wildcards to filtered-search, closes openscd#1006. * Ensure wildcard matches at least one character, add tests * Add documentation * Improve algorithm and refactor * Review comments, split on regex for multiple spaces
1 parent 205449b commit 48ef7ea

File tree

5 files changed

+79
-5
lines changed

5 files changed

+79
-5
lines changed

public/md/Home.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ This is an open repository of information on OpenSCD and IEC 61850-6. You are we
33
You can find information related to topics like
44

55
- [Installation](https://github.com/openscd/open-scd/wiki/Install-OpenSCD)
6+
- [Interface](https://github.com/openscd/open-scd/wiki/Interface)
67
- [Project workflow](https://github.com/openscd/open-scd/wiki/Project-workflow)
78
- [Validators used in OpenSCD](https://github.com/openscd/open-scd/wiki/Validators)
89
- [Extensions](https://github.com/openscd/open-scd/wiki/Extensions)

public/md/Interface.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
OpenSCD makes extensive use of lists which allow filtering by a search query.
2+
3+
You can use wildcards as part of a search query:
4+
5+
* A question mark, `?` will match any single character
6+
* An asterisk, `*` will match one or more characters

public/md/_Sidebar.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
### 1.1 [Install OpenSCD](https://github.com/openscd/open-scd/wiki/Install-OpenSCD)
44

5+
### 1.2 [Interface](https://github.com/openscd/open-scd/wiki/Interface)
6+
57
## 2. [Project management](https://github.com/openscd/open-scd/wiki/Project-workflow)
68

79
### 2.1 [Import IEDs](https://github.com/openscd/open-scd/wiki/Import-IEDs)

src/filtered-list.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { List } from '@material/mwc-list';
1818
import { ListBase } from '@material/mwc-list/mwc-list-base';
1919
import { ListItemBase } from '@material/mwc-list/mwc-list-item-base';
2020
import { TextField } from '@material/mwc-textfield';
21+
import { ReportControlElementEditor } from './editors/publisher/report-control-element-editor';
2122

2223
function slotItem(item: Element): Element {
2324
if (!item.closest('filtered-list') || !item.parentElement) return item;
@@ -38,11 +39,23 @@ function hideFiltered(item: ListItemBase, searchText: string): void {
3839
value
3940
).toUpperCase();
4041

41-
const terms: string[] = searchText.toUpperCase().split(' ');
42-
43-
terms.some(term => !filterTarget.includes(term))
44-
? slotItem(item).classList.add('hidden')
45-
: slotItem(item).classList.remove('hidden');
42+
const terms: string[] = searchText
43+
.toUpperCase()
44+
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
45+
.trim()
46+
.split(/\s+/g);
47+
48+
(terms.length === 1 && terms[0] === '') ||
49+
terms.every(term => {
50+
// regexp escape
51+
const reTerm = new RegExp(
52+
`*${term}*`.replace(/\*/g, '.*').replace(/\?/g, '.{1}'),
53+
'i'
54+
);
55+
return reTerm.test(filterTarget);
56+
})
57+
? slotItem(item).classList.remove('hidden')
58+
: slotItem(item).classList.add('hidden');
4659
}
4760

4861
/**

test/unit/filtered-list.test.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,5 +207,57 @@ describe('filtered-list', () => {
207207
expect(element.children[5].classList.contains('hidden')).to.be.true;
208208
expect(element.children[6].classList.contains('hidden')).to.be.false;
209209
});
210+
211+
it('allows filtering with a ? wildcard', async () => {
212+
element.searchField.value = 'item?';
213+
element.onFilterInput();
214+
element.requestUpdate();
215+
await element.updateComplete;
216+
expect(element.children[0].classList.contains('hidden')).to.be.false;
217+
expect(element.children[1].classList.contains('hidden')).to.be.false;
218+
expect(element.children[2].classList.contains('hidden')).to.be.false;
219+
expect(element.children[3].classList.contains('hidden')).to.be.false;
220+
expect(element.children[4].classList.contains('hidden')).to.be.false;
221+
expect(element.children[5].classList.contains('hidden')).to.be.false;
222+
});
223+
224+
it('allows filtering with a * wildcard', async () => {
225+
element.searchField.value = 'te*sec';
226+
element.onFilterInput();
227+
element.requestUpdate();
228+
await element.updateComplete;
229+
expect(element.children[0].classList.contains('hidden')).to.be.false;
230+
expect(element.children[1].classList.contains('hidden')).to.be.false;
231+
expect(element.children[2].classList.contains('hidden')).to.be.false;
232+
expect(element.children[3].classList.contains('hidden')).to.be.false;
233+
expect(element.children[4].classList.contains('hidden')).to.be.true;
234+
expect(element.children[5].classList.contains('hidden')).to.be.true;
235+
});
236+
237+
it('allows filtering with two ? wildcards', async () => {
238+
element.searchField.value = 'nest??item';
239+
element.onFilterInput();
240+
element.requestUpdate();
241+
await element.updateComplete;
242+
expect(element.children[0].classList.contains('hidden')).to.be.true;
243+
expect(element.children[1].classList.contains('hidden')).to.be.true;
244+
expect(element.children[2].classList.contains('hidden')).to.be.true;
245+
expect(element.children[3].classList.contains('hidden')).to.be.true;
246+
expect(element.children[4].classList.contains('hidden')).to.be.false;
247+
expect(element.children[5].classList.contains('hidden')).to.be.false;
248+
});
249+
250+
it('allows filtering with a * and ? wildcard', async () => {
251+
element.searchField.value = 'n*tem?';
252+
element.onFilterInput();
253+
element.requestUpdate();
254+
await element.updateComplete;
255+
expect(element.children[0].classList.contains('hidden')).to.be.true;
256+
expect(element.children[1].classList.contains('hidden')).to.be.true;
257+
expect(element.children[2].classList.contains('hidden')).to.be.true;
258+
expect(element.children[3].classList.contains('hidden')).to.be.true;
259+
expect(element.children[4].classList.contains('hidden')).to.be.false;
260+
expect(element.children[5].classList.contains('hidden')).to.be.false;
261+
});
210262
});
211263
});

0 commit comments

Comments
 (0)