UNPKG

zim-fuzzysearch

Version:

Client side fuzzy search combined with seperate configurable result highlighting.

451 lines (340 loc) 13.3 kB
# 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 [published code on npmjs](https://www.npmjs.com/package/zim-fuzzysearch) ## Demos & Examples [Code Sandbox](https://codesandbox.io/s/fervent-ptolemy-2j6k4?file=/src/App.js) [Corema Fuzzy Search](https://gams.uni-graz.at/archive/objects/context:corema/methods/sdef:Context/get?mode=fuzzy-recipe-search#/) [DERLA Fuzzy Search](https://gams.uni-graz.at/archive/objects/context:derlapers/methods/sdef:Context/get?locale=de) - 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. ```tsx // 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 ```ts // 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 ```js // ... 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 ```js 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 ```js // 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 ```ts // 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 ```jsx // ... 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 ```tsx <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 ```sh # 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 ```