Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: add filter to searchable mixin #598

Merged
merged 1 commit into from
Jan 3, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 65 additions & 38 deletions packages/veui/src/mixins/searchable.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,27 @@
import { omit } from 'lodash'

function match (item, keywordRE, { searchKey }) {
function match (item, keywordRe, { searchKey }) {
let offsets = []
const searchVal = item[searchKey]
if (searchVal) {
let result = keywordRE.exec(searchVal)
let result = keywordRe.exec(searchVal)
while (result) {
offsets.push([result.index, result.index + result[0].length])
result = keywordRE.exec(searchVal)
result = keywordRe.exec(searchVal)
}
keywordRE.lastIndex = 0
keywordRe.lastIndex = 0
}
return offsets
return offsets.length ? offsets : false
}

const metaRE = /[|\\{}()[\]^$+*?.]/g
function filter (matchResult) {
return Array.isArray(matchResult) ? !!matchResult.length : matchResult
}

const metaRE = /[|\\{}()[\]^$+*?.]/g
function createKeywordRe (keyword, { flags, literal }) {
if (keyword instanceof RegExp) {
return keyword
} else {
keyword = literal ? String(keyword).replace(metaRE, '\\$&') : keyword
return new RegExp(keyword, flags)
}
keyword = literal ? String(keyword).replace(metaRE, '\\$&') : keyword
return new RegExp(keyword, flags)
}

function splitText (text, offsets) {
Expand Down Expand Up @@ -51,37 +50,63 @@ function splitText (text, offsets) {
return result
}

function search (
datasource,
keyword,
options,
matchFn = match,
result = [],
ancestors = []
) {
function search (datasource, keyword, options, result = [], ancestors = []) {
if (!keyword) {
return []
}
let { valueKey, childrenKey, limit, exactMatch, searchKey } = options
keyword = createKeywordRe(keyword, options)

let {
valueKey,
childrenKey,
matchFn,
filterFn,
limit,
exactMatch,
searchKey,
keywordRe
} = options

// 放 options 里面缓存下,保留原来 keyword 变量传给自定义的 matchFn
if (!keywordRe) {
keywordRe = options.keywordRe = createKeywordRe(keyword, options)
}

datasource.some(i => {
let item = {
item: { ...i },
parts: [],
matched: false
}
let offsets = matchFn(item.item, keyword, options)

// match
let offsets =
typeof matchFn === 'function'
? matchFn(item, keyword)
: match(item.item, keywordRe, options)
let isArray = Array.isArray(offsets)
let isBool = !isArray && typeof offsets === 'boolean'
if (!isArray && !isBool) {
throw new Error(
'The return value of the `match` function must either be a boolean or an array.'
)
}
if (i[searchKey] && isArray) {
item.parts = splitText(i[searchKey], offsets)

// filter
let filtered =
typeof filterFn === 'function'
? filterFn(offsets, item.item)
: filter(offsets, item.item)
if (typeof filtered !== 'boolean') {
throw new Error(
'The return value of the `filter` function must be a boolean.'
)
}

// 即使没有匹配成功,为了渲染简单,还是生成 parts
if (i[searchKey]) {
item.parts = splitText(i[searchKey], offsets || [])
}
item.matched = isBool ? offsets : !!offsets.length
item.matched = filtered

let realMatched = exactMatch
? item.matched
Expand All @@ -96,7 +121,7 @@ function search (
})
}
if (i[childrenKey]) {
search(i[childrenKey], keyword, options, matchFn, result, path)
search(i[childrenKey], keyword, options, result, path)
// update limit after searching children
return limit && limit <= result.length
}
Expand All @@ -106,8 +131,7 @@ function search (
return result
}

const call = (val, context, fallback) =>
typeof val === 'function' ? val(context) : fallback
const call = (val, context) => (typeof val === 'function' ? val(context) : val)

/**
* searchable mixin,产出一个 computed
Expand All @@ -116,10 +140,11 @@ const call = (val, context, fallback) =>
* @param {string} options.datasourceKey 输入的数据源的key
* @param {string} options.keywordKey 搜索关键字的key
* @param {string} options.resultKey 产出的 computed 的名称
* @param {string} options.matchKey 搜索方法
* @param {string} options.filterKey 过滤方法
* @param {string} options.valueKey 该字段有值,该项才会作为搜索结果
* @param {string} options.childrenKey children
* @param {string} options.searchKey 被搜索的字段
* @param {string} options.matchKey 搜索方法
* @param {number} options.limit 限制结果的数量,0 表示不限制
* @param {boolean} options.exactMatch 该项有valueKey且该项搜索到值才会被认为满足搜索
* @param {string} options.flags 正则的模式
Expand All @@ -132,7 +157,8 @@ export default function searchable ({
valueKey = 'value',
childrenKey = 'children',
searchKey = 'label',
matchKey = 'match',
matchKey,
filterKey,
flags = 'ig',
limit = 100,
exactMatch = false,
Expand All @@ -142,18 +168,19 @@ export default function searchable ({
computed: {
[resultKey] () {
return search(
call(datasourceKey, this, this[datasourceKey]),
call(keywordKey, this, this[keywordKey]),
this[call(datasourceKey, this)],
this[call(keywordKey, this)],
{
valueKey: call(valueKey, this, valueKey),
childrenKey: call(childrenKey, this, childrenKey),
searchKey: call(searchKey, this, searchKey),
valueKey: call(valueKey, this),
childrenKey: call(childrenKey, this),
searchKey: call(searchKey, this),
matchFn: this[matchKey],
filterFn: this[filterKey],
limit,
exactMatch,
flags,
literal
},
call(matchKey, this, this[matchKey])
}
)
}
}
Expand Down