1.0.0 • Published 3 years ago

zim-fuzzysearch v1.0.0

Weekly downloads
438
License
MIT
Repository
-
Last release
3 years ago

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

Demos & Examples

Code Sandbox

Corema Fuzzy Search

DERLA Fuzzy Search

  • 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

Create react app and useful stuff:

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
1.0.0

3 years ago

0.10.0

3 years ago

0.9.0

3 years ago

0.9.1

3 years ago

0.8.1

3 years ago

0.8.0

3 years ago

0.8.2

3 years ago

0.7.2

3 years ago

0.7.1

3 years ago

0.7.0

3 years ago

0.6.3

3 years ago

0.6.2

3 years ago

0.6.1

3 years ago

0.5.0

3 years ago

0.4.3

3 years ago

0.6.0

3 years ago

0.4.2

3 years ago

0.4.1

3 years ago

0.4.0

3 years ago

0.3.1

3 years ago

0.3.0

3 years ago

0.2.6

3 years ago

0.2.3

3 years ago

0.2.5

3 years ago

0.2.4

3 years ago

0.2.2

3 years ago

0.2.1

3 years ago

0.2.0

3 years ago

0.1.0

3 years ago