Reputation: 4798
I'm trying to create a 'search next' feature where after the user hits enter, a function would be invoked that focuses on the next match. The tricky part is that the text to search is a div container with nested elements, all with their individual text. Im able to create a highlight using pipes, but not sure how to navigate to the next text that matches the search term. Any ideas?
Here is the highlighting pipe:
@Pipe({
name: 'highlight'
})
export class HighlightPipe implements PipeTransform {
transform(value: string, args: string): any {
if (args && value) {
value = String(value); // make sure its a string;
const startIndex = value.toLowerCase().indexOf(args.toLowerCase());
if (startIndex != -1) {
console.log(startIndex);
const endLength = args.length;
const matchingString = value.substr(startIndex, endLength);
return value.replace(matchingString, "<mark>" + matchingString + "</mark>");
}
}
return value;
}
}
The template withe the search input and div container that holds all of the text:
<section *ngFor="let section of country.Section">
<div class="main" [innerHTML]="section.Name | highlight: searchTerm"></div>
<div *ngFor="let sectionParagraph of section.SectionParagraph">
<p class="paragraph" [innerHTML]="sectionParagraph.ParagraphText | highlight: searchTerm"></p>
<p class="paragraph" [innerHTML]="sectionParagraph.ChildParagraphs | highlight: searchTerm"></p>
</div>
<div *ngFor="let subsection of section.SubSection">
<div class="secondary" [innerHTML]="subsection.Name | highlight: searchTerm"></div>
<div *ngFor="let subSubsectionParagraph of subsection.SubSectionParagraph">
<p class="paragraph" [innerHTML]="subSubsectionParagraph.ParagraphText | highlight: searchTerm"></p>
<div *ngFor="let childParagraph of subSubsectionParagraph.ChildParagraphs">
<p class="paragraph" [innerHTML]="childParagraph.ParagraphText | highlight: searchTerm"></p>
</div>
</div>
</div>
<hr>
</section>
And the search input on the same template:
<mat-form-field class="example-full-width" style="margin-left: 120px">
<input matInput placeholder="Search Text" [value]="searchTerm" [(ngModel)]="searchTerm" (input)="highlight($event)" (keyup.enter)="nextTerm()">
</mat-form-field>
Edit: I'd like to avoid using any jQuery for the solution
Upvotes: 0
Views: 245
Reputation: 57939
The idea is use a pipe with two arguments, "search" and "index", then you need change the pipe to take account this changes
transform(value: string, args: string,index:number=-1): any {
if (args && value) {
value = String(value); // make sure its a string;
const startIndex = value.toLowerCase().indexOf(args.toLowerCase(),index);
if (startIndex != -1) {
console.log(startIndex);
const endLength = args.length;
const matchingString = value.substr(startIndex, endLength);
return value.substring(0,startIndex)+"<mark>" + matchingString + "</mark>"+value.substring(startIndex+endLength);
}
}
return value;
}
Then you call the pipe like, I made with a < p >, but the important is how pass the two arguments
<p (click)="click()" class="paragraph"
[innerHTML]="text | highlight: search:index"></p>
The function click() it's like:
//This are the variables I used in a more simple example
index:number=0;
text='Hola mundo, Hola Mundo';
search="Hola";
click()
{
let index=this.text.indexOf(this.search,this.index+1);
this.index=index>0?index:0;
}
You can be an example in the stackblitz
Update If we has a series of text, it's util that our text become object with an index
sections = [
{ text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', index: 0 },
{ text: 'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.', index: 1 },
{ text: 'Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', index: 2 },
{ text: 'Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. ', index: 3 }
]
Then, our pipe can take account the "index"
//the .html
<div *ngFor="let section of sections;let i=index">
<p class="paragraph" [innerHTML]="section.text | highlight: search:index:(active==section.index)"></p>
</div>
<button (click)="click()">next</button>
//the pipe
//we get "active"
transform(value: string, args: string,index:number=-1,active:boolean): any {
//if active=false not do anything
if (args && value && active) {
value = String(value); // make sure its a string;
const startIndex = value.toLowerCase().indexOf(args.toLowerCase(),index);
if (startIndex != -1) {
const endLength = args.length;
const matchingString = value.substr(startIndex, endLength);
return value.substring(0,startIndex)+"<mark>" + matchingString + "</mark>"+value.substring(startIndex+endLength);
}
}
return value;
}
well, out "click" function it's a bit more complex
click() {
let index = -1;
if (this.active < 0) {
this.active = 0;
index = this.sections[this.active].text.indexOf(this.search);
}
else {
if (this.index < 0) {
index = this.sections[this.active].text.indexOf(this.search);
index = this.sections[this.active].text.indexOf(this.search, index + 1);
}
else
index = index = this.sections[this.active].text.indexOf(this.search, this.index + 1);
}
if (index < 0)
{
let count=0;
this.active = (this.active + 1) % this.sections.length;
while (this.sections[this.active].text.indexOf(this.search)<0
&& count<this.sections.length)
{
this.active = (this.active + 1) % this.sections.length;
count++;
}
}
this.index = index;
}
In your case, you need calculate the "index" of the section and subsection to make them consecutive.
The new stackblitz
Upvotes: 0