Skip to content

Latest commit

 

History

History
183 lines (148 loc) · 7.28 KB

File metadata and controls

183 lines (148 loc) · 7.28 KB

AdvancedSearch

Provides advanced search functionality - user-friendly ability to construct a complex query with boolean conditions.

Basic Usage

  import { AdvancedSearch } from '@folio/stripes/components';

  // in component body
  const searchOptions = [{
    id: 'keyword',
    label: 'Keyword',
    value: 'keyword',
  }, {
    id: 'name',
    label: 'Name',
    value: 'name',
  }];

  const keywordOption = 'keyword';

  const firstRowInitialSearch = {};

  <AdvancedSearch
    open={isOpen}
    searchOptions={searchOptions}
    defaultSearchOptionValue={keywordOption}
    firstRowInitialSearch={firstRowInitialSearch}
    onSearch={handleSearch}
    onCancel={handleCancel}
  />

Advanced Usage

In some cases users may want to do custom formatting of query string. This can be done with the use of rowFormatter or queryBuilder. rowFormatter can be used to combine the boolean operator, searched term and search option. For example, if you'd like to change comparison operator from default == to =. queryBuilder gives users complete control over query construction. AdvancedSearch will call this function with an array of object representing rows and users can use that to meet their requirements. More about rowFormatter and queryBuilder in Props. Note: if you change default comparison operator, or default boolean operator symbols - you'll also need to provide splitRows prop for custom parsing of queries into rows.

AdvancedSearch also accepts a function as children and will call it with object containing resetRows function - it will clear internal state. It may be used with "Reset all" buttons on search pages to also clear AdvancedSearch rows.

  import { AdvancedSearch } from '@folio/stripes/components';

  // in component body

  const queryBuilder = (rows, rowFormatter) => {
    const formatRowCondition = (row) => {
      // use default row formatter, but wrap each search term with parentheses
      return `(${rowFormatter(row.searchOption, row.query, '==')})`;
    };

    return rows.reduce((formattedQuery, row, index) => {
      const rowCondition = formatRowCondition(row);

      const boolMap = {
        and: '&&',
        or: '||',
        not: '!',
      };

      return `${formattedQuery} ${boolMap[row.bool]} ${rowCondition}`;
    }, '');
  };

  <AdvancedSearch
    open={isOpen}
    searchOptions={searchOptions}
    defaultSearchOptionValue={keywordOption}
    firstRowInitialSearch={firstRowInitialSearch}
    queryBuilder={queryBuilder}
    rowFormatter={rowFormatter}
    onSearch={handleSearch}
    onCancel={handleCancel}
  >
    {({ resetRows}) => (
      <Button
        onClick={resetRows}
      >
        Reset all
      </Button>
    )}
  </AdvancedSearch>

queryToRow

To handle queries that a user typed in, as opposed to what was generated by <AdvancedSearch> you may need to provide custom queryToRow prop. For example, user types in keyword exactPhrase test or id containsAll 123-456 into <SearchAndSort> query input and opens Advanced Search. To parse this query string into rows you must pass queryToRow where you define how you'd like to parse it:

  // in component body

  const queryToRow = (row) => {
    const queryString = row.query; // this in our example will be equal to `"keyword exactPhrase test or id containsAll 123-456"`

    const splitIntoRowsRegex = /regex-to-split-into-rows/g;

    const matches = [...queryString.matchAll(splitIntoRowsRegex)];

    // let's assume `matches` is `["keyword exactPhrase test", "or id containsAll 123-456"]`
    return matches.map((match) => {
      const { operator, option, _match, value } = parseSingleRow(match);

      return {
        [FIELD_NAMES.QUERY]: value,
        [FIELD_NAMES.BOOL]: operator,
        [FIELD_NAMES.SEARCH_OPTION]: option,
        [FIELD_NAMES.MATCH]: _match,
      };
    });
  };

  <AdvancedSearch
    ...
    queryToRow={queryToRow}
  >
    ...
  </AdvancedSearch>

In the example above, custom rowFormatter will wrap each search term with parentheses. And queryBuilder is written so that boolean conditions will be written as symbols (&&, ||, !) instead of default text representation (and, or, not).

Props

Name Type Description Required
children function Pass any a function that will accept { resetRows }. resetRows can be used to clear AdvancedSearch state. false
open boolean Controls visibility of AdvancedSearch modal false
searchOptions array Array of search options. Format: [{ label, value, id }] id is an optional property. AdvancedSearch will add Query as first option. true
onSearch func Callback fired when search is performed. Called with two arguments: query - formatted query string and rows - array of non-empty rows with shape { bool, query, searchOption } true
onCancel func Callback fired when the user clicks the cancel button. true
defaultSearchOptionValue string One of the options in searchOptions that will be selected by default in all rows false
firstRowInitialSearch object Object with shape { query, option } - will be used to populate first row with default values false
hasMatchSelection boolean Show/hide search match option dropdown false
hasQueryOption boolean Controls whether Query search option should be appended to search options list false
rowFormatter func Function that will be used to combine boolean, query and search option of each row. Signature: (searchOption, query, bool, comparator) => {...}. Returned values will be used by queryBuilder to join them together. Note: no need to add bool to resulting string here - it will be added by queryBuilder. false
queryBuilder func Function that will be used to construct the search query. Signature: (rows, rowFormatter) => {...}. rows - array of shapes { query, searchOption, query }, rowFormatter - the prop. Returned value will be passed as the first argument to onSearch. false
queryToRow func Function that will be used to parse a query string into a row object. false

useAdvancedSearch

stripes-components also provides the useAdvancedSearch hook. It has the same API as the <AdvancedSearch> component, but doesn't render the UI. The hook can be used to get formatted advanced search rows and query outside of <AdvancedSearch> component. For example, when you need to run advanced search on page load with initial query from URL

/// SearchForm.js

const SearchForm = (...) => {
  ...
  return (
    ...
    <AdvancedSearch // regular use of AdvancedSearch that shows the modal
      open
      searchOptions={advancedSearchOptions}
      defaultSearchOptionValue={searchableIndexesValues.KEYWORD}
      firstRowInitialSearch={advancedSearchDefaultSearch}
      onSearch={handleAdvancedSearch}
      onCancel={() => setIsAdvancedSearchOpen(false)}
    >
      ...

    </AdvancedSearch>
  );
}


/// SearchRoute.js

const SearchRoute = (...) => {
  const { query, option } = queryString.parse(location.search);
  
  const { filledRows, query: formattedQuery } = useAdvancedSearch({
    defaultSearchOptionValue: 'keyword',
    firstRowInitialSearch: { query, option },
  });

  // here `formattedQuery` can be used to make a request to BE

  useEffect(() => {
    fetch(`/some-url?query=${formattedQuery}`);
  }, []);

  return (...);
}