zim-fuzzysearch
v1.0.0
Published
Client side fuzzy search combined with seperate configurable result highlighting.
Downloads
63
Maintainers
Readme
ZIMFuzzySearch (+highlighting)
Short Description
Client side fuzzy search combined with seperate configurable result highlighting.
Files from src/lib are compiled to lib folder on root lvl (which is then published to npm)
Uses create-react-app setup for display demo application
Demos & Examples
- take a look at src/demo/index.tsx: via npm run start you may start the dev setup from create-react-app (but there need to be two changes made to the tsconfig.json) react-scripts will print necessary adaptions to console. (change baseUrl + noEmit to true)
Quickstart
- create a component and copy code. (first install ;-) )
- you also need bs4 + fontawesome as external dependencies.
// import component and optionally select type
import { ZIMFuzzySearch, ZIMSearchAble } from "zim-fuzzysearch"
const Demo = () => {
return (
<ZIMFuzzySearch.Comp
data={[
{
label: "Receipe 01",
value: "/link/to/category",
//txt and tags are optional properties
txt: "Nym weichsel vnd thu sy In ein haff",
tags: ["suess", "bitter"]
},
{
label: "Receipe02",
value: "link/to/category",
txt: "Tua rein wos longt und mer",
tags: ["sauer", "bitter"]
},
{
label: "Receipe03",
value: "link/to/somewhere",
txt:
"An fisch tua sulzen rein in spegg, ayn ay dazua tue no drauf amou wist honig dran mach. mit schmalczen reib ein.",
tags: ["sauer", "bitter"]
}
]}
// set to true will demand click on search button (fuzzy search is decoupled from highlight)
// searchOnClick={true}
// results will be renderedto bs4 card grid
// showResultGrid={true}
// etc.
/>
)
}
More elaborate Demo
// import component and optionally select type
import { ZIMFuzzySearch, ZIMSearchAble } from "zim-fuzzysearch"
// call component and pass in data
const Demo = () => {
return
(<ZIMFuzzySearch.Comp
// optionally try with test data
// as long as no data is defined -> will display loader
// data={testData.coremaData}
data={[
{
label: "Receipe 01",
value: "/link/to/category",
//txt and tags are optional properties
txt:"Nym weichsel vnd thu sy In ein haff",
tags: ["suess", "bitter"],
},
{
label:"Receipe02",
value:"link/to/category",
txt:"Tua rein wos longt und mer",
tags:["sauer", "bitter"]
},
{
label:"Receipe03",
value:"link/to/somewhere",
txt:"An fisch tua sulzen rein in spegg, ayn ay dazua tue no drauf amou wist honig dran mach. mit schmalczen reib ein.",
tags: ["sauer", "bitter"]
}
]}
// tagConceptMap leads user to a link when clicked on tag badge
// not all tags have to be stated.
// also activates the filtering for one specific tag
tagConceptMap={{
sauer: {url: "https://google.com"},
bitter: {url: "https://google.com"},
suess: {url: "https://google.com"},
"This value doesn't exist": {url:"https://google.com"},
"not assigned01": {url:"https://google.com"},
"not assigned02": {url:"https://google.com"},
"not assigned03": {url:"https://google.com"}
}}
// suggestions will be applied if given.
// allows to suggest interesting search values to the user without being required to select one.
suggestions={{
"weichsel": {label:"search-hint: Weichsel means cherry in German", value:"weichsel"},
"sulzen": {label:"search-hint: Try to find most relevant receipes meantioning sulzen", value:"sulzen"},
"spegg": {label:"Bacon", value:"spegg"}
}}
// specify labeling of result count
resultCountLabel="Got results: "
// starts fuse search on input change (with short delay) when set to false
// set to true will demand click on search button (fuzzy search is decoupled from highlight)
searchOnClick={true}
// placeholder on input field
placeHolder={"Suche eingeben..."}
//
onSelect={
// what should happen when result link is clicked
// callback gets passed in selected ZimSearchable object
(selected) => console.log(selected)
}
// define what should be happen when a tag is clicked
// works only if the tagmap is defined with the specific value.
onTagSelect = {
(tagObj) => {
console.log(tagObj);
}
}
// display search alert via using the onSearch prop
onSearch={(searchVal) => alert("Searching now for: " + searchVal + " (this message may be customized)")}
/>)
}
External Dependencies
- Component has bootstrap4 and font-awesome6 classnames assigned BUT NOT bundled into. Apply external dependencies if needed.
Advanced
// ... return for rendering:
// (how to import see quickstart)
<ZIMFuzzySearch.Comp
data={testData.coremaData}
fuseOptions={{
keys: ["label","tags"] // keys is required! adjusts where fuse.js should look for data to index
// other properties are directly from fuse.js
// defaults against label, txt and tags properties.
//ignoreLocation: true,
//ignoreFieldNorm: true,
//includeScore: false,
}}
// maps given fuseOption.keys to tab-view below input.
// KEY array MUST HAVE same values and properties below
fuseKeyMap= {
{
label: {label:"Titel"},
tags: {label:"Schlagwörter"}
}
}
// options to adjust mark.js behavior
markOptions={{
accuracy: "exactly", // complementary will mark whole word
exclude: ["h5"], // exclude elems in result from highlighting
}}
// optionally display result as bootstrap grid
showResultGrid={true}
/>
Custom GUI
- see demo in src/demo/CustomGUI
import React from "react";
// import from "zim-fuzzysearch" -- when installed via npm install
import { ZIMFuzzySearch } from "../../lib"
/**
* Example for how to build a CustomGui on top of the
* fuzzy search, using the fuzzyResult and fuzzyInput hook.
*/
const CustomGui: React.FC = () => {
// result of fuzzy search
const { fuzzyResult } = ZIMFuzzySearch.hooks.useFuzzyResult();
// handleInput will init fuzzySearch. In inputVal is the inputVal stored.
const { handleInput, inputVal } = ZIMFuzzySearch.hooks.useFuzzyInput();
return (
<>
<input
value={inputVal}
// init search on input of component
onChange={(evt) => handleInput(evt.currentTarget.value)}
type="text"
></input>
{fuzzyResult
? Object.keys(fuzzyResult).map((key) => <p key={key}>{key}</p>)
: null}
</>
);
};
export default CustomGui;
Fetch Data and use in Fuzzy Search
- see Demo04 ind src/demo
// fetch data in component and assign to state
const [fetchResult, setFetchResult] = React.useState<any | null>(null);
React.useEffect(() => {
if(fetchResult)return;
fetch(
"DEMO_DATA.json" //add entire url from server to file here
).then((resp) => {
resp.json().then((data) => {
setFetchResult(data);
});
});
}, [fetchResult]);
// assign fetched data to fuzzy result component
return (
<ZIMFuzzySearch.Comp
// fetchResult can be null or undefined -> shows loader by default.
data={fetchResult}
fuseOptions={{
keys: ["txt"]
}}
markOptions={{
accuracy: "complementary",
exclude: ["h5"],
wildcards: "enabled"
}}
/>
)
Two-way binding
// returned React component
return <ZIMFuzzySearch.Comp
data={fetchResult}
searchInput="Search value given as prop"
placeHolder="Etwas suchen..."
searchOnClick={true}
// use onSearch to get search input outside
onSearch={(searchVal) => alert("Search for: " + searchVal)}
//use onInput to get search input outside
//onInput={(val) => alert("Input: " + val)}
></ZIMFuzzySearch.Comp>
Example for changing mark.js highlighting reactively
// ... additional component code
// adapt highlighting to selected fuseKeyMap (without fuseKeymap won't work!)
const [markOps, setMarkops] = React.useState<Object>({
element:"b",
accuracy: "complementary",
exclude: []
});
return
<ZIMFuzzySearch.Comp
data={testData.coremaData}
// mark options controled via state to allow control
markOptions={markOps}
// tell fuse which keys to listen to
fuseOptions={{
keys: ["label","tags", "txt"]
}}
// maps given fuseOption.keys to tab-view below input.
// KEY array MUST HAVE same values and properties below
fuseKeyMap= {
{
label: {label:"Titel"},
tags: {label:"Schlagwörter"},
txt: {label:"Volltext"}
}
}
// set all label
fuseKeyAllLabel={"Alle Felder"}
// change mark options onFuseKeySelect
onFuseKeySelect={(fuseKeys) => {
let markExclude = [];
if(fuseKeys.length === 1){
// onFuseKey select gets array of selected fuseKeys as parameter.
// exclude different elements
if(fuseKeys.includes("tags"))markExclude = [".zim-fuzzy--result-txt", ".zim-fuzzy--result-heading", ".zim-fuzzy--result-crumb"]
if(fuseKeys.includes("label"))markExclude = [".zim-fuzzy--result-txt", "a", ".zim-fuzzy--result-tag", ".zim-fuzzy--result-crumb"]
if(fuseKeys.includes("txt"))markExclude = [".zim-fuzzy--result-tag", ".zim-fuzzy--result-heading", ".zim-fuzzy--result-crumb"]
} else {
markExclude = []
}
let markCopy = JSON.parse(JSON.stringify(markOps));
markCopy.exclude = markExclude;
setMarkops(markCopy);
}}
></ZIMFuzzySearch.Comp>
External styling via css classes
Provided classes for external styling.
Main container: zim-fuzzy--main
Input form: zim-fuzzy--input-form
Input group: zim-fuzzy--input-group
Every result in a div: zim-fuzzy--result-div
Result container: zim-fuzzy--result-main
Pagination bar: zim-fuzzy--result-pagination
Individual result text: zim-fuzzy--result-txt
Individual result heading: zim-fuzzy--result-heading
Individual result bread crumb: zim-fuzzy--result-crumb
Individual result tag: zim-fuzzy--result-tag
Assigned classes according to Fuse.js scoring
- you may use this classes
- score must be provided by fuse.js options!
.zim-fuzzy-score-highest .zim-fuzzy-score-higher .zim-fuzzy-score-high .zim-fuzzy-score-medium .zim-fuzzy-score-low .zim-fuzzy-score-lowest
Manipulate css classes according to fuse.js scoring
<ZIMFuzzySearch.Comp
data={testData.coremaData}
// e.g. assign method to calculate different css classes according
// to calculated fuse.js score
// then use css to style the result
// the css classname will be assigned to the css result-div
onFuseScoreToCssClass={(score) => {
if (score < .1){
return "bg-success"
} else {
return "bg-danger"
}
}}
></ZIMFuzzySearch.Comp>
Contribute
Useful Resources
- https://medium.com/@jchiam/publishing-a-typescript-react-component-to-npm-d3cc15b8d0a2
- https://itnext.io/step-by-step-building-and-publishing-an-npm-typescript-package-44fe7164964c
Create react app and useful stuff:
- https://github.com/wmonk/create-react-app-typescript/blob/master/template/README.md#publishing-components-to-npm
Important Scripts
# locally build publishable lib folder
# this folder will be published by npm publish later on
npm run npm:build
# login to npm before
npm login
# apply new version
npm version x.y.z
# then publish to npmjs
npm publish