Skip to content

Commit

Permalink
feat(pro:search): select and treeSelect support onSearch now (#…
Browse files Browse the repository at this point in the history
…1444)

add `onSearch` for to make server-side searching possible
  • Loading branch information
sallerli1 authored and danranVm committed Feb 27, 2023
1 parent 3289784 commit 79a7acc
Show file tree
Hide file tree
Showing 15 changed files with 276 additions and 90 deletions.
14 changes: 14 additions & 0 deletions packages/pro/search/demo/RemoteSearch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
order: 42
title:
zh: 服务端搜索
en: Server-side searching
---

## zh

`'select'``'treeSelect'` 类型搜索项支持服务端搜索。

## en

Server-side searching is supported under field type of `'select'`, `'treeSelect'`.
126 changes: 126 additions & 0 deletions packages/pro/search/demo/RemoteSearch.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
<template>
<IxProSearch
v-model:value="value"
style="width: 100%"
:searchFields="searchFields"
:onChange="onChange"
:onSearch="onSearch"
></IxProSearch>
</template>

<script setup lang="ts">
import type { SearchField, SearchValue } from '@idux/pro/search'
import { computed, ref } from 'vue'
import { filterTree } from '@idux/cdk/utils'
interface SelectData {
key: number
label: string
}
interface TreeSelectData {
key: string
label: string
children?: TreeSelectData[]
}
const labels = ['Archer', 'Berserker', 'Lancer', 'Rider', 'Saber', 'Caster', 'Assassin']
const baseSelectData: SelectData[] = Array.from(new Array(50)).map((_, idx) => {
const label = `${labels[idx % labels.length]}-${Math.ceil(idx / labels.length)}`
return {
key: idx,
label,
}
})
const baseTreeSelectData: TreeSelectData[] = Array.from(new Array(20)).map((_, idx) => {
const label = labels[idx % labels.length]
return {
key: `${idx}`,
label: `${label} ${idx}`,
children: [
{
label: `${label} ${idx}-0`,
key: `${idx}-0`,
children: [
{
label: `${label} ${idx}-0-0`,
key: `${idx}-0-0`,
},
{
label: `${label} ${idx}-0-1`,
key: `${idx}-0-1`,
},
],
},
{
label: `${label} ${idx}-1`,
key: `${idx}-1`,
children: [
{ label: `${label} ${idx}-1-0`, key: `${idx}-1-0` },
{ label: `${label} ${idx}-1-1`, key: `${idx}-1-1` },
],
},
],
}
})
const createSelectData = (searchValue: string) => {
return baseSelectData.filter(item => new RegExp(searchValue.toLowerCase()).test(item.label.toLowerCase()))
}
const createTreeSelectData = (searchValue: string) => {
return filterTree(baseTreeSelectData, 'children', item =>
new RegExp(searchValue.toLowerCase()).test(item.label.toLowerCase()),
)
}
const value = ref<SearchValue[]>()
const selectData = ref<SelectData[]>(createSelectData(''))
const treeSelectData = ref<TreeSelectData[]>(createTreeSelectData(''))
const selectOnSearch = (searchValue: string) => {
selectData.value = createSelectData(searchValue)
}
const treeSelectOnSearch = (searchValue: string) => {
treeSelectData.value = createTreeSelectData(searchValue)
}
const searchFields = computed<SearchField[]>(() => [
{
type: 'select',
label: 'Select Data',
key: 'Select Data',
fieldConfig: {
multiple: true,
searchable: true,
dataSource: selectData.value,
searchFn: () => true,
onSearch: selectOnSearch,
},
},
{
type: 'treeSelect',
label: 'Tree Data',
key: 'tree_data',
fieldConfig: {
multiple: true,
searchable: true,
checkable: true,
cascaderStrategy: 'all',
dataSource: treeSelectData.value,
searchFn: () => true,
onSearch: treeSelectOnSearch,
},
},
])
const onChange = (value: SearchValue[] | undefined, oldValue: SearchValue[] | undefined) => {
console.log(value, oldValue)
}
const onSearch = () => {
console.log('onSearch')
}
</script>

<style scoped lang="less"></style>
2 changes: 2 additions & 0 deletions packages/pro/search/docs/Api.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ SelectSearchFieldConfig
| `showSelectAll` | 是否支持全选 | `boolean` | `true` | - | - |
| `virtual` | 是否支持虚拟滚动 | `boolean` | `false` | - | 默认不支持 |
| `overlayItemWidth` | 选项宽度 | `number` | - | - | - |
| `onSearch` | 搜索回调函数 | `(searchValue: string) => void | ((searchValue: string) => void)[]` | - | - | 在触发搜索值改变时执行 |

> 注:使用 `Ctrl + Enter` 在多选下切换面板中选项选中状态
Expand Down Expand Up @@ -140,6 +141,7 @@ TreeSelectSearchFieldConfig
| `onDrop` | `drop` 触发时调用 | `(options: TreeDragDropOptions<any>) => void | ((options: TreeDragDropOptions<any>) => void)[]` | - | - | 详情参考[Tree](/components/tree/zh) |
| `onExpand` | 点击展开图标时触发 | `(expanded: boolean, node: TreeSelectPanelData) => void | ((expanded: boolean, node: TreeSelectPanelData) => void)[]` | - | - | 详情参考[Tree](/components/tree/zh) |
| `onSelect` | 选中状态发生变化时触发 | `(selected: boolean, node: TreeSelectPanelData) => void | ((selected: boolean, node: TreeSelectPanelData) => void)[]` | - | - | 详情参考[Tree](/components/tree/zh) |
| `onSearch` | 搜索回调函数 | `(searchValue: string) => void | ((searchValue: string) => void)[]` | - | - | 在触发搜索值改变时执行 |
| `onLoaded` | 子节点加载完毕时触发 | `(loadedKeys: any[], node: TreeSelectPanelData) => void | ((loadedKeys: any[], node: TreeSelectPanelData) => void)[]` | - | - | 详情参考[Tree](/components/tree/zh) |
```typescript
Expand Down
1 change: 1 addition & 0 deletions packages/pro/search/src/composables/useSearchItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export function useSearchItems(
key: searchState.key,
optionKey: searchState.fieldKey,
error: searchItemErrors.value?.find(error => error.index === searchState.index),
searchField,
segments: searchState.segmentValues
.map(segmentValue => {
if (segmentValue.name === 'name') {
Expand Down
52 changes: 38 additions & 14 deletions packages/pro/search/src/composables/useSegmentStates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@

import type { ProSearchContext } from '../token'

import { type Ref, ref, watch } from 'vue'
import { type ComputedRef, type Ref, ref, watch } from 'vue'

import { callEmit } from '@idux/cdk/utils'

import { SearchState, tempSearchStateKey } from '../composables/useSearchStates'
import { type SegmentValue, tempSearchStateKey } from '../composables/useSearchStates'
import { type ProSearchProps, type SearchItemProps, Segment, searchDataTypes } from '../types'

type SegmentStates = Record<string, { input: string; value: unknown; index: number }>
export interface SegmentStatesContext {
segmentStates: Ref<SegmentStates>
initSegmentStates: () => void
initSegmentStates: (force?: boolean) => void
handleSegmentInput: (name: string, input: string) => void
handleSegmentChange: (name: string, value: unknown) => void
handleSegmentConfirm: (name: string, confirmItem?: boolean) => void
Expand All @@ -28,6 +28,7 @@ export function useSegmentStates(
props: SearchItemProps,
proSearchProps: ProSearchProps,
proSearchContext: ProSearchContext,
isActive: ComputedRef<boolean>,
): SegmentStatesContext {
const {
getSearchStateByKey,
Expand All @@ -44,9 +45,7 @@ export function useSegmentStates(
} = proSearchContext
const segmentStates = ref<SegmentStates>({})

const _genInitSegmentState = (segment: Segment, searchState: SearchState, index: number) => {
const name = segment.name
const segmentValue = searchState?.segmentValues.find(value => value.name === name)
const _genInitSegmentState = (segment: Segment, segmentValue: SegmentValue | undefined, index: number) => {
return {
input: segment.format(segmentValue?.value) ?? '',
value: segmentValue?.value,
Expand All @@ -55,9 +54,19 @@ export function useSegmentStates(
}

// reset temp segment states
const initSegmentStates = () => {
const initSegmentStates = (force = false) => {
const searchState = getSearchStateByKey(props.searchItem!.key)!

segmentStates.value = props.searchItem!.segments.reduce((states, segment, index) => {
states[segment.name] = _genInitSegmentState(segment, getSearchStateByKey(props.searchItem!.key)!, index)
const currentSegmentState = segmentStates.value[segment.name]
const segmentValue = searchState?.segmentValues.find(value => value.name === segment.name)

if (currentSegmentState && !force) {
states[segment.name] = { ...currentSegmentState, index }
} else {
states[segment.name] = _genInitSegmentState(segment, segmentValue, index)
}

return states
}, {} as SegmentStates)
}
Expand All @@ -69,14 +78,29 @@ export function useSegmentStates(
return
}

const searchState = getSearchStateByKey(props.searchItem!.key)!
const segment = props.searchItem!.segments[segmentState.index]
segmentStates.value[name] = _genInitSegmentState(
segment,
getSearchStateByKey(props.searchItem!.key)!,
segmentState.index,
)
const segmentValue = searchState?.segmentValues.find(value => value.name === segment.name)
segmentStates.value[name] = _genInitSegmentState(segment, segmentValue, segmentState.index)
}
watch(() => props.searchItem?.segments, initSegmentStates, { immediate: true })

watch(
() => props.searchItem?.segments,
(segments, oldSegments) => {
if (
segments?.length !== oldSegments?.length ||
segments?.some(segment => !oldSegments?.find(oldSegment => oldSegment.name === segment.name))
) {
initSegmentStates()
}
},
{ immediate: true },
)
watch([isActive, () => props.searchItem?.searchField], ([active, searchField]) => {
if (!active || !searchField) {
initSegmentStates(true)
}
})

const setSegmentValue = (name: string, value: unknown) => {
if (!segmentStates.value[name]) {
Expand Down
48 changes: 39 additions & 9 deletions packages/pro/search/src/panel/SelectPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,32 +13,59 @@ import {
inject,
onBeforeUnmount,
onMounted,
onUnmounted,
ref,
watch,
} from 'vue'

import { type VKey, useState } from '@idux/cdk/utils'
import { type VKey, callEmit, useState } from '@idux/cdk/utils'
import { IxButton } from '@idux/components/button'
import { IxCheckbox } from '@idux/components/checkbox'
import { IxSelectPanel, type SelectData, type SelectPanelInstance } from '@idux/components/select'

import { proSearchContext } from '../token'
import { type ProSearchSelectPanelProps, proSearchSelectPanelProps } from '../types'
import { type ProSearchSelectPanelProps, type SelectPanelData, proSearchSelectPanelProps } from '../types'
import { filterDataSource, matchRule } from '../utils/selectData'

export default defineComponent({
props: proSearchSelectPanelProps,
setup(props) {
const { locale, mergedPrefixCls } = inject(proSearchContext)!
const [activeValue, setActiveValue] = useState<VKey | undefined>(undefined)
const partiallySelected = computed(() => props.value && props.value.length > 0 && !props.allSelected)
const filteredDataSource = computed(() => {
const { dataSource, searchValue, searchFn } = props
if (!searchValue) {
return dataSource
}

const trimedSearchValue = searchValue?.trim() ?? ''
const mergedSearchFn = searchFn
? (option: SelectPanelData) => searchFn(option, trimedSearchValue)
: (option: SelectPanelData) => matchRule(option.label, trimedSearchValue)

return filterDataSource(dataSource ?? [], mergedSearchFn)
})

watch(
() => props.value,
value => {
const key = value?.[value.length - 1]
key && setActiveValue(key)
},
{ immediate: true },
)
watch(
() => props.searchValue,
searchValue => {
callEmit(props.onSearch, searchValue ?? '')
},
)
onUnmounted(() => {
if (props.searchValue) {
callEmit(props.onSearch, '')
}
})

const panelRef = ref<SelectPanelInstance>()
const changeSelected = (key: VKey) => {
Expand All @@ -48,27 +75,30 @@ export default defineComponent({
const isSelected = targetIndex > -1

if (!multiple) {
props.onChange?.([key])
callEmit(props.onChange, [key])
return
}
if (isSelected) {
props.onChange?.(currValue.filter((_, index) => targetIndex !== index))
callEmit(
props.onChange,
currValue.filter((_, index) => targetIndex !== index),
)
return
}

props.onChange?.([...currValue, key])
callEmit(props.onChange, [...currValue, key])
}
const handleOptionClick = (option: SelectData) => {
changeSelected(option.key!)
}
const handleConfirm = () => {
props.onConfirm?.()
callEmit(props.onConfirm)
}
const handleCancel = () => {
props.onCancel?.()
callEmit(props.onCancel)
}
const handleSelectAllClick = () => {
props.onSelectAllClick?.()
callEmit(props.onSelectAllClick)
}

const handleKeyDown = useOnKeyDown(props, panelRef, activeValue, changeSelected, handleConfirm)
Expand Down Expand Up @@ -120,7 +150,7 @@ export default defineComponent({
const prefixCls = `${mergedPrefixCls.value}-select-panel`
const panelProps = {
activeValue: activeValue.value,
dataSource: props.dataSource,
dataSource: filteredDataSource.value,
multiple: props.multiple,
getKey: 'key',
labelKey: 'label',
Expand Down
14 changes: 13 additions & 1 deletion packages/pro/search/src/panel/TreeSelectPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE
*/

import { type ComputedRef, computed, defineComponent, inject } from 'vue'
import { type ComputedRef, computed, defineComponent, inject, onUnmounted, watch } from 'vue'

import { isFunction } from 'lodash-es'

Expand All @@ -29,6 +29,18 @@ export default defineComponent({
return props.cascaderStrategy
})

watch(
() => props.searchValue,
searchValue => {
callEmit(props.onSearch, searchValue ?? '')
},
)
onUnmounted(() => {
if (props.searchValue) {
callEmit(props.onSearch, '')
}
})

const { expandedKeys, setExpandedKeys } = useExpandedKeys(props)

const changeSelected = (keys: VKey[]) => {
Expand Down
Loading

0 comments on commit 79a7acc

Please sign in to comment.