Public
Edited
Jun 17, 2024
Importers
Insert cell
# @pkg/search - Enhanced Search Input

EnhancedSearch offers advanced search matching features such as exact matching,
OR syntax, and case sensitivity; it also supports remembering previous input
value.

## Usages

```js
import { EnhancedSearch } from "@pkg/search"

viewof search = EnhancedSearch(olympians, {
memoKey: "input-1"
})
```

OR

```js
import { searchFilter, columnFilter } from "@pkg/search"

viewof search2 = Inputs.search(olympians, {
filter: columnFilter(columns)
})
```

## Options

- `memoKey` (String): A unique key used to remember the state of the input
across Observable notebook re-loads.

## Features

- **Exact Match:** You can search for an exact phrase by enclosing the phrase
within double quotes. This forces the search to match the exact sequence of
characters within the quotes.

- **Case Sensitivity:** The search respects the case of the characters, allowing
for precise filtering based on the casing of the input.

- **Conditional Logic:** Use the | operator to perform logical OR searches. This
allows you to search for multiple terms simultaneously.

## Example Queries

- `"Zeus"`: This query returns results that exactly match 'Zeus'.
- `Hera|Zeus`: This query returns results that contain either 'Hera' or 'Zeus'.

Insert cell
EnhancedSearch = (data, options) => {
const { filter, columns } = options || {};

const enhancedFilter =
columns === undefined ? searchFilter : columnFilter(columns);

const search = Inputs.search(data, {
...options,
filter: filter || enhancedFilter
});
const input = search.querySelector("input");

if (options?.memoKey) {
const key = "EnhancedSearchMemo_" + options.memoKey;
const memoValue = localStorage.getItem(key);
if (memoValue && input) {
input.value = memoValue;
input.dispatchEvent(new InputEvent("input"));
}

input?.addEventListener("change", (e) => {
localStorage.setItem(key, e.target.value);
});
}

return search;
}
Insert cell
function searchFilter(query) {
const filters = `${query}`
.split(/\s+/g)
.filter((t) => t)
.map(termFilter);
return (d) => {
if (d == null) return false;
if (typeof d === "object") {
out: for (const filter of filters) {
for (const value of valuesof(d)) {
if (filter.test(value)) {
continue out;
}
}
return false;
}
} else {
for (const filter of filters) {
if (!filter.test(d)) {
return false;
}
}
}
return true;
};
}
Insert cell
function columnFilter(columns) {
return query => {
const filters = `${query}`.split(/\s+/g).filter(t => t).map(termFilter);
return d => {
out: for (const filter of filters) {
for (const column of columns) {
if (filter.test(d[column])) {
continue out;
}
}
return false;
}
return true;
};
};
}
Insert cell
function* valuesof(d) {
for (const key in d) {
yield d[key];
}
}
Insert cell
function termFilter(term) {
if (/".+"/.test(term)) {
return new RegExp(`^${escapeRegExp(term.slice(1, -1))}$`, "u");
}
return new RegExp(`(?:^|[^\\p{L}])${escapeRegExp(term)}`, "iu");
}
Insert cell
function escapeRegExp(text) {
return text.replace(/[\\^$.*+?()[\]{}]/g, "\\$&");
// return text.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
}
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more