Skip to content

Commit 8492b11

Browse files
authored
Merge pull request #2390 from IgniteUI/tzhelev/gridSearch-exactMatch
Add 'exactMatch' option to grid-search API and highlight directive
2 parents 13b6021 + 233ee2c commit 8492b11

File tree

8 files changed

+284
-61
lines changed

8 files changed

+284
-61
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ All notable changes for each version of this project will be documented in this
88
- `IgxOverlayOutlet` directive introducedto mark an element as an `igxOverlay` outlet container. [ReadMe](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/directives/toggle/README.md)
99
- `igxButtonGroup`
1010
- Added the ability to define buttons directly in the template
11+
- `IgxTextHighlightDirective`: The `highlight` method now has a new optional parameter called `exactMatch` (defaults to false).
12+
- If its value is false, all occurrences of the search text will be highlighted in the group's value.
13+
- If its value is true, the entire group's value should equals the search text in order to be highlighted (caseSensitive argument is respected as well).
14+
- `IgxGrid`: The `findNext` and `findPrev` methods now have a new optional parameter called `exactMatch` (defaults to false).
15+
- If its value is false, all occurrences of the search text will be highlighted in the grid's cells.
16+
- If its value is true, the entire value of each cell should equals the search text in order to be highlighted (caseSensitive argument is respected as well).
1117

1218
## 6.1.3
1319
- **General**

projects/igniteui-angular/src/lib/directives/text-highlight/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ Provides a way to highlight text elements.
3131
### Methods
3232
| Name | Type | Arguments | Description |
3333
| :--- |:--- | :--- | :--- |
34-
| highlight | number | The text that should be highlighted and, optionally, if the search should be case sensitive or not (it defaults to false if it isn't specified). | Clears the existing highlight and highlight the searched text. Returns how many times the element contains the searched text. |
34+
| highlight | number | The text that should be highlighted and, optionally, if the search should be case sensitive and/or an exact match (both default to false if they aren't specified). | Clears the existing highlight and highlight the searched text. Returns how many times the element contains the searched text. |
3535
| clearHighlight | void | N/A | Clears any existing highlight |
3636
| activateIfNecessary | void | N/A | Activates the highlight if it is on the currently active row, column and page |
3737
| setActiveHighlight (static)| void| The highlight group, the column, row and page of the directive and the index of the highlight | Activates the highlight at a given index (if such highlight exists) |

projects/igniteui-angular/src/lib/directives/text-highlight/text-highlight.directive.spec.ts

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,50 @@ describe('IgxHighlight', () => {
7575
expect(count).toBe(0);
7676
});
7777

78+
it('Should not highlight anything when there is no exact match, regardless of case sensitivity.', () => {
79+
const fix = TestBed.createComponent(HighlightLoremIpsumComponent);
80+
fix.detectChanges();
81+
82+
const component: HighlightLoremIpsumComponent = fix.debugElement.componentInstance;
83+
let count = component.highlightText('Lorem', false, true);
84+
fix.detectChanges();
85+
let spans = fix.debugElement.nativeElement.querySelectorAll('.' + component.highlightClass);
86+
expect(spans.length).toBe(0);
87+
expect(count).toBe(0);
88+
89+
count = component.highlightText('Lorem', false, false);
90+
fix.detectChanges();
91+
spans = fix.debugElement.nativeElement.querySelectorAll('.' + component.highlightClass);
92+
expect(spans.length).toBe(2);
93+
expect(count).toBe(2);
94+
95+
count = component.highlightText('Lorem', true, true);
96+
fix.detectChanges();
97+
spans = fix.debugElement.nativeElement.querySelectorAll('.' + component.highlightClass);
98+
expect(spans.length).toBe(0);
99+
expect(count).toBe(0);
100+
});
101+
102+
it('Should not highlight with exact match when the group text has changed.', () => {
103+
const fix = TestBed.createComponent(HighlightLoremIpsumComponent);
104+
fix.detectChanges();
105+
106+
const component: HighlightLoremIpsumComponent = fix.debugElement.componentInstance;
107+
const count = component.highlightText(
108+
'LoReM ipsuM dolor sit AMET, consectetur adipiscing elit. Vestibulum vulputate LucTUS dui ut maximus.' +
109+
' Quisque sed suscipit lorem. Vestibulum sit.',
110+
false, true);
111+
fix.detectChanges();
112+
let spans = fix.debugElement.nativeElement.querySelectorAll('.' + component.highlightClass);
113+
expect(spans.length).toBe(1);
114+
expect(count).toBe(1);
115+
116+
component.html += ' additionalText';
117+
fix.detectChanges();
118+
spans = fix.debugElement.nativeElement.querySelectorAll('.' + component.highlightClass);
119+
expect(spans.length).toBe(0);
120+
});
121+
78122
it('Should clear all highlights', () => {
79123
const fix = TestBed.createComponent(HighlightLoremIpsumComponent);
80124
fix.detectChanges();
@@ -250,8 +294,8 @@ class HighlightLoremIpsumComponent {
250294
@ViewChild(forwardRef(() => IgxTextHighlightDirective), { read: IgxTextHighlightDirective })
251295
public highlight: IgxTextHighlightDirective;
252296

253-
public highlightText(text: string, caseSensitive?: boolean) {
254-
return this.highlight.highlight(text, caseSensitive);
297+
public highlightText(text: string, caseSensitive?: boolean, exactMatch?: boolean) {
298+
return this.highlight.highlight(text, caseSensitive, exactMatch);
255299
}
256300

257301
public clearHighlight() {

projects/igniteui-angular/src/lib/directives/text-highlight/text-highlight.directive.ts

Lines changed: 41 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ interface ISearchInfo {
1717
content: string;
1818
matchCount: number;
1919
caseSensitive: boolean;
20+
exactMatch: boolean;
2021
}
2122

2223
export interface IActiveHighlightInfo {
@@ -216,7 +217,7 @@ export class IgxTextHighlightDirective implements AfterViewInit, OnDestroy, OnCh
216217
*/
217218
ngOnChanges(changes: SimpleChanges) {
218219
if (changes.value && !changes.value.firstChange) {
219-
this.highlight(this._lastSearchInfo.searchedText, this._lastSearchInfo.caseSensitive);
220+
this.highlight(this._lastSearchInfo.searchedText, this._lastSearchInfo.caseSensitive, this._lastSearchInfo.exactMatch);
220221
this.activateIfNecessary();
221222
}
222223

@@ -248,7 +249,8 @@ export class IgxTextHighlightDirective implements AfterViewInit, OnDestroy, OnCh
248249
searchedText: '',
249250
content: this.value,
250251
matchCount: 0,
251-
caseSensitive: false
252+
caseSensitive: false,
253+
exactMatch: false
252254
};
253255

254256
this._container = this.parentElement.firstElementChild;
@@ -258,23 +260,26 @@ export class IgxTextHighlightDirective implements AfterViewInit, OnDestroy, OnCh
258260
* Clears the existing highlight and highlights the searched text.
259261
* Returns how many times the element contains the searched text.
260262
*/
261-
public highlight(text: string, caseSensitive?: boolean): number {
263+
public highlight(text: string, caseSensitive?: boolean, exactMatch?: boolean): number {
262264
const caseSensitiveResolved = caseSensitive ? true : false;
265+
const exactMatchResolved = exactMatch ? true : false;
263266

264-
if (this.searchNeedsEvaluation(text, caseSensitiveResolved)) {
267+
if (this.searchNeedsEvaluation(text, caseSensitiveResolved, exactMatchResolved)) {
265268
this._lastSearchInfo.searchedText = text;
266269
this._lastSearchInfo.caseSensitive = caseSensitiveResolved;
270+
this._lastSearchInfo.exactMatch = exactMatchResolved;
267271
this._lastSearchInfo.content = this.value;
268272

269273
if (text === '' || text === undefined || text === null) {
270274
this.clearHighlight();
271275
} else {
272276
this.clearChildElements(true);
273-
this._lastSearchInfo.matchCount = this.getHighlightedText(text, caseSensitive);
277+
this._lastSearchInfo.matchCount = this.getHighlightedText(text, caseSensitive, exactMatch);
274278
}
275279
} else if (this._nodeWasRemoved) {
276280
this._lastSearchInfo.searchedText = text;
277281
this._lastSearchInfo.caseSensitive = caseSensitiveResolved;
282+
this._lastSearchInfo.exactMatch = exactMatchResolved;
278283
}
279284

280285
return this._lastSearchInfo.matchCount;
@@ -323,7 +328,9 @@ export class IgxTextHighlightDirective implements AfterViewInit, OnDestroy, OnCh
323328
this._nodeWasRemoved = false;
324329

325330
this._forceEvaluation = true;
326-
this.highlight(this._lastSearchInfo.searchedText, this._lastSearchInfo.caseSensitive);
331+
this.highlight(this._lastSearchInfo.searchedText,
332+
this._lastSearchInfo.caseSensitive,
333+
this._lastSearchInfo.exactMatch);
327334
this._forceEvaluation = false;
328335

329336
this.activateIfNecessary();
@@ -387,32 +394,43 @@ export class IgxTextHighlightDirective implements AfterViewInit, OnDestroy, OnCh
387394
}
388395
}
389396

390-
private getHighlightedText(searchText: string, caseSensitive: boolean) {
397+
private getHighlightedText(searchText: string, caseSensitive: boolean, exactMatch: boolean) {
391398
this.appendDiv();
392399

393400
const stringValue = String(this.value);
394401
const contentStringResolved = !caseSensitive ? stringValue.toLowerCase() : stringValue;
395402
const searchTextResolved = !caseSensitive ? searchText.toLowerCase() : searchText;
396403

397-
let foundIndex = contentStringResolved.indexOf(searchTextResolved, 0);
398-
let previousMatchEnd = 0;
399404
let matchCount = 0;
400405

401-
while (foundIndex !== -1) {
402-
const start = foundIndex;
403-
const end = foundIndex + searchTextResolved.length;
406+
if (exactMatch) {
407+
if (contentStringResolved === searchTextResolved) {
408+
// tslint:disable-next-line:max-line-length
409+
this.appendSpan(`<span class="${this.cssClass}" style="background:yellow;font-weight:bold;color:black">${stringValue}</span>`);
410+
matchCount++;
411+
} else {
412+
this.appendText(stringValue);
413+
}
414+
} else {
415+
let foundIndex = contentStringResolved.indexOf(searchTextResolved, 0);
416+
let previousMatchEnd = 0;
404417

405-
this.appendText(stringValue.substring(previousMatchEnd, start));
406-
// tslint:disable-next-line:max-line-length
407-
this.appendSpan(`<span class="${this.cssClass}" style="background:yellow;font-weight:bold;color:black">${stringValue.substring(start, end)}</span>`);
418+
while (foundIndex !== -1) {
419+
const start = foundIndex;
420+
const end = foundIndex + searchTextResolved.length;
408421

409-
previousMatchEnd = end;
410-
matchCount++;
422+
this.appendText(stringValue.substring(previousMatchEnd, start));
423+
// tslint:disable-next-line:max-line-length
424+
this.appendSpan(`<span class="${this.cssClass}" style="background:yellow;font-weight:bold;color:black">${stringValue.substring(start, end)}</span>`);
411425

412-
foundIndex = contentStringResolved.indexOf(searchTextResolved, end);
413-
}
426+
previousMatchEnd = end;
427+
matchCount++;
414428

415-
this.appendText(stringValue.substring(previousMatchEnd, stringValue.length));
429+
foundIndex = contentStringResolved.indexOf(searchTextResolved, end);
430+
}
431+
432+
this.appendText(stringValue.substring(previousMatchEnd, stringValue.length));
433+
}
416434

417435
return matchCount;
418436
}
@@ -434,14 +452,15 @@ export class IgxTextHighlightDirective implements AfterViewInit, OnDestroy, OnCh
434452
this.renderer.appendChild(this.parentElement, this._div);
435453
}
436454

437-
private searchNeedsEvaluation(text: string, caseSensitive: boolean): boolean {
455+
private searchNeedsEvaluation(text: string, caseSensitive: boolean, exactMatch: boolean): boolean {
438456
const searchedText = this._lastSearchInfo.searchedText;
439457

440458
return !this._nodeWasRemoved &&
441-
(searchedText === null ||
459+
(searchedText === null ||
442460
searchedText !== text ||
443461
this._lastSearchInfo.content !== this.value ||
444462
this._lastSearchInfo.caseSensitive !== caseSensitive ||
463+
this._lastSearchInfo.exactMatch !== exactMatch ||
445464
this._forceEvaluation);
446465
}
447466
}

projects/igniteui-angular/src/lib/grid/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -240,8 +240,8 @@ Here is a list of all public methods exposed by **igx-grid**:
240240
|`deselectRows(rowIDs: any[])`|Removes the specified row(s) from the grid's selection in the `selectionAPI`.|
241241
|`selectAllRows()`|Marks all rows as selected in the grid `selectionAPI`.|
242242
|`deselectAllRows()`|Sets the grid's row selection in the `selectionAPI` to `[]`.|
243-
|`findNext(text: string, caseSensitive?: boolean)`|Highlights all occurrences of the specified text and marks the next occurrence as active.|
244-
|`findPrev(text: string, caseSensitive?: boolean)`|Highlights all occurrences of the specified text and marks the previous occurrence as active.|
243+
|`findNext(text: string, caseSensitive?: boolean, exactMatch?: boolean)`|Highlights all occurrences of the specified text and marks the next occurrence as active.|
244+
|`findPrev(text: string, caseSensitive?: boolean, exactMatch?: boolean)`|Highlights all occurrences of the specified text and marks the previous occurrence as active.|
245245
|`clearSearch(text: string, caseSensitive?: boolean)`|Removes all search highlights from the grid.|
246246
|`refreshSearch()`|Refreshes the current search.|
247247
|`groupBy(expression: ISortingExpression)`| Groups by a new column based on the provided expression or modifies an existing one.

projects/igniteui-angular/src/lib/grid/cell.component.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -679,7 +679,9 @@ export class IgxGridCellComponent implements OnInit, OnDestroy, AfterViewInit {
679679
*/
680680
public ngAfterViewInit() {
681681
if (this.highlight && this.grid.lastSearchInfo.searchText) {
682-
this.highlight.highlight(this.grid.lastSearchInfo.searchText, this.grid.lastSearchInfo.caseSensitive);
682+
this.highlight.highlight(this.grid.lastSearchInfo.searchText,
683+
this.grid.lastSearchInfo.caseSensitive,
684+
this.grid.lastSearchInfo.exactMatch);
683685
this.highlight.activateIfNecessary();
684686
}
685687
}
@@ -1057,8 +1059,8 @@ export class IgxGridCellComponent implements OnInit, OnDestroy, AfterViewInit {
10571059
* ```
10581060
* @memberof IgxGridCellComponent
10591061
*/
1060-
public highlightText(text: string, caseSensitive?: boolean): number {
1061-
return this.highlight && this.column.searchable ? this.highlight.highlight(text, caseSensitive) : 0;
1062+
public highlightText(text: string, caseSensitive?: boolean, exactMatch?: boolean): number {
1063+
return this.highlight && this.column.searchable ? this.highlight.highlight(text, caseSensitive, exactMatch) : 0;
10621064
}
10631065

10641066
/**

0 commit comments

Comments
 (0)