0

I have a search input that highlights characters within a paragraph if the search input matches any part of the value:

Search Input:

<h4>Keyword Search</h4>
<mat-form-field appearance="outline" class="mat-form-field">
  <mat-label>Search</mat-label>
  <input matInput placeholder="Search Text" [(ngModel)]="searchTerm">
</mat-form-field>

//Area to search: 
 <p [innerHTML]="paragraphText | highlight: searchTerm"></p>

Component file:

searchTerm: string;
paragraphText = "1. Local currency (Kwanza-AOA): up to AOA 50,000.- for residents and non-residents.<br xmlns="http://www.opentravel.org/OTA/2003/05/beta" />2. Foreign currencies:<br xmlns="http://www.opentravel.org/OTA/2003/05/beta" />a. Residents (older than 17 years): up to USD 15,000.- or equivalent;<br xmlns="http://www.opentravel.org/OTA/2003/05/beta" />b. Residents (younger than 18 years): up to USD 5,000.- or equivalent;<br xmlns="http://www.opentravel.org/OTA/2003/05/beta" />c. Non Residents (older than 17 years): up to USD 10,000.- or equivalent;<br xmlns="http://www.opentravel.org/OTA/2003/05/beta" />d. Non Residents (younger than 18 years): up to USD 3,000.- or equivalent. <br xmlns="http://www.opentravel.org/OTA/2003/05/beta" />Exempt: <br xmlns="http://www.opentravel.org/OTA/2003/05/beta" />- If holding a letter (certified by B.N.A./D.O.I.) from a company or entity which took care of payment of all expenses during stay in Angola: foreign currencies up to the amount imported.<br xmlns="http://www.opentravel.org/OTA/2003/05/beta" />- Amounts left with receipts of bills paid or money exchange vouchers. "

Highlight pipe:

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'highlight'
})
export class HighlightPipe implements PipeTransform {

  transform(value: string, searchTerm: string, index= -1 ): any {
    if (searchTerm && value) {
      value = String(value);
      console.log(value);
      const startIndex = value.toLowerCase().indexOf(searchTerm.toLowerCase(),index);
      if (startIndex != -1) {
          const endLength = searchTerm.length;
          const matchingString = value.substr(startIndex, endLength);
          return value.substring(0,startIndex)+"<mark>" + matchingString + "</mark>"+value.substring(startIndex+endLength);
        }

    }
    return value;
  }

}

Current Behavior When typing a letter (ex:'c') into the search field, not all of the 'c's get highlighted. I noticed a pattern that anything after the inline html tags (in the paragraphText property) does not get picked up.

enter image description here

Expected Behavior All characters in the paragraph should be highlighted that match the string in the search field.

What am I doing wrong in the highlight pipe to ensure all values are highlighted??

Kode_12
  • 4,506
  • 11
  • 47
  • 97

2 Answers2

3

I have created the following stackblitz example that shows how to work with highlight Pipes

Pipe :

@Pipe({
  name: 'highlight'
})
export class HighlightSearch implements PipeTransform {
  constructor(private sanitizer: DomSanitizer) { }

  transform(value: any, args: string): any {
    if (!args) {
      return value;
    }
    const specials = [
      // order matters for these
      "-"
      , "["
      , "]"
      // order doesn't matter for any of these
      , "/"
      , "{"
      , "}"
      , "("
      , ")"
      , "*"
      , "+"
      , "?"
      , "."
      , "\\"
      , "^"
      , "$"
      , "|"
    ];

    const rgxEscaper = RegExp('[' + specials.join('\\') + ']', 'g');

    args = args.replace(rgxEscaper, "\\$&");

    // Match in a case insensitive maneer
    const re = new RegExp(`\\\\?${args}` + `(?!([^<]+)?>)`, 'g');
    const match = value.match(re);

    // If there's no match, just return the original value.
    if (!match) {
      return value;
    }

    const replacedValue = value.replace(re, "<mark>" + match[0] + "</mark>")
    return this.sanitizer.bypassSecurityTrustHtml(replacedValue)
  }
}

Component :

@Component({ 
  selector: 'my-app',
  styleUrls: ['./app.component.css'],
  template: `
    <label for="search-term">Search</label>
    <input placeholder="Enter term" (input)="updateSearch($event)" id="search-term">
    <div [innerHTML]="results | highlight: searchTerm"></div>
  `,
})
export class AppComponent {
  results: string;
  searchTerm: string;
  constructor() {
    this.results =  '"1. Local currency (Kwanza-AOA): up to AOA 50,000.- for residents and non-residents.<br xmlns="http://www.opentravel.org/OTA/2003/05/beta" />2. Foreign currencies:<br xmlns="http://www.opentravel.org/OTA/2003/05/beta" />a. Residents (older than 17 years): up to USD 15,000.- or equivalent;<br xmlns="http://www.opentravel.org/OTA/2003/05/beta" />b. Residents (younger than 18 years): up to USD 5,000.- or equivalent;<br xmlns="http://www.opentravel.org/OTA/2003/05/beta" />c. Non Residents (older than 17 years): up to USD 10,000.- or equivalent;<br xmlns="http://www.opentravel.org/OTA/2003/05/beta" />d. Non Residents (younger than 18 years): up to USD 3,000.- or equivalent. <br xmlns="http://www.opentravel.org/OTA/2003/05/beta" />Exempt: <br xmlns="http://www.opentravel.org/OTA/2003/05/beta" />- If holding a letter (certified by B.N.A./D.O.I.) from a company or entity which took care of payment of all expenses during stay in Angola: foreign currencies up to the amount imported.<br xmlns="http://www.opentravel.org/OTA/2003/05/beta" />- Amounts left with receipts of bills paid or money exchange vouchers. "'
  }
    updateSearch(e) {
    this.searchTerm = e.target.value
  }
}
Amit Baranes
  • 7,398
  • 2
  • 31
  • 53
  • Is there a way to ignore any text that are part of an html tag (see what happens when you search the character r --the html tags appear as plain text which is something i wish isn't displayed when searching). – Kode_12 May 08 '19 at 05:44
  • @Kode_12 i updated regex pattern and its working fine now. Please check out my StackBlitz example and let me know . – Amit Baranes May 08 '19 at 13:34
  • Thanks Amit, this solution looks like it's almost there! Last thing I noticed is, when you type the letter 'k', one part of the highlighted text gets uppercased, how can that be adjusted while still keeping the search case insensitive? – Kode_12 May 08 '19 at 15:47
  • @Kode_12 Fixed. i had to change the regex policy from gi to g. https://stackoverflow.com/questions/27916055/whats-the-meaning-of-gi-in-a-regex – Amit Baranes May 08 '19 at 16:18
  • If you find this answer helpful please accept it :) – Amit Baranes May 08 '19 at 16:19
  • I will go ahead and accept. But removing the 'i' removes the case insensitive search, which i needed, any solution for that? – Kode_12 May 09 '19 at 20:42
  • the issue is that the match[0] is hardcoded. How can we make that dynamic? – Kode_12 May 09 '19 at 22:12
  • where is the problem? the search is case sensitive as you asked. Check the stackblitz example – Amit Baranes May 09 '19 at 23:00
  • Hey Amit, there seems to be a weird behavior when searching for a period (.) If that part can be refined i can accept the solution :) – Kode_12 May 10 '19 at 16:26
  • @Kode_12 Fixed, had to add \\ before args to ignore special characters.check out the stackblitz example for live demo. – Amit Baranes May 10 '19 at 18:54
  • great, ill accept this! If you wouldn't mind providing an explanation of the regex and what you did differently from my solution, that would certainly be helpful – Kode_12 May 10 '19 at 19:42
  • Hey Amit, when I type any of the following characters (w, r, t, s, d, f, j, c, v, b, n) there is some weird behavior. I had to uncheck the solution for now. I think there is an issue with the regex. – Kode_12 May 13 '19 at 15:08
  • 1
    Finally, I figure it out.. there are some special characters that needs to be escaped. Check out the stackblitz example. I have checked each key on the keyboard and it's working fine now. – Amit Baranes May 15 '19 at 09:45
  • Awesome, thanks power developer! I would love to understand how your solution works, would you mind adding an explanation in your answer when you get a chance :) – Kode_12 May 15 '19 at 18:25
0

I am using regexp to replace all occurrences of search term

export class HighlightPipe implements PipeTransform {
   transform(text: string, search: string): string {
     if (search && text) {
       let pattern = search.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
       pattern = pattern.split(' ').filter((t) => {
         return t.length > 0;
       }).join('|');
       const regex = new RegExp(pattern, 'gi');
       return text.replace(regex, (match) => `<mark>${match}</mark>`);
     } else {
       return text;
     }
   }
}

Edit: hmmm, thinking about that you are searching in html doc, not just plain text, right? Consider escaping html tags in the text before doing actual search in the pipe. Like turn < into &lt;... I would test that and post update of the function but am on mobile currently..

PeS
  • 3,757
  • 3
  • 40
  • 51
  • precisely, so if you see in Amit's solution above, if you search the letter 'r' in the stackblitz you can see it highlights parts of the tags, which is not what should be searched. Looking forward to your update :) – Kode_12 May 08 '19 at 05:39