Skip to content

Commit

Permalink
Merge pull request #12049 from Budibase/grid-inline-searching
Browse files Browse the repository at this point in the history
Inline searching for grid and grid block
  • Loading branch information
aptkingston authored Oct 25, 2023
2 parents 26cecf2 + 2bb8ba4 commit 91d6130
Show file tree
Hide file tree
Showing 11 changed files with 352 additions and 82 deletions.
220 changes: 183 additions & 37 deletions packages/frontend-core/src/components/grid/cells/HeaderCell.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import { Icon, Popover, Menu, MenuItem, clickOutside } from "@budibase/bbui"
import GridCell from "./GridCell.svelte"
import { getColumnIcon } from "../lib/utils"
import { debounce } from "../../../utils/utils"
import { FieldType, FormulaTypes } from "@budibase/types"
export let column
export let idx
Expand All @@ -24,23 +26,69 @@
definition,
datasource,
schema,
focusedCellId,
filter,
inlineFilters,
} = getContext("grid")
const searchableTypes = [
FieldType.STRING,
FieldType.OPTIONS,
FieldType.NUMBER,
FieldType.BIGINT,
FieldType.ARRAY,
FieldType.LONGFORM,
]
let anchor
let open = false
let editIsOpen = false
let timeout
let popover
let searchValue
let input
$: sortedBy = column.name === $sort.column
$: canMoveLeft = orderable && idx > 0
$: canMoveRight = orderable && idx < $renderedColumns.length - 1
$: ascendingLabel = ["number", "bigint"].includes(column.schema?.type)
? "low-high"
: "A-Z"
$: descendingLabel = ["number", "bigint"].includes(column.schema?.type)
? "high-low"
: "Z-A"
$: sortingLabels = getSortingLabels(column.schema?.type)
$: searchable = isColumnSearchable(column)
$: resetSearchValue(column.name)
$: searching = searchValue != null
$: debouncedUpdateFilter(searchValue)
const getSortingLabels = type => {
switch (type) {
case FieldType.NUMBER:
case FieldType.BIGINT:
return {
ascending: "low-high",
descending: "high-low",
}
case FieldType.DATETIME:
return {
ascending: "old-new",
descending: "new-old",
}
default:
return {
ascending: "A-Z",
descending: "Z-A",
}
}
}
const resetSearchValue = name => {
searchValue = $inlineFilters?.find(x => x.id === `inline-${name}`)?.value
}
const isColumnSearchable = col => {
const { type, formulaType } = col.schema
return (
searchableTypes.includes(type) ||
(type === FieldType.FORMULA && formulaType === FormulaTypes.STATIC)
)
}
const editColumn = async () => {
editIsOpen = true
Expand Down Expand Up @@ -141,12 +189,46 @@
})
}
const startSearching = async () => {
$focusedCellId = null
searchValue = ""
await tick()
input?.focus()
}
const onInputKeyDown = e => {
if (e.key === "Enter") {
updateFilter()
} else if (e.key === "Escape") {
input?.blur()
}
}
const stopSearching = () => {
searchValue = null
updateFilter()
}
const onBlurInput = () => {
if (searchValue === "") {
searchValue = null
}
updateFilter()
}
const updateFilter = () => {
filter.actions.addInlineFilter(column, searchValue)
}
const debouncedUpdateFilter = debounce(updateFilter, 250)
onMount(() => subscribe("close-edit-column", cancelEdit))
</script>
<div
class="header-cell"
class:open
class:searchable
class:searching
style="flex: 0 0 {column.width}px;"
bind:this={anchor}
class:disabled={$isReordering || $isResizing}
Expand All @@ -161,30 +243,49 @@
defaultHeight
center
>
<Icon
size="S"
name={getColumnIcon(column)}
color={`var(--spectrum-global-color-gray-600)`}
/>
{#if searching}
<input
bind:this={input}
type="text"
bind:value={searchValue}
on:blur={onBlurInput}
on:click={() => focusedCellId.set(null)}
on:keydown={onInputKeyDown}
data-grid-ignore
/>
{/if}
<div class="column-icon">
<Icon size="S" name={getColumnIcon(column)} />
</div>
<div class="search-icon" on:click={startSearching}>
<Icon hoverable size="S" name="Search" />
</div>
<div class="name">
{column.label}
</div>
{#if sortedBy}
<div class="sort-indicator">
<Icon
size="S"
name={$sort.order === "descending" ? "SortOrderDown" : "SortOrderUp"}
color="var(--spectrum-global-color-gray-600)"
/>
{#if searching}
<div class="clear-icon" on:click={stopSearching}>
<Icon hoverable size="S" name="Close" />
</div>
{:else}
{#if sortedBy}
<div class="sort-indicator">
<Icon
hoverable
size="S"
name={$sort.order === "descending"
? "SortOrderDown"
: "SortOrderUp"}
/>
</div>
{/if}
<div class="more-icon" on:click={() => (open = true)}>
<Icon hoverable size="S" name="MoreVertical" />
</div>
{/if}
<div class="more" on:click={() => (open = true)}>
<Icon
size="S"
name="MoreVertical"
color="var(--spectrum-global-color-gray-600)"
/>
</div>
</GridCell>
</div>
Expand Down Expand Up @@ -235,15 +336,15 @@
disabled={!canBeSortColumn(column.schema.type) ||
(column.name === $sort.column && $sort.order === "ascending")}
>
Sort {ascendingLabel}
Sort {sortingLabels.ascending}
</MenuItem>
<MenuItem
icon="SortOrderDown"
on:click={sortDescending}
disabled={!canBeSortColumn(column.schema.type) ||
(column.name === $sort.column && $sort.order === "descending")}
>
Sort {descendingLabel}
Sort {sortingLabels.descending}
</MenuItem>
<MenuItem disabled={!canMoveLeft} icon="ChevronLeft" on:click={moveLeft}>
Move left
Expand Down Expand Up @@ -283,30 +384,75 @@
background: var(--grid-background-alt);
}
/* Icon colors */
.header-cell :global(.spectrum-Icon) {
color: var(--spectrum-global-color-gray-600);
}
.header-cell :global(.spectrum-Icon.hoverable:hover) {
color: var(--spectrum-global-color-gray-800) !important;
cursor: pointer;
}
/* Search icon */
.search-icon {
display: none;
}
.header-cell.searchable:not(.open):hover .search-icon,
.header-cell.searchable.searching .search-icon {
display: block;
}
.header-cell.searchable:not(.open):hover .column-icon,
.header-cell.searchable.searching .column-icon {
display: none;
}
/* Main center content */
.name {
flex: 1 1 auto;
width: 0;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.header-cell.searching .name {
opacity: 0;
pointer-events: none;
}
input {
display: none;
font-family: var(--font-sans);
outline: none;
border: 1px solid transparent;
background: transparent;
color: var(--spectrum-global-color-gray-800);
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
padding: 0 30px;
border-radius: 2px;
}
input:focus {
border: 1px solid var(--accent-color);
}
input:not(:focus) {
background: var(--spectrum-global-color-gray-200);
}
.header-cell.searching input {
display: block;
}
.more {
/* Right icons */
.more-icon {
display: none;
padding: 4px;
margin: 0 -4px;
}
.header-cell.open .more,
.header-cell:hover .more {
.header-cell.open .more-icon,
.header-cell:hover .more-icon {
display: block;
}
.more:hover {
cursor: pointer;
}
.more:hover :global(.spectrum-Icon) {
color: var(--spectrum-global-color-gray-800) !important;
}
.header-cell.open .sort-indicator,
.header-cell:hover .sort-indicator {
display: none;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@
rowVerticalInversionIndex,
columnHorizontalInversionIndex,
selectedRows,
loading,
loaded,
refreshing,
config,
filter,
} = getContext("grid")
let visible = false
Expand Down Expand Up @@ -153,7 +155,7 @@
<!-- New row FAB -->
<TempTooltip
text="Click here to create your first row"
condition={hasNoRows && !$loading}
condition={hasNoRows && $loaded && !$filter?.length && !$refreshing}
type={TooltipType.Info}
>
{#if !visible && !selectedRowCount && $config.canAddRows}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
const ignoredOriginSelectors = [
".spectrum-Modal",
"#builder-side-panel-container",
"[data-grid-ignore]",
]
// Global key listener which intercepts all key events
Expand Down
16 changes: 11 additions & 5 deletions packages/frontend-core/src/components/grid/stores/datasource.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
import { derived, get, writable } from "svelte/store"
import { getDatasourceDefinition } from "../../../fetch"
import { derived, get } from "svelte/store"
import { getDatasourceDefinition, getDatasourceSchema } from "../../../fetch"
import { memo } from "../../../utils"

export const createStores = () => {
const definition = writable(null)
const definition = memo(null)

return {
definition,
}
}

export const deriveStores = context => {
const { definition, schemaOverrides, columnWhitelist, datasource } = context
const { API, definition, schemaOverrides, columnWhitelist, datasource } =
context

const schema = derived(definition, $definition => {
let schema = $definition?.schema
let schema = getDatasourceSchema({
API,
datasource: get(datasource),
definition: $definition,
})
if (!schema) {
return null
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ export const initialise = context => {
datasource,
sort,
filter,
inlineFilters,
allFilters,
nonPlus,
initialFilter,
initialSortColumn,
Expand All @@ -87,21 +89,22 @@ export const initialise = context => {

// Wipe state
filter.set(get(initialFilter))
inlineFilters.set([])
sort.set({
column: get(initialSortColumn),
order: get(initialSortOrder) || "ascending",
})

// Update fetch when filter changes
unsubscribers.push(
filter.subscribe($filter => {
allFilters.subscribe($allFilters => {
// Ensure we're updating the correct fetch
const $fetch = get(fetch)
if (!isSameDatasource($fetch?.options?.datasource, $datasource)) {
return
}
$fetch.update({
filter: $filter,
filter: $allFilters,
})
})
)
Expand Down
Loading

0 comments on commit 91d6130

Please sign in to comment.