skip to Main Content

Good Day

Explanation of what I’m trying to do:

I have a component with checkboxes and names:

[x] foo
[ ] bar
[ ] orange

If I select foo, I only want to match records that contain "foo" not "foo (bar)" or any other arrangement. The RegEx I built to do this is:

(?<=(s|^))(${dropdownList.join(‘|’)})(?=(s|$))im

The dropdownList.join will only contain the checked items, so using the list above you’ll get:

(?<=(s|^))(foo)(?=(s|$))im

Now, I’m using this to iterate over a table and look at a value, then disable the row if it doesn’t match. In the table I can have values of foo or foo (bar), what I can’t figure out is why foo (bar) is matching when I run a search for foo.

0 is the return value of "foo".search(regex), which should be -1, if I don’t have "foo", so "foo (bar)" should be -1.

I tested this in https://regex101.com/, and it seems to work, fine when I do it there.

String Value
foo
Search Term
/(?<=(s|^))(foo)(?=(s|$))/im
0

String Value
foo (bar)
Search Term
/(?<=(s|^))(foo)(?=(s|$))/im
0 (Shouldn’t this be -1)

Does anyone know what I did wrong?

Here is the function I’m using for the search, I’ve added updated Regex:

public _validateValues(): Promise < any > {
  return new Promise((r) => {
    const dropdownList = [];
    _.forEach(this.columnSearchHistory, (searchItem: TableSearchHistory) => {
      if (searchItem.type === 'dropdown') {
        dropdownList.push(this.escapeRegExp(searchItem.value?.toString().trim() ?? ''));
      }
    });

    let searchTerm;
    if (dropdownList.length) {
      searchTerm = new RegExp(`(^)(${dropdownList.join('|')})($)`, 'im');
    }

    _.forEach(this.headerList, (row) => {
      row.filtered = false;
    })

    this.activateAllRows();

    _.forEach(this.columnSearchHistory, (searchItem: TableSearchHistory) => {
      this.headerList[searchItem.headerIndex].filtered = true;

      if (searchItem.value && searchItem.type !== 'dropdown') {
        searchTerm = new RegExp(this.escapeRegExp(searchItem.value ? .toString().trim()), 'i');
      } else if (searchItem.type === 'dropdown') {
        searchTerm = new RegExp(`(^)(${dropdownList.join('|')})($)`, 'im');

        console.log('Search Term');
        console.log(searchTerm);
      }

      if (searchItem.type === 'number') {
        if (searchItem.min === null) {
          searchItem.min = Number.NEGATIVE_INFINITY;
        }

        if (searchItem.max === null) {
          searchItem.max = Number.POSITIVE_INFINITY;
        }
      }

      if (searchItem.type === 'date') {
        if (searchItem.min === null) {
          searchItem.min = new Date('1926');
        }

        if (searchItem.max === null) {
          searchItem.max = new Date(Date.now());
        }
      }

      _.forEach(this.data, (row, rowIdx) => {

        let value = row[this.headerList[searchItem.index].key];

        if (searchItem.type === 'number') {

          if (!value && value !== 0) {
            row.active = false;
          }

          if (!value && value !== 0) {
            value = 0;
          }

          const numericValue = Number(value.toString().replace(/[^d.-]/g, ''));

          const max = Number(searchItem.max);
          const min = Number(searchItem.min);

          if (numericValue < min || numericValue > max) {

            row.active = false;
          }
        } else if (searchItem.type === 'date') {
          if (!value) {
            row.active = false;
          }

          if (!value) {
            value = new Date();
          } else {
            value = new Date(value);
          }

          const max = new Date(searchItem.max);
          const min = new Date(searchItem.min);

          if (value < min || value > max) {
            row.active = false;
          }
        } else {
          const stringValue = value ? value : '';

          if (stringValue.toString().trim().search(searchTerm) === -1) {
            row.active = false;
          }
        }
      })
    })

    this.range = {
      low: null,
      high: null,
      search: ''
    };

    r(this.data);
  })
}

Thanks

2

Answers


  1. I think I can help here.

    First, your RegExp is not correct in its current form. The ${ code } section will be interpreted literally. You need to declare your Regex as:

    const rx = new RegExp("(?<=(s|^))("+dropdownList.join('|')+")(?=(s|$))","i");
    

    I’ve removed the m flag. This is for a multiline string which I don’t believe we have here.

    Second, if you try and match the string ‘foo’ it will match any string containing it.

    • foo
    • foo (bar)
    • foo foo foo
    • foo notfoo

    All these will match as they contain the string ‘foo’.

    You may want to do a whole string match so try this:

    const rx = new RegExp("^"+dropdownList.join("|")+"$","i");
    

    This is simply:

    ^ - the start of the string
    foo|bar|orange - whatever your selected options are
    $ - the end of the string. 
    Login or Signup to reply.
  2. Ok this is what I was thinking:

    <div class="example flex_column"></div>
    
    // your strings which you may be filtering...
    const possible_strings = [
      'foo is the bar',
      'foo is all',
      'bar foo',
      'orange this is orange',
      'fooing'
    ];
    
    // utility functions for readability
    const createElement = (type) => document.createElement(type);
    const qs = (el) => document.querySelector(el);
    const qsAll = (el) => document.querySelectorAll(el);
    
    // creates the options you see based on words
    const makeOption = (word) => {
      const $label = createElement('label');
      const $span = createElement('span');
      $span.textContent = word;
      const $input = createElement('input');
      $input.type = 'checkbox';
      $input.name = 'words';
      $label.append($span, $input);
      
      return $label;
    }
    
    
    // updates Map (hashmap) based on options selected
    const getSentences = ($el, hash_map) => {
        const $input = $el.querySelector('input');
        $input.addEventListener('change', (e) => {
             const $label = e.target.closest('label');
             const word_checked = $label.querySelector('span').innerText;
             if ($label.querySelector('input').checked) hash_map.set(word_checked, 1);
             else hash_map.set(word_checked, 0);
        
             const words = [...hash_map.keys() ];
             const words_wanted = words.filter((word) => hash_map.get(word));
             const words_not_wanted = words.filter((word) => !hash_map.get(word));
        
             let keepers = possible_strings.filter((sentence) => {
                 return sentence.match(new RegExp(`(^|\s)(${words_wanted.join('|')})($|\s)`))
             });
        
             keepers = keepers.filter((sentence) => {
                  return !sentence.match(new RegExp(`(^|\s)(${words_not_wanted.join('|')})($|\s)`))
             })
             console.log(keepers);
         });
      
      return $el;
    }
    
    const example = () => {
      // the options you have
      const words = ['foo', 'bar', 'orange'];
      const hash_map = new Map();
      words.forEach((word) => hash_map.set(word, 0));
      const $example = qs('.example');
      $example.append(
        ...words.map((word) => getSentences(makeOption(word), hash_map)));
    }
    
    example();
    
    .flex_column {
      display: flex;
      flex-flow: column;
    }
    

    example:
    https://jsfiddle.net/aq8o25nr/2

    How does it work? It creates options based on "words". I create a hashmap of all the words. ie:

    Map {
    ‘foo’: 0,
    ‘bar’: 0,
    ‘orange’: 0
    }

    each time you click an option it changes 0 to 1. (identifying the words you want whereas 0 identfies the words you dont want).

    That way we just regex the senteces first for the words you want (1s) then we regex that result with words you dont want (the 0s). Thus giving you the matches of checked but omitting the unchecked options. I made it dynamically so you can change the array size of words or change sentences.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search