Uses Fuse.js fuzzy search to filter municipalities

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 <snorremd@gmail.com>
This commit is contained in:
Snorre Magnus Davøen 2017-07-16 20:59:47 +02:00 committed by Nils
parent 0030a6e001
commit 068db211d5
3 changed files with 31 additions and 21 deletions

View File

@ -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",

View File

@ -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<any, State> {
@ -21,44 +23,47 @@ export default class MunicipalityList extends React.Component<any, State> {
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<Municipality>, 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 (
<div>
@ -86,7 +91,7 @@ export default class MunicipalityList extends React.Component<any, State> {
Antall: {municipalities.length}
</div>
<div className="o-layout">
{municipalities.map(elem =>
{municipalities.map((elem: Municipality) =>
<div
key={elem.code}
className="o-layout__item u-1/1 u-1/3@tablet u-1/6@desktop u-1/10@wide"

View File

@ -1771,6 +1771,10 @@ function-bind@^1.0.2:
version "1.1.0"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.0.tgz#16176714c801798e4e8f2cf7f7529467bb4a5771"
fuse.js@^3.0.5:
version "3.0.5"
resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-3.0.5.tgz#b58d85878802321de94461654947b93af1086727"
gauge@~2.7.3:
version "2.7.4"
resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7"