Kode_12
Kode_12

Reputation: 4798

How to search "next" term from a search input

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

Answers (1)

Eliseo
Eliseo

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

Related Questions