From 068db211d5ec60fda421dd12c39620c6db8e36f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Snorre=20Magnus=20Dav=C3=B8en?= Date: Sun, 16 Jul 2017 20:59:47 +0200 Subject: [PATCH] Uses Fuse.js fuzzy search to filter municipalities MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of using naive string search to filter the muncipalities we can use the fuzzy search library Fuse.js to filter them. This way incorrect spelling of municipality names won't stop the user from getting the results she is looking for. Fuse.js http://fusejs.io/ contains no dependencies and is quite light weight. This makes it a good library for such a simple site. Signed-off-by: Snorre Magnus Davøen --- package.json | 1 + src/MunicipalityList.tsx | 47 ++++++++++++++++++++++------------------ yarn.lock | 4 ++++ 3 files changed, 31 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index efe5959..9cd757d 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "@types/query-string": "^4.3.1", "@types/react": "^15.0.37", "@types/react-dom": "^15.5.1", + "fuse.js": "^3.0.5", "gh-pages": "^1.0.0", "inuitcss": "^6.0.0-beta.5", "node-sass-chokidar": "^0.0.3", diff --git a/src/MunicipalityList.tsx b/src/MunicipalityList.tsx index e3317b1..831404a 100644 --- a/src/MunicipalityList.tsx +++ b/src/MunicipalityList.tsx @@ -1,5 +1,6 @@ import * as React from 'react' import * as queryString from 'query-string' +import * as Fuse from 'fuse.js' import { municipalities as municipalitiesData, Municipality @@ -9,6 +10,7 @@ import { MunicipalityCard } from './MunicipalityCard' interface State { filterText: string filterByResource: boolean + municipalities: any } export default class MunicipalityList extends React.Component { @@ -21,44 +23,47 @@ export default class MunicipalityList extends React.Component { super() const stateTemplate = { filterText: '', - filterByResource: false + filterByResource: false, } this.state = Object.assign( stateTemplate, // parse url to see if state has been persisted there - queryString.parse(window.location.hash) + queryString.parse(window.location.hash), + { + municipalities: new Fuse(municipalitiesData, { + keys: ['name'], + minMatchCharLength: 1, + threshold: 0.5, + location: 0, + distance: 10, + shouldSort: true, + tokenize: true, + matchAllTokens: true, + }) + } ) } render() { - const shouldShow = (filterText: string, muni: Municipality) => - JSON.stringify(muni.name) - .toLowerCase() - .indexOf(filterText.toLowerCase()) !== -1 const hasLogo = (muni: Municipality) => !!muni.orgnummer // copy array and sort by name - let municipalities = municipalitiesData + let municipalities = this.state.filterText.length > 0 ? + this.state.municipalities.search(this.state.filterText) : + municipalitiesData + + municipalities = municipalities .slice() - .sort((a, b) => { - if (a.name < b.name) { - return -1 - } - if (a.name > b.name) { - return 1 - } - return 0 - }) - .reduce((acc, elem: Municipality) => { - if (this.state.filterText && !shouldShow(this.state.filterText, elem)) { - return acc - } + .reduce((acc: Array, elem: Municipality) => { if (this.state.filterByResource && !hasLogo(elem)) { return acc } acc.push(elem) return acc }, [] as Municipality[]) + .sort((municipalityA: Municipality, municipalityB: Municipality) => { + return municipalityA.name.localeCompare(municipalityB.name) + }) return (
@@ -86,7 +91,7 @@ export default class MunicipalityList extends React.Component { Antall: {municipalities.length}
- {municipalities.map(elem => + {municipalities.map((elem: Municipality) =>